Total Complexity | 1108 |
Total Lines | 9450 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like Postgres often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Postgres, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
16 | class Postgres extends ADOdbBase |
||
17 | { |
||
18 | use \PHPPgAdmin\HelperTrait; |
||
1 ignored issue
–
show
|
|||
19 | |||
20 | public function __construct(&$conn, $conf) |
||
21 | { |
||
22 | //$this->prtrace('major_version :' . $this->major_version); |
||
23 | $this->conn = $conn; |
||
24 | $this->conf = $conf; |
||
25 | } |
||
26 | |||
27 | /** |
||
28 | * Fetch a URL (or array of URLs) for a given help page. |
||
29 | * |
||
30 | * @param $help |
||
31 | * |
||
32 | * @return null|array|string |
||
33 | */ |
||
34 | public function getHelp($help) |
||
35 | { |
||
36 | $this->getHelpPages(); |
||
37 | |||
38 | if (isset($this->help_page[$help])) { |
||
39 | if (is_array($this->help_page[$help])) { |
||
40 | $urls = []; |
||
41 | foreach ($this->help_page[$help] as $link) { |
||
42 | $urls[] = $this->help_base . $link; |
||
43 | } |
||
44 | |||
45 | return $urls; |
||
46 | } |
||
47 | |||
48 | return $this->help_base . $this->help_page[$help]; |
||
49 | } |
||
50 | |||
51 | return null; |
||
52 | } |
||
53 | |||
54 | /** |
||
55 | * Gets the help pages. |
||
56 | * get help page by instancing the corresponding help class |
||
57 | * if $this->help_page and $this->help_base are set, this function is a noop. |
||
58 | * |
||
59 | * @return void |
||
60 | */ |
||
61 | public function getHelpPages() |
||
62 | { |
||
63 | if ($this->help_page === null || $this->help_base === null) { |
||
64 | $help_classname = '\PHPPgAdmin\Help\PostgresDoc' . str_replace('.', '', $this->major_version); |
||
65 | |||
66 | $help_class = new $help_classname($this->conf, $this->major_version); |
||
67 | |||
68 | $this->help_base = $help_class->getHelpBase(); |
||
69 | } |
||
70 | } |
||
71 | |||
72 | // Formatting functions |
||
73 | |||
74 | /** |
||
75 | * Outputs the HTML code for a particular field. |
||
76 | * |
||
77 | * @param $name The name to give the field |
||
78 | * @param $value The value of the field. Note this could be 'numeric(7,2)' sort of thing... |
||
79 | * @param $type The database type of the field |
||
80 | * @param array $extras An array of attributes name as key and attributes' values as value |
||
81 | */ |
||
82 | public function printField($name, $value, $type, $extras = []) |
||
83 | { |
||
84 | $lang = $this->lang; |
||
85 | |||
86 | // Determine actions string |
||
87 | $extra_str = ''; |
||
88 | foreach ($extras as $k => $v) { |
||
89 | $extra_str .= " {$k}=\"" . htmlspecialchars($v) . '"'; |
||
90 | } |
||
91 | |||
92 | switch (substr($type, 0, 9)) { |
||
93 | case 'bool': |
||
94 | case 'boolean': |
||
95 | if ($value !== null && $value == '') { |
||
96 | $value = null; |
||
97 | } elseif ($value == 'true') { |
||
98 | $value = 't'; |
||
99 | } elseif ($value == 'false') { |
||
100 | $value = 'f'; |
||
101 | } |
||
102 | |||
103 | // If value is null, 't' or 'f'... |
||
104 | if ($value === null || $value == 't' || $value == 'f') { |
||
105 | echo '<select name="', htmlspecialchars($name), "\"{$extra_str}>\n"; |
||
106 | echo '<option value=""', ($value === null) ? ' selected="selected"' : '', "></option>\n"; |
||
107 | echo '<option value="t"', ($value == 't') ? ' selected="selected"' : '', ">{$lang['strtrue']}</option>\n"; |
||
108 | echo '<option value="f"', ($value == 'f') ? ' selected="selected"' : '', ">{$lang['strfalse']}</option>\n"; |
||
109 | echo "</select>\n"; |
||
110 | } else { |
||
111 | echo '<input name="', htmlspecialchars($name), '" value="', htmlspecialchars($value), "\" size=\"35\"{$extra_str} />\n"; |
||
112 | } |
||
113 | |||
114 | break; |
||
115 | case 'bytea': |
||
116 | case 'bytea[]': |
||
117 | if (!is_null($value)) { |
||
118 | $value = $this->escapeBytea($value); |
||
119 | } |
||
120 | // no break |
||
121 | case 'text': |
||
122 | case 'text[]': |
||
123 | case 'json': |
||
124 | case 'jsonb': |
||
125 | case 'xml': |
||
126 | case 'xml[]': |
||
127 | $n = substr_count($value, "\n"); |
||
128 | $n = $n < 5 ? max(2, $n) : $n; |
||
129 | $n = $n > 20 ? 20 : $n; |
||
130 | echo '<textarea name="', htmlspecialchars($name), "\" rows=\"{$n}\" cols=\"85\"{$extra_str}>\n"; |
||
131 | echo htmlspecialchars($value); |
||
132 | echo "</textarea>\n"; |
||
133 | |||
134 | break; |
||
135 | case 'character': |
||
136 | case 'character[]': |
||
137 | $n = substr_count($value, "\n"); |
||
138 | $n = $n < 5 ? 5 : $n; |
||
139 | $n = $n > 20 ? 20 : $n; |
||
140 | echo '<textarea name="', htmlspecialchars($name), "\" rows=\"{$n}\" cols=\"35\"{$extra_str}>\n"; |
||
141 | echo htmlspecialchars($value); |
||
142 | echo "</textarea>\n"; |
||
143 | |||
144 | break; |
||
145 | default: |
||
146 | echo '<input name="', htmlspecialchars($name), '" value="', htmlspecialchars($value), "\" size=\"35\"{$extra_str} />\n"; |
||
147 | |||
148 | break; |
||
149 | } |
||
150 | } |
||
151 | |||
152 | /** |
||
153 | * Escapes bytea data for display on the screen. |
||
154 | * |
||
155 | * @param string $data The bytea data |
||
156 | * |
||
157 | * @return string Data formatted for on-screen display |
||
158 | */ |
||
159 | public function escapeBytea($data) |
||
160 | { |
||
161 | return htmlentities($data, ENT_QUOTES, 'UTF-8'); |
||
162 | } |
||
163 | |||
164 | /** |
||
165 | * Return all information about a particular database. |
||
166 | * |
||
167 | * @param $database The name of the database to retrieve |
||
168 | * |
||
169 | * @return \PHPPgAdmin\ADORecordSet The database info |
||
170 | */ |
||
171 | public function getDatabase($database) |
||
172 | { |
||
173 | $this->clean($database); |
||
174 | $sql = "SELECT * FROM pg_database WHERE datname='{$database}'"; |
||
175 | |||
176 | return $this->selectSet($sql); |
||
177 | } |
||
178 | |||
179 | /** |
||
180 | * Cleans (escapes) a string. |
||
181 | * |
||
182 | * @param string $str The string to clean, by reference |
||
183 | * |
||
184 | * @return string The cleaned string |
||
185 | */ |
||
186 | public function clean(&$str) |
||
187 | { |
||
188 | if ($str === null) { |
||
189 | return null; |
||
190 | } |
||
191 | |||
192 | $str = str_replace("\r\n", "\n", $str); |
||
193 | $str = pg_escape_string($str); |
||
2 ignored issues
–
show
|
|||
194 | |||
195 | return $str; |
||
196 | } |
||
197 | |||
198 | /** |
||
199 | * Return all database available on the server. |
||
200 | * |
||
201 | * @param $currentdatabase database name that should be on top of the resultset |
||
202 | * |
||
203 | * @return \PHPPgAdmin\ADORecordSet A list of databases, sorted alphabetically |
||
204 | */ |
||
205 | public function getDatabases($currentdatabase = null) |
||
206 | { |
||
207 | $conf = $this->conf; |
||
208 | $server_info = $this->server_info; |
||
209 | |||
210 | if (isset($conf['owned_only']) && $conf['owned_only'] && !$this->isSuperUser()) { |
||
211 | $username = $server_info['username']; |
||
212 | $this->clean($username); |
||
213 | $clause = " AND pr.rolname='{$username}'"; |
||
214 | } else { |
||
215 | $clause = ''; |
||
216 | } |
||
217 | if (isset($server_info['useonlydefaultdb']) && $server_info['useonlydefaultdb']) { |
||
218 | $currentdatabase = $server_info['defaultdb']; |
||
219 | $clause .= " AND pdb.datname = '{$currentdatabase}' "; |
||
220 | } |
||
221 | |||
222 | if (isset($server_info['hiddendbs']) && $server_info['hiddendbs']) { |
||
223 | $hiddendbs = $server_info['hiddendbs']; |
||
224 | $not_in = "('" . implode("','", $hiddendbs) . "')"; |
||
225 | $clause .= " AND pdb.datname NOT IN {$not_in} "; |
||
226 | } |
||
227 | |||
228 | if ($currentdatabase != null) { |
||
229 | $this->clean($currentdatabase); |
||
230 | $orderby = "ORDER BY pdb.datname = '{$currentdatabase}' DESC, pdb.datname"; |
||
231 | } else { |
||
232 | $orderby = 'ORDER BY pdb.datname'; |
||
233 | } |
||
234 | |||
235 | if (!$conf['show_system']) { |
||
236 | $where = ' AND NOT pdb.datistemplate'; |
||
237 | } else { |
||
238 | $where = ' AND pdb.datallowconn'; |
||
239 | } |
||
240 | |||
241 | $sql = " |
||
242 | SELECT pdb.datname AS datname, |
||
243 | pr.rolname AS datowner, |
||
244 | pg_encoding_to_char(encoding) AS datencoding, |
||
245 | (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pdb.oid=pd.objoid AND pd.classoid='pg_database'::regclass) AS datcomment, |
||
246 | (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=pdb.dattablespace) AS tablespace, |
||
247 | CASE WHEN pg_catalog.has_database_privilege(current_user, pdb.oid, 'CONNECT') |
||
248 | THEN pg_catalog.pg_database_size(pdb.oid) |
||
249 | ELSE -1 -- set this magic value, which we will convert to no access later |
||
250 | END as dbsize, |
||
251 | pdb.datcollate, |
||
252 | pdb.datctype |
||
253 | FROM pg_catalog.pg_database pdb |
||
254 | LEFT JOIN pg_catalog.pg_roles pr ON (pdb.datdba = pr.oid) |
||
255 | WHERE true |
||
256 | {$where} |
||
257 | {$clause} |
||
258 | {$orderby}"; |
||
259 | |||
260 | return $this->selectSet($sql); |
||
261 | } |
||
262 | |||
263 | /** |
||
264 | * Determines whether or not a user is a super user. |
||
265 | * |
||
266 | * @param string $username The username of the user |
||
267 | * |
||
268 | * @return true if is a super user, false otherwise |
||
269 | */ |
||
270 | public function isSuperUser($username = '') |
||
271 | { |
||
272 | $this->clean($username); |
||
273 | |||
274 | if (empty($usename)) { |
||
275 | $val = pg_parameter_status($this->conn->_connectionID, 'is_superuser'); |
||
276 | if ($val !== false) { |
||
277 | return $val == 'on'; |
||
278 | } |
||
279 | } |
||
280 | |||
281 | $sql = "SELECT usesuper FROM pg_user WHERE usename='{$username}'"; |
||
282 | |||
283 | $usesuper = $this->selectField($sql, 'usesuper'); |
||
284 | if ($usesuper == -1) { |
||
285 | return false; |
||
286 | } |
||
287 | |||
288 | return $usesuper == 't'; |
||
289 | } |
||
290 | |||
291 | /** |
||
292 | * Return the database comment of a db from the shared description table. |
||
293 | * |
||
294 | * @param string $database the name of the database to get the comment for |
||
295 | * |
||
296 | * @return \PHPPgAdmin\ADORecordSet recordset of the db comment info |
||
297 | */ |
||
298 | public function getDatabaseComment($database) |
||
299 | { |
||
300 | $this->clean($database); |
||
301 | $sql = |
||
302 | "SELECT description FROM pg_catalog.pg_database JOIN pg_catalog.pg_shdescription ON (oid=objoid AND classoid='pg_database'::regclass) WHERE pg_database.datname = '{$database}' "; |
||
303 | |||
304 | return $this->selectSet($sql); |
||
305 | } |
||
306 | |||
307 | /** |
||
308 | * Return the database owner of a db. |
||
309 | * |
||
310 | * @param string $database the name of the database to get the owner for |
||
311 | * |
||
312 | * @return \PHPPgAdmin\ADORecordSet recordset of the db owner info |
||
313 | */ |
||
314 | public function getDatabaseOwner($database) |
||
315 | { |
||
316 | $this->clean($database); |
||
317 | $sql = "SELECT usename FROM pg_user, pg_database WHERE pg_user.usesysid = pg_database.datdba AND pg_database.datname = '{$database}' "; |
||
318 | |||
319 | return $this->selectSet($sql); |
||
320 | } |
||
321 | |||
322 | // Help functions |
||
323 | |||
324 | // Database functions |
||
325 | |||
326 | /** |
||
327 | * Returns the current database encoding. |
||
328 | * |
||
329 | * @return string The encoding. eg. SQL_ASCII, UTF-8, etc. |
||
330 | */ |
||
331 | public function getDatabaseEncoding() |
||
332 | { |
||
333 | return pg_parameter_status($this->conn->_connectionID, 'server_encoding'); |
||
334 | } |
||
335 | |||
336 | /** |
||
337 | * Returns the current default_with_oids setting. |
||
338 | * |
||
339 | * @return string default_with_oids setting |
||
340 | */ |
||
341 | public function getDefaultWithOid() |
||
342 | { |
||
343 | $sql = 'SHOW default_with_oids'; |
||
344 | |||
345 | return $this->selectField($sql, 'default_with_oids'); |
||
346 | } |
||
347 | |||
348 | /** |
||
349 | * Creates a database. |
||
350 | * |
||
351 | * @param $database The name of the database to create |
||
352 | * @param $encoding Encoding of the database |
||
353 | * @param string $tablespace (optional) The tablespace name |
||
354 | * @param string $comment |
||
355 | * @param string $template |
||
356 | * @param string $lc_collate |
||
357 | * @param string $lc_ctype |
||
358 | * |
||
359 | * @return int 0 success |
||
360 | */ |
||
361 | public function createDatabase( |
||
362 | $database, |
||
363 | $encoding, |
||
364 | $tablespace = '', |
||
365 | $comment = '', |
||
366 | $template = 'template1', |
||
367 | $lc_collate = '', |
||
368 | $lc_ctype = '' |
||
369 | ) { |
||
370 | $this->fieldClean($database); |
||
371 | $this->clean($encoding); |
||
372 | $this->fieldClean($tablespace); |
||
373 | $this->fieldClean($template); |
||
374 | $this->clean($lc_collate); |
||
375 | $this->clean($lc_ctype); |
||
376 | |||
377 | $sql = "CREATE DATABASE \"{$database}\" WITH TEMPLATE=\"{$template}\""; |
||
378 | |||
379 | if ($encoding != '') { |
||
380 | $sql .= " ENCODING='{$encoding}'"; |
||
381 | } |
||
382 | |||
383 | if ($lc_collate != '') { |
||
384 | $sql .= " LC_COLLATE='{$lc_collate}'"; |
||
385 | } |
||
386 | |||
387 | if ($lc_ctype != '') { |
||
388 | $sql .= " LC_CTYPE='{$lc_ctype}'"; |
||
389 | } |
||
390 | |||
391 | if ($tablespace != '' && $this->hasTablespaces()) { |
||
392 | $sql .= " TABLESPACE \"{$tablespace}\""; |
||
393 | } |
||
394 | |||
395 | $status = $this->execute($sql); |
||
396 | if ($status != 0) { |
||
397 | return -1; |
||
398 | } |
||
399 | |||
400 | if ($comment != '' && $this->hasSharedComments()) { |
||
401 | $status = $this->setComment('DATABASE', $database, '', $comment); |
||
402 | if ($status != 0) { |
||
403 | return -2; |
||
404 | } |
||
405 | } |
||
406 | |||
407 | return 0; |
||
408 | } |
||
409 | |||
410 | /** |
||
411 | * Cleans (escapes) an object name (eg. table, field). |
||
412 | * |
||
413 | * @param string $str The string to clean, by reference |
||
414 | * |
||
415 | * @return string The cleaned string |
||
416 | */ |
||
417 | public function fieldClean(&$str) |
||
418 | { |
||
419 | if ($str === null) { |
||
420 | return null; |
||
421 | } |
||
422 | |||
423 | $str = str_replace('"', '""', $str); |
||
424 | |||
425 | return $str; |
||
426 | } |
||
427 | |||
428 | /** |
||
429 | * Sets the comment for an object in the database. |
||
430 | * |
||
431 | * @pre All parameters must already be cleaned |
||
432 | * |
||
433 | * @param $obj_type One of 'TABLE' | 'COLUMN' | 'VIEW' | 'SCHEMA' | 'SEQUENCE' | 'TYPE' | 'FUNCTION' | 'AGGREGATE' |
||
1 ignored issue
–
show
|
|||
434 | * @param $obj_name the name of the object for which to attach a comment |
||
435 | * @param $table Name of table that $obj_name belongs to. Ignored unless $obj_type is 'TABLE' or 'COLUMN'. |
||
436 | * @param $comment the comment to add |
||
437 | * @param null $basetype |
||
438 | * |
||
439 | * @return int 0 if operation was successful |
||
440 | */ |
||
441 | public function setComment($obj_type, $obj_name, $table, $comment, $basetype = null) |
||
442 | { |
||
443 | $sql = "COMMENT ON {$obj_type} "; |
||
444 | $f_schema = $this->_schema; |
||
445 | $this->fieldClean($f_schema); |
||
446 | $this->clean($comment); // Passing in an already cleaned comment will lead to double escaped data |
||
447 | // So, while counter-intuitive, it is important to not clean comments before |
||
448 | // calling setComment. We will clean it here instead. |
||
449 | /* |
||
450 | $this->fieldClean($table); |
||
451 | $this->fieldClean($obj_name); |
||
452 | */ |
||
453 | |||
454 | switch ($obj_type) { |
||
455 | case 'TABLE': |
||
456 | $sql .= "\"{$f_schema}\".\"{$table}\" IS "; |
||
457 | |||
458 | break; |
||
459 | case 'COLUMN': |
||
460 | $sql .= "\"{$f_schema}\".\"{$table}\".\"{$obj_name}\" IS "; |
||
461 | |||
462 | break; |
||
463 | case 'SEQUENCE': |
||
464 | case 'VIEW': |
||
465 | case 'TEXT SEARCH CONFIGURATION': |
||
466 | case 'TEXT SEARCH DICTIONARY': |
||
467 | case 'TEXT SEARCH TEMPLATE': |
||
468 | case 'TEXT SEARCH PARSER': |
||
469 | case 'TYPE': |
||
470 | $sql .= "\"{$f_schema}\"."; |
||
471 | // no break |
||
472 | case 'DATABASE': |
||
473 | case 'ROLE': |
||
474 | case 'SCHEMA': |
||
475 | case 'TABLESPACE': |
||
476 | $sql .= "\"{$obj_name}\" IS "; |
||
477 | |||
478 | break; |
||
479 | case 'FUNCTION': |
||
480 | $sql .= "\"{$f_schema}\".{$obj_name} IS "; |
||
481 | |||
482 | break; |
||
483 | case 'AGGREGATE': |
||
484 | $sql .= "\"{$f_schema}\".\"{$obj_name}\" (\"{$basetype}\") IS "; |
||
485 | |||
486 | break; |
||
487 | default: |
||
488 | // Unknown object type |
||
489 | return -1; |
||
490 | } |
||
491 | |||
492 | if ($comment != '') { |
||
493 | $sql .= "'{$comment}';"; |
||
494 | } else { |
||
495 | $sql .= 'NULL;'; |
||
496 | } |
||
497 | |||
498 | return $this->execute($sql); |
||
499 | } |
||
500 | |||
501 | /** |
||
502 | * Drops a database. |
||
503 | * |
||
504 | * @param $database The name of the database to drop |
||
505 | * |
||
506 | * @return integer 0 if operation was successful |
||
507 | */ |
||
508 | public function dropDatabase($database) |
||
509 | { |
||
510 | $this->fieldClean($database); |
||
511 | $sql = "DROP DATABASE \"{$database}\""; |
||
512 | |||
513 | return $this->execute($sql); |
||
514 | } |
||
515 | |||
516 | /** |
||
517 | * Alters a database |
||
518 | * the multiple return vals are for postgres 8+ which support more functionality in alter database. |
||
519 | * |
||
520 | * @param $dbName The name of the database |
||
521 | * @param $newName new name for the database |
||
522 | * @param string $newOwner The new owner for the database |
||
523 | * @param string $comment |
||
524 | * |
||
525 | * @return bool|int 0 success |
||
526 | */ |
||
527 | public function alterDatabase($dbName, $newName, $newOwner = '', $comment = '') |
||
528 | { |
||
529 | $status = $this->beginTransaction(); |
||
530 | if ($status != 0) { |
||
531 | $this->rollbackTransaction(); |
||
532 | |||
533 | return -1; |
||
534 | } |
||
535 | |||
536 | if ($dbName != $newName) { |
||
537 | $status = $this->alterDatabaseRename($dbName, $newName); |
||
538 | if ($status != 0) { |
||
539 | $this->rollbackTransaction(); |
||
540 | |||
541 | return -3; |
||
542 | } |
||
543 | $dbName = $newName; |
||
544 | } |
||
545 | |||
546 | if ($newOwner != '') { |
||
547 | $status = $this->alterDatabaseOwner($newName, $newOwner); |
||
548 | if ($status != 0) { |
||
549 | $this->rollbackTransaction(); |
||
550 | |||
551 | return -2; |
||
552 | } |
||
553 | } |
||
554 | |||
555 | $this->fieldClean($dbName); |
||
556 | $status = $this->setComment('DATABASE', $dbName, '', $comment); |
||
557 | if ($status != 0) { |
||
558 | $this->rollbackTransaction(); |
||
559 | |||
560 | return -4; |
||
561 | } |
||
562 | |||
563 | return $this->endTransaction(); |
||
564 | } |
||
565 | |||
566 | /** |
||
567 | * Renames a database, note that this operation cannot be |
||
568 | * performed on a database that is currently being connected to. |
||
569 | * |
||
570 | * @param string $oldName name of database to rename |
||
571 | * @param string $newName new name of database |
||
572 | * |
||
573 | * @return int 0 on success |
||
574 | */ |
||
575 | public function alterDatabaseRename($oldName, $newName) |
||
576 | { |
||
577 | $this->fieldClean($oldName); |
||
578 | $this->fieldClean($newName); |
||
579 | |||
580 | if ($oldName != $newName) { |
||
581 | $sql = "ALTER DATABASE \"{$oldName}\" RENAME TO \"{$newName}\""; |
||
582 | |||
583 | return $this->execute($sql); |
||
584 | } |
||
585 | |||
586 | return 0; |
||
587 | } |
||
588 | |||
589 | /** |
||
590 | * Changes ownership of a database |
||
591 | * This can only be done by a superuser or the owner of the database. |
||
592 | * |
||
593 | * @param string $dbName database to change ownership of |
||
594 | * @param string $newOwner user that will own the database |
||
595 | * |
||
596 | * @return int 0 on success |
||
597 | */ |
||
598 | public function alterDatabaseOwner($dbName, $newOwner) |
||
599 | { |
||
600 | $this->fieldClean($dbName); |
||
601 | $this->fieldClean($newOwner); |
||
602 | |||
603 | $sql = "ALTER DATABASE \"{$dbName}\" OWNER TO \"{$newOwner}\""; |
||
604 | |||
605 | return $this->execute($sql); |
||
606 | } |
||
607 | |||
608 | /** |
||
609 | * Returns prepared transactions information. |
||
610 | * |
||
611 | * @param $database (optional) Find only prepared transactions executed in a specific database |
||
612 | * |
||
613 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
614 | */ |
||
615 | public function getPreparedXacts($database = null) |
||
616 | { |
||
617 | if ($database === null) { |
||
618 | $sql = 'SELECT * FROM pg_prepared_xacts'; |
||
619 | } else { |
||
620 | $this->clean($database); |
||
621 | $sql = "SELECT transaction, gid, prepared, owner FROM pg_prepared_xacts |
||
622 | WHERE database='{$database}' ORDER BY owner"; |
||
623 | } |
||
624 | |||
625 | return $this->selectSet($sql); |
||
626 | } |
||
627 | |||
628 | /** |
||
629 | * Searches all system catalogs to find objects that match a certain name. |
||
630 | * |
||
631 | * @param $term The search term |
||
632 | * @param $filter The object type to restrict to ('' means no restriction) |
||
633 | * |
||
634 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
635 | */ |
||
636 | public function findObject($term, $filter) |
||
637 | { |
||
638 | $conf = $this->conf; |
||
639 | |||
640 | /*about escaping: |
||
641 | * SET standard_conforming_string is not available before 8.2 |
||
642 | * So we must use PostgreSQL specific notation :/ |
||
643 | * E'' notation is not available before 8.1 |
||
644 | * $$ is available since 8.0 |
||
645 | * Nothing specific from 7.4 |
||
646 | */ |
||
647 | |||
648 | // Escape search term for ILIKE match |
||
649 | $this->clean($term); |
||
650 | $this->clean($filter); |
||
651 | $term = str_replace('_', '\_', $term); |
||
652 | $term = str_replace('%', '\%', $term); |
||
653 | |||
654 | // Exclude system relations if necessary |
||
655 | if (!$conf['show_system']) { |
||
656 | // XXX: The mention of information_schema here is in the wrong place, but |
||
657 | // it's the quickest fix to exclude the info schema from 7.4 |
||
658 | $where = " AND pn.nspname NOT LIKE \$_PATERN_\$pg\\_%\$_PATERN_\$ AND pn.nspname != 'information_schema'"; |
||
659 | $lan_where = 'AND pl.lanispl'; |
||
660 | } else { |
||
661 | $where = ''; |
||
662 | $lan_where = ''; |
||
663 | } |
||
664 | |||
665 | // Apply outer filter |
||
666 | $sql = ''; |
||
667 | if ($filter != '') { |
||
668 | $sql = 'SELECT * FROM ('; |
||
669 | } |
||
670 | |||
671 | $term = "\$_PATERN_\$%{$term}%\$_PATERN_\$"; |
||
672 | |||
673 | $sql .= " |
||
674 | SELECT 'SCHEMA' AS type, oid, NULL AS schemaname, NULL AS relname, nspname AS name |
||
675 | FROM pg_catalog.pg_namespace pn WHERE nspname ILIKE {$term} {$where} |
||
676 | UNION ALL |
||
677 | SELECT CASE WHEN relkind='r' THEN 'TABLE' WHEN relkind='v' THEN 'VIEW' WHEN relkind='S' THEN 'SEQUENCE' END, pc.oid, |
||
678 | pn.nspname, NULL, pc.relname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn |
||
679 | WHERE pc.relnamespace=pn.oid AND relkind IN ('r', 'v', 'S') AND relname ILIKE {$term} {$where} |
||
680 | UNION ALL |
||
681 | SELECT CASE WHEN pc.relkind='r' THEN 'COLUMNTABLE' ELSE 'COLUMNVIEW' END, NULL, pn.nspname, pc.relname, pa.attname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn, |
||
682 | pg_catalog.pg_attribute pa WHERE pc.relnamespace=pn.oid AND pc.oid=pa.attrelid |
||
683 | AND pa.attname ILIKE {$term} AND pa.attnum > 0 AND NOT pa.attisdropped AND pc.relkind IN ('r', 'v') {$where} |
||
684 | UNION ALL |
||
685 | SELECT 'FUNCTION', pp.oid, pn.nspname, NULL, pp.proname || '(' || pg_catalog.oidvectortypes(pp.proargtypes) || ')' FROM pg_catalog.pg_proc pp, pg_catalog.pg_namespace pn |
||
686 | WHERE pp.pronamespace=pn.oid AND NOT pp.proisagg AND pp.proname ILIKE {$term} {$where} |
||
687 | UNION ALL |
||
688 | SELECT 'INDEX', NULL, pn.nspname, pc.relname, pc2.relname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn, |
||
689 | pg_catalog.pg_index pi, pg_catalog.pg_class pc2 WHERE pc.relnamespace=pn.oid AND pc.oid=pi.indrelid |
||
690 | AND pi.indexrelid=pc2.oid |
||
691 | AND NOT EXISTS ( |
||
692 | SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c |
||
693 | ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) |
||
694 | WHERE d.classid = pc2.tableoid AND d.objid = pc2.oid AND d.deptype = 'i' AND c.contype IN ('u', 'p') |
||
695 | ) |
||
696 | AND pc2.relname ILIKE {$term} {$where} |
||
697 | UNION ALL |
||
698 | SELECT 'CONSTRAINTTABLE', NULL, pn.nspname, pc.relname, pc2.conname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn, |
||
699 | pg_catalog.pg_constraint pc2 WHERE pc.relnamespace=pn.oid AND pc.oid=pc2.conrelid AND pc2.conrelid != 0 |
||
700 | AND CASE WHEN pc2.contype IN ('f', 'c') THEN TRUE ELSE NOT EXISTS ( |
||
701 | SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c |
||
702 | ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) |
||
703 | WHERE d.classid = pc2.tableoid AND d.objid = pc2.oid AND d.deptype = 'i' AND c.contype IN ('u', 'p') |
||
704 | ) END |
||
705 | AND pc2.conname ILIKE {$term} {$where} |
||
706 | UNION ALL |
||
707 | SELECT 'CONSTRAINTDOMAIN', pt.oid, pn.nspname, pt.typname, pc.conname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn, |
||
708 | pg_catalog.pg_constraint pc WHERE pt.typnamespace=pn.oid AND pt.oid=pc.contypid AND pc.contypid != 0 |
||
709 | AND pc.conname ILIKE {$term} {$where} |
||
710 | UNION ALL |
||
711 | SELECT 'TRIGGER', NULL, pn.nspname, pc.relname, pt.tgname FROM pg_catalog.pg_class pc, pg_catalog.pg_namespace pn, |
||
712 | pg_catalog.pg_trigger pt WHERE pc.relnamespace=pn.oid AND pc.oid=pt.tgrelid |
||
713 | AND ( pt.tgconstraint = 0 OR NOT EXISTS |
||
714 | (SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c |
||
715 | ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) |
||
716 | WHERE d.classid = pt.tableoid AND d.objid = pt.oid AND d.deptype = 'i' AND c.contype = 'f')) |
||
717 | AND pt.tgname ILIKE {$term} {$where} |
||
718 | UNION ALL |
||
719 | SELECT 'RULETABLE', NULL, pn.nspname AS schemaname, c.relname AS tablename, r.rulename FROM pg_catalog.pg_rewrite r |
||
720 | JOIN pg_catalog.pg_class c ON c.oid = r.ev_class |
||
721 | LEFT JOIN pg_catalog.pg_namespace pn ON pn.oid = c.relnamespace |
||
722 | WHERE c.relkind='r' AND r.rulename != '_RETURN' AND r.rulename ILIKE {$term} {$where} |
||
723 | UNION ALL |
||
724 | SELECT 'RULEVIEW', NULL, pn.nspname AS schemaname, c.relname AS tablename, r.rulename FROM pg_catalog.pg_rewrite r |
||
725 | JOIN pg_catalog.pg_class c ON c.oid = r.ev_class |
||
726 | LEFT JOIN pg_catalog.pg_namespace pn ON pn.oid = c.relnamespace |
||
727 | WHERE c.relkind='v' AND r.rulename != '_RETURN' AND r.rulename ILIKE {$term} {$where} |
||
728 | "; |
||
729 | |||
730 | // Add advanced objects if show_advanced is set |
||
731 | if ($conf['show_advanced']) { |
||
732 | $sql .= " |
||
733 | UNION ALL |
||
734 | SELECT CASE WHEN pt.typtype='d' THEN 'DOMAIN' ELSE 'TYPE' END, pt.oid, pn.nspname, NULL, |
||
735 | pt.typname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn |
||
736 | WHERE pt.typnamespace=pn.oid AND typname ILIKE {$term} |
||
737 | AND (pt.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = pt.typrelid)) |
||
738 | {$where} |
||
739 | UNION ALL |
||
740 | SELECT 'OPERATOR', po.oid, pn.nspname, NULL, po.oprname FROM pg_catalog.pg_operator po, pg_catalog.pg_namespace pn |
||
741 | WHERE po.oprnamespace=pn.oid AND oprname ILIKE {$term} {$where} |
||
742 | UNION ALL |
||
743 | SELECT 'CONVERSION', pc.oid, pn.nspname, NULL, pc.conname FROM pg_catalog.pg_conversion pc, |
||
744 | pg_catalog.pg_namespace pn WHERE pc.connamespace=pn.oid AND conname ILIKE {$term} {$where} |
||
745 | UNION ALL |
||
746 | SELECT 'LANGUAGE', pl.oid, NULL, NULL, pl.lanname FROM pg_catalog.pg_language pl |
||
747 | WHERE lanname ILIKE {$term} {$lan_where} |
||
748 | UNION ALL |
||
749 | SELECT DISTINCT ON (p.proname) 'AGGREGATE', p.oid, pn.nspname, NULL, p.proname FROM pg_catalog.pg_proc p |
||
750 | LEFT JOIN pg_catalog.pg_namespace pn ON p.pronamespace=pn.oid |
||
751 | WHERE p.proisagg AND p.proname ILIKE {$term} {$where} |
||
752 | UNION ALL |
||
753 | SELECT DISTINCT ON (po.opcname) 'OPCLASS', po.oid, pn.nspname, NULL, po.opcname FROM pg_catalog.pg_opclass po, |
||
754 | pg_catalog.pg_namespace pn WHERE po.opcnamespace=pn.oid |
||
755 | AND po.opcname ILIKE {$term} {$where} |
||
756 | "; |
||
757 | } // Otherwise just add domains |
||
758 | else { |
||
759 | $sql .= " |
||
760 | UNION ALL |
||
761 | SELECT 'DOMAIN', pt.oid, pn.nspname, NULL, |
||
762 | pt.typname FROM pg_catalog.pg_type pt, pg_catalog.pg_namespace pn |
||
763 | WHERE pt.typnamespace=pn.oid AND pt.typtype='d' AND typname ILIKE {$term} |
||
764 | AND (pt.typrelid = 0 OR (SELECT c.relkind = 'c' FROM pg_catalog.pg_class c WHERE c.oid = pt.typrelid)) |
||
765 | {$where} |
||
766 | "; |
||
767 | } |
||
768 | |||
769 | if ($filter != '') { |
||
770 | // We use like to make RULE, CONSTRAINT and COLUMN searches work |
||
771 | $sql .= ") AS sub WHERE type LIKE '{$filter}%' "; |
||
772 | } |
||
773 | |||
774 | $sql .= 'ORDER BY type, schemaname, relname, name'; |
||
775 | |||
776 | return $this->selectSet($sql); |
||
777 | } |
||
778 | |||
779 | /** |
||
780 | * Returns all available variable information. |
||
781 | * |
||
782 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
783 | */ |
||
784 | public function getVariables() |
||
785 | { |
||
786 | $sql = 'SHOW ALL'; |
||
787 | |||
788 | return $this->selectSet($sql); |
||
789 | } |
||
790 | |||
791 | // Schema functons |
||
792 | |||
793 | /** |
||
794 | * Return all schemas in the current database. |
||
795 | * |
||
796 | * @return \PHPPgAdmin\ADORecordSet All schemas, sorted alphabetically |
||
797 | */ |
||
798 | public function getSchemas() |
||
799 | { |
||
800 | $conf = $this->conf; |
||
801 | |||
802 | if (!$conf['show_system']) { |
||
803 | $where = "WHERE nspname NOT LIKE 'pg@_%' ESCAPE '@' AND nspname != 'information_schema'"; |
||
804 | } else { |
||
805 | $where = "WHERE nspname !~ '^pg_t(emp_[0-9]+|oast)$'"; |
||
806 | } |
||
807 | |||
808 | $sql = " |
||
809 | SELECT pn.nspname, |
||
810 | pu.rolname AS nspowner, |
||
811 | pg_catalog.obj_description(pn.oid, 'pg_namespace') AS nspcomment, |
||
812 | pg_size_pretty(SUM(pg_total_relation_size(pg_class.oid))) as schema_size |
||
813 | FROM pg_catalog.pg_class |
||
814 | JOIN pg_catalog.pg_namespace pn ON relnamespace = pn.oid |
||
815 | LEFT JOIN pg_catalog.pg_roles pu ON (pn.nspowner = pu.oid) |
||
816 | {$where} |
||
817 | GROUP BY pn.nspname, pu.rolname, pg_catalog.obj_description(pn.oid, 'pg_namespace') |
||
818 | ORDER BY nspname"; |
||
819 | |||
820 | return $this->selectSet($sql); |
||
821 | } |
||
822 | |||
823 | /** |
||
824 | * Sets the current working schema. Will also set Class variable. |
||
825 | * |
||
826 | * @param $schema The the name of the schema to work in |
||
827 | * |
||
828 | * @return int 0 if operation was successful |
||
829 | */ |
||
830 | public function setSchema($schema) |
||
831 | { |
||
832 | // Get the current schema search path, including 'pg_catalog'. |
||
833 | $search_path = $this->getSearchPath(); |
||
834 | // Prepend $schema to search path |
||
835 | array_unshift($search_path, $schema); |
||
836 | $status = $this->setSearchPath($search_path); |
||
837 | if ($status == 0) { |
||
838 | $this->_schema = $schema; |
||
839 | |||
840 | return 0; |
||
841 | } |
||
842 | |||
843 | return $status; |
||
844 | } |
||
845 | |||
846 | /** |
||
847 | * Return the current schema search path. |
||
848 | * |
||
849 | * @return \PHPPgAdmin\ADORecordSet array of schema names |
||
850 | */ |
||
851 | public function getSearchPath() |
||
852 | { |
||
853 | $sql = 'SELECT current_schemas(false) AS search_path'; |
||
854 | |||
855 | return $this->phpArray($this->selectField($sql, 'search_path')); |
||
856 | } |
||
857 | |||
858 | /** |
||
859 | * Sets the current schema search path. |
||
860 | * |
||
861 | * @param $paths An array of schemas in required search order |
||
862 | * |
||
863 | * @return int 0 if operation was successful |
||
864 | */ |
||
865 | public function setSearchPath($paths) |
||
866 | { |
||
867 | if (!is_array($paths)) { |
||
868 | return -1; |
||
869 | } |
||
870 | |||
871 | if (sizeof($paths) == 0) { |
||
872 | return -2; |
||
873 | } |
||
874 | if (sizeof($paths) == 1 && $paths[0] == '') { |
||
875 | // Need to handle empty paths in some cases |
||
876 | $paths[0] = 'pg_catalog'; |
||
877 | } |
||
878 | |||
879 | // Loop over all the paths to check that none are empty |
||
880 | $temp = []; |
||
881 | foreach ($paths as $schema) { |
||
882 | if ($schema != '') { |
||
883 | $temp[] = $schema; |
||
884 | } |
||
885 | } |
||
886 | $this->fieldArrayClean($temp); |
||
887 | |||
888 | $sql = 'SET SEARCH_PATH TO "' . implode('","', $temp) . '"'; |
||
889 | |||
890 | return $this->execute($sql); |
||
891 | } |
||
892 | |||
893 | /** |
||
894 | * Cleans (escapes) an array of field names. |
||
895 | * |
||
896 | * @param $arr The array to clean, by reference |
||
897 | * |
||
898 | * @return The cleaned array |
||
899 | */ |
||
900 | public function fieldArrayClean(&$arr) |
||
901 | { |
||
902 | foreach ($arr as $k => $v) { |
||
903 | if ($v === null) { |
||
904 | continue; |
||
905 | } |
||
906 | |||
907 | $arr[$k] = str_replace('"', '""', $v); |
||
908 | } |
||
909 | |||
910 | return $arr; |
||
911 | } |
||
912 | |||
913 | /** |
||
914 | * Creates a new schema. |
||
915 | * |
||
916 | * @param $schemaname The name of the schema to create |
||
917 | * @param string $authorization (optional) The username to create the schema for |
||
918 | * @param string $comment (optional) If omitted, defaults to nothing |
||
919 | * |
||
920 | * @return bool|int 0 success |
||
921 | */ |
||
922 | public function createSchema($schemaname, $authorization = '', $comment = '') |
||
923 | { |
||
924 | $this->fieldClean($schemaname); |
||
925 | $this->fieldClean($authorization); |
||
926 | |||
927 | $sql = "CREATE SCHEMA \"{$schemaname}\""; |
||
928 | if ($authorization != '') { |
||
929 | $sql .= " AUTHORIZATION \"{$authorization}\""; |
||
930 | } |
||
931 | |||
932 | if ($comment != '') { |
||
933 | $status = $this->beginTransaction(); |
||
934 | if ($status != 0) { |
||
935 | return -1; |
||
936 | } |
||
937 | } |
||
938 | |||
939 | // Create the new schema |
||
940 | $status = $this->execute($sql); |
||
941 | if ($status != 0) { |
||
942 | $this->rollbackTransaction(); |
||
943 | |||
944 | return -1; |
||
945 | } |
||
946 | |||
947 | // Set the comment |
||
948 | if ($comment != '') { |
||
949 | $status = $this->setComment('SCHEMA', $schemaname, '', $comment); |
||
950 | if ($status != 0) { |
||
951 | $this->rollbackTransaction(); |
||
952 | |||
953 | return -1; |
||
954 | } |
||
955 | |||
956 | return $this->endTransaction(); |
||
957 | } |
||
958 | |||
959 | return 0; |
||
960 | } |
||
961 | |||
962 | /** |
||
963 | * Updates a schema. |
||
964 | * |
||
965 | * @param $schemaname The name of the schema to drop |
||
966 | * @param $comment The new comment for this schema |
||
967 | * @param $name |
||
968 | * @param $owner The new owner for this schema |
||
969 | * |
||
970 | * @return bool|int 0 success |
||
971 | */ |
||
972 | public function updateSchema($schemaname, $comment, $name, $owner) |
||
973 | { |
||
974 | $this->fieldClean($schemaname); |
||
975 | $this->fieldClean($name); |
||
976 | $this->fieldClean($owner); |
||
977 | |||
978 | $status = $this->beginTransaction(); |
||
979 | if ($status != 0) { |
||
980 | $this->rollbackTransaction(); |
||
981 | |||
982 | return -1; |
||
983 | } |
||
984 | |||
985 | $status = $this->setComment('SCHEMA', $schemaname, '', $comment); |
||
986 | if ($status != 0) { |
||
987 | $this->rollbackTransaction(); |
||
988 | |||
989 | return -1; |
||
990 | } |
||
991 | |||
992 | $schema_rs = $this->getSchemaByName($schemaname); |
||
993 | /* Only if the owner change */ |
||
994 | if ($schema_rs->fields['ownername'] != $owner) { |
||
995 | $sql = "ALTER SCHEMA \"{$schemaname}\" OWNER TO \"{$owner}\""; |
||
996 | $status = $this->execute($sql); |
||
997 | if ($status != 0) { |
||
998 | $this->rollbackTransaction(); |
||
999 | |||
1000 | return -1; |
||
1001 | } |
||
1002 | } |
||
1003 | |||
1004 | // Only if the name has changed |
||
1005 | if ($name != $schemaname) { |
||
1006 | $sql = "ALTER SCHEMA \"{$schemaname}\" RENAME TO \"{$name}\""; |
||
1007 | $status = $this->execute($sql); |
||
1008 | if ($status != 0) { |
||
1009 | $this->rollbackTransaction(); |
||
1010 | |||
1011 | return -1; |
||
1012 | } |
||
1013 | } |
||
1014 | |||
1015 | return $this->endTransaction(); |
||
1016 | } |
||
1017 | |||
1018 | /** |
||
1019 | * Return all information relating to a schema. |
||
1020 | * |
||
1021 | * @param $schema The name of the schema |
||
1022 | * |
||
1023 | * @return Schema information |
||
1024 | */ |
||
1025 | public function getSchemaByName($schema) |
||
1026 | { |
||
1027 | $this->clean($schema); |
||
1028 | $sql = " |
||
1029 | SELECT nspname, nspowner, r.rolname AS ownername, nspacl, |
||
1030 | pg_catalog.obj_description(pn.oid, 'pg_namespace') as nspcomment |
||
1031 | FROM pg_catalog.pg_namespace pn |
||
1032 | LEFT JOIN pg_roles as r ON pn.nspowner = r.oid |
||
1033 | WHERE nspname='{$schema}'"; |
||
1034 | |||
1035 | return $this->selectSet($sql); |
||
1036 | } |
||
1037 | |||
1038 | // Table functions |
||
1039 | |||
1040 | /** |
||
1041 | * Drops a schema. |
||
1042 | * |
||
1043 | * @param $schemaname The name of the schema to drop |
||
1044 | * @param $cascade True to cascade drop, false to restrict |
||
1045 | * |
||
1046 | * @return integer 0 if operation was successful |
||
1047 | */ |
||
1048 | public function dropSchema($schemaname, $cascade) |
||
1049 | { |
||
1050 | $this->fieldClean($schemaname); |
||
1051 | |||
1052 | $sql = "DROP SCHEMA \"{$schemaname}\""; |
||
1053 | if ($cascade) { |
||
1054 | $sql .= ' CASCADE'; |
||
1055 | } |
||
1056 | |||
1057 | return $this->execute($sql); |
||
1058 | } |
||
1059 | |||
1060 | /** |
||
1061 | * Return all tables in current database (and schema). |
||
1062 | * |
||
1063 | * @param bool|true $all True to fetch all tables, false for just in current schema |
||
1064 | * |
||
1065 | * @return \PHPPgAdmin\ADORecordSet All tables, sorted alphabetically |
||
1066 | */ |
||
1067 | public function getTables($all = false) |
||
1068 | { |
||
1069 | $c_schema = $this->_schema; |
||
1070 | $this->clean($c_schema); |
||
1071 | if ($all) { |
||
1072 | // Exclude pg_catalog and information_schema tables |
||
1073 | $sql = "SELECT |
||
1074 | schemaname AS nspname, |
||
1075 | tablename AS relname, |
||
1076 | tableowner AS relowner |
||
1077 | FROM pg_catalog.pg_tables |
||
1078 | WHERE schemaname NOT IN ('pg_catalog', 'information_schema', 'pg_toast') |
||
1079 | ORDER BY schemaname, tablename"; |
||
1080 | } else { |
||
1081 | $sql = " |
||
1082 | SELECT c.relname, |
||
1083 | pg_catalog.pg_get_userbyid(c.relowner) AS relowner, |
||
1084 | pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment, |
||
1085 | reltuples::bigint, |
||
1086 | pt.spcname as tablespace, |
||
1087 | pg_size_pretty(pg_total_relation_size(c.oid)) as table_size |
||
1088 | FROM pg_catalog.pg_class c |
||
1089 | LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace |
||
1090 | LEFT JOIN pg_catalog.pg_tablespace pt ON pt.oid=c.reltablespace |
||
1091 | WHERE c.relkind = 'r' |
||
1092 | AND nspname='{$c_schema}' |
||
1093 | ORDER BY c.relname"; |
||
1094 | } |
||
1095 | |||
1096 | return $this->selectSet($sql); |
||
1097 | } |
||
1098 | |||
1099 | /** |
||
1100 | * Finds the names and schemas of parent tables (in order). |
||
1101 | * |
||
1102 | * @param $table The table to find the parents for |
||
1103 | * |
||
1104 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
1105 | */ |
||
1106 | public function getTableParents($table) |
||
1107 | { |
||
1108 | $c_schema = $this->_schema; |
||
1109 | $this->clean($c_schema); |
||
1110 | $this->clean($table); |
||
1111 | |||
1112 | $sql = " |
||
1113 | SELECT |
||
1114 | pn.nspname, relname |
||
1115 | FROM |
||
1116 | pg_catalog.pg_class pc, pg_catalog.pg_inherits pi, pg_catalog.pg_namespace pn |
||
1117 | WHERE |
||
1118 | pc.oid=pi.inhparent |
||
1119 | AND pc.relnamespace=pn.oid |
||
1120 | AND pi.inhrelid = (SELECT oid from pg_catalog.pg_class WHERE relname='{$table}' |
||
1121 | AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$c_schema}')) |
||
1122 | ORDER BY |
||
1123 | pi.inhseqno |
||
1124 | "; |
||
1125 | |||
1126 | return $this->selectSet($sql); |
||
1127 | } |
||
1128 | |||
1129 | /** |
||
1130 | * Finds the names and schemas of child tables. |
||
1131 | * |
||
1132 | * @param $table The table to find the children for |
||
1133 | * |
||
1134 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
1135 | */ |
||
1136 | public function getTableChildren($table) |
||
1137 | { |
||
1138 | $c_schema = $this->_schema; |
||
1139 | $this->clean($c_schema); |
||
1140 | $this->clean($table); |
||
1141 | |||
1142 | $sql = " |
||
1143 | SELECT |
||
1144 | pn.nspname, relname |
||
1145 | FROM |
||
1146 | pg_catalog.pg_class pc, pg_catalog.pg_inherits pi, pg_catalog.pg_namespace pn |
||
1147 | WHERE |
||
1148 | pc.oid=pi.inhrelid |
||
1149 | AND pc.relnamespace=pn.oid |
||
1150 | AND pi.inhparent = (SELECT oid from pg_catalog.pg_class WHERE relname='{$table}' |
||
1151 | AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname = '{$c_schema}')) |
||
1152 | "; |
||
1153 | |||
1154 | return $this->selectSet($sql); |
||
1155 | } |
||
1156 | |||
1157 | /** |
||
1158 | * Returns the SQL definition for the table. |
||
1159 | * |
||
1160 | * @pre MUST be run within a transaction |
||
1161 | * |
||
1162 | * @param $table The table to define |
||
1 ignored issue
–
show
|
|||
1163 | * @param bool|true $clean True to issue drop command, false otherwise |
||
1164 | * |
||
1165 | * @return string A string containing the formatted SQL code |
||
1166 | */ |
||
1167 | public function getTableDefPrefix($table, $clean = false) |
||
1168 | { |
||
1169 | // Fetch table |
||
1170 | $t = $this->getTable($table); |
||
1171 | if (!is_object($t) || $t->recordCount() != 1) { |
||
1172 | $this->rollbackTransaction(); |
||
1173 | |||
1174 | return null; |
||
1175 | } |
||
1176 | $this->fieldClean($t->fields['relname']); |
||
1177 | $this->fieldClean($t->fields['nspname']); |
||
1178 | |||
1179 | // Fetch attributes |
||
1180 | $atts = $this->getTableAttributes($table); |
||
1181 | if (!is_object($atts)) { |
||
1182 | $this->rollbackTransaction(); |
||
1183 | |||
1184 | return null; |
||
1185 | } |
||
1186 | |||
1187 | // Fetch constraints |
||
1188 | $cons = $this->getConstraints($table); |
||
1189 | if (!is_object($cons)) { |
||
1190 | $this->rollbackTransaction(); |
||
1191 | |||
1192 | return null; |
||
1193 | } |
||
1194 | |||
1195 | // Output a reconnect command to create the table as the correct user |
||
1196 | $sql = $this->getChangeUserSQL($t->fields['relowner']) . "\n\n"; |
||
1197 | |||
1198 | // Set schema search path |
||
1199 | $sql .= "SET search_path = \"{$t->fields['nspname']}\", pg_catalog;\n\n"; |
||
1200 | |||
1201 | // Begin CREATE TABLE definition |
||
1202 | $sql .= "-- Definition\n\n"; |
||
1203 | // DROP TABLE must be fully qualified in case a table with the same name exists |
||
1204 | // in pg_catalog. |
||
1205 | if (!$clean) { |
||
1206 | $sql .= '-- '; |
||
1207 | } |
||
1208 | |||
1209 | $sql .= 'DROP TABLE '; |
||
1210 | $sql .= "\"{$t->fields['nspname']}\".\"{$t->fields['relname']}\";\n"; |
||
1211 | $sql .= "CREATE TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" (\n"; |
||
1212 | |||
1213 | // Output all table columns |
||
1214 | $col_comments_sql = ''; // Accumulate comments on columns |
||
1215 | $num = $atts->recordCount() + $cons->recordCount(); |
||
1216 | $i = 1; |
||
1217 | while (!$atts->EOF) { |
||
1218 | $this->fieldClean($atts->fields['attname']); |
||
1219 | $sql .= " \"{$atts->fields['attname']}\""; |
||
1220 | // Dump SERIAL and BIGSERIAL columns correctly |
||
1221 | if ($this->phpBool($atts->fields['attisserial']) && |
||
1222 | ($atts->fields['type'] == 'integer' || $atts->fields['type'] == 'bigint')) { |
||
2 ignored issues
–
show
|
|||
1223 | if ($atts->fields['type'] == 'integer') { |
||
1224 | $sql .= ' SERIAL'; |
||
1225 | } else { |
||
1226 | $sql .= ' BIGSERIAL'; |
||
1227 | } |
||
1228 | } else { |
||
1229 | $sql .= ' ' . $this->formatType($atts->fields['type'], $atts->fields['atttypmod']); |
||
1230 | |||
1231 | // Add NOT NULL if necessary |
||
1232 | if ($this->phpBool($atts->fields['attnotnull'])) { |
||
1233 | $sql .= ' NOT NULL'; |
||
1234 | } |
||
1235 | |||
1236 | // Add default if necessary |
||
1237 | if ($atts->fields['adsrc'] !== null) { |
||
1238 | $sql .= " DEFAULT {$atts->fields['adsrc']}"; |
||
1239 | } |
||
1240 | } |
||
1241 | |||
1242 | // Output comma or not |
||
1243 | if ($i < $num) { |
||
1244 | $sql .= ",\n"; |
||
1245 | } else { |
||
1246 | $sql .= "\n"; |
||
1247 | } |
||
1248 | |||
1249 | // Does this column have a comment? |
||
1250 | if ($atts->fields['comment'] !== null) { |
||
1251 | $this->clean($atts->fields['comment']); |
||
1252 | $col_comments_sql .= "COMMENT ON COLUMN \"{$t->fields['relname']}\".\"{$atts->fields['attname']}\" IS '{$atts->fields['comment']}';\n"; |
||
1253 | } |
||
1254 | |||
1255 | $atts->moveNext(); |
||
1256 | ++$i; |
||
1257 | } |
||
1258 | // Output all table constraints |
||
1259 | while (!$cons->EOF) { |
||
1260 | $this->fieldClean($cons->fields['conname']); |
||
1261 | $sql .= " CONSTRAINT \"{$cons->fields['conname']}\" "; |
||
1262 | // Nasty hack to support pre-7.4 PostgreSQL |
||
1263 | if ($cons->fields['consrc'] !== null) { |
||
1264 | $sql .= $cons->fields['consrc']; |
||
1265 | } else { |
||
1266 | switch ($cons->fields['contype']) { |
||
1267 | case 'p': |
||
1268 | $keys = $this->getAttributeNames($table, explode(' ', $cons->fields['indkey'])); |
||
1269 | $sql .= 'PRIMARY KEY (' . join(',', $keys) . ')'; |
||
1270 | |||
1271 | break; |
||
1272 | case 'u': |
||
1273 | $keys = $this->getAttributeNames($table, explode(' ', $cons->fields['indkey'])); |
||
1274 | $sql .= 'UNIQUE (' . join(',', $keys) . ')'; |
||
1275 | |||
1276 | break; |
||
1277 | default: |
||
1278 | // Unrecognised constraint |
||
1279 | $this->rollbackTransaction(); |
||
1280 | |||
1281 | return null; |
||
1282 | } |
||
1283 | } |
||
1284 | |||
1285 | // Output comma or not |
||
1286 | if ($i < $num) { |
||
1287 | $sql .= ",\n"; |
||
1288 | } else { |
||
1289 | $sql .= "\n"; |
||
1290 | } |
||
1291 | |||
1292 | $cons->moveNext(); |
||
1293 | ++$i; |
||
1294 | } |
||
1295 | |||
1296 | $sql .= ')'; |
||
1297 | |||
1298 | // @@@@ DUMP CLUSTERING INFORMATION |
||
1299 | |||
1300 | // Inherits |
||
1301 | /* |
||
1302 | * XXX: This is currently commented out as handling inheritance isn't this simple. |
||
1303 | * You also need to make sure you don't dump inherited columns and defaults, as well |
||
1304 | * as inherited NOT NULL and CHECK constraints. So for the time being, we just do |
||
1305 | * not claim to support inheritance. |
||
1306 | $parents = $this->getTableParents($table); |
||
1307 | if ($parents->recordCount() > 0) { |
||
1308 | $sql .= " INHERITS ("; |
||
1309 | while (!$parents->EOF) { |
||
1310 | $this->fieldClean($parents->fields['relname']); |
||
1311 | // Qualify the parent table if it's in another schema |
||
1312 | if ($parents->fields['schemaname'] != $this->_schema) { |
||
1313 | $this->fieldClean($parents->fields['schemaname']); |
||
1314 | $sql .= "\"{$parents->fields['schemaname']}\"."; |
||
1315 | } |
||
1316 | $sql .= "\"{$parents->fields['relname']}\""; |
||
1317 | |||
1318 | $parents->moveNext(); |
||
1319 | if (!$parents->EOF) $sql .= ', '; |
||
1320 | } |
||
1321 | $sql .= ")"; |
||
1322 | } |
||
1323 | */ |
||
1324 | |||
1325 | // Handle WITHOUT OIDS |
||
1326 | if ($this->hasObjectID($table)) { |
||
1327 | $sql .= ' WITH OIDS'; |
||
1328 | } else { |
||
1329 | $sql .= ' WITHOUT OIDS'; |
||
1330 | } |
||
1331 | |||
1332 | $sql .= ";\n"; |
||
1333 | |||
1334 | // Column storage and statistics |
||
1335 | $atts->moveFirst(); |
||
1336 | $first = true; |
||
1337 | while (!$atts->EOF) { |
||
1338 | $this->fieldClean($atts->fields['attname']); |
||
1339 | // Statistics first |
||
1340 | if ($atts->fields['attstattarget'] >= 0) { |
||
1341 | if ($first) { |
||
1342 | $sql .= "\n"; |
||
1343 | $first = false; |
||
1344 | } |
||
1345 | $sql .= "ALTER TABLE ONLY \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STATISTICS {$atts->fields['attstattarget']};\n"; |
||
1346 | } |
||
1347 | // Then storage |
||
1348 | if ($atts->fields['attstorage'] != $atts->fields['typstorage']) { |
||
1349 | switch ($atts->fields['attstorage']) { |
||
1350 | case 'p': |
||
1351 | $storage = 'PLAIN'; |
||
1352 | |||
1353 | break; |
||
1354 | case 'e': |
||
1355 | $storage = 'EXTERNAL'; |
||
1356 | |||
1357 | break; |
||
1358 | case 'm': |
||
1359 | $storage = 'MAIN'; |
||
1360 | |||
1361 | break; |
||
1362 | case 'x': |
||
1363 | $storage = 'EXTENDED'; |
||
1364 | |||
1365 | break; |
||
1366 | default: |
||
1367 | // Unknown storage type |
||
1368 | $this->rollbackTransaction(); |
||
1369 | |||
1370 | return null; |
||
1371 | } |
||
1372 | $sql .= "ALTER TABLE ONLY \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" ALTER COLUMN \"{$atts->fields['attname']}\" SET STORAGE {$storage};\n"; |
||
1373 | } |
||
1374 | |||
1375 | $atts->moveNext(); |
||
1376 | } |
||
1377 | |||
1378 | // Comment |
||
1379 | if ($t->fields['relcomment'] !== null) { |
||
1380 | $this->clean($t->fields['relcomment']); |
||
1381 | $sql .= "\n-- Comment\n\n"; |
||
1382 | $sql .= "COMMENT ON TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" IS '{$t->fields['relcomment']}';\n"; |
||
1383 | } |
||
1384 | |||
1385 | // Add comments on columns, if any |
||
1386 | if ($col_comments_sql != '') { |
||
1387 | $sql .= $col_comments_sql; |
||
1388 | } |
||
1389 | |||
1390 | // Privileges |
||
1391 | $privs = $this->getPrivileges($table, 'table'); |
||
1392 | if (!is_array($privs)) { |
||
1393 | $this->rollbackTransaction(); |
||
1394 | |||
1395 | return null; |
||
1396 | } |
||
1397 | |||
1398 | if (sizeof($privs) > 0) { |
||
1399 | $sql .= "\n-- Privileges\n\n"; |
||
1400 | /* |
||
1401 | * Always start with REVOKE ALL FROM PUBLIC, so that we don't have to |
||
1402 | * wire-in knowledge about the default public privileges for different |
||
1403 | * kinds of objects. |
||
1404 | */ |
||
1405 | $sql .= "REVOKE ALL ON TABLE \"{$t->fields['nspname']}\".\"{$t->fields['relname']}\" FROM PUBLIC;\n"; |
||
1406 | foreach ($privs as $v) { |
||
1407 | // Get non-GRANT OPTION privs |
||
1408 | $nongrant = array_diff($v[2], $v[4]); |
||
1409 | |||
1410 | // Skip empty or owner ACEs |
||
1411 | if (sizeof($v[2]) == 0 || ($v[0] == 'user' && $v[1] == $t->fields['relowner'])) { |
||
1412 | continue; |
||
1413 | } |
||
1414 | |||
1415 | // Change user if necessary |
||
1416 | if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) { |
||
1417 | $grantor = $v[3]; |
||
1418 | $this->clean($grantor); |
||
1419 | $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n"; |
||
1420 | } |
||
1421 | |||
1422 | // Output privileges with no GRANT OPTION |
||
1423 | $sql .= 'GRANT ' . join(', ', $nongrant) . " ON TABLE \"{$t->fields['relname']}\" TO "; |
||
1424 | switch ($v[0]) { |
||
1425 | case 'public': |
||
1426 | $sql .= "PUBLIC;\n"; |
||
1427 | |||
1428 | break; |
||
1429 | case 'user': |
||
1430 | $this->fieldClean($v[1]); |
||
1431 | $sql .= "\"{$v[1]}\";\n"; |
||
1432 | |||
1433 | break; |
||
1434 | case 'group': |
||
1435 | $this->fieldClean($v[1]); |
||
1436 | $sql .= "GROUP \"{$v[1]}\";\n"; |
||
1437 | |||
1438 | break; |
||
1439 | default: |
||
1440 | // Unknown privilege type - fail |
||
1441 | $this->rollbackTransaction(); |
||
1442 | |||
1443 | return null; |
||
1444 | } |
||
1445 | |||
1446 | // Reset user if necessary |
||
1447 | if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) { |
||
1448 | $sql .= "RESET SESSION AUTHORIZATION;\n"; |
||
1449 | } |
||
1450 | |||
1451 | // Output privileges with GRANT OPTION |
||
1452 | |||
1453 | // Skip empty or owner ACEs |
||
1454 | if (!$this->hasGrantOption() || sizeof($v[4]) == 0) { |
||
1455 | continue; |
||
1456 | } |
||
1457 | |||
1458 | // Change user if necessary |
||
1459 | if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) { |
||
1460 | $grantor = $v[3]; |
||
1461 | $this->clean($grantor); |
||
1462 | $sql .= "SET SESSION AUTHORIZATION '{$grantor}';\n"; |
||
1463 | } |
||
1464 | |||
1465 | $sql .= 'GRANT ' . join(', ', $v[4]) . " ON \"{$t->fields['relname']}\" TO "; |
||
1466 | switch ($v[0]) { |
||
1467 | case 'public': |
||
1468 | $sql .= 'PUBLIC'; |
||
1469 | |||
1470 | break; |
||
1471 | case 'user': |
||
1472 | $this->fieldClean($v[1]); |
||
1473 | $sql .= "\"{$v[1]}\""; |
||
1474 | |||
1475 | break; |
||
1476 | case 'group': |
||
1477 | $this->fieldClean($v[1]); |
||
1478 | $sql .= "GROUP \"{$v[1]}\""; |
||
1479 | |||
1480 | break; |
||
1481 | default: |
||
1482 | // Unknown privilege type - fail |
||
1483 | return null; |
||
1484 | } |
||
1485 | $sql .= " WITH GRANT OPTION;\n"; |
||
1486 | |||
1487 | // Reset user if necessary |
||
1488 | if ($this->hasGrantOption() && $v[3] != $t->fields['relowner']) { |
||
1489 | $sql .= "RESET SESSION AUTHORIZATION;\n"; |
||
1490 | } |
||
1491 | } |
||
1492 | } |
||
1493 | |||
1494 | // Add a newline to separate data that follows (if any) |
||
1495 | $sql .= "\n"; |
||
1496 | |||
1497 | return $sql; |
||
1498 | } |
||
1499 | |||
1500 | /** |
||
1501 | * Returns table information. |
||
1502 | * |
||
1503 | * @param $table The name of the table |
||
1504 | * |
||
1505 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
1506 | */ |
||
1507 | public function getTable($table) |
||
1508 | { |
||
1509 | $c_schema = $this->_schema; |
||
1510 | $this->clean($c_schema); |
||
1511 | $this->clean($table); |
||
1512 | |||
1513 | $sql = " |
||
1514 | SELECT |
||
1515 | c.relname, n.nspname, u.usename AS relowner, |
||
1516 | pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment, |
||
1517 | (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=c.reltablespace) AS tablespace |
||
1518 | FROM pg_catalog.pg_class c |
||
1519 | LEFT JOIN pg_catalog.pg_user u ON u.usesysid = c.relowner |
||
1520 | LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace |
||
1521 | WHERE c.relkind = 'r' |
||
1522 | AND n.nspname = '{$c_schema}' |
||
1523 | AND n.oid = c.relnamespace |
||
1524 | AND c.relname = '{$table}'"; |
||
1525 | |||
1526 | return $this->selectSet($sql); |
||
1527 | } |
||
1528 | |||
1529 | /** |
||
1530 | * Retrieve the attribute definition of a table. |
||
1531 | * |
||
1532 | * @param $table The name of the table |
||
1533 | * @param $field (optional) The name of a field to return |
||
1534 | * |
||
1535 | * @return All attributes in order |
||
1536 | */ |
||
1537 | public function getTableAttributes($table, $field = '') |
||
1538 | { |
||
1539 | $c_schema = $this->_schema; |
||
1540 | $this->clean($c_schema); |
||
1541 | $this->clean($table); |
||
1542 | $this->clean($field); |
||
1543 | |||
1544 | if ($field == '') { |
||
1545 | // This query is made much more complex by the addition of the 'attisserial' field. |
||
1546 | // The subquery to get that field checks to see if there is an internally dependent |
||
1547 | // sequence on the field. |
||
1548 | $sql = " |
||
1549 | SELECT |
||
1550 | a.attname, a.attnum, |
||
1551 | pg_catalog.format_type(a.atttypid, a.atttypmod) as type, |
||
1552 | a.atttypmod, |
||
1553 | a.attnotnull, a.atthasdef, pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) as adsrc, |
||
1554 | a.attstattarget, a.attstorage, t.typstorage, |
||
1555 | ( |
||
1556 | SELECT 1 FROM pg_catalog.pg_depend pd, pg_catalog.pg_class pc |
||
1557 | WHERE pd.objid=pc.oid |
||
1558 | AND pd.classid=pc.tableoid |
||
1559 | AND pd.refclassid=pc.tableoid |
||
1560 | AND pd.refobjid=a.attrelid |
||
1561 | AND pd.refobjsubid=a.attnum |
||
1562 | AND pd.deptype='i' |
||
1563 | AND pc.relkind='S' |
||
1564 | ) IS NOT NULL AS attisserial, |
||
1565 | pg_catalog.col_description(a.attrelid, a.attnum) AS comment |
||
1566 | FROM |
||
1567 | pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_attrdef adef |
||
1568 | ON a.attrelid=adef.adrelid |
||
1569 | AND a.attnum=adef.adnum |
||
1570 | LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid |
||
1571 | WHERE |
||
1572 | a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' |
||
1573 | AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE |
||
1574 | nspname = '{$c_schema}')) |
||
1575 | AND a.attnum > 0 AND NOT a.attisdropped |
||
1576 | ORDER BY a.attnum"; |
||
1577 | } else { |
||
1578 | $sql = " |
||
1579 | SELECT |
||
1580 | a.attname, a.attnum, |
||
1581 | pg_catalog.format_type(a.atttypid, a.atttypmod) as type, |
||
1582 | pg_catalog.format_type(a.atttypid, NULL) as base_type, |
||
1583 | a.atttypmod, |
||
1584 | a.attnotnull, a.atthasdef, pg_catalog.pg_get_expr(adef.adbin, adef.adrelid, true) as adsrc, |
||
1585 | a.attstattarget, a.attstorage, t.typstorage, |
||
1586 | pg_catalog.col_description(a.attrelid, a.attnum) AS comment |
||
1587 | FROM |
||
1588 | pg_catalog.pg_attribute a LEFT JOIN pg_catalog.pg_attrdef adef |
||
1589 | ON a.attrelid=adef.adrelid |
||
1590 | AND a.attnum=adef.adnum |
||
1591 | LEFT JOIN pg_catalog.pg_type t ON a.atttypid=t.oid |
||
1592 | WHERE |
||
1593 | a.attrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' |
||
1594 | AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE |
||
1595 | nspname = '{$c_schema}')) |
||
1596 | AND a.attname = '{$field}'"; |
||
1597 | } |
||
1598 | |||
1599 | return $this->selectSet($sql); |
||
1600 | } |
||
1601 | |||
1602 | /** |
||
1603 | * Returns a list of all constraints on a table. |
||
1604 | * |
||
1605 | * @param $table The table to find rules for |
||
1606 | * |
||
1607 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
1608 | */ |
||
1609 | public function getConstraints($table) |
||
1610 | { |
||
1611 | $c_schema = $this->_schema; |
||
1612 | $this->clean($c_schema); |
||
1613 | $this->clean($table); |
||
1614 | |||
1615 | // This SQL is greatly complicated by the need to retrieve |
||
1616 | // index clustering information for primary and unique constraints |
||
1617 | $sql = "SELECT |
||
1618 | pc.conname, |
||
1619 | pg_catalog.pg_get_constraintdef(pc.oid, true) AS consrc, |
||
1620 | pc.contype, |
||
1621 | CASE WHEN pc.contype='u' OR pc.contype='p' THEN ( |
||
1622 | SELECT |
||
1623 | indisclustered |
||
1624 | FROM |
||
1625 | pg_catalog.pg_depend pd, |
||
1626 | pg_catalog.pg_class pl, |
||
1627 | pg_catalog.pg_index pi |
||
1628 | WHERE |
||
1629 | pd.refclassid=pc.tableoid |
||
1630 | AND pd.refobjid=pc.oid |
||
1631 | AND pd.objid=pl.oid |
||
1632 | AND pl.oid=pi.indexrelid |
||
1633 | ) ELSE |
||
1634 | NULL |
||
1635 | END AS indisclustered |
||
1636 | FROM |
||
1637 | pg_catalog.pg_constraint pc |
||
1638 | WHERE |
||
1639 | pc.conrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' |
||
1640 | AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace |
||
1641 | WHERE nspname='{$c_schema}')) |
||
1642 | ORDER BY |
||
1643 | 1 |
||
1644 | "; |
||
1645 | |||
1646 | return $this->selectSet($sql); |
||
1647 | } |
||
1648 | |||
1649 | /** |
||
1650 | * Returns the SQL for changing the current user. |
||
1651 | * |
||
1652 | * @param $user The user to change to |
||
1653 | * |
||
1654 | * @return The SQL |
||
1655 | */ |
||
1656 | public function getChangeUserSQL($user) |
||
1657 | { |
||
1658 | $this->clean($user); |
||
1659 | |||
1660 | return "SET SESSION AUTHORIZATION '{$user}';"; |
||
1661 | } |
||
1662 | |||
1663 | /** |
||
1664 | * Change a parameter from 't' or 'f' to a boolean, (others evaluate to false). |
||
1665 | * |
||
1666 | * @param $parameter the parameter |
||
1667 | * |
||
1668 | * @return bool|\PHPPgAdmin\Database\the |
||
1669 | */ |
||
1670 | public function phpBool($parameter) |
||
1671 | { |
||
1672 | $parameter = ($parameter == 't'); |
||
1673 | |||
1674 | return $parameter; |
||
1675 | } |
||
1676 | |||
1677 | /** |
||
1678 | * Formats a type correctly for display. Postgres 7.0 had no 'format_type' |
||
1679 | * built-in function, and hence we need to do it manually. |
||
1680 | * |
||
1681 | * @param $typname The name of the type |
||
1682 | * @param $typmod The contents of the typmod field |
||
1683 | * |
||
1684 | * @return bool|string |
||
1685 | */ |
||
1686 | public function formatType($typname, $typmod) |
||
1687 | { |
||
1688 | // This is a specific constant in the 7.0 source |
||
1689 | $varhdrsz = 4; |
||
1690 | |||
1691 | // If the first character is an underscore, it's an array type |
||
1692 | $is_array = false; |
||
1693 | if (substr($typname, 0, 1) == '_') { |
||
1694 | $is_array = true; |
||
1695 | $typname = substr($typname, 1); |
||
1696 | } |
||
1697 | |||
1698 | // Show lengths on bpchar and varchar |
||
1699 | if ($typname == 'bpchar') { |
||
1700 | $len = $typmod - $varhdrsz; |
||
1701 | $temp = 'character'; |
||
1702 | if ($len > 1) { |
||
1703 | $temp .= "({$len})"; |
||
1704 | } |
||
1705 | } elseif ($typname == 'varchar') { |
||
1706 | $temp = 'character varying'; |
||
1707 | if ($typmod != -1) { |
||
1708 | $temp .= '(' . ($typmod - $varhdrsz) . ')'; |
||
1709 | } |
||
1710 | } elseif ($typname == 'numeric') { |
||
1711 | $temp = 'numeric'; |
||
1712 | if ($typmod != -1) { |
||
1713 | $tmp_typmod = $typmod - $varhdrsz; |
||
1714 | $precision = ($tmp_typmod >> 16) & 0xffff; |
||
1715 | $scale = $tmp_typmod & 0xffff; |
||
1716 | $temp .= "({$precision}, {$scale})"; |
||
1717 | } |
||
1718 | } else { |
||
1719 | $temp = $typname; |
||
1720 | } |
||
1721 | |||
1722 | // Add array qualifier if it's an array |
||
1723 | if ($is_array) { |
||
1724 | $temp .= '[]'; |
||
1725 | } |
||
1726 | |||
1727 | return $temp; |
||
1728 | } |
||
1729 | |||
1730 | /** |
||
1731 | * Given an array of attnums and a relation, returns an array mapping |
||
1732 | * attribute number to attribute name. |
||
1733 | * |
||
1734 | * @param $table The table to get attributes for |
||
1735 | * @param $atts An array of attribute numbers |
||
1736 | * |
||
1737 | * @return An array mapping attnum to attname |
||
1738 | * @return -1 $atts must be an array |
||
1739 | * @return -2 wrong number of attributes found |
||
1740 | */ |
||
1741 | public function getAttributeNames($table, $atts) |
||
1742 | { |
||
1743 | $c_schema = $this->_schema; |
||
1744 | $this->clean($c_schema); |
||
1745 | $this->clean($table); |
||
1746 | $this->arrayClean($atts); |
||
1747 | |||
1748 | if (!is_array($atts)) { |
||
1749 | return -1; |
||
1750 | } |
||
1751 | |||
1752 | if (sizeof($atts) == 0) { |
||
1753 | return []; |
||
1754 | } |
||
1755 | |||
1756 | $sql = "SELECT attnum, attname FROM pg_catalog.pg_attribute WHERE |
||
1757 | attrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' AND |
||
1758 | relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}')) |
||
1759 | AND attnum IN ('" . join("','", $atts) . "')"; |
||
1760 | |||
1761 | $rs = $this->selectSet($sql); |
||
1762 | if ($rs->recordCount() != sizeof($atts)) { |
||
1763 | return -2; |
||
1764 | } |
||
1765 | |||
1766 | $temp = []; |
||
1767 | while (!$rs->EOF) { |
||
1768 | $temp[$rs->fields['attnum']] = $rs->fields['attname']; |
||
1769 | $rs->moveNext(); |
||
1770 | } |
||
1771 | |||
1772 | return $temp; |
||
1773 | } |
||
1774 | |||
1775 | /** |
||
1776 | * Cleans (escapes) an array. |
||
1777 | * |
||
1778 | * @param $arr The array to clean, by reference |
||
1779 | * |
||
1780 | * @return The cleaned array |
||
1781 | */ |
||
1782 | public function arrayClean(&$arr) |
||
1783 | { |
||
1784 | foreach ($arr as $k => $v) { |
||
1785 | if ($v === null) { |
||
1786 | continue; |
||
1787 | } |
||
1788 | |||
1789 | $arr[$k] = pg_escape_string($v); |
||
1 ignored issue
–
show
|
|||
1790 | } |
||
1791 | |||
1792 | return $arr; |
||
1793 | } |
||
1794 | |||
1795 | /** |
||
1796 | * Checks to see whether or not a table has a unique id column. |
||
1797 | * |
||
1798 | * @param $table The table name |
||
1799 | * |
||
1800 | * @return true if it has a unique id, false otherwise |
||
1801 | */ |
||
1802 | public function hasObjectID($table) |
||
1803 | { |
||
1804 | $c_schema = $this->_schema; |
||
1805 | $this->clean($c_schema); |
||
1806 | $this->clean($table); |
||
1807 | |||
1808 | $sql = "SELECT relhasoids FROM pg_catalog.pg_class WHERE relname='{$table}' |
||
1809 | AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}')"; |
||
1810 | |||
1811 | $rs = $this->selectSet($sql); |
||
1812 | if ($rs->recordCount() != 1) { |
||
1813 | return null; |
||
1814 | } |
||
1815 | |||
1816 | $rs->fields['relhasoids'] = $this->phpBool($rs->fields['relhasoids']); |
||
1817 | |||
1818 | return $rs->fields['relhasoids']; |
||
1819 | } |
||
1820 | |||
1821 | /** |
||
1822 | * Grabs an array of users and their privileges for an object, |
||
1823 | * given its type. |
||
1824 | * |
||
1825 | * @param $object The name of the object whose privileges are to be retrieved |
||
1826 | * @param $type The type of the object (eg. database, schema, relation, function or language) |
||
1827 | * @param $table Optional, column's table if type = column |
||
1828 | * |
||
1829 | * @return Privileges array |
||
1830 | * @return -1 invalid type |
||
1831 | * @return -2 object not found |
||
1832 | * @return -3 unknown privilege type |
||
1833 | */ |
||
1834 | public function getPrivileges($object, $type, $table = null) |
||
1835 | { |
||
1836 | $c_schema = $this->_schema; |
||
1837 | $this->clean($c_schema); |
||
1838 | $this->clean($object); |
||
1839 | |||
1840 | switch ($type) { |
||
1841 | case 'column': |
||
1842 | $this->clean($table); |
||
1843 | $sql = " |
||
1844 | SELECT E'{' || pg_catalog.array_to_string(attacl, E',') || E'}' as acl |
||
1845 | FROM pg_catalog.pg_attribute a |
||
1846 | LEFT JOIN pg_catalog.pg_class c ON (a.attrelid = c.oid) |
||
1847 | LEFT JOIN pg_catalog.pg_namespace n ON (c.relnamespace=n.oid) |
||
1848 | WHERE n.nspname='{$c_schema}' |
||
1849 | AND c.relname='{$table}' |
||
1850 | AND a.attname='{$object}'"; |
||
1851 | |||
1852 | break; |
||
1853 | case 'table': |
||
1854 | case 'view': |
||
1855 | case 'sequence': |
||
1856 | $sql = " |
||
1857 | SELECT relacl AS acl FROM pg_catalog.pg_class |
||
1858 | WHERE relname='{$object}' |
||
1859 | AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace |
||
1860 | WHERE nspname='{$c_schema}')"; |
||
1861 | |||
1862 | break; |
||
1863 | case 'database': |
||
1864 | $sql = "SELECT datacl AS acl FROM pg_catalog.pg_database WHERE datname='{$object}'"; |
||
1865 | |||
1866 | break; |
||
1867 | case 'function': |
||
1868 | // Since we fetch functions by oid, they are already constrained to |
||
1869 | // the current schema. |
||
1870 | $sql = "SELECT proacl AS acl FROM pg_catalog.pg_proc WHERE oid='{$object}'"; |
||
1871 | |||
1872 | break; |
||
1873 | case 'language': |
||
1874 | $sql = "SELECT lanacl AS acl FROM pg_catalog.pg_language WHERE lanname='{$object}'"; |
||
1875 | |||
1876 | break; |
||
1877 | case 'schema': |
||
1878 | $sql = "SELECT nspacl AS acl FROM pg_catalog.pg_namespace WHERE nspname='{$object}'"; |
||
1879 | |||
1880 | break; |
||
1881 | case 'tablespace': |
||
1882 | $sql = "SELECT spcacl AS acl FROM pg_catalog.pg_tablespace WHERE spcname='{$object}'"; |
||
1883 | |||
1884 | break; |
||
1885 | default: |
||
1886 | return -1; |
||
1887 | } |
||
1888 | |||
1889 | // Fetch the ACL for object |
||
1890 | $acl = $this->selectField($sql, 'acl'); |
||
1891 | if ($acl == -1) { |
||
1892 | return -2; |
||
1893 | } |
||
1894 | |||
1895 | if ($acl == '' || $acl == null) { |
||
1896 | return []; |
||
1897 | } |
||
1898 | |||
1899 | return $this->_parseACL($acl); |
||
1900 | } |
||
1901 | |||
1902 | /** |
||
1903 | * Internal function used for parsing ACLs. |
||
1904 | * |
||
1905 | * @param $acl The ACL to parse (of type aclitem[]) |
||
1906 | * |
||
1907 | * @return Privileges array |
||
1908 | */ |
||
1909 | public function _parseACL($acl) |
||
1 ignored issue
–
show
|
|||
1910 | { |
||
1911 | // Take off the first and last characters (the braces) |
||
1912 | $acl = substr($acl, 1, strlen($acl) - 2); |
||
1913 | |||
1914 | // Pick out individual ACE's by carefully parsing. This is necessary in order |
||
1915 | // to cope with usernames and stuff that contain commas |
||
1916 | $aces = []; |
||
1917 | $i = $j = 0; |
||
1918 | $in_quotes = false; |
||
1919 | while ($i < strlen($acl)) { |
||
1920 | // If current char is a double quote and it's not escaped, then |
||
1921 | // enter quoted bit |
||
1922 | $char = substr($acl, $i, 1); |
||
1923 | if ($char == '"' && ($i == 0 || substr($acl, $i - 1, 1) != '\\')) { |
||
1924 | $in_quotes = !$in_quotes; |
||
1 ignored issue
–
show
|
|||
1925 | } elseif ($char == ',' && !$in_quotes) { |
||
1926 | // Add text so far to the array |
||
1927 | $aces[] = substr($acl, $j, $i - $j); |
||
1928 | $j = $i + 1; |
||
1929 | } |
||
1930 | ++$i; |
||
1931 | } |
||
1932 | // Add final text to the array |
||
1933 | $aces[] = substr($acl, $j); |
||
1934 | |||
1935 | // Create the array to be returned |
||
1936 | $temp = []; |
||
1937 | |||
1938 | // For each ACE, generate an entry in $temp |
||
1939 | foreach ($aces as $v) { |
||
1940 | // If the ACE begins with a double quote, strip them off both ends |
||
1941 | // and unescape backslashes and double quotes |
||
1942 | $unquote = false; |
||
1943 | if (strpos($v, '"') === 0) { |
||
1944 | $v = substr($v, 1, strlen($v) - 2); |
||
1945 | $v = str_replace('\\"', '"', $v); |
||
1946 | $v = str_replace('\\\\', '\\', $v); |
||
1947 | } |
||
1948 | |||
1949 | // Figure out type of ACE (public, user or group) |
||
1950 | if (strpos($v, '=') === 0) { |
||
1951 | $atype = 'public'; |
||
1952 | } else { |
||
1953 | if ($this->hasRoles()) { |
||
1954 | $atype = 'role'; |
||
1955 | } else { |
||
1956 | if (strpos($v, 'group ') === 0) { |
||
1957 | $atype = 'group'; |
||
1958 | // Tear off 'group' prefix |
||
1959 | $v = substr($v, 6); |
||
1960 | } else { |
||
1961 | $atype = 'user'; |
||
1962 | } |
||
1963 | } |
||
1964 | } |
||
1965 | |||
1966 | // Break on unquoted equals sign... |
||
1967 | $i = 0; |
||
1968 | $in_quotes = false; |
||
1969 | $entity = null; |
||
1970 | $chars = null; |
||
1971 | while ($i < strlen($v)) { |
||
1972 | // If current char is a double quote and it's not escaped, then |
||
1973 | // enter quoted bit |
||
1974 | $char = substr($v, $i, 1); |
||
1975 | $next_char = substr($v, $i + 1, 1); |
||
1976 | if ($char == '"' && ($i == 0 || $next_char != '"')) { |
||
1977 | $in_quotes = !$in_quotes; |
||
1 ignored issue
–
show
|
|||
1978 | } // Skip over escaped double quotes |
||
1979 | elseif ($char == '"' && $next_char == '"') { |
||
1980 | ++$i; |
||
1981 | } elseif ($char == '=' && !$in_quotes) { |
||
1982 | // Split on current equals sign |
||
1983 | $entity = substr($v, 0, $i); |
||
1984 | $chars = substr($v, $i + 1); |
||
1985 | |||
1986 | break; |
||
1987 | } |
||
1988 | ++$i; |
||
1989 | } |
||
1990 | |||
1991 | // Check for quoting on entity name, and unescape if necessary |
||
1992 | if (strpos($entity, '"') === 0) { |
||
1993 | $entity = substr($entity, 1, strlen($entity) - 2); |
||
1994 | $entity = str_replace('""', '"', $entity); |
||
1995 | } |
||
1996 | |||
1997 | // New row to be added to $temp |
||
1998 | // (type, grantee, privileges, grantor, grant option? |
||
1999 | $row = [$atype, $entity, [], '', []]; |
||
2000 | |||
2001 | // Loop over chars and add privs to $row |
||
2002 | for ($i = 0; $i < strlen($chars); ++$i) { |
||
2003 | // Append to row's privs list the string representing |
||
2004 | // the privilege |
||
2005 | $char = substr($chars, $i, 1); |
||
2006 | if ($char == '*') { |
||
2007 | $row[4][] = $this->privmap[substr($chars, $i - 1, 1)]; |
||
2008 | } elseif ($char == '/') { |
||
2009 | $grantor = substr($chars, $i + 1); |
||
2010 | // Check for quoting |
||
2011 | if (strpos($grantor, '"') === 0) { |
||
2012 | $grantor = substr($grantor, 1, strlen($grantor) - 2); |
||
2013 | $grantor = str_replace('""', '"', $grantor); |
||
2014 | } |
||
2015 | $row[3] = $grantor; |
||
2016 | |||
2017 | break; |
||
2018 | } else { |
||
2019 | if (!isset($this->privmap[$char])) { |
||
2020 | return -3; |
||
2021 | } |
||
2022 | |||
2023 | $row[2][] = $this->privmap[$char]; |
||
2024 | } |
||
2025 | } |
||
2026 | |||
2027 | // Append row to temp |
||
2028 | $temp[] = $row; |
||
2029 | } |
||
2030 | |||
2031 | return $temp; |
||
2032 | } |
||
2033 | |||
2034 | /** |
||
2035 | * Returns extra table definition information that is most usefully |
||
2036 | * dumped after the table contents for speed and efficiency reasons. |
||
2037 | * |
||
2038 | * @param $table The table to define |
||
2039 | * |
||
2040 | * @return A string containing the formatted SQL code |
||
2041 | */ |
||
2042 | public function getTableDefSuffix($table) |
||
2043 | { |
||
2044 | $sql = ''; |
||
2045 | |||
2046 | // Indexes |
||
2047 | $indexes = $this->getIndexes($table); |
||
2048 | if (!is_object($indexes)) { |
||
2049 | $this->rollbackTransaction(); |
||
2050 | |||
2051 | return null; |
||
2052 | } |
||
2053 | |||
2054 | if ($indexes->recordCount() > 0) { |
||
2055 | $sql .= "\n-- Indexes\n\n"; |
||
2056 | while (!$indexes->EOF) { |
||
2057 | $sql .= $indexes->fields['inddef'] . ";\n"; |
||
2058 | |||
2059 | $indexes->moveNext(); |
||
2060 | } |
||
2061 | } |
||
2062 | |||
2063 | // Triggers |
||
2064 | $triggers = $this->getTriggers($table); |
||
2065 | if (!is_object($triggers)) { |
||
2066 | $this->rollbackTransaction(); |
||
2067 | |||
2068 | return null; |
||
2069 | } |
||
2070 | |||
2071 | if ($triggers->recordCount() > 0) { |
||
2072 | $sql .= "\n-- Triggers\n\n"; |
||
2073 | while (!$triggers->EOF) { |
||
2074 | $sql .= $triggers->fields['tgdef']; |
||
2075 | $sql .= ";\n"; |
||
2076 | |||
2077 | $triggers->moveNext(); |
||
2078 | } |
||
2079 | } |
||
2080 | |||
2081 | // Rules |
||
2082 | $rules = $this->getRules($table); |
||
2083 | if (!is_object($rules)) { |
||
2084 | $this->rollbackTransaction(); |
||
2085 | |||
2086 | return null; |
||
2087 | } |
||
2088 | |||
2089 | if ($rules->recordCount() > 0) { |
||
2090 | $sql .= "\n-- Rules\n\n"; |
||
2091 | while (!$rules->EOF) { |
||
2092 | $sql .= $rules->fields['definition'] . "\n"; |
||
2093 | |||
2094 | $rules->moveNext(); |
||
2095 | } |
||
2096 | } |
||
2097 | |||
2098 | return $sql; |
||
2099 | } |
||
2100 | |||
2101 | /** |
||
2102 | * Grabs a list of indexes for a table. |
||
2103 | * |
||
2104 | * @param string $table The name of a table whose indexes to retrieve |
||
2105 | * @param bool|\PHPPgAdmin\Database\Only $unique Only get unique/pk indexes |
||
2106 | * |
||
2107 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
2108 | */ |
||
2109 | public function getIndexes($table = '', $unique = false) |
||
2110 | { |
||
2111 | $this->clean($table); |
||
2112 | |||
2113 | $sql = " |
||
2114 | SELECT c2.relname AS indname, i.indisprimary, i.indisunique, i.indisclustered, |
||
2115 | pg_catalog.pg_get_indexdef(i.indexrelid, 0, true) AS inddef |
||
2116 | FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i |
||
2117 | WHERE c.relname = '{$table}' AND pg_catalog.pg_table_is_visible(c.oid) |
||
2118 | AND c.oid = i.indrelid AND i.indexrelid = c2.oid |
||
2119 | "; |
||
2120 | if ($unique) { |
||
2121 | $sql .= ' AND i.indisunique '; |
||
2122 | } |
||
2123 | |||
2124 | $sql .= ' ORDER BY c2.relname'; |
||
2125 | |||
2126 | return $this->selectSet($sql); |
||
2127 | } |
||
2128 | |||
2129 | /** |
||
2130 | * Grabs a list of triggers on a table. |
||
2131 | * |
||
2132 | * @param string $table The name of a table whose triggers to retrieve |
||
2133 | * |
||
2134 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
2135 | */ |
||
2136 | public function getTriggers($table = '') |
||
2137 | { |
||
2138 | $c_schema = $this->_schema; |
||
2139 | $this->clean($c_schema); |
||
2140 | $this->clean($table); |
||
2141 | |||
2142 | $sql = "SELECT |
||
2143 | t.tgname, pg_catalog.pg_get_triggerdef(t.oid) AS tgdef, |
||
2144 | CASE WHEN t.tgenabled = 'D' THEN FALSE ELSE TRUE END AS tgenabled, p.oid AS prooid, |
||
2145 | p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto, |
||
2146 | ns.nspname AS pronamespace |
||
2147 | FROM pg_catalog.pg_trigger t, pg_catalog.pg_proc p, pg_catalog.pg_namespace ns |
||
2148 | WHERE t.tgrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' |
||
2149 | AND relnamespace=(SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}')) |
||
2150 | AND ( tgconstraint = 0 OR NOT EXISTS |
||
2151 | (SELECT 1 FROM pg_catalog.pg_depend d JOIN pg_catalog.pg_constraint c |
||
2152 | ON (d.refclassid = c.tableoid AND d.refobjid = c.oid) |
||
2153 | WHERE d.classid = t.tableoid AND d.objid = t.oid AND d.deptype = 'i' AND c.contype = 'f')) |
||
2154 | AND p.oid=t.tgfoid |
||
2155 | AND p.pronamespace = ns.oid"; |
||
2156 | |||
2157 | return $this->selectSet($sql); |
||
2158 | } |
||
2159 | |||
2160 | /** |
||
2161 | * Returns a list of all rules on a table OR view. |
||
2162 | * |
||
2163 | * @param $table The table to find rules for |
||
2164 | * |
||
2165 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
2166 | */ |
||
2167 | public function getRules($table) |
||
2168 | { |
||
2169 | $c_schema = $this->_schema; |
||
2170 | $this->clean($c_schema); |
||
2171 | $this->clean($table); |
||
2172 | |||
2173 | $sql = " |
||
2174 | SELECT * |
||
2175 | FROM pg_catalog.pg_rules |
||
2176 | WHERE |
||
2177 | schemaname='{$c_schema}' AND tablename='{$table}' |
||
2178 | ORDER BY rulename |
||
2179 | "; |
||
2180 | |||
2181 | return $this->selectSet($sql); |
||
2182 | } |
||
2183 | |||
2184 | /** |
||
2185 | * Creates a new table in the database. |
||
2186 | * |
||
2187 | * @param $name The name of the table |
||
2188 | * @param $fields The number of fields |
||
2189 | * @param $field An array of field names |
||
2190 | * @param $type An array of field types |
||
2191 | * @param $array An array of '' or '[]' for each type if it's an array or not |
||
2192 | * @param $length An array of field lengths |
||
2193 | * @param $notnull An array of not null |
||
2194 | * @param $default An array of default values |
||
2195 | * @param $withoutoids True if WITHOUT OIDS, false otherwise |
||
2196 | * @param $colcomment An array of comments |
||
2197 | * @param $tblcomment |
||
2198 | * @param $tablespace The tablespace name ('' means none/default) |
||
2199 | * @param $uniquekey An Array indicating the fields that are unique (those indexes that are set) |
||
2200 | * @param $primarykey An Array indicating the field used for the primarykey (those indexes that are set) |
||
2201 | * |
||
2202 | * @return bool|int 0 success |
||
2203 | * |
||
2204 | * @internal param \PHPPgAdmin\Database\Table $comment comment |
||
2205 | */ |
||
2206 | public function createTable( |
||
2207 | $name, |
||
2208 | $fields, |
||
2209 | $field, |
||
2210 | $type, |
||
2211 | $array, |
||
2212 | $length, |
||
2213 | $notnull, |
||
2214 | $default, |
||
2215 | $withoutoids, |
||
2216 | $colcomment, |
||
2217 | $tblcomment, |
||
2218 | $tablespace, |
||
2219 | $uniquekey, |
||
2220 | $primarykey |
||
2221 | ) { |
||
2222 | $f_schema = $this->_schema; |
||
2223 | $this->fieldClean($f_schema); |
||
2224 | $this->fieldClean($name); |
||
2225 | |||
2226 | $status = $this->beginTransaction(); |
||
2227 | if ($status != 0) { |
||
2228 | return -1; |
||
2229 | } |
||
2230 | |||
2231 | $found = false; |
||
2232 | $first = true; |
||
2233 | $comment_sql = ''; //Accumulate comments for the columns |
||
2234 | $sql = "CREATE TABLE \"{$f_schema}\".\"{$name}\" ("; |
||
2235 | for ($i = 0; $i < $fields; ++$i) { |
||
2236 | $this->fieldClean($field[$i]); |
||
2237 | $this->clean($type[$i]); |
||
2238 | $this->clean($length[$i]); |
||
2239 | $this->clean($colcomment[$i]); |
||
2240 | |||
2241 | // Skip blank columns - for user convenience |
||
2242 | if ($field[$i] == '' || $type[$i] == '') { |
||
2243 | continue; |
||
2244 | } |
||
2245 | |||
2246 | // If not the first column, add a comma |
||
2247 | if (!$first) { |
||
2248 | $sql .= ', '; |
||
2249 | } else { |
||
2250 | $first = false; |
||
2251 | } |
||
2252 | |||
2253 | switch ($type[$i]) { |
||
2254 | // Have to account for weird placing of length for with/without |
||
2255 | // time zone types |
||
2256 | case 'timestamp with time zone': |
||
2257 | case 'timestamp without time zone': |
||
2258 | $qual = substr($type[$i], 9); |
||
2259 | $sql .= "\"{$field[$i]}\" timestamp"; |
||
2260 | if ($length[$i] != '') { |
||
2261 | $sql .= "({$length[$i]})"; |
||
2262 | } |
||
2263 | |||
2264 | $sql .= $qual; |
||
2265 | |||
2266 | break; |
||
2267 | case 'time with time zone': |
||
2268 | case 'time without time zone': |
||
2269 | $qual = substr($type[$i], 4); |
||
2270 | $sql .= "\"{$field[$i]}\" time"; |
||
2271 | if ($length[$i] != '') { |
||
2272 | $sql .= "({$length[$i]})"; |
||
2273 | } |
||
2274 | |||
2275 | $sql .= $qual; |
||
2276 | |||
2277 | break; |
||
2278 | default: |
||
2279 | $sql .= "\"{$field[$i]}\" {$type[$i]}"; |
||
2280 | if ($length[$i] != '') { |
||
2281 | $sql .= "({$length[$i]})"; |
||
2282 | } |
||
2283 | } |
||
2284 | // Add array qualifier if necessary |
||
2285 | if ($array[$i] == '[]') { |
||
2286 | $sql .= '[]'; |
||
2287 | } |
||
2288 | |||
2289 | // Add other qualifiers |
||
2290 | if (!isset($primarykey[$i])) { |
||
2291 | if (isset($uniquekey[$i])) { |
||
2292 | $sql .= ' UNIQUE'; |
||
2293 | } |
||
2294 | |||
2295 | if (isset($notnull[$i])) { |
||
2296 | $sql .= ' NOT NULL'; |
||
2297 | } |
||
2298 | } |
||
2299 | if ($default[$i] != '') { |
||
2300 | $sql .= " DEFAULT {$default[$i]}"; |
||
2301 | } |
||
2302 | |||
2303 | if ($colcomment[$i] != '') { |
||
2304 | $comment_sql .= "COMMENT ON COLUMN \"{$name}\".\"{$field[$i]}\" IS '{$colcomment[$i]}';\n"; |
||
2305 | } |
||
2306 | |||
2307 | $found = true; |
||
2308 | } |
||
2309 | |||
2310 | if (!$found) { |
||
2311 | return -1; |
||
2312 | } |
||
2313 | |||
2314 | // PRIMARY KEY |
||
2315 | $primarykeycolumns = []; |
||
2316 | for ($i = 0; $i < $fields; ++$i) { |
||
2317 | if (isset($primarykey[$i])) { |
||
2318 | $primarykeycolumns[] = "\"{$field[$i]}\""; |
||
2319 | } |
||
2320 | } |
||
2321 | if (count($primarykeycolumns) > 0) { |
||
2322 | $sql .= ', PRIMARY KEY (' . implode(', ', $primarykeycolumns) . ')'; |
||
2323 | } |
||
2324 | |||
2325 | $sql .= ')'; |
||
2326 | |||
2327 | // WITHOUT OIDS |
||
2328 | if ($withoutoids) { |
||
2329 | $sql .= ' WITHOUT OIDS'; |
||
2330 | } else { |
||
2331 | $sql .= ' WITH OIDS'; |
||
2332 | } |
||
2333 | |||
2334 | // Tablespace |
||
2335 | if ($this->hasTablespaces() && $tablespace != '') { |
||
2336 | $this->fieldClean($tablespace); |
||
2337 | $sql .= " TABLESPACE \"{$tablespace}\""; |
||
2338 | } |
||
2339 | |||
2340 | $status = $this->execute($sql); |
||
2341 | if ($status) { |
||
2342 | $this->rollbackTransaction(); |
||
2343 | |||
2344 | return -1; |
||
2345 | } |
||
2346 | |||
2347 | if ($tblcomment != '') { |
||
2348 | $status = $this->setComment('TABLE', '', $name, $tblcomment, true); |
||
2349 | if ($status) { |
||
2350 | $this->rollbackTransaction(); |
||
2351 | |||
2352 | return -1; |
||
2353 | } |
||
2354 | } |
||
2355 | |||
2356 | if ($comment_sql != '') { |
||
2357 | $status = $this->execute($comment_sql); |
||
2358 | if ($status) { |
||
2359 | $this->rollbackTransaction(); |
||
2360 | |||
2361 | return -1; |
||
2362 | } |
||
2363 | } |
||
2364 | |||
2365 | return $this->endTransaction(); |
||
2366 | } |
||
2367 | |||
2368 | /** |
||
2369 | * Creates a new table in the database copying attribs and other properties from another table. |
||
2370 | * |
||
2371 | * @param $name The name of the table |
||
2372 | * @param $like an array giving the schema ans the name of the table from which attribs are copying |
||
2373 | * from: array( |
||
2374 | * 'table' => table name, |
||
2375 | * 'schema' => the schema name, |
||
2376 | * ) |
||
2377 | * @param bool $defaults if true, copy the defaults values as well |
||
2378 | * @param bool $constraints if true, copy the constraints as well (CHECK on table & attr) |
||
2379 | * @param bool $idx |
||
2380 | * @param string $tablespace The tablespace name ('' means none/default) |
||
2381 | * |
||
2382 | * @return bool|int |
||
2383 | */ |
||
2384 | public function createTableLike($name, $like, $defaults = false, $constraints = false, $idx = false, $tablespace = '') |
||
2385 | { |
||
2386 | $f_schema = $this->_schema; |
||
2387 | $this->fieldClean($f_schema); |
||
2388 | $this->fieldClean($name); |
||
2389 | $this->fieldClean($like['schema']); |
||
2390 | $this->fieldClean($like['table']); |
||
2391 | $like = "\"{$like['schema']}\".\"{$like['table']}\""; |
||
2392 | |||
2393 | $status = $this->beginTransaction(); |
||
2394 | if ($status != 0) { |
||
2395 | return -1; |
||
2396 | } |
||
2397 | |||
2398 | $sql = "CREATE TABLE \"{$f_schema}\".\"{$name}\" (LIKE {$like}"; |
||
2399 | |||
2400 | if ($defaults) { |
||
2401 | $sql .= ' INCLUDING DEFAULTS'; |
||
2402 | } |
||
2403 | |||
2404 | if ($this->hasCreateTableLikeWithConstraints() && $constraints) { |
||
2405 | $sql .= ' INCLUDING CONSTRAINTS'; |
||
2406 | } |
||
2407 | |||
2408 | if ($this->hasCreateTableLikeWithIndexes() && $idx) { |
||
2409 | $sql .= ' INCLUDING INDEXES'; |
||
2410 | } |
||
2411 | |||
2412 | $sql .= ')'; |
||
2413 | |||
2414 | if ($this->hasTablespaces() && $tablespace != '') { |
||
2415 | $this->fieldClean($tablespace); |
||
2416 | $sql .= " TABLESPACE \"{$tablespace}\""; |
||
2417 | } |
||
2418 | |||
2419 | $status = $this->execute($sql); |
||
2420 | if ($status) { |
||
2421 | $this->rollbackTransaction(); |
||
2422 | |||
2423 | return -1; |
||
2424 | } |
||
2425 | |||
2426 | return $this->endTransaction(); |
||
2427 | } |
||
2428 | |||
2429 | /** |
||
2430 | * Alter table properties. |
||
2431 | * |
||
2432 | * @param $table The name of the table |
||
2433 | * @param $name The new name for the table |
||
2434 | * @param $owner The new owner for the table |
||
2435 | * @param $schema The new schema for the table |
||
2436 | * @param $comment The comment on the table |
||
2437 | * @param $tablespace The new tablespace for the table ('' means leave as is) |
||
2438 | * |
||
2439 | * @return bool|int 0 success |
||
2440 | */ |
||
2441 | public function alterTable($table, $name, $owner, $schema, $comment, $tablespace) |
||
2442 | { |
||
2443 | $data = $this->getTable($table); |
||
2444 | |||
2445 | if ($data->recordCount() != 1) { |
||
2446 | return -2; |
||
2447 | } |
||
2448 | |||
2449 | $status = $this->beginTransaction(); |
||
2450 | if ($status != 0) { |
||
2451 | $this->rollbackTransaction(); |
||
2452 | |||
2453 | return -1; |
||
2454 | } |
||
2455 | |||
2456 | $status = $this->_alterTable($data, $name, $owner, $schema, $comment, $tablespace); |
||
2457 | |||
2458 | if ($status != 0) { |
||
2459 | $this->rollbackTransaction(); |
||
2460 | |||
2461 | return $status; |
||
2462 | } |
||
2463 | |||
2464 | return $this->endTransaction(); |
||
2465 | } |
||
2466 | |||
2467 | /** |
||
2468 | * Protected method which alter a table |
||
2469 | * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION. |
||
2470 | * |
||
2471 | * @param $tblrs The table recordSet returned by getTable() |
||
2472 | * @param $name The new name for the table |
||
2473 | * @param $owner The new owner for the table |
||
2474 | * @param $schema The new schema for the table |
||
2475 | * @param $comment The comment on the table |
||
2476 | * @param $tablespace The new tablespace for the table ('' means leave as is) |
||
2477 | * |
||
2478 | * @return int 0 success |
||
2479 | */ |
||
2480 | protected function _alterTable($tblrs, $name, $owner, $schema, $comment, $tablespace) |
||
1 ignored issue
–
show
|
|||
2481 | { |
||
2482 | $this->fieldArrayClean($tblrs->fields); |
||
2483 | |||
2484 | // Comment |
||
2485 | $status = $this->setComment('TABLE', '', $tblrs->fields['relname'], $comment); |
||
2486 | if ($status != 0) { |
||
2487 | return -4; |
||
2488 | } |
||
2489 | |||
2490 | // Owner |
||
2491 | $this->fieldClean($owner); |
||
2492 | $status = $this->alterTableOwner($tblrs, $owner); |
||
2493 | if ($status != 0) { |
||
2494 | return -5; |
||
2495 | } |
||
2496 | |||
2497 | // Tablespace |
||
2498 | $this->fieldClean($tablespace); |
||
2499 | $status = $this->alterTableTablespace($tblrs, $tablespace); |
||
2500 | if ($status != 0) { |
||
2501 | return -6; |
||
2502 | } |
||
2503 | |||
2504 | // Rename |
||
2505 | $this->fieldClean($name); |
||
2506 | $status = $this->alterTableName($tblrs, $name); |
||
2507 | if ($status != 0) { |
||
2508 | return -3; |
||
2509 | } |
||
2510 | |||
2511 | // Schema |
||
2512 | $this->fieldClean($schema); |
||
2513 | $status = $this->alterTableSchema($tblrs, $schema); |
||
2514 | if ($status != 0) { |
||
2515 | return -7; |
||
2516 | } |
||
2517 | |||
2518 | return 0; |
||
2519 | } |
||
2520 | |||
2521 | /** |
||
2522 | * Alter a table's owner |
||
2523 | * /!\ this function is called from _alterTable which take care of escaping fields. |
||
2524 | * |
||
2525 | * @param $tblrs The table RecordSet returned by getTable() |
||
2526 | * @param null $owner |
||
2527 | * |
||
2528 | * @return int 0 if operation was successful |
||
2529 | * |
||
2530 | * @internal param \PHPPgAdmin\Database\The $name new table's owner |
||
2531 | */ |
||
2532 | public function alterTableOwner($tblrs, $owner = null) |
||
2533 | { |
||
2534 | /* vars cleaned in _alterTable */ |
||
2535 | if (!empty($owner) && ($tblrs->fields['relowner'] != $owner)) { |
||
2536 | $f_schema = $this->_schema; |
||
2537 | $this->fieldClean($f_schema); |
||
2538 | // If owner has been changed, then do the alteration. We are |
||
2539 | // careful to avoid this generally as changing owner is a |
||
2540 | // superuser only function. |
||
2541 | $sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" OWNER TO \"{$owner}\""; |
||
2542 | |||
2543 | return $this->execute($sql); |
||
2544 | } |
||
2545 | |||
2546 | return 0; |
||
2547 | } |
||
2548 | |||
2549 | /** |
||
2550 | * Alter a table's tablespace |
||
2551 | * /!\ this function is called from _alterTable which take care of escaping fields. |
||
2552 | * |
||
2553 | * @param $tblrs The table RecordSet returned by getTable() |
||
2554 | * @param null $tablespace |
||
2555 | * |
||
2556 | * @return int 0 if operation was successful |
||
2557 | * |
||
2558 | * @internal param \PHPPgAdmin\Database\The $name new table's tablespace |
||
2559 | */ |
||
2560 | public function alterTableTablespace($tblrs, $tablespace = null) |
||
2561 | { |
||
2562 | /* vars cleaned in _alterTable */ |
||
2563 | if (!empty($tablespace) && ($tblrs->fields['tablespace'] != $tablespace)) { |
||
2564 | $f_schema = $this->_schema; |
||
2565 | $this->fieldClean($f_schema); |
||
2566 | |||
2567 | // If tablespace has been changed, then do the alteration. We |
||
2568 | // don't want to do this unnecessarily. |
||
2569 | $sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" SET TABLESPACE \"{$tablespace}\""; |
||
2570 | |||
2571 | return $this->execute($sql); |
||
2572 | } |
||
2573 | |||
2574 | return 0; |
||
2575 | } |
||
2576 | |||
2577 | /** |
||
2578 | * Alter a table's name |
||
2579 | * /!\ this function is called from _alterTable which take care of escaping fields. |
||
2580 | * |
||
2581 | * @param $tblrs The table RecordSet returned by getTable() |
||
2582 | * @param $name The new table's name |
||
2583 | * |
||
2584 | * @return int 0 if operation was successful |
||
2585 | */ |
||
2586 | public function alterTableName($tblrs, $name = null) |
||
2587 | { |
||
2588 | /* vars cleaned in _alterTable */ |
||
2589 | // Rename (only if name has changed) |
||
2590 | if (!empty($name) && ($name != $tblrs->fields['relname'])) { |
||
2591 | $f_schema = $this->_schema; |
||
2592 | $this->fieldClean($f_schema); |
||
2593 | |||
2594 | $sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" RENAME TO \"{$name}\""; |
||
2595 | $status = $this->execute($sql); |
||
2596 | if ($status == 0) { |
||
2597 | $tblrs->fields['relname'] = $name; |
||
2598 | } else { |
||
2599 | return $status; |
||
2600 | } |
||
2601 | } |
||
2602 | |||
2603 | return 0; |
||
2604 | } |
||
2605 | |||
2606 | // Row functions |
||
2607 | |||
2608 | /** |
||
2609 | * Alter a table's schema |
||
2610 | * /!\ this function is called from _alterTable which take care of escaping fields. |
||
2611 | * |
||
2612 | * @param $tblrs The table RecordSet returned by getTable() |
||
2613 | * @param null $schema |
||
2614 | * |
||
2615 | * @return int 0 if operation was successful |
||
2616 | * |
||
2617 | * @internal param \PHPPgAdmin\Database\The $name new table's schema |
||
2618 | */ |
||
2619 | public function alterTableSchema($tblrs, $schema = null) |
||
2620 | { |
||
2621 | /* vars cleaned in _alterTable */ |
||
2622 | if (!empty($schema) && ($tblrs->fields['nspname'] != $schema)) { |
||
2623 | $f_schema = $this->_schema; |
||
2624 | $this->fieldClean($f_schema); |
||
2625 | // If tablespace has been changed, then do the alteration. We |
||
2626 | // don't want to do this unnecessarily. |
||
2627 | $sql = "ALTER TABLE \"{$f_schema}\".\"{$tblrs->fields['relname']}\" SET SCHEMA \"{$schema}\""; |
||
2628 | |||
2629 | return $this->execute($sql); |
||
2630 | } |
||
2631 | |||
2632 | return 0; |
||
2633 | } |
||
2634 | |||
2635 | /** |
||
2636 | * Empties a table in the database. |
||
2637 | * |
||
2638 | * @param $table The table to be emptied |
||
2639 | * @param $cascade True to cascade truncate, false to restrict |
||
2640 | * |
||
2641 | * @return integer 0 if operation was successful |
||
2642 | */ |
||
2643 | public function emptyTable($table, $cascade) |
||
2644 | { |
||
2645 | $f_schema = $this->_schema; |
||
2646 | $this->fieldClean($f_schema); |
||
2647 | $this->fieldClean($table); |
||
2648 | |||
2649 | $sql = "TRUNCATE TABLE \"{$f_schema}\".\"{$table}\" "; |
||
2650 | if ($cascade) { |
||
2651 | $sql = $sql . ' CASCADE'; |
||
2652 | } |
||
2653 | |||
2654 | $status = $this->execute($sql); |
||
2655 | |||
2656 | return [$status, $sql]; |
||
2657 | } |
||
2658 | |||
2659 | /** |
||
2660 | * Removes a table from the database. |
||
2661 | * |
||
2662 | * @param $table The table to drop |
||
2663 | * @param $cascade True to cascade drop, false to restrict |
||
2664 | * |
||
2665 | * @return integer 0 if operation was successful |
||
2666 | */ |
||
2667 | public function dropTable($table, $cascade) |
||
2668 | { |
||
2669 | $f_schema = $this->_schema; |
||
2670 | $this->fieldClean($f_schema); |
||
2671 | $this->fieldClean($table); |
||
2672 | |||
2673 | $sql = "DROP TABLE \"{$f_schema}\".\"{$table}\""; |
||
2674 | if ($cascade) { |
||
2675 | $sql .= ' CASCADE'; |
||
2676 | } |
||
2677 | |||
2678 | return $this->execute($sql); |
||
2679 | } |
||
2680 | |||
2681 | /** |
||
2682 | * Add a new column to a table. |
||
2683 | * |
||
2684 | * @param $table The table to add to |
||
2685 | * @param $column The name of the new column |
||
2686 | * @param $type The type of the column |
||
2687 | * @param $array True if array type, false otherwise |
||
2688 | * @param $length The optional size of the column (ie. 30 for varchar(30)) |
||
2689 | * @param $notnull True if NOT NULL, false otherwise |
||
2690 | * @param $default The default for the column. '' for none. |
||
2691 | * @param $comment |
||
2692 | * |
||
2693 | * @return bool|int 0 success |
||
2694 | */ |
||
2695 | public function addColumn($table, $column, $type, $array, $length, $notnull, $default, $comment) |
||
2696 | { |
||
2697 | $f_schema = $this->_schema; |
||
2698 | $this->fieldClean($f_schema); |
||
2699 | $this->fieldClean($table); |
||
2700 | $this->fieldClean($column); |
||
2701 | $this->clean($type); |
||
2702 | $this->clean($length); |
||
2703 | |||
2704 | if ($length == '') { |
||
2705 | $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" {$type}"; |
||
2706 | } else { |
||
2707 | switch ($type) { |
||
2708 | // Have to account for weird placing of length for with/without |
||
2709 | // time zone types |
||
2710 | case 'timestamp with time zone': |
||
2711 | case 'timestamp without time zone': |
||
2712 | $qual = substr($type, 9); |
||
2713 | $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" timestamp({$length}){$qual}"; |
||
2714 | |||
2715 | break; |
||
2716 | case 'time with time zone': |
||
2717 | case 'time without time zone': |
||
2718 | $qual = substr($type, 4); |
||
2719 | $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" time({$length}){$qual}"; |
||
2720 | |||
2721 | break; |
||
2722 | default: |
||
2723 | $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD COLUMN \"{$column}\" {$type}({$length})"; |
||
2724 | } |
||
2725 | } |
||
2726 | |||
2727 | // Add array qualifier, if requested |
||
2728 | if ($array) { |
||
2729 | $sql .= '[]'; |
||
2730 | } |
||
2731 | |||
2732 | // If we have advanced column adding, add the extra qualifiers |
||
2733 | if ($this->hasCreateFieldWithConstraints()) { |
||
2734 | // NOT NULL clause |
||
2735 | if ($notnull) { |
||
2736 | $sql .= ' NOT NULL'; |
||
2737 | } |
||
2738 | |||
2739 | // DEFAULT clause |
||
2740 | if ($default != '') { |
||
2741 | $sql .= ' DEFAULT ' . $default; |
||
2742 | } |
||
2743 | } |
||
2744 | |||
2745 | $status = $this->beginTransaction(); |
||
2746 | if ($status != 0) { |
||
2747 | return -1; |
||
2748 | } |
||
2749 | |||
2750 | $status = $this->execute($sql); |
||
2751 | if ($status != 0) { |
||
2752 | $this->rollbackTransaction(); |
||
2753 | |||
2754 | return -1; |
||
2755 | } |
||
2756 | |||
2757 | $status = $this->setComment('COLUMN', $column, $table, $comment); |
||
2758 | if ($status != 0) { |
||
2759 | $this->rollbackTransaction(); |
||
2760 | |||
2761 | return -1; |
||
2762 | } |
||
2763 | |||
2764 | return $this->endTransaction(); |
||
2765 | } |
||
2766 | |||
2767 | /** |
||
2768 | * Alters a column in a table. |
||
2769 | * |
||
2770 | * @param $table The table in which the column resides |
||
2771 | * @param $column The column to alter |
||
2772 | * @param $name The new name for the column |
||
2773 | * @param $notnull (boolean) True if not null, false otherwise |
||
2774 | * @param $oldnotnull (boolean) True if column is already not null, false otherwise |
||
2775 | * @param $default The new default for the column |
||
2776 | * @param $olddefault The old default for the column |
||
2777 | * @param $type The new type for the column |
||
2778 | * @param $length The optional size of the column (ie. 30 for varchar(30)) |
||
2779 | * @param $array True if array type, false otherwise |
||
2780 | * @param $oldtype The old type for the column |
||
2781 | * @param $comment Comment for the column |
||
2782 | * |
||
2783 | * @return array 0 success |
||
2784 | */ |
||
2785 | public function alterColumn( |
||
2786 | $table, |
||
2787 | $column, |
||
2788 | $name, |
||
2789 | $notnull, |
||
2790 | $oldnotnull, |
||
2791 | $default, |
||
2792 | $olddefault, |
||
2793 | $type, |
||
2794 | $length, |
||
2795 | $array, |
||
2796 | $oldtype, |
||
2797 | $comment |
||
2798 | ) { |
||
2799 | // Begin transaction |
||
2800 | $status = $this->beginTransaction(); |
||
2801 | $sql = ''; |
||
2802 | if ($status != 0) { |
||
2803 | $this->rollbackTransaction(); |
||
2804 | |||
2805 | return [-6, $sql]; |
||
2806 | } |
||
2807 | |||
2808 | // Rename the column, if it has been changed |
||
2809 | if ($column != $name) { |
||
2810 | $status = $this->renameColumn($table, $column, $name); |
||
2811 | if ($status != 0) { |
||
2812 | $this->rollbackTransaction(); |
||
2813 | |||
2814 | return [-4, $sql]; |
||
2815 | } |
||
2816 | } |
||
2817 | |||
2818 | $f_schema = $this->_schema; |
||
2819 | $this->fieldClean($f_schema); |
||
2820 | $this->fieldClean($name); |
||
2821 | $this->fieldClean($table); |
||
2822 | $this->fieldClean($column); |
||
2823 | |||
2824 | $toAlter = []; |
||
2825 | // Create the command for changing nullability |
||
2826 | if ($notnull != $oldnotnull) { |
||
2827 | $toAlter[] = "ALTER COLUMN \"{$name}\" " . ($notnull ? 'SET' : 'DROP') . ' NOT NULL'; |
||
2828 | } |
||
2829 | |||
2830 | // Add default, if it has changed |
||
2831 | if ($default != $olddefault) { |
||
2832 | if ($default == '') { |
||
2833 | $toAlter[] = "ALTER COLUMN \"{$name}\" DROP DEFAULT"; |
||
2834 | } else { |
||
2835 | $toAlter[] = "ALTER COLUMN \"{$name}\" SET DEFAULT {$default}"; |
||
2836 | } |
||
2837 | } |
||
2838 | |||
2839 | // Add type, if it has changed |
||
2840 | if ($length == '') { |
||
2841 | $ftype = $type; |
||
2842 | } else { |
||
2843 | switch ($type) { |
||
2844 | // Have to account for weird placing of length for with/without |
||
2845 | // time zone types |
||
2846 | case 'timestamp with time zone': |
||
2847 | case 'timestamp without time zone': |
||
2848 | $qual = substr($type, 9); |
||
2849 | $ftype = "timestamp({$length}){$qual}"; |
||
2850 | |||
2851 | break; |
||
2852 | case 'time with time zone': |
||
2853 | case 'time without time zone': |
||
2854 | $qual = substr($type, 4); |
||
2855 | $ftype = "time({$length}){$qual}"; |
||
2856 | |||
2857 | break; |
||
2858 | default: |
||
2859 | $ftype = "{$type}({$length})"; |
||
2860 | } |
||
2861 | } |
||
2862 | |||
2863 | // Add array qualifier, if requested |
||
2864 | if ($array) { |
||
2865 | $ftype .= '[]'; |
||
2866 | } |
||
2867 | |||
2868 | if ($ftype != $oldtype) { |
||
2869 | $toAlter[] = "ALTER COLUMN \"{$name}\" TYPE {$ftype}"; |
||
2870 | } |
||
2871 | |||
2872 | // Attempt to process the batch alteration, if anything has been changed |
||
2873 | if (!empty($toAlter)) { |
||
2874 | // Initialise an empty SQL string |
||
2875 | $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" " |
||
2876 | . implode(',', $toAlter); |
||
2877 | |||
2878 | $status = $this->execute($sql); |
||
2879 | if ($status != 0) { |
||
2880 | $this->rollbackTransaction(); |
||
2881 | |||
2882 | return [-1, $sql]; |
||
2883 | } |
||
2884 | } |
||
2885 | |||
2886 | // Update the comment on the column |
||
2887 | $status = $this->setComment('COLUMN', $name, $table, $comment); |
||
2888 | if ($status != 0) { |
||
2889 | $this->rollbackTransaction(); |
||
2890 | |||
2891 | return [-5, $sql]; |
||
2892 | } |
||
2893 | |||
2894 | return [$this->endTransaction(), $sql]; |
||
2895 | } |
||
2896 | |||
2897 | /** |
||
2898 | * Renames a column in a table. |
||
2899 | * |
||
2900 | * @param $table The table containing the column to be renamed |
||
2901 | * @param $column The column to be renamed |
||
2902 | * @param $newName The new name for the column |
||
2903 | * |
||
2904 | * @return integer 0 if operation was successful |
||
2905 | */ |
||
2906 | public function renameColumn($table, $column, $newName) |
||
2907 | { |
||
2908 | $f_schema = $this->_schema; |
||
2909 | $this->fieldClean($f_schema); |
||
2910 | $this->fieldClean($table); |
||
2911 | $this->fieldClean($column); |
||
2912 | $this->fieldClean($newName); |
||
2913 | |||
2914 | $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" RENAME COLUMN \"{$column}\" TO \"{$newName}\""; |
||
2915 | |||
2916 | return $this->execute($sql); |
||
2917 | } |
||
2918 | |||
2919 | /** |
||
2920 | * Sets default value of a column. |
||
2921 | * |
||
2922 | * @param $table The table from which to drop |
||
2923 | * @param $column The column name to set |
||
2924 | * @param $default The new default value |
||
2925 | * |
||
2926 | * @return integer 0 if operation was successful |
||
2927 | */ |
||
2928 | public function setColumnDefault($table, $column, $default) |
||
2929 | { |
||
2930 | $f_schema = $this->_schema; |
||
2931 | $this->fieldClean($f_schema); |
||
2932 | $this->fieldClean($table); |
||
2933 | $this->fieldClean($column); |
||
2934 | |||
2935 | $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" SET DEFAULT {$default}"; |
||
2936 | |||
2937 | return $this->execute($sql); |
||
2938 | } |
||
2939 | |||
2940 | /** |
||
2941 | * Sets whether or not a column can contain NULLs. |
||
2942 | * |
||
2943 | * @param $table The table that contains the column |
||
2944 | * @param $column The column to alter |
||
2945 | * @param $state True to set null, false to set not null |
||
2946 | * |
||
2947 | * @return integer 0 if operation was successful |
||
2948 | */ |
||
2949 | public function setColumnNull($table, $column, $state) |
||
2950 | { |
||
2951 | $f_schema = $this->_schema; |
||
2952 | $this->fieldClean($f_schema); |
||
2953 | $this->fieldClean($table); |
||
2954 | $this->fieldClean($column); |
||
2955 | |||
2956 | $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" " . ($state ? 'DROP' : 'SET') . ' NOT NULL'; |
||
2957 | |||
2958 | return $this->execute($sql); |
||
2959 | } |
||
2960 | |||
2961 | /** |
||
2962 | * Drops a column from a table. |
||
2963 | * |
||
2964 | * @param $table The table from which to drop a column |
||
2965 | * @param $column The column to be dropped |
||
2966 | * @param $cascade True to cascade drop, false to restrict |
||
2967 | * |
||
2968 | * @return integer 0 if operation was successful |
||
2969 | */ |
||
2970 | public function dropColumn($table, $column, $cascade) |
||
2971 | { |
||
2972 | $f_schema = $this->_schema; |
||
2973 | $this->fieldClean($f_schema); |
||
2974 | $this->fieldClean($table); |
||
2975 | $this->fieldClean($column); |
||
2976 | |||
2977 | $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" DROP COLUMN \"{$column}\""; |
||
2978 | if ($cascade) { |
||
2979 | $sql .= ' CASCADE'; |
||
2980 | } |
||
2981 | |||
2982 | return $this->execute($sql); |
||
2983 | } |
||
2984 | |||
2985 | /** |
||
2986 | * Drops default value of a column. |
||
2987 | * |
||
2988 | * @param $table The table from which to drop |
||
2989 | * @param $column The column name to drop default |
||
2990 | * |
||
2991 | * @return integer 0 if operation was successful |
||
2992 | */ |
||
2993 | public function dropColumnDefault($table, $column) |
||
2994 | { |
||
2995 | $f_schema = $this->_schema; |
||
2996 | $this->fieldClean($f_schema); |
||
2997 | $this->fieldClean($table); |
||
2998 | $this->fieldClean($column); |
||
2999 | |||
3000 | $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ALTER COLUMN \"{$column}\" DROP DEFAULT"; |
||
3001 | |||
3002 | return $this->execute($sql); |
||
3003 | } |
||
3004 | |||
3005 | /** |
||
3006 | * Sets up the data object for a dump. eg. Starts the appropriate |
||
3007 | * transaction, sets variables, etc. |
||
3008 | * |
||
3009 | * @return int 0 success |
||
3010 | */ |
||
3011 | public function beginDump() |
||
3012 | { |
||
3013 | // Begin serializable transaction (to dump consistent data) |
||
3014 | $status = $this->beginTransaction(); |
||
3015 | if ($status != 0) { |
||
3016 | return -1; |
||
3017 | } |
||
3018 | |||
3019 | // Set serializable |
||
3020 | $sql = 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE'; |
||
3021 | $status = $this->execute($sql); |
||
3022 | if ($status != 0) { |
||
3023 | $this->rollbackTransaction(); |
||
3024 | |||
3025 | return -1; |
||
3026 | } |
||
3027 | |||
3028 | // Set datestyle to ISO |
||
3029 | $sql = 'SET DATESTYLE = ISO'; |
||
3030 | $status = $this->execute($sql); |
||
3031 | if ($status != 0) { |
||
3032 | $this->rollbackTransaction(); |
||
3033 | |||
3034 | return -1; |
||
3035 | } |
||
3036 | |||
3037 | // Set extra_float_digits to 2 |
||
3038 | $sql = 'SET extra_float_digits TO 2'; |
||
3039 | $status = $this->execute($sql); |
||
3040 | if ($status != 0) { |
||
3041 | $this->rollbackTransaction(); |
||
3042 | |||
3043 | return -1; |
||
3044 | } |
||
3045 | |||
3046 | return 0; |
||
3047 | } |
||
3048 | |||
3049 | /** |
||
3050 | * Ends the data object for a dump. |
||
3051 | * |
||
3052 | * @return bool 0 success |
||
3053 | */ |
||
3054 | public function endDump() |
||
3055 | { |
||
3056 | return $this->endTransaction(); |
||
3057 | } |
||
3058 | |||
3059 | /** |
||
3060 | * Returns a recordset of all columns in a relation. Used for data export. |
||
3061 | * |
||
3062 | * @@ Note: Really needs to use a cursor |
||
3063 | * |
||
3064 | * @param $relation The name of a relation |
||
1 ignored issue
–
show
|
|||
3065 | * @param $oids |
||
3066 | * |
||
3067 | * @return \PHPPgAdmin\ADORecordSet A recordset on success |
||
3068 | */ |
||
3069 | public function dumpRelation($relation, $oids) |
||
3070 | { |
||
3071 | $this->fieldClean($relation); |
||
3072 | |||
3073 | // Actually retrieve the rows |
||
3074 | if ($oids) { |
||
3075 | $oid_str = $this->id . ', '; |
||
3076 | } else { |
||
3077 | $oid_str = ''; |
||
3078 | } |
||
3079 | |||
3080 | return $this->selectSet("SELECT {$oid_str}* FROM \"{$relation}\""); |
||
3081 | } |
||
3082 | |||
3083 | /** |
||
3084 | * Returns all available autovacuum per table information. |
||
3085 | * |
||
3086 | * @param \PHPPgAdmin\Database\if|string $table if given, return autovacuum info for the given table or return all informations for all table |
||
3087 | * |
||
3088 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
3089 | */ |
||
3090 | public function getTableAutovacuum($table = '') |
||
3091 | { |
||
3092 | $sql = ''; |
||
3093 | |||
3094 | if ($table !== '') { |
||
3095 | $this->clean($table); |
||
3096 | $c_schema = $this->_schema; |
||
3097 | $this->clean($c_schema); |
||
3098 | |||
3099 | $sql = "SELECT c.oid, nspname, relname, pg_catalog.array_to_string(reloptions, E',') AS reloptions |
||
3100 | FROM pg_class c |
||
3101 | LEFT JOIN pg_namespace n ON n.oid = c.relnamespace |
||
3102 | WHERE c.relkind = 'r'::\"char\" |
||
3103 | AND n.nspname NOT IN ('pg_catalog','information_schema') |
||
3104 | AND c.reloptions IS NOT NULL |
||
3105 | AND c.relname = '{$table}' AND n.nspname = '{$c_schema}' |
||
3106 | ORDER BY nspname, relname"; |
||
3107 | } else { |
||
3108 | $sql = "SELECT c.oid, nspname, relname, pg_catalog.array_to_string(reloptions, E',') AS reloptions |
||
3109 | FROM pg_class c |
||
3110 | LEFT JOIN pg_namespace n ON n.oid = c.relnamespace |
||
3111 | WHERE c.relkind = 'r'::\"char\" |
||
3112 | AND n.nspname NOT IN ('pg_catalog','information_schema') |
||
3113 | AND c.reloptions IS NOT NULL |
||
3114 | ORDER BY nspname, relname"; |
||
3115 | } |
||
3116 | |||
3117 | /* tmp var to parse the results */ |
||
3118 | $_autovacs = $this->selectSet($sql); |
||
3119 | |||
3120 | /* result aray to return as RS */ |
||
3121 | $autovacs = []; |
||
3122 | while (!$_autovacs->EOF) { |
||
3123 | $_ = [ |
||
3124 | 'nspname' => $_autovacs->fields['nspname'], |
||
3125 | 'relname' => $_autovacs->fields['relname'], |
||
3126 | ]; |
||
3127 | |||
3128 | foreach (explode(',', $_autovacs->fields['reloptions']) as $var) { |
||
3129 | list($o, $v) = explode('=', $var); |
||
3130 | $_[$o] = $v; |
||
3131 | } |
||
3132 | |||
3133 | $autovacs[] = $_; |
||
3134 | |||
3135 | $_autovacs->moveNext(); |
||
3136 | } |
||
3137 | |||
3138 | return new \PHPPgAdmin\ArrayRecordSet($autovacs); |
||
3139 | } |
||
3140 | |||
3141 | /** |
||
3142 | * Get the fields for uniquely identifying a row in a table. |
||
3143 | * |
||
3144 | * @param $table The table for which to retrieve the identifier |
||
3145 | * |
||
3146 | * @return An array mapping attribute number to attribute name, empty for no identifiers |
||
3147 | * @return -1 error |
||
3148 | */ |
||
3149 | public function getRowIdentifier($table) |
||
3150 | { |
||
3151 | $oldtable = $table; |
||
3152 | $c_schema = $this->_schema; |
||
3153 | $this->clean($c_schema); |
||
3154 | $this->clean($table); |
||
3155 | |||
3156 | $status = $this->beginTransaction(); |
||
3157 | if ($status != 0) { |
||
3158 | return -1; |
||
3159 | } |
||
3160 | |||
3161 | // Get the first primary or unique index (sorting primary keys first) that |
||
3162 | // is NOT a partial index. |
||
3163 | $sql = " |
||
3164 | SELECT indrelid, indkey |
||
3165 | FROM pg_catalog.pg_index |
||
3166 | WHERE indisunique AND indrelid=( |
||
3167 | SELECT oid FROM pg_catalog.pg_class |
||
3168 | WHERE relname='{$table}' AND relnamespace=( |
||
3169 | SELECT oid FROM pg_catalog.pg_namespace |
||
3170 | WHERE nspname='{$c_schema}' |
||
3171 | ) |
||
3172 | ) AND indpred IS NULL AND indexprs IS NULL |
||
3173 | ORDER BY indisprimary DESC LIMIT 1"; |
||
3174 | $rs = $this->selectSet($sql); |
||
3175 | |||
3176 | // If none, check for an OID column. Even though OIDs can be duplicated, the edit and delete row |
||
3177 | // functions check that they're only modiying a single row. Otherwise, return empty array. |
||
3178 | if ($rs->recordCount() == 0) { |
||
3179 | // Check for OID column |
||
3180 | $temp = []; |
||
3181 | if ($this->hasObjectID($table)) { |
||
3182 | $temp = ['oid']; |
||
3183 | } |
||
3184 | $this->endTransaction(); |
||
3185 | |||
3186 | return $temp; |
||
3187 | } // Otherwise find the names of the keys |
||
3188 | |||
3189 | $attnames = $this->getAttributeNames($oldtable, explode(' ', $rs->fields['indkey'])); |
||
3190 | if (!is_array($attnames)) { |
||
3191 | $this->rollbackTransaction(); |
||
3192 | |||
3193 | return -1; |
||
3194 | } |
||
3195 | |||
3196 | $this->endTransaction(); |
||
3197 | |||
3198 | return $attnames; |
||
3199 | } |
||
3200 | |||
3201 | /** |
||
3202 | * Adds a new row to a table. |
||
3203 | * |
||
3204 | * @param $table The table in which to insert |
||
3205 | * @param $fields Array of given field in values |
||
3206 | * @param $values Array of new values for the row |
||
3207 | * @param $nulls An array mapping column => something if it is to be null |
||
3208 | * @param $format An array of the data type (VALUE or EXPRESSION) |
||
3209 | * @param $types An array of field types |
||
3210 | * |
||
3211 | * @return int 0 if operation was successful |
||
3212 | */ |
||
3213 | public function insertRow($table, $fields, $values, $nulls, $format, $types) |
||
3214 | { |
||
3215 | if (!is_array($fields) || !is_array($values) || !is_array($nulls) |
||
3216 | || !is_array($format) || !is_array($types) |
||
3217 | || (count($fields) != count($values)) |
||
3218 | ) { |
||
3219 | return -1; |
||
3220 | } |
||
3221 | |||
3222 | // Build clause |
||
3223 | if (count($values) > 0) { |
||
3224 | // Escape all field names |
||
3225 | $fields = array_map(['\PHPPgAdmin\Database\Postgres', 'fieldClean'], $fields); |
||
3226 | $f_schema = $this->_schema; |
||
3227 | $this->fieldClean($table); |
||
3228 | $this->fieldClean($f_schema); |
||
3229 | |||
3230 | $sql = ''; |
||
3231 | foreach ($values as $i => $value) { |
||
3232 | // Handle NULL values |
||
3233 | if (isset($nulls[$i])) { |
||
3234 | $sql .= ',NULL'; |
||
3235 | } else { |
||
3236 | $sql .= ',' . $this->formatValue($types[$i], $format[$i], $value); |
||
3237 | } |
||
3238 | } |
||
3239 | |||
3240 | $sql = "INSERT INTO \"{$f_schema}\".\"{$table}\" (\"" . implode('","', $fields) . '") |
||
3241 | VALUES (' . substr($sql, 1) . ')'; |
||
3242 | |||
3243 | return $this->execute($sql); |
||
3244 | } |
||
3245 | |||
3246 | return -1; |
||
3247 | } |
||
3248 | |||
3249 | /** |
||
3250 | * Formats a value or expression for sql purposes. |
||
3251 | * |
||
3252 | * @param $type The type of the field |
||
3253 | * @param $format VALUE or EXPRESSION |
||
3254 | * @param $value The actual value entered in the field. Can be NULL |
||
3255 | * |
||
3256 | * @return The suitably quoted and escaped value |
||
3257 | */ |
||
3258 | public function formatValue($type, $format, $value) |
||
3259 | { |
||
3260 | switch ($type) { |
||
3261 | case 'bool': |
||
3262 | case 'boolean': |
||
3263 | if ($value == 't') { |
||
3264 | return 'TRUE'; |
||
3265 | } |
||
3266 | |||
3267 | if ($value == 'f') { |
||
3268 | return 'FALSE'; |
||
3269 | } |
||
3270 | if ($value == '') { |
||
3271 | return 'NULL'; |
||
3272 | } |
||
3273 | |||
3274 | return $value; |
||
3275 | break; |
||
1 ignored issue
–
show
|
|||
3276 | default: |
||
3277 | // Checking variable fields is difficult as there might be a size |
||
3278 | // attribute... |
||
3279 | if (strpos($type, 'time') === 0) { |
||
3280 | // Assume it's one of the time types... |
||
3281 | if ($value == '') { |
||
3282 | return "''"; |
||
3283 | } |
||
3284 | |||
3285 | if (strcasecmp($value, 'CURRENT_TIMESTAMP') == 0 |
||
3286 | || strcasecmp($value, 'CURRENT_TIME') == 0 |
||
3287 | || strcasecmp($value, 'CURRENT_DATE') == 0 |
||
3288 | || strcasecmp($value, 'LOCALTIME') == 0 |
||
3289 | || strcasecmp($value, 'LOCALTIMESTAMP') == 0) { |
||
1 ignored issue
–
show
|
|||
3290 | return $value; |
||
3291 | } |
||
3292 | if ($format == 'EXPRESSION') { |
||
3293 | return $value; |
||
3294 | } |
||
3295 | $this->clean($value); |
||
3296 | |||
3297 | return "'{$value}'"; |
||
3298 | } |
||
3299 | if ($format == 'VALUE') { |
||
3300 | $this->clean($value); |
||
3301 | |||
3302 | return "'{$value}'"; |
||
3303 | } |
||
3304 | |||
3305 | return $value; |
||
3306 | } |
||
3307 | } |
||
3308 | |||
3309 | // View functions |
||
3310 | |||
3311 | /** |
||
3312 | * Updates a row in a table. |
||
3313 | * |
||
3314 | * @param $table The table in which to update |
||
3315 | * @param $vars An array mapping new values for the row |
||
3316 | * @param $nulls An array mapping column => something if it is to be null |
||
3317 | * @param $format An array of the data type (VALUE or EXPRESSION) |
||
3318 | * @param $types An array of field types |
||
3319 | * @param $keyarr An array mapping column => value to update |
||
3320 | * |
||
3321 | * @return bool|int 0 success |
||
3322 | */ |
||
3323 | public function editRow($table, $vars, $nulls, $format, $types, $keyarr) |
||
3324 | { |
||
3325 | if (!is_array($vars) || !is_array($nulls) || !is_array($format) || !is_array($types)) { |
||
3326 | return -1; |
||
3327 | } |
||
3328 | |||
3329 | $f_schema = $this->_schema; |
||
3330 | $this->fieldClean($f_schema); |
||
3331 | $this->fieldClean($table); |
||
3332 | |||
3333 | // Build clause |
||
3334 | if (sizeof($vars) > 0) { |
||
3335 | foreach ($vars as $key => $value) { |
||
3336 | $this->fieldClean($key); |
||
3337 | |||
3338 | // Handle NULL values |
||
3339 | if (isset($nulls[$key])) { |
||
3340 | $tmp = 'NULL'; |
||
3341 | } else { |
||
3342 | $tmp = $this->formatValue($types[$key], $format[$key], $value); |
||
3343 | } |
||
3344 | |||
3345 | if (isset($sql)) { |
||
3346 | $sql .= ", \"{$key}\"={$tmp}"; |
||
3347 | } else { |
||
3348 | $sql = "UPDATE \"{$f_schema}\".\"{$table}\" SET \"{$key}\"={$tmp}"; |
||
3349 | } |
||
3350 | } |
||
3351 | $first = true; |
||
3352 | foreach ($keyarr as $k => $v) { |
||
3353 | $this->fieldClean($k); |
||
3354 | $this->clean($v); |
||
3355 | if ($first) { |
||
3356 | $sql .= " WHERE \"{$k}\"='{$v}'"; |
||
3357 | $first = false; |
||
3358 | } else { |
||
3359 | $sql .= " AND \"{$k}\"='{$v}'"; |
||
3360 | } |
||
3361 | } |
||
3362 | } |
||
3363 | |||
3364 | // Begin transaction. We do this so that we can ensure only one row is |
||
3365 | // edited |
||
3366 | $status = $this->beginTransaction(); |
||
3367 | if ($status != 0) { |
||
3368 | $this->rollbackTransaction(); |
||
3369 | |||
3370 | return -1; |
||
3371 | } |
||
3372 | |||
3373 | $status = $this->execute($sql); |
||
3374 | if ($status != 0) { |
||
3375 | // update failed |
||
3376 | $this->rollbackTransaction(); |
||
3377 | |||
3378 | return -1; |
||
3379 | } |
||
3380 | |||
3381 | if ($this->conn->Affected_Rows() != 1) { |
||
3382 | // more than one row could be updated |
||
3383 | $this->rollbackTransaction(); |
||
3384 | |||
3385 | return -2; |
||
3386 | } |
||
3387 | |||
3388 | // End transaction |
||
3389 | return $this->endTransaction(); |
||
3390 | } |
||
3391 | |||
3392 | /** |
||
3393 | * Delete a row from a table. |
||
3394 | * |
||
3395 | * @param $table The table from which to delete |
||
3396 | * @param $key An array mapping column => value to delete |
||
3397 | * @param bool $schema |
||
3398 | * |
||
3399 | * @return bool|int 0 success |
||
3400 | */ |
||
3401 | public function deleteRow($table, $key, $schema = false) |
||
3402 | { |
||
3403 | if (!is_array($key)) { |
||
3404 | return -1; |
||
3405 | } |
||
3406 | |||
3407 | // Begin transaction. We do this so that we can ensure only one row is |
||
3408 | // deleted |
||
3409 | $status = $this->beginTransaction(); |
||
3410 | if ($status != 0) { |
||
3411 | $this->rollbackTransaction(); |
||
3412 | |||
3413 | return -1; |
||
3414 | } |
||
3415 | |||
3416 | if ($schema === false) { |
||
3417 | $schema = $this->_schema; |
||
3418 | } |
||
3419 | |||
3420 | $status = $this->delete($table, $key, $schema); |
||
3421 | if ($status != 0 || $this->conn->Affected_Rows() != 1) { |
||
3422 | $this->rollbackTransaction(); |
||
3423 | |||
3424 | return -2; |
||
3425 | } |
||
3426 | |||
3427 | // End transaction |
||
3428 | return $this->endTransaction(); |
||
3429 | } |
||
3430 | |||
3431 | /** |
||
3432 | * Returns all sequences in the current database. |
||
3433 | * |
||
3434 | * @param bool $all |
||
3435 | * |
||
3436 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
3437 | */ |
||
3438 | public function getSequences($all = false) |
||
3439 | { |
||
3440 | if ($all) { |
||
3441 | // Exclude pg_catalog and information_schema tables |
||
3442 | $sql = "SELECT n.nspname, c.relname AS seqname, u.usename AS seqowner |
||
3443 | FROM pg_catalog.pg_class c, pg_catalog.pg_user u, pg_catalog.pg_namespace n |
||
3444 | WHERE c.relowner=u.usesysid AND c.relnamespace=n.oid |
||
3445 | AND c.relkind = 'S' |
||
3446 | AND n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast') |
||
3447 | ORDER BY nspname, seqname"; |
||
3448 | } else { |
||
3449 | $c_schema = $this->_schema; |
||
3450 | $this->clean($c_schema); |
||
3451 | $sql = "SELECT c.relname AS seqname, u.usename AS seqowner, pg_catalog.obj_description(c.oid, 'pg_class') AS seqcomment, |
||
3452 | (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=c.reltablespace) AS tablespace |
||
3453 | FROM pg_catalog.pg_class c, pg_catalog.pg_user u, pg_catalog.pg_namespace n |
||
3454 | WHERE c.relowner=u.usesysid AND c.relnamespace=n.oid |
||
3455 | AND c.relkind = 'S' AND n.nspname='{$c_schema}' ORDER BY seqname"; |
||
3456 | } |
||
3457 | |||
3458 | return $this->selectSet($sql); |
||
3459 | } |
||
3460 | |||
3461 | /** |
||
3462 | * Execute nextval on a given sequence. |
||
3463 | * |
||
3464 | * @param $sequence Sequence name |
||
3465 | * |
||
3466 | * @return integer 0 if operation was successful |
||
3467 | */ |
||
3468 | public function nextvalSequence($sequence) |
||
3469 | { |
||
3470 | /* This double-cleaning is deliberate */ |
||
3471 | $f_schema = $this->_schema; |
||
3472 | $this->fieldClean($f_schema); |
||
3473 | $this->clean($f_schema); |
||
3474 | $this->fieldClean($sequence); |
||
3475 | $this->clean($sequence); |
||
3476 | |||
3477 | $sql = "SELECT pg_catalog.NEXTVAL('\"{$f_schema}\".\"{$sequence}\"')"; |
||
3478 | |||
3479 | return $this->execute($sql); |
||
3480 | } |
||
3481 | |||
3482 | /** |
||
3483 | * Execute setval on a given sequence. |
||
3484 | * |
||
3485 | * @param $sequence Sequence name |
||
3486 | * @param $nextvalue The next value |
||
3487 | * |
||
3488 | * @return integer 0 if operation was successful |
||
3489 | */ |
||
3490 | public function setvalSequence($sequence, $nextvalue) |
||
3491 | { |
||
3492 | /* This double-cleaning is deliberate */ |
||
3493 | $f_schema = $this->_schema; |
||
3494 | $this->fieldClean($f_schema); |
||
3495 | $this->clean($f_schema); |
||
3496 | $this->fieldClean($sequence); |
||
3497 | $this->clean($sequence); |
||
3498 | $this->clean($nextvalue); |
||
3499 | |||
3500 | $sql = "SELECT pg_catalog.SETVAL('\"{$f_schema}\".\"{$sequence}\"', '{$nextvalue}')"; |
||
3501 | |||
3502 | return $this->execute($sql); |
||
3503 | } |
||
3504 | |||
3505 | /** |
||
3506 | * Restart a given sequence to its start value. |
||
3507 | * |
||
3508 | * @param $sequence Sequence name |
||
3509 | * |
||
3510 | * @return integer 0 if operation was successful |
||
3511 | */ |
||
3512 | public function restartSequence($sequence) |
||
3513 | { |
||
3514 | $f_schema = $this->_schema; |
||
3515 | $this->fieldClean($f_schema); |
||
3516 | $this->fieldClean($sequence); |
||
3517 | |||
3518 | $sql = "ALTER SEQUENCE \"{$f_schema}\".\"{$sequence}\" RESTART;"; |
||
3519 | |||
3520 | return $this->execute($sql); |
||
3521 | } |
||
3522 | |||
3523 | /** |
||
3524 | * Resets a given sequence to min value of sequence. |
||
3525 | * |
||
3526 | * @param $sequence Sequence name |
||
3527 | * |
||
3528 | * @return int 0 if operation was successful |
||
3529 | */ |
||
3530 | public function resetSequence($sequence) |
||
3531 | { |
||
3532 | // Get the minimum value of the sequence |
||
3533 | $seq = $this->getSequence($sequence); |
||
3534 | if ($seq->recordCount() != 1) { |
||
3535 | return -1; |
||
3536 | } |
||
3537 | |||
3538 | $minvalue = $seq->fields['min_value']; |
||
3539 | |||
3540 | $f_schema = $this->_schema; |
||
3541 | $this->fieldClean($f_schema); |
||
3542 | /* This double-cleaning is deliberate */ |
||
3543 | $this->fieldClean($sequence); |
||
3544 | $this->clean($sequence); |
||
3545 | |||
3546 | $sql = "SELECT pg_catalog.SETVAL('\"{$f_schema}\".\"{$sequence}\"', {$minvalue})"; |
||
3547 | |||
3548 | return $this->execute($sql); |
||
3549 | } |
||
3550 | |||
3551 | /** |
||
3552 | * Returns properties of a single sequence. |
||
3553 | * |
||
3554 | * @param $sequence Sequence name |
||
3555 | * |
||
3556 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
3557 | */ |
||
3558 | public function getSequence($sequence) |
||
3559 | { |
||
3560 | $c_schema = $this->_schema; |
||
3561 | $this->clean($c_schema); |
||
3562 | $c_sequence = $sequence; |
||
3563 | $this->fieldClean($sequence); |
||
3564 | $this->clean($c_sequence); |
||
3565 | |||
3566 | $sql = " |
||
3567 | SELECT c.relname AS seqname, s.*, |
||
3568 | pg_catalog.obj_description(s.tableoid, 'pg_class') AS seqcomment, |
||
3569 | u.usename AS seqowner, n.nspname |
||
3570 | FROM \"{$sequence}\" AS s, pg_catalog.pg_class c, pg_catalog.pg_user u, pg_catalog.pg_namespace n |
||
3571 | WHERE c.relowner=u.usesysid AND c.relnamespace=n.oid |
||
3572 | AND c.relname = '{$c_sequence}' AND c.relkind = 'S' AND n.nspname='{$c_schema}' |
||
3573 | AND n.oid = c.relnamespace"; |
||
3574 | |||
3575 | return $this->selectSet($sql); |
||
3576 | } |
||
3577 | |||
3578 | /** |
||
3579 | * Creates a new sequence. |
||
3580 | * |
||
3581 | * @param $sequence Sequence name |
||
3582 | * @param $increment The increment |
||
3583 | * @param $minvalue The min value |
||
3584 | * @param $maxvalue The max value |
||
3585 | * @param $startvalue The starting value |
||
3586 | * @param $cachevalue The cache value |
||
3587 | * @param $cycledvalue True if cycled, false otherwise |
||
3588 | * |
||
3589 | * @return integer 0 if operation was successful |
||
3590 | */ |
||
3591 | public function createSequence( |
||
3592 | $sequence, |
||
3593 | $increment, |
||
3594 | $minvalue, |
||
3595 | $maxvalue, |
||
3596 | $startvalue, |
||
3597 | $cachevalue, |
||
3598 | $cycledvalue |
||
3599 | ) { |
||
3600 | $f_schema = $this->_schema; |
||
3601 | $this->fieldClean($f_schema); |
||
3602 | $this->fieldClean($sequence); |
||
3603 | $this->clean($increment); |
||
3604 | $this->clean($minvalue); |
||
3605 | $this->clean($maxvalue); |
||
3606 | $this->clean($startvalue); |
||
3607 | $this->clean($cachevalue); |
||
3608 | |||
3609 | $sql = "CREATE SEQUENCE \"{$f_schema}\".\"{$sequence}\""; |
||
3610 | if ($increment != '') { |
||
3611 | $sql .= " INCREMENT {$increment}"; |
||
3612 | } |
||
3613 | |||
3614 | if ($minvalue != '') { |
||
3615 | $sql .= " MINVALUE {$minvalue}"; |
||
3616 | } |
||
3617 | |||
3618 | if ($maxvalue != '') { |
||
3619 | $sql .= " MAXVALUE {$maxvalue}"; |
||
3620 | } |
||
3621 | |||
3622 | if ($startvalue != '') { |
||
3623 | $sql .= " START {$startvalue}"; |
||
3624 | } |
||
3625 | |||
3626 | if ($cachevalue != '') { |
||
3627 | $sql .= " CACHE {$cachevalue}"; |
||
3628 | } |
||
3629 | |||
3630 | if ($cycledvalue) { |
||
3631 | $sql .= ' CYCLE'; |
||
3632 | } |
||
3633 | |||
3634 | return $this->execute($sql); |
||
3635 | } |
||
3636 | |||
3637 | /** |
||
3638 | * Alters a sequence. |
||
3639 | * |
||
3640 | * @param $sequence The name of the sequence |
||
3641 | * @param $name The new name for the sequence |
||
3642 | * @param $comment The comment on the sequence |
||
3643 | * @param $owner The new owner for the sequence |
||
3644 | * @param $schema The new schema for the sequence |
||
3645 | * @param $increment The increment |
||
3646 | * @param $minvalue The min value |
||
3647 | * @param $maxvalue The max value |
||
3648 | * @param $restartvalue The starting value |
||
3649 | * @param $cachevalue The cache value |
||
3650 | * @param $cycledvalue True if cycled, false otherwise |
||
3651 | * @param $startvalue The sequence start value when issueing a restart |
||
3652 | * |
||
3653 | * @return bool|int 0 success |
||
3654 | */ |
||
3655 | public function alterSequence( |
||
3656 | $sequence, |
||
3657 | $name, |
||
3658 | $comment, |
||
3659 | $owner = null, |
||
3660 | $schema = null, |
||
3661 | $increment = null, |
||
3662 | $minvalue = null, |
||
3663 | $maxvalue = null, |
||
3664 | $restartvalue = null, |
||
3665 | $cachevalue = null, |
||
3666 | $cycledvalue = null, |
||
3667 | $startvalue = null |
||
3668 | ) { |
||
3669 | $this->fieldClean($sequence); |
||
3670 | |||
3671 | $data = $this->getSequence($sequence); |
||
3672 | |||
3673 | if ($data->recordCount() != 1) { |
||
3674 | return -2; |
||
3675 | } |
||
3676 | |||
3677 | $status = $this->beginTransaction(); |
||
3678 | if ($status != 0) { |
||
3679 | $this->rollbackTransaction(); |
||
3680 | |||
3681 | return -1; |
||
3682 | } |
||
3683 | |||
3684 | $status = $this->_alterSequence( |
||
3685 | $data, |
||
3686 | $name, |
||
3687 | $comment, |
||
3688 | $owner, |
||
3689 | $schema, |
||
3690 | $increment, |
||
3691 | $minvalue, |
||
3692 | $maxvalue, |
||
3693 | $restartvalue, |
||
3694 | $cachevalue, |
||
3695 | $cycledvalue, |
||
3696 | $startvalue |
||
3697 | ); |
||
3698 | |||
3699 | if ($status != 0) { |
||
3700 | $this->rollbackTransaction(); |
||
3701 | |||
3702 | return $status; |
||
3703 | } |
||
3704 | |||
3705 | return $this->endTransaction(); |
||
3706 | } |
||
3707 | |||
3708 | /** |
||
3709 | * Protected method which alter a sequence |
||
3710 | * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION. |
||
3711 | * |
||
3712 | * @param $seqrs The sequence recordSet returned by getSequence() |
||
3713 | * @param $name The new name for the sequence |
||
3714 | * @param $comment The comment on the sequence |
||
3715 | * @param $owner The new owner for the sequence |
||
3716 | * @param $schema The new schema for the sequence |
||
3717 | * @param $increment The increment |
||
3718 | * @param $minvalue The min value |
||
3719 | * @param $maxvalue The max value |
||
3720 | * @param $restartvalue The starting value |
||
3721 | * @param $cachevalue The cache value |
||
3722 | * @param $cycledvalue True if cycled, false otherwise |
||
3723 | * @param $startvalue The sequence start value when issueing a restart |
||
3724 | * |
||
3725 | * @return int 0 success |
||
3726 | */ |
||
3727 | protected function _alterSequence( |
||
1 ignored issue
–
show
|
|||
3728 | $seqrs, |
||
3729 | $name, |
||
3730 | $comment, |
||
3731 | $owner, |
||
3732 | $schema, |
||
3733 | $increment, |
||
3734 | $minvalue, |
||
3735 | $maxvalue, |
||
3736 | $restartvalue, |
||
3737 | $cachevalue, |
||
3738 | $cycledvalue, |
||
3739 | $startvalue |
||
3740 | ) { |
||
3741 | $this->fieldArrayClean($seqrs->fields); |
||
3742 | |||
3743 | // Comment |
||
3744 | $status = $this->setComment('SEQUENCE', $seqrs->fields['seqname'], '', $comment); |
||
3745 | if ($status != 0) { |
||
3746 | return -4; |
||
3747 | } |
||
3748 | |||
3749 | // Owner |
||
3750 | $this->fieldClean($owner); |
||
3751 | $status = $this->alterSequenceOwner($seqrs, $owner); |
||
3752 | if ($status != 0) { |
||
3753 | return -5; |
||
3754 | } |
||
3755 | |||
3756 | // Props |
||
3757 | $this->clean($increment); |
||
3758 | $this->clean($minvalue); |
||
3759 | $this->clean($maxvalue); |
||
3760 | $this->clean($restartvalue); |
||
3761 | $this->clean($cachevalue); |
||
3762 | $this->clean($cycledvalue); |
||
3763 | $this->clean($startvalue); |
||
3764 | $status = $this->alterSequenceProps( |
||
3765 | $seqrs, |
||
3766 | $increment, |
||
3767 | $minvalue, |
||
3768 | $maxvalue, |
||
3769 | $restartvalue, |
||
3770 | $cachevalue, |
||
3771 | $cycledvalue, |
||
3772 | $startvalue |
||
3773 | ); |
||
3774 | if ($status != 0) { |
||
3775 | return -6; |
||
3776 | } |
||
3777 | |||
3778 | // Rename |
||
3779 | $this->fieldClean($name); |
||
3780 | $status = $this->alterSequenceName($seqrs, $name); |
||
3781 | if ($status != 0) { |
||
3782 | return -3; |
||
3783 | } |
||
3784 | |||
3785 | // Schema |
||
3786 | $this->clean($schema); |
||
3787 | $status = $this->alterSequenceSchema($seqrs, $schema); |
||
3788 | if ($status != 0) { |
||
3789 | return -7; |
||
3790 | } |
||
3791 | |||
3792 | return 0; |
||
3793 | } |
||
3794 | |||
3795 | // Index functions |
||
3796 | |||
3797 | /** |
||
3798 | * Alter a sequence's owner. |
||
3799 | * |
||
3800 | * @param $seqrs The sequence RecordSet returned by getSequence() |
||
3801 | * @param $owner |
||
3802 | * |
||
3803 | * @return int 0 if operation was successful |
||
3804 | * |
||
3805 | * @internal param \PHPPgAdmin\Database\The $name new owner for the sequence |
||
3806 | */ |
||
3807 | public function alterSequenceOwner($seqrs, $owner) |
||
3808 | { |
||
3809 | // If owner has been changed, then do the alteration. We are |
||
3810 | // careful to avoid this generally as changing owner is a |
||
3811 | // superuser only function. |
||
3812 | /* vars are cleaned in _alterSequence */ |
||
3813 | if (!empty($owner) && ($seqrs->fields['seqowner'] != $owner)) { |
||
3814 | $f_schema = $this->_schema; |
||
3815 | $this->fieldClean($f_schema); |
||
3816 | $sql = "ALTER SEQUENCE \"{$f_schema}\".\"{$seqrs->fields['seqname']}\" OWNER TO \"{$owner}\""; |
||
3817 | |||
3818 | return $this->execute($sql); |
||
3819 | } |
||
3820 | |||
3821 | return 0; |
||
3822 | } |
||
3823 | |||
3824 | /** |
||
3825 | * Alter a sequence's properties. |
||
3826 | * |
||
3827 | * @param $seqrs The sequence RecordSet returned by getSequence() |
||
3828 | * @param $increment The sequence incremental value |
||
3829 | * @param $minvalue The sequence minimum value |
||
3830 | * @param $maxvalue The sequence maximum value |
||
3831 | * @param $restartvalue The sequence current value |
||
3832 | * @param $cachevalue The sequence cache value |
||
3833 | * @param $cycledvalue Sequence can cycle ? |
||
3834 | * @param $startvalue The sequence start value when issueing a restart |
||
3835 | * |
||
3836 | * @return int 0 if operation was successful |
||
3837 | */ |
||
3838 | public function alterSequenceProps( |
||
3839 | $seqrs, |
||
3840 | $increment, |
||
3841 | $minvalue, |
||
3842 | $maxvalue, |
||
3843 | $restartvalue, |
||
3844 | $cachevalue, |
||
3845 | $cycledvalue, |
||
3846 | $startvalue |
||
3847 | ) { |
||
3848 | $sql = ''; |
||
3849 | /* vars are cleaned in _alterSequence */ |
||
3850 | if (!empty($increment) && ($increment != $seqrs->fields['increment_by'])) { |
||
3851 | $sql .= " INCREMENT {$increment}"; |
||
3852 | } |
||
3853 | |||
3854 | if (!empty($minvalue) && ($minvalue != $seqrs->fields['min_value'])) { |
||
3855 | $sql .= " MINVALUE {$minvalue}"; |
||
3856 | } |
||
3857 | |||
3858 | if (!empty($maxvalue) && ($maxvalue != $seqrs->fields['max_value'])) { |
||
3859 | $sql .= " MAXVALUE {$maxvalue}"; |
||
3860 | } |
||
3861 | |||
3862 | if (!empty($restartvalue) && ($restartvalue != $seqrs->fields['last_value'])) { |
||
3863 | $sql .= " RESTART {$restartvalue}"; |
||
3864 | } |
||
3865 | |||
3866 | if (!empty($cachevalue) && ($cachevalue != $seqrs->fields['cache_value'])) { |
||
3867 | $sql .= " CACHE {$cachevalue}"; |
||
3868 | } |
||
3869 | |||
3870 | if (!empty($startvalue) && ($startvalue != $seqrs->fields['start_value'])) { |
||
3871 | $sql .= " START {$startvalue}"; |
||
3872 | } |
||
3873 | |||
3874 | // toggle cycle yes/no |
||
3875 | if (!is_null($cycledvalue)) { |
||
3876 | $sql .= (!$cycledvalue ? ' NO ' : '') . ' CYCLE'; |
||
3877 | } |
||
3878 | |||
3879 | if ($sql != '') { |
||
3880 | $f_schema = $this->_schema; |
||
3881 | $this->fieldClean($f_schema); |
||
3882 | $sql = "ALTER SEQUENCE \"{$f_schema}\".\"{$seqrs->fields['seqname']}\" {$sql}"; |
||
3883 | |||
3884 | return $this->execute($sql); |
||
3885 | } |
||
3886 | |||
3887 | return 0; |
||
3888 | } |
||
3889 | |||
3890 | /** |
||
3891 | * Rename a sequence. |
||
3892 | * |
||
3893 | * @param $seqrs The sequence RecordSet returned by getSequence() |
||
3894 | * @param $name The new name for the sequence |
||
3895 | * |
||
3896 | * @return int 0 if operation was successful |
||
3897 | */ |
||
3898 | public function alterSequenceName($seqrs, $name) |
||
3899 | { |
||
3900 | /* vars are cleaned in _alterSequence */ |
||
3901 | if (!empty($name) && ($seqrs->fields['seqname'] != $name)) { |
||
3902 | $f_schema = $this->_schema; |
||
3903 | $this->fieldClean($f_schema); |
||
3904 | $sql = "ALTER SEQUENCE \"{$f_schema}\".\"{$seqrs->fields['seqname']}\" RENAME TO \"{$name}\""; |
||
3905 | $status = $this->execute($sql); |
||
3906 | if ($status == 0) { |
||
3907 | $seqrs->fields['seqname'] = $name; |
||
3908 | } else { |
||
3909 | return $status; |
||
3910 | } |
||
3911 | } |
||
3912 | |||
3913 | return 0; |
||
3914 | } |
||
3915 | |||
3916 | /** |
||
3917 | * Alter a sequence's schema. |
||
3918 | * |
||
3919 | * @param $seqrs The sequence RecordSet returned by getSequence() |
||
3920 | * @param $schema |
||
3921 | * |
||
3922 | * @return int 0 if operation was successful |
||
3923 | * |
||
3924 | * @internal param The $name new schema for the sequence |
||
3925 | */ |
||
3926 | public function alterSequenceSchema($seqrs, $schema) |
||
3927 | { |
||
3928 | /* vars are cleaned in _alterSequence */ |
||
3929 | if (!empty($schema) && ($seqrs->fields['nspname'] != $schema)) { |
||
3930 | $f_schema = $this->_schema; |
||
3931 | $this->fieldClean($f_schema); |
||
3932 | $sql = "ALTER SEQUENCE \"{$f_schema}\".\"{$seqrs->fields['seqname']}\" SET SCHEMA {$schema}"; |
||
3933 | |||
3934 | return $this->execute($sql); |
||
3935 | } |
||
3936 | |||
3937 | return 0; |
||
3938 | } |
||
3939 | |||
3940 | /** |
||
3941 | * Drops a given sequence. |
||
3942 | * |
||
3943 | * @param $sequence Sequence name |
||
3944 | * @param $cascade True to cascade drop, false to restrict |
||
3945 | * |
||
3946 | * @return integer 0 if operation was successful |
||
3947 | */ |
||
3948 | public function dropSequence($sequence, $cascade) |
||
3949 | { |
||
3950 | $f_schema = $this->_schema; |
||
3951 | $this->fieldClean($f_schema); |
||
3952 | $this->fieldClean($sequence); |
||
3953 | |||
3954 | $sql = "DROP SEQUENCE \"{$f_schema}\".\"{$sequence}\""; |
||
3955 | if ($cascade) { |
||
3956 | $sql .= ' CASCADE'; |
||
3957 | } |
||
3958 | |||
3959 | return $this->execute($sql); |
||
3960 | } |
||
3961 | |||
3962 | /** |
||
3963 | * Returns a list of all views in the database. |
||
3964 | * |
||
3965 | * @return \PHPPgAdmin\ADORecordSet All views |
||
3966 | */ |
||
3967 | public function getViews() |
||
3968 | { |
||
3969 | $c_schema = $this->_schema; |
||
3970 | $this->clean($c_schema); |
||
3971 | $sql = " |
||
3972 | SELECT c.relname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner, |
||
3973 | pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment |
||
3974 | FROM pg_catalog.pg_class c |
||
3975 | LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace) |
||
3976 | WHERE (n.nspname='{$c_schema}') AND (c.relkind = 'v'::\"char\") |
||
3977 | ORDER BY relname"; |
||
3978 | |||
3979 | return $this->selectSet($sql); |
||
3980 | } |
||
3981 | |||
3982 | // Constraint functions |
||
3983 | |||
3984 | /** |
||
3985 | * Returns a list of all materialized views in the database. |
||
3986 | * |
||
3987 | * @return \PHPPgAdmin\ADORecordSet All materialized views |
||
3988 | */ |
||
3989 | public function getMaterializedViews() |
||
3990 | { |
||
3991 | $c_schema = $this->_schema; |
||
3992 | $this->clean($c_schema); |
||
3993 | $sql = " |
||
3994 | SELECT c.relname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner, |
||
3995 | pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment |
||
3996 | FROM pg_catalog.pg_class c |
||
3997 | LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace) |
||
3998 | WHERE (n.nspname='{$c_schema}') AND (c.relkind = 'm'::\"char\") |
||
3999 | ORDER BY relname"; |
||
4000 | |||
4001 | return $this->selectSet($sql); |
||
4002 | } |
||
4003 | |||
4004 | /** |
||
4005 | * Updates a view. |
||
4006 | * |
||
4007 | * @param string $viewname The name fo the view to update |
||
4008 | * @param string $definition The new definition for the view |
||
4009 | * @param string $comment |
||
4010 | * |
||
4011 | * @return bool|int 0 success |
||
4012 | */ |
||
4013 | public function setView($viewname, $definition, $comment) |
||
4014 | { |
||
4015 | return $this->createView($viewname, $definition, true, $comment); |
||
4016 | } |
||
4017 | |||
4018 | /** |
||
4019 | * Creates a new view. |
||
4020 | * |
||
4021 | * @param string $viewname The name of the view to create |
||
4022 | * @param string $definition The definition for the new view |
||
4023 | * @param bool $replace True to replace the view, false otherwise |
||
4024 | * @param string $comment |
||
4025 | * |
||
4026 | * @return bool|int 0 success |
||
4027 | */ |
||
4028 | public function createView($viewname, $definition, $replace, $comment) |
||
4029 | { |
||
4030 | $status = $this->beginTransaction(); |
||
4031 | if ($status != 0) { |
||
4032 | return -1; |
||
4033 | } |
||
4034 | |||
4035 | $f_schema = $this->_schema; |
||
4036 | $this->fieldClean($f_schema); |
||
4037 | $this->fieldClean($viewname); |
||
4038 | |||
4039 | // Note: $definition not cleaned |
||
4040 | |||
4041 | $sql = 'CREATE '; |
||
4042 | if ($replace) { |
||
4043 | $sql .= 'OR REPLACE '; |
||
4044 | } |
||
4045 | |||
4046 | $sql .= "VIEW \"{$f_schema}\".\"{$viewname}\" AS {$definition}"; |
||
4047 | |||
4048 | $status = $this->execute($sql); |
||
4049 | if ($status) { |
||
4050 | $this->rollbackTransaction(); |
||
4051 | |||
4052 | return -1; |
||
4053 | } |
||
4054 | |||
4055 | if ($comment != '') { |
||
4056 | $status = $this->setComment('VIEW', $viewname, '', $comment); |
||
4057 | if ($status) { |
||
4058 | $this->rollbackTransaction(); |
||
4059 | |||
4060 | return -1; |
||
4061 | } |
||
4062 | } |
||
4063 | |||
4064 | return $this->endTransaction(); |
||
4065 | } |
||
4066 | |||
4067 | /** |
||
4068 | * Alter view properties. |
||
4069 | * |
||
4070 | * @param string $view The name of the view |
||
4071 | * @param string $name The new name for the view |
||
4072 | * @param string $owner The new owner for the view |
||
4073 | * @param string $schema The new schema for the view |
||
4074 | * @param string $comment The comment on the view |
||
4075 | * |
||
4076 | * @return bool|int 0 success |
||
4077 | */ |
||
4078 | public function alterView($view, $name, $owner, $schema, $comment) |
||
4079 | { |
||
4080 | $data = $this->getView($view); |
||
4081 | if ($data->recordCount() != 1) { |
||
4082 | return -2; |
||
4083 | } |
||
4084 | |||
4085 | $status = $this->beginTransaction(); |
||
4086 | if ($status != 0) { |
||
4087 | $this->rollbackTransaction(); |
||
4088 | |||
4089 | return -1; |
||
4090 | } |
||
4091 | |||
4092 | $status = $this->_alterView($data, $name, $owner, $schema, $comment); |
||
4093 | |||
4094 | if ($status != 0) { |
||
4095 | $this->rollbackTransaction(); |
||
4096 | |||
4097 | return $status; |
||
4098 | } |
||
4099 | |||
4100 | return $this->endTransaction(); |
||
4101 | } |
||
4102 | |||
4103 | /** |
||
4104 | * Returns all details for a particular view. |
||
4105 | * |
||
4106 | * @param string $view The name of the view to retrieve |
||
4107 | * |
||
4108 | * @return \PHPPgAdmin\ADORecordSet View info |
||
4109 | */ |
||
4110 | public function getView($view) |
||
4111 | { |
||
4112 | $c_schema = $this->_schema; |
||
4113 | $this->clean($c_schema); |
||
4114 | $this->clean($view); |
||
4115 | |||
4116 | $sql = " |
||
4117 | SELECT c.relname, n.nspname, pg_catalog.pg_get_userbyid(c.relowner) AS relowner, |
||
4118 | pg_catalog.pg_get_viewdef(c.oid, true) AS vwdefinition, |
||
4119 | pg_catalog.obj_description(c.oid, 'pg_class') AS relcomment |
||
4120 | FROM pg_catalog.pg_class c |
||
4121 | LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.relnamespace) |
||
4122 | WHERE (c.relname = '{$view}') AND n.nspname='{$c_schema}'"; |
||
4123 | |||
4124 | return $this->selectSet($sql); |
||
4125 | } |
||
4126 | |||
4127 | /** |
||
4128 | * Protected method which alter a view |
||
4129 | * SHOULDN'T BE CALLED OUTSIDE OF A TRANSACTION. |
||
4130 | * |
||
4131 | * @param $vwrs The view recordSet returned by getView() |
||
4132 | * @param $name The new name for the view |
||
4133 | * @param $owner The new owner for the view |
||
4134 | * @param $schema |
||
4135 | * @param $comment The comment on the view |
||
4136 | * |
||
4137 | * @return int 0 success |
||
4138 | */ |
||
4139 | protected function _alterView($vwrs, $name, $owner, $schema, $comment) |
||
1 ignored issue
–
show
|
|||
4140 | { |
||
4141 | $this->fieldArrayClean($vwrs->fields); |
||
4142 | |||
4143 | // Comment |
||
4144 | if ($this->setComment('VIEW', $vwrs->fields['relname'], '', $comment) != 0) { |
||
4145 | return -4; |
||
4146 | } |
||
4147 | |||
4148 | // Owner |
||
4149 | $this->fieldClean($owner); |
||
4150 | $status = $this->alterViewOwner($vwrs, $owner); |
||
4151 | if ($status != 0) { |
||
4152 | return -5; |
||
4153 | } |
||
4154 | |||
4155 | // Rename |
||
4156 | $this->fieldClean($name); |
||
4157 | $status = $this->alterViewName($vwrs, $name); |
||
4158 | if ($status != 0) { |
||
4159 | return -3; |
||
4160 | } |
||
4161 | |||
4162 | // Schema |
||
4163 | $this->fieldClean($schema); |
||
4164 | $status = $this->alterViewSchema($vwrs, $schema); |
||
4165 | if ($status != 0) { |
||
4166 | return -6; |
||
4167 | } |
||
4168 | |||
4169 | return 0; |
||
4170 | } |
||
4171 | |||
4172 | /** |
||
4173 | * Alter a view's owner. |
||
4174 | * |
||
4175 | * @param \PHPPgAdmin\ADORecordSet $vwrs The view recordSet returned by getView() |
||
4176 | * @param null|string $owner |
||
4177 | * |
||
4178 | * @return int 0 if operation was successful |
||
4179 | * |
||
4180 | * @internal param $name new view's owner |
||
4181 | */ |
||
4182 | public function alterViewOwner($vwrs, $owner = null) |
||
4183 | { |
||
4184 | /* $vwrs and $owner are cleaned in _alterView */ |
||
4185 | if ((!empty($owner)) && ($vwrs->fields['relowner'] != $owner)) { |
||
4186 | $f_schema = $this->_schema; |
||
4187 | $this->fieldClean($f_schema); |
||
4188 | // If owner has been changed, then do the alteration. We are |
||
4189 | // careful to avoid this generally as changing owner is a |
||
4190 | // superuser only function. |
||
4191 | $sql = "ALTER TABLE \"{$f_schema}\".\"{$vwrs->fields['relname']}\" OWNER TO \"{$owner}\""; |
||
4192 | |||
4193 | return $this->execute($sql); |
||
4194 | } |
||
4195 | |||
4196 | return 0; |
||
4197 | } |
||
4198 | |||
4199 | /** |
||
4200 | * Rename a view. |
||
4201 | * |
||
4202 | * @param $vwrs The view recordSet returned by getView() |
||
4203 | * @param $name The new view's name |
||
4204 | * |
||
4205 | * @return int 0 if operation was successful |
||
4206 | */ |
||
4207 | public function alterViewName($vwrs, $name) |
||
4208 | { |
||
4209 | // Rename (only if name has changed) |
||
4210 | /* $vwrs and $name are cleaned in _alterView */ |
||
4211 | if (!empty($name) && ($name != $vwrs->fields['relname'])) { |
||
4212 | $f_schema = $this->_schema; |
||
4213 | $this->fieldClean($f_schema); |
||
4214 | $sql = "ALTER VIEW \"{$f_schema}\".\"{$vwrs->fields['relname']}\" RENAME TO \"{$name}\""; |
||
4215 | $status = $this->execute($sql); |
||
4216 | if ($status == 0) { |
||
4217 | $vwrs->fields['relname'] = $name; |
||
4218 | } else { |
||
4219 | return $status; |
||
4220 | } |
||
4221 | } |
||
4222 | |||
4223 | return 0; |
||
4224 | } |
||
4225 | |||
4226 | /** |
||
4227 | * Alter a view's schema. |
||
4228 | * |
||
4229 | * @param \PHPPgAdmin\ADORecordSet $vwrs The view recordSet returned by getView() |
||
4230 | * @param string $schema |
||
4231 | * |
||
4232 | * @return int 0 if operation was successful |
||
4233 | * |
||
4234 | * @internal param The $name new view's schema |
||
4235 | */ |
||
4236 | public function alterViewSchema($vwrs, $schema) |
||
4237 | { |
||
4238 | /* $vwrs and $schema are cleaned in _alterView */ |
||
4239 | if (!empty($schema) && ($vwrs->fields['nspname'] != $schema)) { |
||
4240 | $f_schema = $this->_schema; |
||
4241 | $this->fieldClean($f_schema); |
||
4242 | // If tablespace has been changed, then do the alteration. We |
||
4243 | // don't want to do this unnecessarily. |
||
4244 | $sql = "ALTER TABLE \"{$f_schema}\".\"{$vwrs->fields['relname']}\" SET SCHEMA \"{$schema}\""; |
||
4245 | |||
4246 | return $this->execute($sql); |
||
4247 | } |
||
4248 | |||
4249 | return 0; |
||
4250 | } |
||
4251 | |||
4252 | /** |
||
4253 | * Drops a view. |
||
4254 | * |
||
4255 | * @param string $viewname The name of the view to drop |
||
4256 | * @param string $cascade True to cascade drop, false to restrict |
||
4257 | * |
||
4258 | * @return integer 0 if operation was successful |
||
4259 | */ |
||
4260 | public function dropView($viewname, $cascade) |
||
4261 | { |
||
4262 | $f_schema = $this->_schema; |
||
4263 | $this->fieldClean($f_schema); |
||
4264 | $this->fieldClean($viewname); |
||
4265 | |||
4266 | $sql = "DROP VIEW \"{$f_schema}\".\"{$viewname}\""; |
||
4267 | if ($cascade) { |
||
4268 | $sql .= ' CASCADE'; |
||
4269 | } |
||
4270 | |||
4271 | return $this->execute($sql); |
||
4272 | } |
||
4273 | |||
4274 | // Domain functions |
||
4275 | |||
4276 | /** |
||
4277 | * test if a table has been clustered on an index. |
||
1 ignored issue
–
show
|
|||
4278 | * |
||
4279 | * @param string $table The table to test |
||
4280 | * |
||
4281 | * @return bool true if the table has been already clustered |
||
4282 | */ |
||
4283 | public function alreadyClustered($table) |
||
4284 | { |
||
4285 | $c_schema = $this->_schema; |
||
4286 | $this->clean($c_schema); |
||
4287 | $this->clean($table); |
||
4288 | |||
4289 | $sql = "SELECT i.indisclustered |
||
4290 | FROM pg_catalog.pg_class c, pg_catalog.pg_index i |
||
4291 | WHERE c.relname = '{$table}' |
||
4292 | AND c.oid = i.indrelid AND i.indisclustered |
||
4293 | AND c.relnamespace = (SELECT oid FROM pg_catalog.pg_namespace |
||
4294 | WHERE nspname='{$c_schema}') |
||
4295 | "; |
||
4296 | |||
4297 | $v = $this->selectSet($sql); |
||
4298 | |||
4299 | return !($v->recordCount() == 0); |
||
4300 | } |
||
4301 | |||
4302 | /** |
||
4303 | * Creates an index. |
||
4304 | * |
||
4305 | * @param string $name The index name |
||
4306 | * @param string $table The table on which to add the index |
||
4307 | * @param array $columns An array of columns that form the index or a string expression for a functional index |
||
4308 | * @param string $type The index type |
||
4309 | * @param bool $unique True if unique, false otherwise |
||
4310 | * @param string $where Index predicate ('' for none) |
||
4311 | * @param string $tablespace The tablespaces ('' means none/default) |
||
4312 | * @param bool $concurrently true to create index concurrently |
||
4313 | * |
||
4314 | * @return integer 0 if operation was successful |
||
4315 | */ |
||
4316 | public function createIndex($name, $table, $columns, $type, $unique, $where, $tablespace, $concurrently) |
||
4317 | { |
||
4318 | $f_schema = $this->_schema; |
||
4319 | $this->fieldClean($f_schema); |
||
4320 | $this->fieldClean($name); |
||
4321 | $this->fieldClean($table); |
||
4322 | |||
4323 | $sql = 'CREATE'; |
||
4324 | if ($unique) { |
||
4325 | $sql .= ' UNIQUE'; |
||
4326 | } |
||
4327 | |||
4328 | $sql .= ' INDEX'; |
||
4329 | if ($concurrently) { |
||
4330 | $sql .= ' CONCURRENTLY'; |
||
4331 | } |
||
4332 | |||
4333 | $sql .= " \"{$name}\" ON \"{$f_schema}\".\"{$table}\" USING {$type} "; |
||
4334 | |||
4335 | if (is_array($columns)) { |
||
4336 | $this->arrayClean($columns); |
||
4337 | $sql .= '("' . implode('","', $columns) . '")'; |
||
4338 | } else { |
||
4339 | $sql .= '(' . $columns . ')'; |
||
4340 | } |
||
4341 | |||
4342 | // Tablespace |
||
4343 | if ($this->hasTablespaces() && $tablespace != '') { |
||
4344 | $this->fieldClean($tablespace); |
||
4345 | $sql .= " TABLESPACE \"{$tablespace}\""; |
||
4346 | } |
||
4347 | |||
4348 | // Predicate |
||
4349 | if (trim($where) != '') { |
||
4350 | $sql .= " WHERE ({$where})"; |
||
4351 | } |
||
4352 | |||
4353 | return $this->execute($sql); |
||
4354 | } |
||
4355 | |||
4356 | /** |
||
4357 | * Removes an index from the database. |
||
4358 | * |
||
4359 | * @param string $index The index to drop |
||
4360 | * @param bool $cascade True to cascade drop, false to restrict |
||
4361 | * |
||
4362 | * @return integer 0 if operation was successful |
||
4363 | */ |
||
4364 | public function dropIndex($index, $cascade) |
||
4365 | { |
||
4366 | $f_schema = $this->_schema; |
||
4367 | $this->fieldClean($f_schema); |
||
4368 | $this->fieldClean($index); |
||
4369 | |||
4370 | $sql = "DROP INDEX \"{$f_schema}\".\"{$index}\""; |
||
4371 | if ($cascade) { |
||
4372 | $sql .= ' CASCADE'; |
||
4373 | } |
||
4374 | |||
4375 | $status = $this->execute($sql); |
||
4376 | return [$status, $sql]; |
||
4377 | } |
||
4378 | |||
4379 | /** |
||
4380 | * Rebuild indexes. |
||
4381 | * |
||
4382 | * @param string $type 'DATABASE' or 'TABLE' or 'INDEX' |
||
4383 | * @param string $name The name of the specific database, table, or index to be reindexed |
||
4384 | * @param bool $force If true, recreates indexes forcedly in PostgreSQL 7.0-7.1, forces rebuild of system indexes in |
||
4385 | * 7.2-7.3, ignored in >=7.4 |
||
4386 | * |
||
4387 | * @return int 0 if operation was successful |
||
4388 | */ |
||
4389 | public function reindex($type, $name, $force = false) |
||
4390 | { |
||
4391 | $f_schema = $this->_schema; |
||
4392 | $this->fieldClean($f_schema); |
||
4393 | $this->fieldClean($name); |
||
4394 | switch ($type) { |
||
4395 | case 'DATABASE': |
||
4396 | $sql = "REINDEX {$type} \"{$name}\""; |
||
4397 | if ($force) { |
||
4398 | $sql .= ' FORCE'; |
||
4399 | } |
||
4400 | |||
4401 | break; |
||
4402 | case 'TABLE': |
||
4403 | case 'INDEX': |
||
4404 | $sql = "REINDEX {$type} \"{$f_schema}\".\"{$name}\""; |
||
4405 | if ($force) { |
||
4406 | $sql .= ' FORCE'; |
||
4407 | } |
||
4408 | |||
4409 | break; |
||
4410 | default: |
||
4411 | return -1; |
||
4412 | } |
||
4413 | |||
4414 | return $this->execute($sql); |
||
4415 | } |
||
4416 | |||
4417 | /** |
||
4418 | * Clusters an index. |
||
4419 | * |
||
4420 | * @param string $table The table the index is on |
||
4421 | * @param string $index The name of the index |
||
4422 | * |
||
4423 | * @return integer 0 if operation was successful |
||
4424 | */ |
||
4425 | public function clusterIndex($table = '', $index = '') |
||
4426 | { |
||
4427 | $sql = 'CLUSTER'; |
||
4428 | |||
4429 | // We don't bother with a transaction here, as there's no point rolling |
||
4430 | // back an expensive cluster if a cheap analyze fails for whatever reason |
||
4431 | |||
4432 | if (!empty($table)) { |
||
4433 | $f_schema = $this->_schema; |
||
4434 | $this->fieldClean($f_schema); |
||
4435 | $this->fieldClean($table); |
||
4436 | $sql .= " \"{$f_schema}\".\"{$table}\""; |
||
4437 | |||
4438 | if (!empty($index)) { |
||
4439 | $this->fieldClean($index); |
||
4440 | $sql .= " USING \"{$index}\""; |
||
4441 | } |
||
4442 | } |
||
4443 | |||
4444 | $status = $this->execute($sql); |
||
4445 | |||
4446 | return [$status, $sql]; |
||
4447 | } |
||
4448 | |||
4449 | /** |
||
4450 | * Returns a list of all constraints on a table, |
||
4451 | * including constraint name, definition, related col and referenced namespace, |
||
4452 | * table and col if needed. |
||
4453 | * |
||
4454 | * @param string $table the table where we are looking for fk |
||
4455 | * |
||
4456 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
4457 | */ |
||
4458 | public function getConstraintsWithFields($table) |
||
4459 | { |
||
4460 | $c_schema = $this->_schema; |
||
4461 | $this->clean($c_schema); |
||
4462 | $this->clean($table); |
||
4463 | |||
4464 | // get the max number of col used in a constraint for the table |
||
4465 | $sql = "SELECT DISTINCT |
||
4466 | max(SUBSTRING(array_dims(c.conkey) FROM \$patern\$^\\[.*:(.*)\\]$\$patern\$)) as nb |
||
4467 | FROM pg_catalog.pg_constraint AS c |
||
4468 | JOIN pg_catalog.pg_class AS r ON (c.conrelid=r.oid) |
||
4469 | JOIN pg_catalog.pg_namespace AS ns ON (r.relnamespace=ns.oid) |
||
4470 | WHERE |
||
4471 | r.relname = '{$table}' AND ns.nspname='{$c_schema}'"; |
||
4472 | |||
4473 | $rs = $this->selectSet($sql); |
||
4474 | |||
4475 | if ($rs->EOF) { |
||
4476 | $max_col = 0; |
||
1 ignored issue
–
show
|
|||
4477 | } else { |
||
4478 | $max_col = $rs->fields['nb']; |
||
4479 | } |
||
4480 | |||
4481 | $sql = ' |
||
4482 | SELECT |
||
4483 | c.oid AS conid, c.contype, c.conname, pg_catalog.pg_get_constraintdef(c.oid, true) AS consrc, |
||
4484 | ns1.nspname as p_schema, r1.relname as p_table, ns2.nspname as f_schema, |
||
4485 | r2.relname as f_table, f1.attname as p_field, f1.attnum AS p_attnum, f2.attname as f_field, |
||
4486 | f2.attnum AS f_attnum, pg_catalog.obj_description(c.oid, \'pg_constraint\') AS constcomment, |
||
4487 | c.conrelid, c.confrelid |
||
4488 | FROM |
||
4489 | pg_catalog.pg_constraint AS c |
||
4490 | JOIN pg_catalog.pg_class AS r1 ON (c.conrelid=r1.oid) |
||
4491 | JOIN pg_catalog.pg_attribute AS f1 ON (f1.attrelid=r1.oid AND (f1.attnum=c.conkey[1]'; |
||
4492 | for ($i = 2; $i <= $rs->fields['nb']; ++$i) { |
||
4493 | $sql .= " OR f1.attnum=c.conkey[${i}]"; |
||
4494 | } |
||
4495 | $sql .= ')) |
||
4496 | JOIN pg_catalog.pg_namespace AS ns1 ON r1.relnamespace=ns1.oid |
||
4497 | LEFT JOIN ( |
||
4498 | pg_catalog.pg_class AS r2 JOIN pg_catalog.pg_namespace AS ns2 ON (r2.relnamespace=ns2.oid) |
||
4499 | ) ON (c.confrelid=r2.oid) |
||
4500 | LEFT JOIN pg_catalog.pg_attribute AS f2 ON |
||
4501 | (f2.attrelid=r2.oid AND ((c.confkey[1]=f2.attnum AND c.conkey[1]=f1.attnum)'; |
||
4502 | for ($i = 2; $i <= $rs->fields['nb']; ++$i) { |
||
4503 | $sql .= " OR (c.confkey[${i}]=f2.attnum AND c.conkey[${i}]=f1.attnum)"; |
||
4504 | } |
||
4505 | |||
4506 | $sql .= sprintf(")) |
||
1 ignored issue
–
show
|
|||
4507 | WHERE |
||
4508 | r1.relname = '%s' AND ns1.nspname='%s' |
||
4509 | ORDER BY 1", $table, $c_schema); |
||
1 ignored issue
–
show
|
|||
4510 | |||
4511 | return $this->selectSet($sql); |
||
4512 | } |
||
4513 | |||
4514 | /** |
||
4515 | * Adds a primary key constraint to a table. |
||
4516 | * |
||
4517 | * @param $table The table to which to add the primery key |
||
4518 | * @param $fields (array) An array of fields over which to add the primary key |
||
4519 | * @param string $name (optional) The name to give the key, otherwise default name is assigned |
||
4520 | * @param string $tablespace (optional) The tablespace for the schema, '' indicates default |
||
4521 | * |
||
4522 | * @return int 0 if operation was successful |
||
4523 | */ |
||
4524 | public function addPrimaryKey($table, $fields, $name = '', $tablespace = '') |
||
4525 | { |
||
4526 | if (!is_array($fields) || sizeof($fields) == 0) { |
||
4527 | return -1; |
||
4528 | } |
||
4529 | |||
4530 | $f_schema = $this->_schema; |
||
4531 | $this->fieldClean($f_schema); |
||
4532 | $this->fieldClean($table); |
||
4533 | $this->fieldArrayClean($fields); |
||
4534 | $this->fieldClean($name); |
||
4535 | $this->fieldClean($tablespace); |
||
4536 | |||
4537 | $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD "; |
||
4538 | if ($name != '') { |
||
4539 | $sql .= "CONSTRAINT \"{$name}\" "; |
||
4540 | } |
||
4541 | |||
4542 | $sql .= 'PRIMARY KEY ("' . join('","', $fields) . '")'; |
||
4543 | |||
4544 | if ($tablespace != '' && $this->hasTablespaces()) { |
||
4545 | $sql .= " USING INDEX TABLESPACE \"{$tablespace}\""; |
||
4546 | } |
||
4547 | |||
4548 | return $this->execute($sql); |
||
4549 | } |
||
4550 | |||
4551 | /** |
||
4552 | * Adds a unique constraint to a table. |
||
4553 | * |
||
4554 | * @param $table The table to which to add the unique key |
||
4555 | * @param $fields (array) An array of fields over which to add the unique key |
||
4556 | * @param string $name (optional) The name to give the key, otherwise default name is assigned |
||
4557 | * @param string $tablespace (optional) The tablespace for the schema, '' indicates default |
||
4558 | * |
||
4559 | * @return int 0 if operation was successful |
||
4560 | */ |
||
4561 | public function addUniqueKey($table, $fields, $name = '', $tablespace = '') |
||
4562 | { |
||
4563 | if (!is_array($fields) || sizeof($fields) == 0) { |
||
4564 | return -1; |
||
4565 | } |
||
4566 | |||
4567 | $f_schema = $this->_schema; |
||
4568 | $this->fieldClean($f_schema); |
||
4569 | $this->fieldClean($table); |
||
4570 | $this->fieldArrayClean($fields); |
||
4571 | $this->fieldClean($name); |
||
4572 | $this->fieldClean($tablespace); |
||
4573 | |||
4574 | $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD "; |
||
4575 | if ($name != '') { |
||
4576 | $sql .= "CONSTRAINT \"{$name}\" "; |
||
4577 | } |
||
4578 | |||
4579 | $sql .= 'UNIQUE ("' . join('","', $fields) . '")'; |
||
4580 | |||
4581 | if ($tablespace != '' && $this->hasTablespaces()) { |
||
4582 | $sql .= " USING INDEX TABLESPACE \"{$tablespace}\""; |
||
4583 | } |
||
4584 | |||
4585 | return $this->execute($sql); |
||
4586 | } |
||
4587 | |||
4588 | // Function functions |
||
4589 | |||
4590 | /** |
||
4591 | * Adds a check constraint to a table. |
||
4592 | * |
||
4593 | * @param $table The table to which to add the check |
||
4594 | * @param $definition The definition of the check |
||
4595 | * @param string $name (optional) The name to give the check, otherwise default name is assigned |
||
4596 | * |
||
4597 | * @return integer 0 if operation was successful |
||
4598 | */ |
||
4599 | public function addCheckConstraint($table, $definition, $name = '') |
||
4600 | { |
||
4601 | $f_schema = $this->_schema; |
||
4602 | $this->fieldClean($f_schema); |
||
4603 | $this->fieldClean($table); |
||
4604 | $this->fieldClean($name); |
||
4605 | // @@ How the heck do you clean a definition??? |
||
4606 | |||
4607 | $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD "; |
||
4608 | if ($name != '') { |
||
4609 | $sql .= "CONSTRAINT \"{$name}\" "; |
||
4610 | } |
||
4611 | |||
4612 | $sql .= "CHECK ({$definition})"; |
||
4613 | |||
4614 | return $this->execute($sql); |
||
4615 | } |
||
4616 | |||
4617 | /** |
||
4618 | * Drops a check constraint from a table. |
||
4619 | * |
||
4620 | * @param $table The table from which to drop the check |
||
4621 | * @param $name The name of the check to be dropped |
||
4622 | * |
||
4623 | * @return bool|int 0 success |
||
4624 | */ |
||
4625 | public function dropCheckConstraint($table, $name) |
||
4626 | { |
||
4627 | $f_schema = $this->_schema; |
||
4628 | $this->fieldClean($f_schema); |
||
4629 | $c_schema = $this->_schema; |
||
4630 | $this->clean($c_schema); |
||
4631 | $c_table = $table; |
||
4632 | $this->fieldClean($table); |
||
4633 | $this->clean($c_table); |
||
4634 | $this->clean($name); |
||
4635 | |||
4636 | // Begin transaction |
||
4637 | $status = $this->beginTransaction(); |
||
4638 | if ($status != 0) { |
||
4639 | return -2; |
||
4640 | } |
||
4641 | |||
4642 | // Properly lock the table |
||
4643 | $sql = "LOCK TABLE \"{$f_schema}\".\"{$table}\" IN ACCESS EXCLUSIVE MODE"; |
||
4644 | $status = $this->execute($sql); |
||
4645 | if ($status != 0) { |
||
4646 | $this->rollbackTransaction(); |
||
4647 | |||
4648 | return -3; |
||
4649 | } |
||
4650 | |||
4651 | // Delete the check constraint |
||
4652 | $sql = "DELETE FROM pg_relcheck WHERE rcrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='{$c_table}' |
||
4653 | AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE |
||
4654 | nspname = '{$c_schema}')) AND rcname='{$name}'"; |
||
4655 | $status = $this->execute($sql); |
||
4656 | if ($status != 0) { |
||
4657 | $this->rollbackTransaction(); |
||
4658 | |||
4659 | return -4; |
||
4660 | } |
||
4661 | |||
4662 | // Update the pg_class catalog to reflect the new number of checks |
||
4663 | $sql = "UPDATE pg_class SET relchecks=(SELECT COUNT(*) FROM pg_relcheck WHERE |
||
4664 | rcrelid=(SELECT oid FROM pg_catalog.pg_class WHERE relname='{$c_table}' |
||
4665 | AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE |
||
4666 | nspname = '{$c_schema}'))) |
||
4667 | WHERE relname='{$c_table}'"; |
||
4668 | $status = $this->execute($sql); |
||
4669 | if ($status != 0) { |
||
4670 | $this->rollbackTransaction(); |
||
4671 | |||
4672 | return -4; |
||
4673 | } |
||
4674 | |||
4675 | // Otherwise, close the transaction |
||
4676 | return $this->endTransaction(); |
||
4677 | } |
||
4678 | |||
4679 | /** |
||
4680 | * Adds a foreign key constraint to a table. |
||
4681 | * |
||
4682 | * @param string $table The table on which to add an FK |
||
4683 | * @param string $targschema The schema that houses the target table to which to add the foreign key |
||
4684 | * @param string $targtable The table to which to add the foreign key |
||
4685 | * @param array $sfields (array) An array of source fields over which to add the foreign key |
||
4686 | * @param array $tfields (array) An array of target fields over which to add the foreign key |
||
4687 | * @param string $upd_action The action for updates (eg. RESTRICT) |
||
4688 | * @param string $del_action The action for deletes (eg. RESTRICT) |
||
4689 | * @param string $match The match type (eg. MATCH FULL) |
||
4690 | * @param string $deferrable The deferrability (eg. NOT DEFERRABLE) |
||
4691 | * @param string $initially The initially parameter for the FK (eg. INITIALLY IMMEDIATE) |
||
4692 | * @param string $name [optional] The name to give the key, otherwise default name is assigned |
||
4693 | * |
||
4694 | * @return integer 0 if operation was successful |
||
4695 | * |
||
4696 | * @internal param \PHPPgAdmin\Database\The $target table that contains the target columns |
||
4697 | * @internal param \PHPPgAdmin\Database\The $intially initial deferrability (eg. INITIALLY IMMEDIATE) |
||
4698 | */ |
||
4699 | public function addForeignKey( |
||
4700 | $table, |
||
4701 | $targschema, |
||
4702 | $targtable, |
||
4703 | $sfields, |
||
4704 | $tfields, |
||
4705 | $upd_action, |
||
4706 | $del_action, |
||
4707 | $match, |
||
4708 | $deferrable, |
||
4709 | $initially, |
||
4710 | $name = '' |
||
4711 | ) { |
||
4712 | if (!is_array($sfields) || sizeof($sfields) == 0 || |
||
1 ignored issue
–
show
|
|||
4713 | !is_array($tfields) || sizeof($tfields) == 0) { |
||
2 ignored issues
–
show
|
|||
4714 | return -1; |
||
4715 | } |
||
4716 | |||
4717 | $f_schema = $this->_schema; |
||
4718 | $this->fieldClean($f_schema); |
||
4719 | $this->fieldClean($table); |
||
4720 | $this->fieldClean($targschema); |
||
4721 | $this->fieldClean($targtable); |
||
4722 | $this->fieldArrayClean($sfields); |
||
4723 | $this->fieldArrayClean($tfields); |
||
4724 | $this->fieldClean($name); |
||
4725 | |||
4726 | $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ADD "; |
||
4727 | if ($name != '') { |
||
4728 | $sql .= "CONSTRAINT \"{$name}\" "; |
||
4729 | } |
||
4730 | |||
4731 | $sql .= 'FOREIGN KEY ("' . join('","', $sfields) . '") '; |
||
4732 | // Target table needs to be fully qualified |
||
4733 | $sql .= "REFERENCES \"{$targschema}\".\"{$targtable}\"(\"" . join('","', $tfields) . '") '; |
||
4734 | if ($match != $this->fkmatches[0]) { |
||
4735 | $sql .= " {$match}"; |
||
4736 | } |
||
4737 | |||
4738 | if ($upd_action != $this->fkactions[0]) { |
||
4739 | $sql .= " ON UPDATE {$upd_action}"; |
||
4740 | } |
||
4741 | |||
4742 | if ($del_action != $this->fkactions[0]) { |
||
4743 | $sql .= " ON DELETE {$del_action}"; |
||
4744 | } |
||
4745 | |||
4746 | if ($deferrable != $this->fkdeferrable[0]) { |
||
4747 | $sql .= " {$deferrable}"; |
||
4748 | } |
||
4749 | |||
4750 | if ($initially != $this->fkinitial[0]) { |
||
4751 | $sql .= " {$initially}"; |
||
4752 | } |
||
4753 | |||
4754 | return $this->execute($sql); |
||
4755 | } |
||
4756 | |||
4757 | /** |
||
4758 | * Removes a constraint from a relation. |
||
4759 | * |
||
4760 | * @param string $constraint The constraint to drop |
||
4761 | * @param string $relation The relation from which to drop |
||
4762 | * @param string $type The type of constraint (c, f, u or p) |
||
4763 | * @param bool $cascade True to cascade drop, false to restrict |
||
4764 | * |
||
4765 | * @return integer 0 if operation was successful |
||
4766 | */ |
||
4767 | public function dropConstraint($constraint, $relation, $type, $cascade) |
||
1 ignored issue
–
show
|
|||
4768 | { |
||
4769 | $f_schema = $this->_schema; |
||
4770 | $this->fieldClean($f_schema); |
||
4771 | $this->fieldClean($constraint); |
||
4772 | $this->fieldClean($relation); |
||
4773 | |||
4774 | $sql = "ALTER TABLE \"{$f_schema}\".\"{$relation}\" DROP CONSTRAINT \"{$constraint}\""; |
||
4775 | if ($cascade) { |
||
4776 | $sql .= ' CASCADE'; |
||
4777 | } |
||
4778 | |||
4779 | return $this->execute($sql); |
||
4780 | } |
||
4781 | |||
4782 | /** |
||
4783 | * A function for getting all columns linked by foreign keys given a group of tables. |
||
4784 | * |
||
4785 | * @param array $tables multi dimensional assoc array that holds schema and table name |
||
4786 | * |
||
4787 | * @return \PHPPgAdmin\ADORecordSet|integer recordset of linked tables and columns or -1 if $tables isn't an array |
||
4788 | */ |
||
4789 | public function getLinkingKeys($tables) |
||
4790 | { |
||
4791 | if (!is_array($tables)) { |
||
1 ignored issue
–
show
|
|||
4792 | return -1; |
||
4793 | } |
||
4794 | |||
4795 | $this->clean($tables[0]['tablename']); |
||
4796 | $this->clean($tables[0]['schemaname']); |
||
4797 | $tables_list = "'{$tables[0]['tablename']}'"; |
||
4798 | $schema_list = "'{$tables[0]['schemaname']}'"; |
||
4799 | $schema_tables_list = "'{$tables[0]['schemaname']}.{$tables[0]['tablename']}'"; |
||
4800 | |||
4801 | for ($i = 1; $i < sizeof($tables); ++$i) { |
||
4802 | $this->clean($tables[$i]['tablename']); |
||
4803 | $this->clean($tables[$i]['schemaname']); |
||
4804 | $tables_list .= ", '{$tables[$i]['tablename']}'"; |
||
4805 | $schema_list .= ", '{$tables[$i]['schemaname']}'"; |
||
4806 | $schema_tables_list .= ", '{$tables[$i]['schemaname']}.{$tables[$i]['tablename']}'"; |
||
4807 | } |
||
4808 | |||
4809 | $maxDimension = 1; |
||
4810 | |||
4811 | $sql = " |
||
4812 | SELECT DISTINCT |
||
4813 | array_dims(pc.conkey) AS arr_dim, |
||
4814 | pgc1.relname AS p_table |
||
4815 | FROM |
||
4816 | pg_catalog.pg_constraint AS pc, |
||
4817 | pg_catalog.pg_class AS pgc1 |
||
4818 | WHERE |
||
4819 | pc.contype = 'f' |
||
4820 | AND (pc.conrelid = pgc1.relfilenode OR pc.confrelid = pgc1.relfilenode) |
||
4821 | AND pgc1.relname IN (${tables_list}) |
||
4822 | "; |
||
4823 | |||
4824 | //parse our output to find the highest dimension of foreign keys since pc.conkey is stored in an array |
||
4825 | $rs = $this->selectSet($sql); |
||
4826 | while (!$rs->EOF) { |
||
4827 | $arrData = explode(':', $rs->fields['arr_dim']); |
||
4828 | $tmpDimension = (int) (substr($arrData[1], 0, strlen($arrData[1] - 1))); |
||
4829 | $maxDimension = $tmpDimension > $maxDimension ? $tmpDimension : $maxDimension; |
||
4830 | $rs->MoveNext(); |
||
4831 | } |
||
4832 | |||
4833 | //we know the highest index for foreign keys that conkey goes up to, expand for us in an IN query |
||
4834 | $cons_str = '( (pfield.attnum = conkey[1] AND cfield.attnum = confkey[1]) '; |
||
4835 | for ($i = 2; $i <= $maxDimension; ++$i) { |
||
4836 | $cons_str .= "OR (pfield.attnum = conkey[{$i}] AND cfield.attnum = confkey[{$i}]) "; |
||
4837 | } |
||
4838 | $cons_str .= ') '; |
||
4839 | |||
4840 | $sql = " |
||
4841 | SELECT |
||
4842 | pgc1.relname AS p_table, |
||
4843 | pgc2.relname AS f_table, |
||
4844 | pfield.attname AS p_field, |
||
4845 | cfield.attname AS f_field, |
||
4846 | pgns1.nspname AS p_schema, |
||
4847 | pgns2.nspname AS f_schema |
||
4848 | FROM |
||
4849 | pg_catalog.pg_constraint AS pc, |
||
4850 | pg_catalog.pg_class AS pgc1, |
||
4851 | pg_catalog.pg_class AS pgc2, |
||
4852 | pg_catalog.pg_attribute AS pfield, |
||
4853 | pg_catalog.pg_attribute AS cfield, |
||
4854 | (SELECT oid AS ns_id, nspname FROM pg_catalog.pg_namespace WHERE nspname IN (${schema_list}) ) AS pgns1, |
||
4855 | (SELECT oid AS ns_id, nspname FROM pg_catalog.pg_namespace WHERE nspname IN (${schema_list}) ) AS pgns2 |
||
4856 | WHERE |
||
4857 | pc.contype = 'f' |
||
4858 | AND pgc1.relnamespace = pgns1.ns_id |
||
4859 | AND pgc2.relnamespace = pgns2.ns_id |
||
4860 | AND pc.conrelid = pgc1.relfilenode |
||
4861 | AND pc.confrelid = pgc2.relfilenode |
||
4862 | AND pfield.attrelid = pc.conrelid |
||
4863 | AND cfield.attrelid = pc.confrelid |
||
4864 | AND ${cons_str} |
||
4865 | AND pgns1.nspname || '.' || pgc1.relname IN (${schema_tables_list}) |
||
4866 | AND pgns2.nspname || '.' || pgc2.relname IN (${schema_tables_list}) |
||
4867 | "; |
||
4868 | |||
4869 | return $this->selectSet($sql); |
||
4870 | } |
||
4871 | |||
4872 | /** |
||
4873 | * Finds the foreign keys that refer to the specified table. |
||
4874 | * |
||
4875 | * @param string $table The table to find referrers for |
||
4876 | * |
||
4877 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
4878 | */ |
||
4879 | public function getReferrers($table) |
||
4880 | { |
||
4881 | $this->clean($table); |
||
4882 | |||
4883 | $status = $this->beginTransaction(); |
||
4884 | if ($status != 0) { |
||
4885 | return -1; |
||
4886 | } |
||
4887 | |||
4888 | $c_schema = $this->_schema; |
||
4889 | $this->clean($c_schema); |
||
4890 | |||
4891 | $sql = " |
||
4892 | SELECT |
||
4893 | pn.nspname, |
||
4894 | pl.relname, |
||
4895 | pc.conname, |
||
4896 | pg_catalog.pg_get_constraintdef(pc.oid) AS consrc |
||
4897 | FROM |
||
4898 | pg_catalog.pg_constraint pc, |
||
4899 | pg_catalog.pg_namespace pn, |
||
4900 | pg_catalog.pg_class pl |
||
4901 | WHERE |
||
4902 | pc.connamespace = pn.oid |
||
4903 | AND pc.conrelid = pl.oid |
||
4904 | AND pc.contype = 'f' |
||
4905 | AND confrelid = (SELECT oid FROM pg_catalog.pg_class WHERE relname='{$table}' |
||
4906 | AND relnamespace = (SELECT oid FROM pg_catalog.pg_namespace |
||
4907 | WHERE nspname='{$c_schema}')) |
||
4908 | ORDER BY 1,2,3 |
||
4909 | "; |
||
4910 | |||
4911 | return $this->selectSet($sql); |
||
4912 | } |
||
4913 | |||
4914 | // Type functions |
||
4915 | |||
4916 | /** |
||
4917 | * Gets all information for a single domain. |
||
4918 | * |
||
4919 | * @param string $domain The name of the domain to fetch |
||
4920 | * |
||
4921 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
4922 | */ |
||
4923 | public function getDomain($domain) |
||
4924 | { |
||
4925 | $c_schema = $this->_schema; |
||
4926 | $this->clean($c_schema); |
||
4927 | $this->clean($domain); |
||
4928 | |||
4929 | $sql = " |
||
4930 | SELECT |
||
4931 | t.typname AS domname, |
||
4932 | pg_catalog.format_type(t.typbasetype, t.typtypmod) AS domtype, |
||
4933 | t.typnotnull AS domnotnull, |
||
4934 | t.typdefault AS domdef, |
||
4935 | pg_catalog.pg_get_userbyid(t.typowner) AS domowner, |
||
4936 | pg_catalog.obj_description(t.oid, 'pg_type') AS domcomment |
||
4937 | FROM |
||
4938 | pg_catalog.pg_type t |
||
4939 | WHERE |
||
4940 | t.typtype = 'd' |
||
4941 | AND t.typname = '{$domain}' |
||
4942 | AND t.typnamespace = (SELECT oid FROM pg_catalog.pg_namespace |
||
4943 | WHERE nspname = '{$c_schema}')"; |
||
4944 | |||
4945 | return $this->selectSet($sql); |
||
4946 | } |
||
4947 | |||
4948 | /** |
||
4949 | * Return all domains in current schema. Excludes domain constraints. |
||
4950 | * |
||
4951 | * @return \PHPPgAdmin\ADORecordSet All tables, sorted alphabetically |
||
4952 | */ |
||
4953 | public function getDomains() |
||
4954 | { |
||
4955 | $c_schema = $this->_schema; |
||
4956 | $this->clean($c_schema); |
||
4957 | |||
4958 | $sql = " |
||
4959 | SELECT |
||
4960 | t.typname AS domname, |
||
4961 | pg_catalog.format_type(t.typbasetype, t.typtypmod) AS domtype, |
||
4962 | t.typnotnull AS domnotnull, |
||
4963 | t.typdefault AS domdef, |
||
4964 | pg_catalog.pg_get_userbyid(t.typowner) AS domowner, |
||
4965 | pg_catalog.obj_description(t.oid, 'pg_type') AS domcomment |
||
4966 | FROM |
||
4967 | pg_catalog.pg_type t |
||
4968 | WHERE |
||
4969 | t.typtype = 'd' |
||
4970 | AND t.typnamespace = (SELECT oid FROM pg_catalog.pg_namespace |
||
4971 | WHERE nspname='{$c_schema}') |
||
4972 | ORDER BY t.typname"; |
||
4973 | |||
4974 | return $this->selectSet($sql); |
||
4975 | } |
||
4976 | |||
4977 | /** |
||
4978 | * Get domain constraints. |
||
4979 | * |
||
4980 | * @param $domain The name of the domain whose constraints to fetch |
||
4981 | * |
||
4982 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
4983 | */ |
||
4984 | public function getDomainConstraints($domain) |
||
4985 | { |
||
4986 | $c_schema = $this->_schema; |
||
4987 | $this->clean($c_schema); |
||
4988 | $this->clean($domain); |
||
4989 | |||
4990 | $sql = " |
||
4991 | SELECT |
||
4992 | conname, |
||
4993 | contype, |
||
4994 | pg_catalog.pg_get_constraintdef(oid, true) AS consrc |
||
4995 | FROM |
||
4996 | pg_catalog.pg_constraint |
||
4997 | WHERE |
||
4998 | contypid = ( |
||
4999 | SELECT oid FROM pg_catalog.pg_type |
||
5000 | WHERE typname='{$domain}' |
||
5001 | AND typnamespace = ( |
||
5002 | SELECT oid FROM pg_catalog.pg_namespace |
||
5003 | WHERE nspname = '{$c_schema}') |
||
5004 | ) |
||
5005 | ORDER BY conname"; |
||
5006 | |||
5007 | return $this->selectSet($sql); |
||
5008 | } |
||
5009 | |||
5010 | /** |
||
5011 | * Creates a domain. |
||
5012 | * |
||
5013 | * @param string $domain The name of the domain to create |
||
5014 | * @param string $type The base type for the domain |
||
5015 | * @param string $length Optional type length |
||
5016 | * @param bool $array True for array type, false otherwise |
||
5017 | * @param bool $notnull True for NOT NULL, false otherwise |
||
5018 | * @param string $default Default value for domain |
||
5019 | * @param string $check A CHECK constraint if there is one |
||
5020 | * |
||
5021 | * @return integer 0 if operation was successful |
||
5022 | */ |
||
5023 | public function createDomain($domain, $type, $length, $array, $notnull, $default, $check) |
||
5024 | { |
||
5025 | $f_schema = $this->_schema; |
||
5026 | $this->fieldClean($f_schema); |
||
5027 | $this->fieldClean($domain); |
||
5028 | |||
5029 | $sql = "CREATE DOMAIN \"{$f_schema}\".\"{$domain}\" AS "; |
||
5030 | |||
5031 | if ($length == '') { |
||
5032 | $sql .= $type; |
||
5033 | } else { |
||
5034 | switch ($type) { |
||
5035 | // Have to account for weird placing of length for with/without |
||
5036 | // time zone types |
||
5037 | case 'timestamp with time zone': |
||
5038 | case 'timestamp without time zone': |
||
5039 | $qual = substr($type, 9); |
||
5040 | $sql .= "timestamp({$length}){$qual}"; |
||
5041 | |||
5042 | break; |
||
5043 | case 'time with time zone': |
||
5044 | case 'time without time zone': |
||
5045 | $qual = substr($type, 4); |
||
5046 | $sql .= "time({$length}){$qual}"; |
||
5047 | |||
5048 | break; |
||
5049 | default: |
||
5050 | $sql .= "{$type}({$length})"; |
||
5051 | } |
||
5052 | } |
||
5053 | |||
5054 | // Add array qualifier, if requested |
||
5055 | if ($array) { |
||
5056 | $sql .= '[]'; |
||
5057 | } |
||
5058 | |||
5059 | if ($notnull) { |
||
5060 | $sql .= ' NOT NULL'; |
||
5061 | } |
||
5062 | |||
5063 | if ($default != '') { |
||
5064 | $sql .= " DEFAULT {$default}"; |
||
5065 | } |
||
5066 | |||
5067 | if ($this->hasDomainConstraints() && $check != '') { |
||
5068 | $sql .= " CHECK ({$check})"; |
||
5069 | } |
||
5070 | |||
5071 | return $this->execute($sql); |
||
5072 | } |
||
5073 | |||
5074 | /** |
||
5075 | * Alters a domain. |
||
5076 | * |
||
5077 | * @param string $domain The domain to alter |
||
5078 | * @param string $domdefault The domain default |
||
5079 | * @param bool $domnotnull True for NOT NULL, false otherwise |
||
5080 | * @param string $domowner The domain owner |
||
5081 | * |
||
5082 | * @return bool|int 0 success |
||
5083 | */ |
||
5084 | public function alterDomain($domain, $domdefault, $domnotnull, $domowner) |
||
5085 | { |
||
5086 | $f_schema = $this->_schema; |
||
5087 | $this->fieldClean($f_schema); |
||
5088 | $this->fieldClean($domain); |
||
5089 | $this->fieldClean($domowner); |
||
5090 | |||
5091 | $status = $this->beginTransaction(); |
||
5092 | if ($status != 0) { |
||
5093 | $this->rollbackTransaction(); |
||
5094 | |||
5095 | return -1; |
||
5096 | } |
||
5097 | |||
5098 | // Default |
||
5099 | if ($domdefault == '') { |
||
5100 | $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" DROP DEFAULT"; |
||
5101 | } else { |
||
5102 | $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" SET DEFAULT {$domdefault}"; |
||
5103 | } |
||
5104 | |||
5105 | $status = $this->execute($sql); |
||
5106 | if ($status != 0) { |
||
5107 | $this->rollbackTransaction(); |
||
5108 | |||
5109 | return -2; |
||
5110 | } |
||
5111 | |||
5112 | // NOT NULL |
||
5113 | if ($domnotnull) { |
||
5114 | $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" SET NOT NULL"; |
||
5115 | } else { |
||
5116 | $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" DROP NOT NULL"; |
||
5117 | } |
||
5118 | |||
5119 | $status = $this->execute($sql); |
||
5120 | if ($status != 0) { |
||
5121 | $this->rollbackTransaction(); |
||
5122 | |||
5123 | return -3; |
||
5124 | } |
||
5125 | |||
5126 | // Owner |
||
5127 | $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" OWNER TO \"{$domowner}\""; |
||
5128 | |||
5129 | $status = $this->execute($sql); |
||
5130 | if ($status != 0) { |
||
5131 | $this->rollbackTransaction(); |
||
5132 | |||
5133 | return -4; |
||
5134 | } |
||
5135 | |||
5136 | return $this->endTransaction(); |
||
5137 | } |
||
5138 | |||
5139 | /** |
||
5140 | * Drops a domain. |
||
5141 | * |
||
5142 | * @param string $domain The name of the domain to drop |
||
5143 | * @param string $cascade True to cascade drop, false to restrict |
||
5144 | * |
||
5145 | * @return integer 0 if operation was successful |
||
5146 | */ |
||
5147 | public function dropDomain($domain, $cascade) |
||
5148 | { |
||
5149 | $f_schema = $this->_schema; |
||
5150 | $this->fieldClean($f_schema); |
||
5151 | $this->fieldClean($domain); |
||
5152 | |||
5153 | $sql = "DROP DOMAIN \"{$f_schema}\".\"{$domain}\""; |
||
5154 | if ($cascade) { |
||
5155 | $sql .= ' CASCADE'; |
||
5156 | } |
||
5157 | |||
5158 | return $this->execute($sql); |
||
5159 | } |
||
5160 | |||
5161 | /** |
||
5162 | * Adds a check constraint to a domain. |
||
5163 | * |
||
5164 | * @param string $domain The domain to which to add the check |
||
5165 | * @param string $definition The definition of the check |
||
5166 | * @param string $name (optional) The name to give the check, otherwise default name is assigned |
||
5167 | * |
||
5168 | * @return integer 0 if operation was successful |
||
5169 | */ |
||
5170 | public function addDomainCheckConstraint($domain, $definition, $name = '') |
||
5171 | { |
||
5172 | $f_schema = $this->_schema; |
||
5173 | $this->fieldClean($f_schema); |
||
5174 | $this->fieldClean($domain); |
||
5175 | $this->fieldClean($name); |
||
5176 | |||
5177 | $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" ADD "; |
||
5178 | if ($name != '') { |
||
5179 | $sql .= "CONSTRAINT \"{$name}\" "; |
||
5180 | } |
||
5181 | |||
5182 | $sql .= "CHECK ({$definition})"; |
||
5183 | |||
5184 | return $this->execute($sql); |
||
5185 | } |
||
5186 | |||
5187 | /** |
||
5188 | * Drops a domain constraint. |
||
5189 | * |
||
5190 | * @param string $domain The domain from which to remove the constraint |
||
5191 | * @param string $constraint The constraint to remove |
||
5192 | * @param bool $cascade True to cascade, false otherwise |
||
5193 | * |
||
5194 | * @return integer 0 if operation was successful |
||
5195 | */ |
||
5196 | public function dropDomainConstraint($domain, $constraint, $cascade) |
||
5197 | { |
||
5198 | $f_schema = $this->_schema; |
||
5199 | $this->fieldClean($f_schema); |
||
5200 | $this->fieldClean($domain); |
||
5201 | $this->fieldClean($constraint); |
||
5202 | |||
5203 | $sql = "ALTER DOMAIN \"{$f_schema}\".\"{$domain}\" DROP CONSTRAINT \"{$constraint}\""; |
||
5204 | if ($cascade) { |
||
5205 | $sql .= ' CASCADE'; |
||
5206 | } |
||
5207 | |||
5208 | return $this->execute($sql); |
||
5209 | } |
||
5210 | |||
5211 | // Rule functions |
||
5212 | |||
5213 | /** |
||
5214 | * Returns an array containing a function's properties. |
||
5215 | * |
||
5216 | * @param array $f The array of data for the function |
||
5217 | * |
||
5218 | * @return array An array containing the properties |
||
5219 | */ |
||
5220 | public function getFunctionProperties($f) |
||
5221 | { |
||
5222 | $temp = []; |
||
5223 | |||
5224 | // Volatility |
||
5225 | if ($f['provolatile'] == 'v') { |
||
5226 | $temp[] = 'VOLATILE'; |
||
5227 | } elseif ($f['provolatile'] == 'i') { |
||
5228 | $temp[] = 'IMMUTABLE'; |
||
5229 | } elseif ($f['provolatile'] == 's') { |
||
5230 | $temp[] = 'STABLE'; |
||
5231 | } else { |
||
5232 | return -1; |
||
5233 | } |
||
5234 | |||
5235 | // Null handling |
||
5236 | $f['proisstrict'] = $this->phpBool($f['proisstrict']); |
||
5237 | if ($f['proisstrict']) { |
||
5238 | $temp[] = 'RETURNS NULL ON NULL INPUT'; |
||
5239 | } else { |
||
5240 | $temp[] = 'CALLED ON NULL INPUT'; |
||
5241 | } |
||
5242 | |||
5243 | // Security |
||
5244 | $f['prosecdef'] = $this->phpBool($f['prosecdef']); |
||
5245 | if ($f['prosecdef']) { |
||
5246 | $temp[] = 'SECURITY DEFINER'; |
||
5247 | } else { |
||
5248 | $temp[] = 'SECURITY INVOKER'; |
||
5249 | } |
||
5250 | |||
5251 | return $temp; |
||
5252 | } |
||
5253 | |||
5254 | /** |
||
5255 | * Updates (replaces) a function. |
||
5256 | * |
||
5257 | * @param int $function_oid The OID of the function |
||
5258 | * @param string $funcname The name of the function to create |
||
5259 | * @param string $newname The new name for the function |
||
5260 | * @param array $args The array of argument types |
||
5261 | * @param string $returns The return type |
||
5262 | * @param string $definition The definition for the new function |
||
5263 | * @param string $language The language the function is written for |
||
5264 | * @param array $flags An array of optional flags |
||
5265 | * @param bool $setof True if returns a set, false otherwise |
||
5266 | * @param string $funcown |
||
5267 | * @param string $newown |
||
5268 | * @param string $funcschema |
||
5269 | * @param string $newschema |
||
5270 | * @param float $cost |
||
5271 | * @param integer $rows |
||
5272 | * @param string $comment The comment on the function |
||
5273 | * |
||
5274 | * @return bool|int 0 success |
||
5275 | */ |
||
5276 | public function setFunction( |
||
5277 | $function_oid, |
||
5278 | $funcname, |
||
5279 | $newname, |
||
5280 | $args, |
||
5281 | $returns, |
||
5282 | $definition, |
||
5283 | $language, |
||
5284 | $flags, |
||
5285 | $setof, |
||
5286 | $funcown, |
||
5287 | $newown, |
||
5288 | $funcschema, |
||
5289 | $newschema, |
||
5290 | $cost, |
||
5291 | $rows, |
||
5292 | $comment |
||
5293 | ) { |
||
5294 | // Begin a transaction |
||
5295 | $status = $this->beginTransaction(); |
||
5296 | if ($status != 0) { |
||
5297 | $this->rollbackTransaction(); |
||
5298 | |||
5299 | return -1; |
||
5300 | } |
||
5301 | |||
5302 | // Replace the existing function |
||
5303 | $status = $this->createFunction($funcname, $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, $comment, true); |
||
5304 | if ($status != 0) { |
||
5305 | $this->rollbackTransaction(); |
||
5306 | |||
5307 | return $status; |
||
5308 | } |
||
5309 | |||
5310 | $f_schema = $this->_schema; |
||
5311 | $this->fieldClean($f_schema); |
||
5312 | |||
5313 | // Rename the function, if necessary |
||
5314 | $this->fieldClean($newname); |
||
5315 | /* $funcname is escaped in createFunction */ |
||
5316 | if ($funcname != $newname) { |
||
5317 | $sql = "ALTER FUNCTION \"{$f_schema}\".\"{$funcname}\"({$args}) RENAME TO \"{$newname}\""; |
||
5318 | $status = $this->execute($sql); |
||
5319 | if ($status != 0) { |
||
5320 | $this->rollbackTransaction(); |
||
5321 | |||
5322 | return -5; |
||
5323 | } |
||
5324 | |||
5325 | $funcname = $newname; |
||
5326 | } |
||
5327 | |||
5328 | // Alter the owner, if necessary |
||
5329 | if ($this->hasFunctionAlterOwner()) { |
||
5330 | $this->fieldClean($newown); |
||
5331 | if ($funcown != $newown) { |
||
5332 | $sql = "ALTER FUNCTION \"{$f_schema}\".\"{$funcname}\"({$args}) OWNER TO \"{$newown}\""; |
||
5333 | $status = $this->execute($sql); |
||
5334 | if ($status != 0) { |
||
5335 | $this->rollbackTransaction(); |
||
5336 | |||
5337 | return -6; |
||
5338 | } |
||
5339 | } |
||
5340 | } |
||
5341 | |||
5342 | // Alter the schema, if necessary |
||
5343 | if ($this->hasFunctionAlterSchema()) { |
||
5344 | $this->fieldClean($newschema); |
||
5345 | /* $funcschema is escaped in createFunction */ |
||
5346 | if ($funcschema != $newschema) { |
||
5347 | $sql = "ALTER FUNCTION \"{$f_schema}\".\"{$funcname}\"({$args}) SET SCHEMA \"{$newschema}\""; |
||
5348 | $status = $this->execute($sql); |
||
5349 | if ($status != 0) { |
||
5350 | $this->rollbackTransaction(); |
||
5351 | |||
5352 | return -7; |
||
5353 | } |
||
5354 | } |
||
5355 | } |
||
5356 | |||
5357 | return $this->endTransaction(); |
||
5358 | } |
||
5359 | |||
5360 | /** |
||
5361 | * Creates a new function. |
||
5362 | * |
||
5363 | * @param string $funcname The name of the function to create |
||
5364 | * @param string $args A comma separated string of types |
||
5365 | * @param string $returns The return type |
||
5366 | * @param string $definition The definition for the new function |
||
5367 | * @param string $language The language the function is written for |
||
5368 | * @param array $flags An array of optional flags |
||
5369 | * @param bool $setof True if it returns a set, false otherwise |
||
5370 | * @param string $cost cost the planner should use in the function execution step |
||
5371 | * @param integer $rows number of rows planner should estimate will be returned |
||
5372 | * @param string $comment Comment for the function |
||
5373 | * @param bool $replace (optional) True if OR REPLACE, false for |
||
5374 | * normal |
||
5375 | * |
||
5376 | * @return bool|int 0 success |
||
5377 | */ |
||
5378 | public function createFunction($funcname, $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, $comment, $replace = false) |
||
5379 | { |
||
5380 | // Begin a transaction |
||
5381 | $status = $this->beginTransaction(); |
||
5382 | if ($status != 0) { |
||
5383 | $this->rollbackTransaction(); |
||
5384 | |||
5385 | return -1; |
||
5386 | } |
||
5387 | |||
5388 | $this->fieldClean($funcname); |
||
5389 | $this->clean($args); |
||
5390 | $this->fieldClean($language); |
||
5391 | $this->arrayClean($flags); |
||
5392 | $this->clean($cost); |
||
5393 | $this->clean($rows); |
||
5394 | $f_schema = $this->_schema; |
||
5395 | $this->fieldClean($f_schema); |
||
5396 | |||
5397 | $sql = 'CREATE'; |
||
5398 | if ($replace) { |
||
5399 | $sql .= ' OR REPLACE'; |
||
5400 | } |
||
5401 | |||
5402 | $sql .= " FUNCTION \"{$f_schema}\".\"{$funcname}\" ("; |
||
5403 | |||
5404 | if ($args != '') { |
||
5405 | $sql .= $args; |
||
5406 | } |
||
5407 | |||
5408 | // For some reason, the returns field cannot have quotes... |
||
5409 | $sql .= ') RETURNS '; |
||
5410 | if ($setof) { |
||
5411 | $sql .= 'SETOF '; |
||
5412 | } |
||
5413 | |||
5414 | $sql .= "{$returns} AS "; |
||
5415 | |||
5416 | if (is_array($definition)) { |
||
1 ignored issue
–
show
|
|||
5417 | $this->arrayClean($definition); |
||
5418 | $sql .= "'" . $definition[0] . "'"; |
||
5419 | if ($definition[1]) { |
||
5420 | $sql .= ",'" . $definition[1] . "'"; |
||
5421 | } |
||
5422 | } else { |
||
5423 | $this->clean($definition); |
||
5424 | $sql .= "'" . $definition . "'"; |
||
5425 | } |
||
5426 | |||
5427 | $sql .= " LANGUAGE \"{$language}\""; |
||
5428 | |||
5429 | // Add costs |
||
5430 | if (!empty($cost)) { |
||
5431 | $sql .= " COST {$cost}"; |
||
5432 | } |
||
5433 | |||
5434 | if ($rows != 0) { |
||
5435 | $sql .= " ROWS {$rows}"; |
||
5436 | } |
||
5437 | |||
5438 | // Add flags |
||
5439 | foreach ($flags as $v) { |
||
5440 | // Skip default flags |
||
5441 | if ($v == '') { |
||
5442 | continue; |
||
5443 | } |
||
5444 | |||
5445 | $sql .= "\n{$v}"; |
||
5446 | } |
||
5447 | |||
5448 | $status = $this->execute($sql); |
||
5449 | if ($status != 0) { |
||
5450 | $this->rollbackTransaction(); |
||
5451 | |||
5452 | return -3; |
||
5453 | } |
||
5454 | |||
5455 | /* set the comment */ |
||
5456 | $status = $this->setComment('FUNCTION', "\"{$funcname}\"({$args})", null, $comment); |
||
5457 | if ($status != 0) { |
||
5458 | $this->rollbackTransaction(); |
||
5459 | |||
5460 | return -4; |
||
5461 | } |
||
5462 | |||
5463 | return $this->endTransaction(); |
||
5464 | } |
||
5465 | |||
5466 | /** |
||
5467 | * Drops a function. |
||
5468 | * |
||
5469 | * @param int $function_oid The OID of the function to drop |
||
5470 | * @param bool $cascade True to cascade drop, false to restrict |
||
5471 | * |
||
5472 | * @return integer 0 if operation was successful |
||
5473 | */ |
||
5474 | public function dropFunction($function_oid, $cascade) |
||
5475 | { |
||
5476 | // Function comes in with $object as function OID |
||
5477 | $fn = $this->getFunction($function_oid); |
||
5478 | $f_schema = $this->_schema; |
||
5479 | $this->fieldClean($f_schema); |
||
5480 | $this->fieldClean($fn->fields['proname']); |
||
5481 | |||
5482 | $sql = "DROP FUNCTION \"{$f_schema}\".\"{$fn->fields['proname']}\"({$fn->fields['proarguments']})"; |
||
5483 | if ($cascade) { |
||
5484 | $sql .= ' CASCADE'; |
||
5485 | } |
||
5486 | |||
5487 | return $this->execute($sql); |
||
5488 | } |
||
5489 | |||
5490 | /** |
||
5491 | * Returns all details for a particular function. |
||
5492 | * |
||
5493 | * @param int $function_oid |
||
5494 | * |
||
5495 | * @return \PHPPgAdmin\ADORecordSet Function info |
||
5496 | * |
||
5497 | * @internal param string The $func name of the function to retrieve |
||
5498 | */ |
||
5499 | public function getFunction($function_oid) |
||
5500 | { |
||
5501 | $this->clean($function_oid); |
||
5502 | |||
5503 | $sql = " |
||
5504 | SELECT |
||
5505 | pc.oid AS prooid, proname, |
||
5506 | pg_catalog.pg_get_userbyid(proowner) AS proowner, |
||
5507 | nspname as proschema, lanname as prolanguage, procost, prorows, |
||
5508 | pg_catalog.format_type(prorettype, NULL) as proresult, prosrc, |
||
5509 | probin, proretset, proisstrict, provolatile, prosecdef, |
||
5510 | pg_catalog.oidvectortypes(pc.proargtypes) AS proarguments, |
||
5511 | proargnames AS proargnames, |
||
5512 | pg_catalog.obj_description(pc.oid, 'pg_proc') AS procomment, |
||
5513 | proconfig, |
||
5514 | (select array_agg( (select typname from pg_type pt |
||
5515 | where pt.oid = p.oid) ) from unnest(proallargtypes) p) |
||
5516 | AS proallarguments, |
||
5517 | proargmodes |
||
5518 | FROM |
||
5519 | pg_catalog.pg_proc pc, pg_catalog.pg_language pl, |
||
5520 | pg_catalog.pg_namespace pn |
||
5521 | WHERE |
||
5522 | pc.oid = '{$function_oid}'::oid AND pc.prolang = pl.oid |
||
5523 | AND pc.pronamespace = pn.oid |
||
5524 | "; |
||
5525 | |||
5526 | return $this->selectSet($sql); |
||
5527 | } |
||
5528 | |||
5529 | /** |
||
5530 | * Returns all details for a particular type. |
||
5531 | * |
||
5532 | * @param string $typname The name of the view to retrieve |
||
5533 | * |
||
5534 | * @return \PHPPgAdmin\ADORecordSet info |
||
5535 | */ |
||
5536 | public function getType($typname) |
||
5537 | { |
||
5538 | $this->clean($typname); |
||
5539 | |||
5540 | $sql = "SELECT typtype, typbyval, typname, typinput AS typin, typoutput AS typout, typlen, typalign |
||
5541 | FROM pg_type WHERE typname='{$typname}'"; |
||
5542 | |||
5543 | return $this->selectSet($sql); |
||
5544 | } |
||
5545 | |||
5546 | /** |
||
5547 | * Returns a list of all types in the database. |
||
5548 | * |
||
5549 | * @param bool $all If true, will find all available types, if false just those in search path |
||
5550 | * @param bool $tabletypes If true, will include table types |
||
5551 | * @param bool $domains If true, will include domains |
||
5552 | * |
||
5553 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
5554 | */ |
||
5555 | public function getTypes($all = false, $tabletypes = false, $domains = false) |
||
5556 | { |
||
5557 | if ($all) { |
||
5558 | $where = '1 = 1'; |
||
5559 | } else { |
||
5560 | $c_schema = $this->_schema; |
||
5561 | $this->clean($c_schema); |
||
5562 | $where = "n.nspname = '{$c_schema}'"; |
||
5563 | } |
||
5564 | // Never show system table types |
||
5565 | $where2 = "AND c.relnamespace NOT IN (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname LIKE 'pg@_%' ESCAPE '@')"; |
||
5566 | |||
5567 | // Create type filter |
||
5568 | $tqry = "'c'"; |
||
5569 | if ($tabletypes) { |
||
5570 | $tqry .= ", 'r', 'v'"; |
||
5571 | } |
||
5572 | |||
5573 | // Create domain filter |
||
5574 | if (!$domains) { |
||
5575 | $where .= " AND t.typtype != 'd'"; |
||
5576 | } |
||
5577 | |||
5578 | $sql = "SELECT |
||
5579 | t.typname AS basename, |
||
5580 | pg_catalog.format_type(t.oid, NULL) AS typname, |
||
5581 | pu.usename AS typowner, |
||
5582 | t.typtype, |
||
5583 | pg_catalog.obj_description(t.oid, 'pg_type') AS typcomment |
||
5584 | FROM (pg_catalog.pg_type t |
||
5585 | LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace) |
||
5586 | LEFT JOIN pg_catalog.pg_user pu ON t.typowner = pu.usesysid |
||
5587 | WHERE (t.typrelid = 0 OR (SELECT c.relkind IN ({$tqry}) FROM pg_catalog.pg_class c WHERE c.oid = t.typrelid {$where2})) |
||
5588 | AND t.typname !~ '^_' |
||
5589 | AND {$where} |
||
5590 | ORDER BY typname |
||
5591 | "; |
||
5592 | |||
5593 | return $this->selectSet($sql); |
||
5594 | } |
||
5595 | |||
5596 | /** |
||
5597 | * Creates a new type. |
||
5598 | * |
||
5599 | * @param string $typname |
||
5600 | * @param string $typin |
||
5601 | * @param string $typout |
||
5602 | * @param string $typlen |
||
5603 | * @param string $typdef |
||
5604 | * @param string $typelem |
||
5605 | * @param string $typdelim |
||
5606 | * @param string $typbyval |
||
5607 | * @param string $typalign |
||
5608 | * @param string $typstorage |
||
5609 | * |
||
5610 | * @return integer 0 if operation was successful |
||
5611 | * |
||
5612 | * @internal param $ ... |
||
5613 | */ |
||
5614 | public function createType( |
||
5615 | $typname, |
||
5616 | $typin, |
||
5617 | $typout, |
||
5618 | $typlen, |
||
5619 | $typdef, |
||
5620 | $typelem, |
||
5621 | $typdelim, |
||
5622 | $typbyval, |
||
5623 | $typalign, |
||
5624 | $typstorage |
||
5625 | ) { |
||
5626 | $f_schema = $this->_schema; |
||
5627 | $this->fieldClean($f_schema); |
||
5628 | $this->fieldClean($typname); |
||
5629 | $this->fieldClean($typin); |
||
5630 | $this->fieldClean($typout); |
||
5631 | |||
5632 | $sql = " |
||
5633 | CREATE TYPE \"{$f_schema}\".\"{$typname}\" ( |
||
5634 | INPUT = \"{$typin}\", |
||
5635 | OUTPUT = \"{$typout}\", |
||
5636 | INTERNALLENGTH = {$typlen}"; |
||
5637 | if ($typdef != '') { |
||
5638 | $sql .= ", DEFAULT = {$typdef}"; |
||
5639 | } |
||
5640 | |||
5641 | if ($typelem != '') { |
||
5642 | $sql .= ", ELEMENT = {$typelem}"; |
||
5643 | } |
||
5644 | |||
5645 | if ($typdelim != '') { |
||
5646 | $sql .= ", DELIMITER = {$typdelim}"; |
||
5647 | } |
||
5648 | |||
5649 | if ($typbyval) { |
||
5650 | $sql .= ', PASSEDBYVALUE, '; |
||
5651 | } |
||
5652 | |||
5653 | if ($typalign != '') { |
||
5654 | $sql .= ", ALIGNMENT = {$typalign}"; |
||
5655 | } |
||
5656 | |||
5657 | if ($typstorage != '') { |
||
5658 | $sql .= ", STORAGE = {$typstorage}"; |
||
5659 | } |
||
5660 | |||
5661 | $sql .= ')'; |
||
5662 | |||
5663 | return $this->execute($sql); |
||
5664 | } |
||
5665 | |||
5666 | /** |
||
5667 | * Drops a type. |
||
5668 | * |
||
5669 | * @param $typname The name of the type to drop |
||
5670 | * @param $cascade True to cascade drop, false to restrict |
||
5671 | * |
||
5672 | * @return integer 0 if operation was successful |
||
5673 | */ |
||
5674 | public function dropType($typname, $cascade) |
||
5675 | { |
||
5676 | $f_schema = $this->_schema; |
||
5677 | $this->fieldClean($f_schema); |
||
5678 | $this->fieldClean($typname); |
||
5679 | |||
5680 | $sql = "DROP TYPE \"{$f_schema}\".\"{$typname}\""; |
||
5681 | if ($cascade) { |
||
5682 | $sql .= ' CASCADE'; |
||
5683 | } |
||
5684 | |||
5685 | return $this->execute($sql); |
||
5686 | } |
||
5687 | |||
5688 | /** |
||
5689 | * Creates a new enum type in the database. |
||
5690 | * |
||
5691 | * @param $name The name of the type |
||
5692 | * @param $values An array of values |
||
5693 | * @param $typcomment Type comment |
||
5694 | * |
||
5695 | * @return bool|int 0 success |
||
5696 | */ |
||
5697 | public function createEnumType($name, $values, $typcomment) |
||
5698 | { |
||
5699 | $f_schema = $this->_schema; |
||
5700 | $this->fieldClean($f_schema); |
||
5701 | $this->fieldClean($name); |
||
5702 | |||
5703 | if (empty($values)) { |
||
5704 | return -2; |
||
5705 | } |
||
5706 | |||
5707 | $status = $this->beginTransaction(); |
||
5708 | if ($status != 0) { |
||
5709 | return -1; |
||
5710 | } |
||
5711 | |||
5712 | $values = array_unique($values); |
||
5713 | |||
5714 | $nbval = count($values); |
||
5715 | |||
5716 | for ($i = 0; $i < $nbval; ++$i) { |
||
5717 | $this->clean($values[$i]); |
||
5718 | } |
||
5719 | |||
5720 | $sql = "CREATE TYPE \"{$f_schema}\".\"{$name}\" AS ENUM ('"; |
||
5721 | $sql .= implode("','", $values); |
||
5722 | $sql .= "')"; |
||
5723 | |||
5724 | $status = $this->execute($sql); |
||
5725 | if ($status) { |
||
5726 | $this->rollbackTransaction(); |
||
5727 | |||
5728 | return -1; |
||
5729 | } |
||
5730 | |||
5731 | if ($typcomment != '') { |
||
5732 | $status = $this->setComment('TYPE', $name, '', $typcomment, true); |
||
5733 | if ($status) { |
||
5734 | $this->rollbackTransaction(); |
||
5735 | |||
5736 | return -1; |
||
5737 | } |
||
5738 | } |
||
5739 | |||
5740 | return $this->endTransaction(); |
||
5741 | } |
||
5742 | |||
5743 | /** |
||
5744 | * Get defined values for a given enum. |
||
5745 | * |
||
5746 | * @param $name |
||
5747 | * |
||
5748 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
5749 | */ |
||
5750 | public function getEnumValues($name) |
||
5751 | { |
||
5752 | $this->clean($name); |
||
5753 | |||
5754 | $sql = "SELECT enumlabel AS enumval |
||
5755 | FROM pg_catalog.pg_type t JOIN pg_catalog.pg_enum e ON (t.oid=e.enumtypid) |
||
5756 | WHERE t.typname = '{$name}' ORDER BY e.oid"; |
||
5757 | |||
5758 | return $this->selectSet($sql); |
||
5759 | } |
||
5760 | |||
5761 | // Operator functions |
||
5762 | |||
5763 | /** |
||
5764 | * Creates a new composite type in the database. |
||
5765 | * |
||
5766 | * @param $name The name of the type |
||
5767 | * @param $fields The number of fields |
||
5768 | * @param $field An array of field names |
||
5769 | * @param $type An array of field types |
||
5770 | * @param $array An array of '' or '[]' for each type if it's an array or not |
||
5771 | * @param $length An array of field lengths |
||
5772 | * @param $colcomment An array of comments |
||
5773 | * @param $typcomment Type comment |
||
5774 | * |
||
5775 | * @return bool|int 0 success |
||
5776 | */ |
||
5777 | public function createCompositeType($name, $fields, $field, $type, $array, $length, $colcomment, $typcomment) |
||
5778 | { |
||
5779 | $f_schema = $this->_schema; |
||
5780 | $this->fieldClean($f_schema); |
||
5781 | $this->fieldClean($name); |
||
5782 | |||
5783 | $status = $this->beginTransaction(); |
||
5784 | if ($status != 0) { |
||
5785 | return -1; |
||
5786 | } |
||
5787 | |||
5788 | $found = false; |
||
5789 | $first = true; |
||
5790 | $comment_sql = ''; // Accumulate comments for the columns |
||
5791 | $sql = "CREATE TYPE \"{$f_schema}\".\"{$name}\" AS ("; |
||
5792 | for ($i = 0; $i < $fields; ++$i) { |
||
5793 | $this->fieldClean($field[$i]); |
||
5794 | $this->clean($type[$i]); |
||
5795 | $this->clean($length[$i]); |
||
5796 | $this->clean($colcomment[$i]); |
||
5797 | |||
5798 | // Skip blank columns - for user convenience |
||
5799 | if ($field[$i] == '' || $type[$i] == '') { |
||
5800 | continue; |
||
5801 | } |
||
5802 | |||
5803 | // If not the first column, add a comma |
||
5804 | if (!$first) { |
||
5805 | $sql .= ', '; |
||
5806 | } else { |
||
5807 | $first = false; |
||
5808 | } |
||
5809 | |||
5810 | switch ($type[$i]) { |
||
5811 | // Have to account for weird placing of length for with/without |
||
5812 | // time zone types |
||
5813 | case 'timestamp with time zone': |
||
5814 | case 'timestamp without time zone': |
||
5815 | $qual = substr($type[$i], 9); |
||
5816 | $sql .= "\"{$field[$i]}\" timestamp"; |
||
5817 | if ($length[$i] != '') { |
||
5818 | $sql .= "({$length[$i]})"; |
||
5819 | } |
||
5820 | |||
5821 | $sql .= $qual; |
||
5822 | |||
5823 | break; |
||
5824 | case 'time with time zone': |
||
5825 | case 'time without time zone': |
||
5826 | $qual = substr($type[$i], 4); |
||
5827 | $sql .= "\"{$field[$i]}\" time"; |
||
5828 | if ($length[$i] != '') { |
||
5829 | $sql .= "({$length[$i]})"; |
||
5830 | } |
||
5831 | |||
5832 | $sql .= $qual; |
||
5833 | |||
5834 | break; |
||
5835 | default: |
||
5836 | $sql .= "\"{$field[$i]}\" {$type[$i]}"; |
||
5837 | if ($length[$i] != '') { |
||
5838 | $sql .= "({$length[$i]})"; |
||
5839 | } |
||
5840 | } |
||
5841 | // Add array qualifier if necessary |
||
5842 | if ($array[$i] == '[]') { |
||
5843 | $sql .= '[]'; |
||
5844 | } |
||
5845 | |||
5846 | if ($colcomment[$i] != '') { |
||
5847 | $comment_sql .= "COMMENT ON COLUMN \"{$f_schema}\".\"{$name}\".\"{$field[$i]}\" IS '{$colcomment[$i]}';\n"; |
||
5848 | } |
||
5849 | |||
5850 | $found = true; |
||
5851 | } |
||
5852 | |||
5853 | if (!$found) { |
||
5854 | return -1; |
||
5855 | } |
||
5856 | |||
5857 | $sql .= ')'; |
||
5858 | |||
5859 | $status = $this->execute($sql); |
||
5860 | if ($status) { |
||
5861 | $this->rollbackTransaction(); |
||
5862 | |||
5863 | return -1; |
||
5864 | } |
||
5865 | |||
5866 | if ($typcomment != '') { |
||
5867 | $status = $this->setComment('TYPE', $name, '', $typcomment, true); |
||
5868 | if ($status) { |
||
5869 | $this->rollbackTransaction(); |
||
5870 | |||
5871 | return -1; |
||
5872 | } |
||
5873 | } |
||
5874 | |||
5875 | if ($comment_sql != '') { |
||
5876 | $status = $this->execute($comment_sql); |
||
5877 | if ($status) { |
||
5878 | $this->rollbackTransaction(); |
||
5879 | |||
5880 | return -1; |
||
5881 | } |
||
5882 | } |
||
5883 | |||
5884 | return $this->endTransaction(); |
||
5885 | } |
||
5886 | |||
5887 | /** |
||
5888 | * Returns a list of all casts in the database. |
||
5889 | * |
||
5890 | * @return \PHPPgAdmin\ADORecordSet All casts |
||
5891 | */ |
||
5892 | public function getCasts() |
||
5893 | { |
||
5894 | $conf = $this->conf; |
||
5895 | |||
5896 | if ($conf['show_system']) { |
||
5897 | $where = ''; |
||
5898 | } else { |
||
5899 | $where = ' |
||
5900 | AND n1.nspname NOT LIKE $$pg\_%$$ |
||
5901 | AND n2.nspname NOT LIKE $$pg\_%$$ |
||
5902 | AND n3.nspname NOT LIKE $$pg\_%$$ |
||
5903 | '; |
||
5904 | } |
||
5905 | |||
5906 | $sql = " |
||
5907 | SELECT |
||
5908 | c.castsource::pg_catalog.regtype AS castsource, |
||
5909 | c.casttarget::pg_catalog.regtype AS casttarget, |
||
5910 | CASE WHEN c.castfunc=0 THEN NULL |
||
5911 | ELSE c.castfunc::pg_catalog.regprocedure END AS castfunc, |
||
5912 | c.castcontext, |
||
5913 | obj_description(c.oid, 'pg_cast') as castcomment |
||
5914 | FROM |
||
5915 | (pg_catalog.pg_cast c LEFT JOIN pg_catalog.pg_proc p ON c.castfunc=p.oid JOIN pg_catalog.pg_namespace n3 ON p.pronamespace=n3.oid), |
||
5916 | pg_catalog.pg_type t1, |
||
5917 | pg_catalog.pg_type t2, |
||
5918 | pg_catalog.pg_namespace n1, |
||
5919 | pg_catalog.pg_namespace n2 |
||
5920 | WHERE |
||
5921 | c.castsource=t1.oid |
||
5922 | AND c.casttarget=t2.oid |
||
5923 | AND t1.typnamespace=n1.oid |
||
5924 | AND t2.typnamespace=n2.oid |
||
5925 | {$where} |
||
5926 | ORDER BY 1, 2 |
||
5927 | "; |
||
5928 | |||
5929 | return $this->selectSet($sql); |
||
5930 | } |
||
5931 | |||
5932 | /** |
||
5933 | * Returns a list of all conversions in the database. |
||
5934 | * |
||
5935 | * @return \PHPPgAdmin\ADORecordSet All conversions |
||
5936 | */ |
||
5937 | public function getConversions() |
||
5938 | { |
||
5939 | $c_schema = $this->_schema; |
||
5940 | $this->clean($c_schema); |
||
5941 | $sql = " |
||
5942 | SELECT |
||
5943 | c.conname, |
||
5944 | pg_catalog.pg_encoding_to_char(c.conforencoding) AS conforencoding, |
||
5945 | pg_catalog.pg_encoding_to_char(c.contoencoding) AS contoencoding, |
||
5946 | c.condefault, |
||
5947 | pg_catalog.obj_description(c.oid, 'pg_conversion') AS concomment |
||
5948 | FROM pg_catalog.pg_conversion c, pg_catalog.pg_namespace n |
||
5949 | WHERE n.oid = c.connamespace |
||
5950 | AND n.nspname='{$c_schema}' |
||
5951 | ORDER BY 1; |
||
5952 | "; |
||
5953 | |||
5954 | return $this->selectSet($sql); |
||
5955 | } |
||
5956 | |||
5957 | // Operator Class functions |
||
5958 | |||
5959 | /** |
||
5960 | * Edits a rule on a table OR view. |
||
5961 | * |
||
5962 | * @param $name The name of the new rule |
||
5963 | * @param $event SELECT, INSERT, UPDATE or DELETE |
||
5964 | * @param $table Table on which to create the rule |
||
5965 | * @param $where When to execute the rule, '' indicates always |
||
5966 | * @param $instead True if an INSTEAD rule, false otherwise |
||
5967 | * @param $type NOTHING for a do nothing rule, SOMETHING to use given action |
||
5968 | * @param $action The action to take |
||
5969 | * |
||
5970 | * @return integer 0 if operation was successful |
||
5971 | */ |
||
5972 | public function setRule($name, $event, $table, $where, $instead, $type, $action) |
||
5973 | { |
||
5974 | return $this->createRule($name, $event, $table, $where, $instead, $type, $action, true); |
||
5975 | } |
||
5976 | |||
5977 | // FTS functions |
||
5978 | |||
5979 | /** |
||
5980 | * Creates a rule. |
||
5981 | * |
||
5982 | * @param string $name The name of the new rule |
||
5983 | * @param string $event SELECT, INSERT, UPDATE or DELETE |
||
5984 | * @param string $table Table on which to create the rule |
||
5985 | * @param string $where When to execute the rule, '' indicates always |
||
5986 | * @param boolean $instead True if an INSTEAD rule, false otherwise |
||
5987 | * @param string $type NOTHING for a do nothing rule, SOMETHING to use given action |
||
5988 | * @param string $action The action to take |
||
5989 | * @param bool $replace (optional) True to replace existing rule, false |
||
5990 | * otherwise |
||
5991 | * |
||
5992 | * @return integer 0 if operation was successful |
||
5993 | */ |
||
5994 | public function createRule($name, $event, $table, $where, $instead, $type, $action, $replace = false) |
||
5995 | { |
||
5996 | $f_schema = $this->_schema; |
||
5997 | $this->fieldClean($f_schema); |
||
5998 | $this->fieldClean($name); |
||
5999 | $this->fieldClean($table); |
||
6000 | if (!in_array($event, $this->rule_events, true)) { |
||
6001 | return -1; |
||
6002 | } |
||
6003 | |||
6004 | $sql = 'CREATE'; |
||
6005 | if ($replace) { |
||
6006 | $sql .= ' OR REPLACE'; |
||
6007 | } |
||
6008 | |||
6009 | $sql .= " RULE \"{$name}\" AS ON {$event} TO \"{$f_schema}\".\"{$table}\""; |
||
6010 | // Can't escape WHERE clause |
||
6011 | if ($where != '') { |
||
6012 | $sql .= " WHERE {$where}"; |
||
6013 | } |
||
6014 | |||
6015 | $sql .= ' DO'; |
||
6016 | if ($instead) { |
||
6017 | $sql .= ' INSTEAD'; |
||
6018 | } |
||
6019 | |||
6020 | if ($type == 'NOTHING') { |
||
6021 | $sql .= ' NOTHING'; |
||
6022 | } else { |
||
6023 | $sql .= " ({$action})"; |
||
6024 | } |
||
6025 | |||
6026 | return $this->execute($sql); |
||
6027 | } |
||
6028 | |||
6029 | /** |
||
6030 | * Removes a rule from a table OR view. |
||
6031 | * |
||
6032 | * @param string $rule The rule to drop |
||
6033 | * @param string $relation The relation from which to drop |
||
6034 | * @param string $cascade True to cascade drop, false to restrict |
||
6035 | * |
||
6036 | * @return integer 0 if operation was successful |
||
6037 | */ |
||
6038 | public function dropRule($rule, $relation, $cascade) |
||
6039 | { |
||
6040 | $f_schema = $this->_schema; |
||
6041 | $this->fieldClean($f_schema); |
||
6042 | $this->fieldClean($rule); |
||
6043 | $this->fieldClean($relation); |
||
6044 | |||
6045 | $sql = "DROP RULE \"{$rule}\" ON \"{$f_schema}\".\"{$relation}\""; |
||
6046 | if ($cascade) { |
||
6047 | $sql .= ' CASCADE'; |
||
6048 | } |
||
6049 | |||
6050 | return $this->execute($sql); |
||
6051 | } |
||
6052 | |||
6053 | /** |
||
6054 | * Grabs a single trigger. |
||
6055 | * |
||
6056 | * @param string $table The name of a table whose triggers to retrieve |
||
6057 | * @param string $trigger The name of the trigger to retrieve |
||
6058 | * |
||
6059 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
6060 | */ |
||
6061 | public function getTrigger($table, $trigger) |
||
6062 | { |
||
6063 | $c_schema = $this->_schema; |
||
6064 | $this->clean($c_schema); |
||
6065 | $this->clean($table); |
||
6066 | $this->clean($trigger); |
||
6067 | |||
6068 | $sql = " |
||
6069 | SELECT * FROM pg_catalog.pg_trigger t, pg_catalog.pg_class c |
||
6070 | WHERE t.tgrelid=c.oid AND c.relname='{$table}' AND t.tgname='{$trigger}' |
||
6071 | AND c.relnamespace=( |
||
6072 | SELECT oid FROM pg_catalog.pg_namespace |
||
6073 | WHERE nspname='{$c_schema}')"; |
||
6074 | |||
6075 | return $this->selectSet($sql); |
||
6076 | } |
||
6077 | |||
6078 | /** |
||
6079 | * A helper function for getTriggers that translates |
||
6080 | * an array of attribute numbers to an array of field names. |
||
6081 | * Note: Only needed for pre-7.4 servers, this function is deprecated. |
||
6082 | * |
||
6083 | * @param string $trigger An array containing fields from the trigger table |
||
6084 | * |
||
6085 | * @return string The trigger definition string |
||
6086 | */ |
||
6087 | public function getTriggerDef($trigger) |
||
6088 | { |
||
6089 | $this->fieldArrayClean($trigger); |
||
6090 | // Constants to figure out tgtype |
||
6091 | if (!defined('TRIGGER_TYPE_ROW')) { |
||
6092 | define('TRIGGER_TYPE_ROW', 1 << 0); |
||
6093 | } |
||
6094 | |||
6095 | if (!defined('TRIGGER_TYPE_BEFORE')) { |
||
6096 | define('TRIGGER_TYPE_BEFORE', 1 << 1); |
||
6097 | } |
||
6098 | |||
6099 | if (!defined('TRIGGER_TYPE_INSERT')) { |
||
6100 | define('TRIGGER_TYPE_INSERT', 1 << 2); |
||
6101 | } |
||
6102 | |||
6103 | if (!defined('TRIGGER_TYPE_DELETE')) { |
||
6104 | define('TRIGGER_TYPE_DELETE', 1 << 3); |
||
6105 | } |
||
6106 | |||
6107 | if (!defined('TRIGGER_TYPE_UPDATE')) { |
||
6108 | define('TRIGGER_TYPE_UPDATE', 1 << 4); |
||
6109 | } |
||
6110 | |||
6111 | $trigger['tgisconstraint'] = $this->phpBool($trigger['tgisconstraint']); |
||
6112 | $trigger['tgdeferrable'] = $this->phpBool($trigger['tgdeferrable']); |
||
6113 | $trigger['tginitdeferred'] = $this->phpBool($trigger['tginitdeferred']); |
||
6114 | |||
6115 | // Constraint trigger or normal trigger |
||
6116 | if ($trigger['tgisconstraint']) { |
||
6117 | $tgdef = 'CREATE CONSTRAINT TRIGGER '; |
||
6118 | } else { |
||
6119 | $tgdef = 'CREATE TRIGGER '; |
||
6120 | } |
||
6121 | |||
6122 | $tgdef .= "\"{$trigger['tgname']}\" "; |
||
6123 | |||
6124 | // Trigger type |
||
6125 | $findx = 0; |
||
6126 | if (($trigger['tgtype'] & TRIGGER_TYPE_BEFORE) == TRIGGER_TYPE_BEFORE) { |
||
6127 | $tgdef .= 'BEFORE'; |
||
6128 | } else { |
||
6129 | $tgdef .= 'AFTER'; |
||
6130 | } |
||
6131 | |||
6132 | if (($trigger['tgtype'] & TRIGGER_TYPE_INSERT) == TRIGGER_TYPE_INSERT) { |
||
6133 | $tgdef .= ' INSERT'; |
||
6134 | ++$findx; |
||
6135 | } |
||
6136 | if (($trigger['tgtype'] & TRIGGER_TYPE_DELETE) == TRIGGER_TYPE_DELETE) { |
||
6137 | if ($findx > 0) { |
||
6138 | $tgdef .= ' OR DELETE'; |
||
6139 | } else { |
||
6140 | $tgdef .= ' DELETE'; |
||
6141 | ++$findx; |
||
6142 | } |
||
6143 | } |
||
6144 | if (($trigger['tgtype'] & TRIGGER_TYPE_UPDATE) == TRIGGER_TYPE_UPDATE) { |
||
6145 | if ($findx > 0) { |
||
6146 | $tgdef .= ' OR UPDATE'; |
||
6147 | } else { |
||
6148 | $tgdef .= ' UPDATE'; |
||
6149 | } |
||
6150 | } |
||
6151 | |||
6152 | $f_schema = $this->_schema; |
||
6153 | $this->fieldClean($f_schema); |
||
6154 | // Table name |
||
6155 | $tgdef .= " ON \"{$f_schema}\".\"{$trigger['relname']}\" "; |
||
6156 | |||
6157 | // Deferrability |
||
6158 | if ($trigger['tgisconstraint']) { |
||
6159 | if ($trigger['tgconstrrelid'] != 0) { |
||
6160 | // Assume constrelname is not null |
||
6161 | $tgdef .= " FROM \"{$trigger['tgconstrrelname']}\" "; |
||
6162 | } |
||
6163 | if (!$trigger['tgdeferrable']) { |
||
6164 | $tgdef .= 'NOT '; |
||
6165 | } |
||
6166 | |||
6167 | $tgdef .= 'DEFERRABLE INITIALLY '; |
||
6168 | if ($trigger['tginitdeferred']) { |
||
6169 | $tgdef .= 'DEFERRED '; |
||
6170 | } else { |
||
6171 | $tgdef .= 'IMMEDIATE '; |
||
6172 | } |
||
6173 | } |
||
6174 | |||
6175 | // Row or statement |
||
6176 | if ($trigger['tgtype'] & TRIGGER_TYPE_ROW == TRIGGER_TYPE_ROW) { |
||
6177 | $tgdef .= 'FOR EACH ROW '; |
||
6178 | } else { |
||
6179 | $tgdef .= 'FOR EACH STATEMENT '; |
||
6180 | } |
||
6181 | |||
6182 | // Execute procedure |
||
6183 | $tgdef .= "EXECUTE PROCEDURE \"{$trigger['tgfname']}\"("; |
||
6184 | |||
6185 | // Parameters |
||
6186 | // Escape null characters |
||
6187 | $v = addcslashes($trigger['tgargs'], "\0"); |
||
6188 | // Split on escaped null characters |
||
6189 | $params = explode('\\000', $v); |
||
6190 | for ($findx = 0; $findx < $trigger['tgnargs']; ++$findx) { |
||
6191 | $param = "'" . str_replace('\'', '\\\'', $params[$findx]) . "'"; |
||
6192 | $tgdef .= $param; |
||
6193 | if ($findx < ($trigger['tgnargs'] - 1)) { |
||
6194 | $tgdef .= ', '; |
||
6195 | } |
||
6196 | } |
||
6197 | |||
6198 | // Finish it off |
||
6199 | $tgdef .= ')'; |
||
6200 | |||
6201 | return $tgdef; |
||
6202 | } |
||
6203 | |||
6204 | /** |
||
6205 | * Returns a list of all functions that can be used in triggers. |
||
6206 | */ |
||
6207 | public function getTriggerFunctions() |
||
6208 | { |
||
6209 | return $this->getFunctions(true, 'trigger'); |
||
6210 | } |
||
6211 | |||
6212 | /** |
||
6213 | * Returns a list of all functions in the database. |
||
6214 | * |
||
6215 | * @param bool $all If true, will find all available functions, if false just those in search path |
||
6216 | * @param $type If not null, will find all functions with return value = type |
||
6217 | * |
||
6218 | * @return \PHPPgAdmin\ADORecordSet All functions |
||
6219 | */ |
||
6220 | public function getFunctions($all = false, $type = null) |
||
6221 | { |
||
6222 | if ($all) { |
||
6223 | $where = 'pg_catalog.pg_function_is_visible(p.oid)'; |
||
6224 | $distinct = 'DISTINCT ON (p.proname)'; |
||
6225 | |||
6226 | if ($type) { |
||
6227 | $where .= " AND p.prorettype = (select oid from pg_catalog.pg_type p where p.typname = 'trigger') "; |
||
6228 | } |
||
6229 | } else { |
||
6230 | $c_schema = $this->_schema; |
||
6231 | $this->clean($c_schema); |
||
6232 | $where = "n.nspname = '{$c_schema}'"; |
||
6233 | $distinct = ''; |
||
6234 | } |
||
6235 | |||
6236 | $sql = " |
||
6237 | SELECT |
||
6238 | {$distinct} |
||
6239 | p.oid AS prooid, |
||
6240 | p.proname, |
||
6241 | p.proretset, |
||
6242 | pg_catalog.format_type(p.prorettype, NULL) AS proresult, |
||
6243 | pg_catalog.oidvectortypes(p.proargtypes) AS proarguments, |
||
6244 | pl.lanname AS prolanguage, |
||
6245 | pg_catalog.obj_description(p.oid, 'pg_proc') AS procomment, |
||
6246 | p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto, |
||
6247 | CASE WHEN p.proretset THEN 'setof ' ELSE '' END || pg_catalog.format_type(p.prorettype, NULL) AS proreturns, |
||
6248 | coalesce(u.usename::text,p.proowner::text) AS proowner |
||
6249 | |||
6250 | FROM pg_catalog.pg_proc p |
||
6251 | INNER JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace |
||
6252 | INNER JOIN pg_catalog.pg_language pl ON pl.oid = p.prolang |
||
6253 | LEFT JOIN pg_catalog.pg_user u ON u.usesysid = p.proowner |
||
6254 | WHERE NOT p.proisagg |
||
6255 | AND {$where} |
||
6256 | ORDER BY p.proname, proresult |
||
6257 | "; |
||
6258 | |||
6259 | return $this->selectSet($sql); |
||
6260 | } |
||
6261 | |||
6262 | /** |
||
6263 | * Creates a trigger. |
||
6264 | * |
||
6265 | * @param $tgname The name of the trigger to create |
||
6266 | * @param $table The name of the table |
||
6267 | * @param $tgproc The function to execute |
||
6268 | * @param $tgtime BEFORE or AFTER |
||
6269 | * @param $tgevent Event |
||
6270 | * @param $tgfrequency |
||
6271 | * @param $tgargs The function arguments |
||
6272 | * |
||
6273 | * @return integer 0 if operation was successful |
||
6274 | */ |
||
6275 | public function createTrigger($tgname, $table, $tgproc, $tgtime, $tgevent, $tgfrequency, $tgargs) |
||
6276 | { |
||
6277 | $f_schema = $this->_schema; |
||
6278 | $this->fieldClean($f_schema); |
||
6279 | $this->fieldClean($tgname); |
||
6280 | $this->fieldClean($table); |
||
6281 | $this->fieldClean($tgproc); |
||
6282 | |||
6283 | /* No Statement Level Triggers in PostgreSQL (by now) */ |
||
6284 | $sql = "CREATE TRIGGER \"{$tgname}\" {$tgtime} |
||
6285 | {$tgevent} ON \"{$f_schema}\".\"{$table}\" |
||
6286 | FOR EACH {$tgfrequency} EXECUTE PROCEDURE \"{$tgproc}\"({$tgargs})"; |
||
6287 | |||
6288 | return $this->execute($sql); |
||
6289 | } |
||
6290 | |||
6291 | /** |
||
6292 | * Alters a trigger. |
||
6293 | * |
||
6294 | * @param $table The name of the table containing the trigger |
||
6295 | * @param $trigger The name of the trigger to alter |
||
6296 | * @param $name The new name for the trigger |
||
6297 | * |
||
6298 | * @return integer 0 if operation was successful |
||
6299 | */ |
||
6300 | public function alterTrigger($table, $trigger, $name) |
||
6301 | { |
||
6302 | $f_schema = $this->_schema; |
||
6303 | $this->fieldClean($f_schema); |
||
6304 | $this->fieldClean($table); |
||
6305 | $this->fieldClean($trigger); |
||
6306 | $this->fieldClean($name); |
||
6307 | |||
6308 | $sql = "ALTER TRIGGER \"{$trigger}\" ON \"{$f_schema}\".\"{$table}\" RENAME TO \"{$name}\""; |
||
6309 | |||
6310 | return $this->execute($sql); |
||
6311 | } |
||
6312 | |||
6313 | /** |
||
6314 | * Drops a trigger. |
||
6315 | * |
||
6316 | * @param $tgname The name of the trigger to drop |
||
6317 | * @param $table The table from which to drop the trigger |
||
6318 | * @param $cascade True to cascade drop, false to restrict |
||
6319 | * |
||
6320 | * @return integer 0 if operation was successful |
||
6321 | */ |
||
6322 | public function dropTrigger($tgname, $table, $cascade) |
||
6323 | { |
||
6324 | $f_schema = $this->_schema; |
||
6325 | $this->fieldClean($f_schema); |
||
6326 | $this->fieldClean($tgname); |
||
6327 | $this->fieldClean($table); |
||
6328 | |||
6329 | $sql = "DROP TRIGGER \"{$tgname}\" ON \"{$f_schema}\".\"{$table}\""; |
||
6330 | if ($cascade) { |
||
6331 | $sql .= ' CASCADE'; |
||
6332 | } |
||
6333 | |||
6334 | return $this->execute($sql); |
||
6335 | } |
||
6336 | |||
6337 | /** |
||
6338 | * Enables a trigger. |
||
6339 | * |
||
6340 | * @param $tgname The name of the trigger to enable |
||
6341 | * @param $table The table in which to enable the trigger |
||
6342 | * |
||
6343 | * @return integer 0 if operation was successful |
||
6344 | */ |
||
6345 | public function enableTrigger($tgname, $table) |
||
6346 | { |
||
6347 | $f_schema = $this->_schema; |
||
6348 | $this->fieldClean($f_schema); |
||
6349 | $this->fieldClean($tgname); |
||
6350 | $this->fieldClean($table); |
||
6351 | |||
6352 | $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" ENABLE TRIGGER \"{$tgname}\""; |
||
6353 | |||
6354 | return $this->execute($sql); |
||
6355 | } |
||
6356 | |||
6357 | /** |
||
6358 | * Disables a trigger. |
||
6359 | * |
||
6360 | * @param $tgname The name of the trigger to disable |
||
6361 | * @param $table The table in which to disable the trigger |
||
6362 | * |
||
6363 | * @return integer 0 if operation was successful |
||
6364 | */ |
||
6365 | public function disableTrigger($tgname, $table) |
||
6366 | { |
||
6367 | $f_schema = $this->_schema; |
||
6368 | $this->fieldClean($f_schema); |
||
6369 | $this->fieldClean($tgname); |
||
6370 | $this->fieldClean($table); |
||
6371 | |||
6372 | $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" DISABLE TRIGGER \"{$tgname}\""; |
||
6373 | |||
6374 | return $this->execute($sql); |
||
6375 | } |
||
6376 | |||
6377 | /** |
||
6378 | * Returns a list of all operators in the database. |
||
6379 | * |
||
6380 | * @return \PHPPgAdmin\ADORecordSet All operators |
||
6381 | */ |
||
6382 | public function getOperators() |
||
6383 | { |
||
6384 | $c_schema = $this->_schema; |
||
6385 | $this->clean($c_schema); |
||
6386 | // We stick with the subselects here, as you cannot ORDER BY a regtype |
||
6387 | $sql = " |
||
6388 | SELECT |
||
6389 | po.oid, po.oprname, |
||
6390 | (SELECT pg_catalog.format_type(oid, NULL) FROM pg_catalog.pg_type pt WHERE pt.oid=po.oprleft) AS oprleftname, |
||
6391 | (SELECT pg_catalog.format_type(oid, NULL) FROM pg_catalog.pg_type pt WHERE pt.oid=po.oprright) AS oprrightname, |
||
6392 | po.oprresult::pg_catalog.regtype AS resultname, |
||
6393 | pg_catalog.obj_description(po.oid, 'pg_operator') AS oprcomment |
||
6394 | FROM |
||
6395 | pg_catalog.pg_operator po |
||
6396 | WHERE |
||
6397 | po.oprnamespace = (SELECT oid FROM pg_catalog.pg_namespace WHERE nspname='{$c_schema}') |
||
6398 | ORDER BY |
||
6399 | po.oprname, oprleftname, oprrightname |
||
6400 | "; |
||
6401 | |||
6402 | return $this->selectSet($sql); |
||
6403 | } |
||
6404 | |||
6405 | /** |
||
6406 | * Drops an operator. |
||
6407 | * |
||
6408 | * @param $operator_oid The OID of the operator to drop |
||
6409 | * @param $cascade True to cascade drop, false to restrict |
||
6410 | * |
||
6411 | * @return integer 0 if operation was successful |
||
6412 | */ |
||
6413 | public function dropOperator($operator_oid, $cascade) |
||
6414 | { |
||
6415 | // Function comes in with $object as operator OID |
||
6416 | $opr = $this->getOperator($operator_oid); |
||
6417 | $f_schema = $this->_schema; |
||
6418 | $this->fieldClean($f_schema); |
||
6419 | $this->fieldClean($opr->fields['oprname']); |
||
6420 | |||
6421 | $sql = "DROP OPERATOR \"{$f_schema}\".{$opr->fields['oprname']} ("; |
||
6422 | // Quoting or formatting here??? |
||
6423 | if ($opr->fields['oprleftname'] !== null) { |
||
6424 | $sql .= $opr->fields['oprleftname'] . ', '; |
||
6425 | } else { |
||
6426 | $sql .= 'NONE, '; |
||
6427 | } |
||
6428 | |||
6429 | if ($opr->fields['oprrightname'] !== null) { |
||
6430 | $sql .= $opr->fields['oprrightname'] . ')'; |
||
6431 | } else { |
||
6432 | $sql .= 'NONE)'; |
||
6433 | } |
||
6434 | |||
6435 | if ($cascade) { |
||
6436 | $sql .= ' CASCADE'; |
||
6437 | } |
||
6438 | |||
6439 | return $this->execute($sql); |
||
6440 | } |
||
6441 | |||
6442 | /** |
||
6443 | * Returns all details for a particular operator. |
||
6444 | * |
||
6445 | * @param $operator_oid The oid of the operator |
||
6446 | * |
||
6447 | * @return Function info |
||
6448 | */ |
||
6449 | public function getOperator($operator_oid) |
||
6450 | { |
||
6451 | $this->clean($operator_oid); |
||
6452 | |||
6453 | $sql = " |
||
6454 | SELECT |
||
6455 | po.oid, po.oprname, |
||
6456 | oprleft::pg_catalog.regtype AS oprleftname, |
||
6457 | oprright::pg_catalog.regtype AS oprrightname, |
||
6458 | oprresult::pg_catalog.regtype AS resultname, |
||
6459 | po.oprcanhash, |
||
6460 | oprcanmerge, |
||
6461 | oprcom::pg_catalog.regoperator AS oprcom, |
||
6462 | oprnegate::pg_catalog.regoperator AS oprnegate, |
||
6463 | po.oprcode::pg_catalog.regproc AS oprcode, |
||
6464 | po.oprrest::pg_catalog.regproc AS oprrest, |
||
6465 | po.oprjoin::pg_catalog.regproc AS oprjoin |
||
6466 | FROM |
||
6467 | pg_catalog.pg_operator po |
||
6468 | WHERE |
||
6469 | po.oid='{$operator_oid}' |
||
6470 | "; |
||
6471 | |||
6472 | return $this->selectSet($sql); |
||
6473 | } |
||
6474 | |||
6475 | /** |
||
6476 | * Gets all opclasses. |
||
6477 | * |
||
6478 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
6479 | */ |
||
6480 | public function getOpClasses() |
||
6481 | { |
||
6482 | $c_schema = $this->_schema; |
||
6483 | $this->clean($c_schema); |
||
6484 | $sql = " |
||
6485 | SELECT |
||
6486 | pa.amname, po.opcname, |
||
6487 | po.opcintype::pg_catalog.regtype AS opcintype, |
||
6488 | po.opcdefault, |
||
6489 | pg_catalog.obj_description(po.oid, 'pg_opclass') AS opccomment |
||
6490 | FROM |
||
6491 | pg_catalog.pg_opclass po, pg_catalog.pg_am pa, pg_catalog.pg_namespace pn |
||
6492 | WHERE |
||
6493 | po.opcmethod=pa.oid |
||
6494 | AND po.opcnamespace=pn.oid |
||
6495 | AND pn.nspname='{$c_schema}' |
||
6496 | ORDER BY 1,2 |
||
6497 | "; |
||
6498 | |||
6499 | return $this->selectSet($sql); |
||
6500 | } |
||
6501 | |||
6502 | /** |
||
6503 | * Creates a new FTS configuration. |
||
6504 | * |
||
6505 | * @param string $cfgname The name of the FTS configuration to create |
||
6506 | * @param string $parser The parser to be used in new FTS configuration |
||
6507 | * @param string $template The existing FTS configuration to be used as template for the new one |
||
6508 | * @param string $comment If omitted, defaults to nothing |
||
6509 | * |
||
6510 | * @return bool|int 0 success |
||
6511 | * |
||
6512 | * @internal param string $locale Locale of the FTS configuration |
||
6513 | * @internal param string $withmap Should we copy whole map of existing FTS configuration to the new one |
||
6514 | * @internal param string $makeDefault Should this configuration be the default for locale given |
||
6515 | */ |
||
6516 | public function createFtsConfiguration($cfgname, $parser = '', $template = '', $comment = '') |
||
6517 | { |
||
6518 | $f_schema = $this->_schema; |
||
6519 | $this->fieldClean($f_schema); |
||
6520 | $this->fieldClean($cfgname); |
||
6521 | |||
6522 | $sql = "CREATE TEXT SEARCH CONFIGURATION \"{$f_schema}\".\"{$cfgname}\" ("; |
||
6523 | if ($parser != '') { |
||
6524 | $this->fieldClean($parser['schema']); |
||
6525 | $this->fieldClean($parser['parser']); |
||
6526 | $parser = "\"{$parser['schema']}\".\"{$parser['parser']}\""; |
||
6527 | $sql .= " PARSER = {$parser}"; |
||
6528 | } |
||
6529 | if ($template != '') { |
||
6530 | $this->fieldClean($template['schema']); |
||
6531 | $this->fieldClean($template['name']); |
||
6532 | $sql .= " COPY = \"{$template['schema']}\".\"{$template['name']}\""; |
||
6533 | } |
||
6534 | $sql .= ')'; |
||
6535 | |||
6536 | if ($comment != '') { |
||
6537 | $status = $this->beginTransaction(); |
||
6538 | if ($status != 0) { |
||
6539 | return -1; |
||
6540 | } |
||
6541 | } |
||
6542 | |||
6543 | // Create the FTS configuration |
||
6544 | $status = $this->execute($sql); |
||
6545 | if ($status != 0) { |
||
6546 | $this->rollbackTransaction(); |
||
6547 | |||
6548 | return -1; |
||
6549 | } |
||
6550 | |||
6551 | // Set the comment |
||
6552 | if ($comment != '') { |
||
6553 | $status = $this->setComment('TEXT SEARCH CONFIGURATION', $cfgname, '', $comment); |
||
6554 | if ($status != 0) { |
||
6555 | $this->rollbackTransaction(); |
||
6556 | |||
6557 | return -1; |
||
6558 | } |
||
6559 | |||
6560 | return $this->endTransaction(); |
||
6561 | } |
||
6562 | |||
6563 | return 0; |
||
6564 | } |
||
6565 | |||
6566 | // Language functions |
||
6567 | |||
6568 | /** |
||
6569 | * Returns available FTS configurations. |
||
6570 | * |
||
6571 | * @param bool $all if false, returns schema qualified FTS confs |
||
6572 | * |
||
6573 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
6574 | */ |
||
6575 | public function getFtsConfigurations($all = true) |
||
6576 | { |
||
6577 | $c_schema = $this->_schema; |
||
6578 | $this->clean($c_schema); |
||
6579 | $sql = " |
||
6580 | SELECT |
||
6581 | n.nspname as schema, |
||
6582 | c.cfgname as name, |
||
6583 | pg_catalog.obj_description(c.oid, 'pg_ts_config') as comment |
||
6584 | FROM |
||
6585 | pg_catalog.pg_ts_config c |
||
6586 | JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace |
||
6587 | WHERE |
||
6588 | pg_catalog.pg_ts_config_is_visible(c.oid)"; |
||
6589 | |||
6590 | if (!$all) { |
||
6591 | $sql .= " AND n.nspname='{$c_schema}'\n"; |
||
6592 | } |
||
6593 | |||
6594 | $sql .= 'ORDER BY name'; |
||
6595 | |||
6596 | return $this->selectSet($sql); |
||
6597 | } |
||
6598 | |||
6599 | // Aggregate functions |
||
6600 | |||
6601 | /** |
||
6602 | * Returns the map of FTS configuration given |
||
6603 | * (list of mappings (tokens) and their processing dictionaries). |
||
6604 | * |
||
6605 | * @param string $ftscfg Name of the FTS configuration |
||
6606 | * |
||
6607 | * @return \PHPPgAdmin\ADORecordSet recordset |
||
6608 | */ |
||
6609 | public function getFtsConfigurationMap($ftscfg) |
||
6610 | { |
||
6611 | $c_schema = $this->_schema; |
||
6612 | $this->clean($c_schema); |
||
6613 | $this->fieldClean($ftscfg); |
||
6614 | |||
6615 | $oidSet = $this->selectSet("SELECT c.oid |
||
1 ignored issue
–
show
|
|||
6616 | FROM pg_catalog.pg_ts_config AS c |
||
6617 | LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = c.cfgnamespace) |
||
6618 | WHERE c.cfgname = '{$ftscfg}' |
||
6619 | AND n.nspname='{$c_schema}'"); |
||
1 ignored issue
–
show
|
|||
6620 | |||
6621 | $oid = $oidSet->fields['oid']; |
||
6622 | |||
6623 | $sql = " |
||
6624 | SELECT |
||
6625 | (SELECT t.alias FROM pg_catalog.ts_token_type(c.cfgparser) AS t WHERE t.tokid = m.maptokentype) AS name, |
||
6626 | (SELECT t.description FROM pg_catalog.ts_token_type(c.cfgparser) AS t WHERE t.tokid = m.maptokentype) AS description, |
||
6627 | c.cfgname AS cfgname, n.nspname ||'.'|| d.dictname as dictionaries |
||
6628 | FROM |
||
6629 | pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m, pg_catalog.pg_ts_dict d, |
||
6630 | pg_catalog.pg_namespace n |
||
6631 | WHERE |
||
6632 | c.oid = {$oid} |
||
6633 | AND m.mapcfg = c.oid |
||
6634 | AND m.mapdict = d.oid |
||
6635 | AND d.dictnamespace = n.oid |
||
6636 | ORDER BY name |
||
6637 | "; |
||
6638 | |||
6639 | return $this->selectSet($sql); |
||
6640 | } |
||
6641 | |||
6642 | /** |
||
6643 | * Returns FTS parsers available. |
||
6644 | * |
||
6645 | * @param bool $all if false, return only Parsers from the current schema |
||
6646 | * |
||
6647 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
6648 | */ |
||
6649 | public function getFtsParsers($all = true) |
||
6650 | { |
||
6651 | $c_schema = $this->_schema; |
||
6652 | $this->clean($c_schema); |
||
6653 | $sql = " |
||
6654 | SELECT |
||
6655 | n.nspname as schema, |
||
6656 | p.prsname as name, |
||
6657 | pg_catalog.obj_description(p.oid, 'pg_ts_parser') as comment |
||
6658 | FROM pg_catalog.pg_ts_parser p |
||
6659 | LEFT JOIN pg_catalog.pg_namespace n ON (n.oid = p.prsnamespace) |
||
6660 | WHERE pg_catalog.pg_ts_parser_is_visible(p.oid)"; |
||
6661 | |||
6662 | if (!$all) { |
||
6663 | $sql .= " AND n.nspname='{$c_schema}'\n"; |
||
6664 | } |
||
6665 | |||
6666 | $sql .= 'ORDER BY name'; |
||
6667 | |||
6668 | return $this->selectSet($sql); |
||
6669 | } |
||
6670 | |||
6671 | /** |
||
6672 | * Returns FTS dictionaries available. |
||
6673 | * |
||
6674 | * @param bool $all if false, return only Dics from the current schema |
||
6675 | * |
||
6676 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
6677 | */ |
||
6678 | public function getFtsDictionaries($all = true) |
||
6679 | { |
||
6680 | $c_schema = $this->_schema; |
||
6681 | $this->clean($c_schema); |
||
6682 | $sql = " |
||
6683 | SELECT |
||
6684 | n.nspname as schema, d.dictname as name, |
||
6685 | pg_catalog.obj_description(d.oid, 'pg_ts_dict') as comment |
||
6686 | FROM pg_catalog.pg_ts_dict d |
||
6687 | LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace |
||
6688 | WHERE pg_catalog.pg_ts_dict_is_visible(d.oid)"; |
||
6689 | |||
6690 | if (!$all) { |
||
6691 | $sql .= " AND n.nspname='{$c_schema}'\n"; |
||
6692 | } |
||
6693 | |||
6694 | $sql .= 'ORDER BY name;'; |
||
6695 | |||
6696 | return $this->selectSet($sql); |
||
6697 | } |
||
6698 | |||
6699 | /** |
||
6700 | * Returns all FTS dictionary templates available. |
||
6701 | */ |
||
6702 | public function getFtsDictionaryTemplates() |
||
6703 | { |
||
6704 | $sql = " |
||
6705 | SELECT |
||
6706 | n.nspname as schema, |
||
6707 | t.tmplname as name, |
||
6708 | ( SELECT COALESCE(np.nspname, '(null)')::pg_catalog.text || '.' || p.proname |
||
6709 | FROM pg_catalog.pg_proc p |
||
6710 | LEFT JOIN pg_catalog.pg_namespace np ON np.oid = p.pronamespace |
||
6711 | WHERE t.tmplinit = p.oid ) AS init, |
||
6712 | ( SELECT COALESCE(np.nspname, '(null)')::pg_catalog.text || '.' || p.proname |
||
6713 | FROM pg_catalog.pg_proc p |
||
6714 | LEFT JOIN pg_catalog.pg_namespace np ON np.oid = p.pronamespace |
||
6715 | WHERE t.tmpllexize = p.oid ) AS lexize, |
||
6716 | pg_catalog.obj_description(t.oid, 'pg_ts_template') as comment |
||
6717 | FROM pg_catalog.pg_ts_template t |
||
6718 | LEFT JOIN pg_catalog.pg_namespace n ON n.oid = t.tmplnamespace |
||
6719 | WHERE pg_catalog.pg_ts_template_is_visible(t.oid) |
||
6720 | ORDER BY name;"; |
||
6721 | |||
6722 | return $this->selectSet($sql); |
||
6723 | } |
||
6724 | |||
6725 | /** |
||
6726 | * Drops FTS coniguration. |
||
6727 | * |
||
6728 | * @param $ftscfg The configuration's name |
||
6729 | * @param $cascade Cascade to dependenced objects |
||
6730 | * |
||
6731 | * @return integer 0 if operation was successful |
||
6732 | */ |
||
6733 | public function dropFtsConfiguration($ftscfg, $cascade) |
||
6734 | { |
||
6735 | $f_schema = $this->_schema; |
||
6736 | $this->fieldClean($f_schema); |
||
6737 | $this->fieldClean($ftscfg); |
||
6738 | |||
6739 | $sql = "DROP TEXT SEARCH CONFIGURATION \"{$f_schema}\".\"{$ftscfg}\""; |
||
6740 | if ($cascade) { |
||
6741 | $sql .= ' CASCADE'; |
||
6742 | } |
||
6743 | |||
6744 | return $this->execute($sql); |
||
6745 | } |
||
6746 | |||
6747 | /** |
||
6748 | * Drops FTS dictionary. |
||
6749 | * |
||
6750 | * @param $ftsdict The dico's name |
||
6751 | * @param $cascade Cascade to dependenced objects |
||
6752 | * |
||
6753 | * @return integer 0 if operation was successful |
||
6754 | * |
||
6755 | * @todo Support of dictionary templates dropping |
||
6756 | */ |
||
6757 | public function dropFtsDictionary($ftsdict, $cascade) |
||
6758 | { |
||
6759 | $f_schema = $this->_schema; |
||
6760 | $this->fieldClean($f_schema); |
||
6761 | $this->fieldClean($ftsdict); |
||
6762 | |||
6763 | $sql = 'DROP TEXT SEARCH DICTIONARY'; |
||
6764 | $sql .= " \"{$f_schema}\".\"{$ftsdict}\""; |
||
6765 | if ($cascade) { |
||
6766 | $sql .= ' CASCADE'; |
||
6767 | } |
||
6768 | |||
6769 | return $this->execute($sql); |
||
6770 | } |
||
6771 | |||
6772 | /** |
||
6773 | * Alters FTS configuration. |
||
6774 | * |
||
6775 | * @param $cfgname The conf's name |
||
6776 | * @param $comment A comment on for the conf |
||
6777 | * @param $name The new conf name |
||
6778 | * |
||
6779 | * @return bool|int 0 on success |
||
6780 | */ |
||
6781 | public function updateFtsConfiguration($cfgname, $comment, $name) |
||
6782 | { |
||
6783 | $status = $this->beginTransaction(); |
||
6784 | if ($status != 0) { |
||
6785 | $this->rollbackTransaction(); |
||
6786 | |||
6787 | return -1; |
||
6788 | } |
||
6789 | |||
6790 | $this->fieldClean($cfgname); |
||
6791 | |||
6792 | $status = $this->setComment('TEXT SEARCH CONFIGURATION', $cfgname, '', $comment); |
||
6793 | if ($status != 0) { |
||
6794 | $this->rollbackTransaction(); |
||
6795 | |||
6796 | return -1; |
||
6797 | } |
||
6798 | |||
6799 | // Only if the name has changed |
||
6800 | if ($name != $cfgname) { |
||
6801 | $f_schema = $this->_schema; |
||
6802 | $this->fieldClean($f_schema); |
||
6803 | $this->fieldClean($name); |
||
6804 | |||
6805 | $sql = "ALTER TEXT SEARCH CONFIGURATION \"{$f_schema}\".\"{$cfgname}\" RENAME TO \"{$name}\""; |
||
6806 | $status = $this->execute($sql); |
||
6807 | if ($status != 0) { |
||
6808 | $this->rollbackTransaction(); |
||
6809 | |||
6810 | return -1; |
||
6811 | } |
||
6812 | } |
||
6813 | |||
6814 | return $this->endTransaction(); |
||
6815 | } |
||
6816 | |||
6817 | /** |
||
6818 | * Creates a new FTS dictionary or FTS dictionary template. |
||
6819 | * |
||
6820 | * @param string $dictname The name of the FTS dictionary to create |
||
6821 | * @param bool $isTemplate Flag whether we create usual dictionary or dictionary template |
||
6822 | * @param string $template The existing FTS dictionary to be used as template for the new one |
||
6823 | * @param string $lexize The name of the function, which does transformation of input word |
||
6824 | * @param string $init The name of the function, which initializes dictionary |
||
6825 | * @param string $option Usually, it stores various options required for the dictionary |
||
6826 | * @param string $comment If omitted, defaults to nothing |
||
6827 | * |
||
6828 | * @return bool|int 0 success |
||
6829 | */ |
||
6830 | public function createFtsDictionary( |
||
6831 | $dictname, |
||
6832 | $isTemplate = false, |
||
6833 | $template = '', |
||
6834 | $lexize = '', |
||
6835 | $init = '', |
||
6836 | $option = '', |
||
6837 | $comment = '' |
||
6838 | ) { |
||
6839 | $f_schema = $this->_schema; |
||
6840 | $this->fieldClean($f_schema); |
||
6841 | $this->fieldClean($dictname); |
||
6842 | $this->fieldClean($template); |
||
6843 | $this->fieldClean($lexize); |
||
6844 | $this->fieldClean($init); |
||
6845 | $this->fieldClean($option); |
||
6846 | |||
6847 | $sql = 'CREATE TEXT SEARCH'; |
||
6848 | if ($isTemplate) { |
||
6849 | $sql .= " TEMPLATE \"{$f_schema}\".\"{$dictname}\" ("; |
||
6850 | if ($lexize != '') { |
||
6851 | $sql .= " LEXIZE = {$lexize}"; |
||
6852 | } |
||
6853 | |||
6854 | if ($init != '') { |
||
6855 | $sql .= ", INIT = {$init}"; |
||
6856 | } |
||
6857 | |||
6858 | $sql .= ')'; |
||
6859 | $whatToComment = 'TEXT SEARCH TEMPLATE'; |
||
6860 | } else { |
||
6861 | $sql .= " DICTIONARY \"{$f_schema}\".\"{$dictname}\" ("; |
||
6862 | if ($template != '') { |
||
6863 | $this->fieldClean($template['schema']); |
||
6864 | $this->fieldClean($template['name']); |
||
6865 | $template = "\"{$template['schema']}\".\"{$template['name']}\""; |
||
6866 | |||
6867 | $sql .= " TEMPLATE = {$template}"; |
||
6868 | } |
||
6869 | if ($option != '') { |
||
6870 | $sql .= ", {$option}"; |
||
6871 | } |
||
6872 | |||
6873 | $sql .= ')'; |
||
6874 | $whatToComment = 'TEXT SEARCH DICTIONARY'; |
||
6875 | } |
||
6876 | |||
6877 | /* if comment, begin a transaction to |
||
6878 | * run both commands */ |
||
6879 | if ($comment != '') { |
||
6880 | $status = $this->beginTransaction(); |
||
6881 | if ($status != 0) { |
||
6882 | return -1; |
||
6883 | } |
||
6884 | } |
||
6885 | |||
6886 | // Create the FTS dictionary |
||
6887 | $status = $this->execute($sql); |
||
6888 | if ($status != 0) { |
||
6889 | $this->rollbackTransaction(); |
||
6890 | |||
6891 | return -1; |
||
6892 | } |
||
6893 | |||
6894 | // Set the comment |
||
6895 | if ($comment != '') { |
||
6896 | $status = $this->setComment($whatToComment, $dictname, '', $comment); |
||
6897 | if ($status != 0) { |
||
6898 | $this->rollbackTransaction(); |
||
6899 | |||
6900 | return -1; |
||
6901 | } |
||
6902 | } |
||
6903 | |||
6904 | return $this->endTransaction(); |
||
6905 | } |
||
6906 | |||
6907 | // Role, User/Group functions |
||
6908 | |||
6909 | /** |
||
6910 | * Alters FTS dictionary or dictionary template. |
||
6911 | * |
||
6912 | * @param $dictname The dico's name |
||
6913 | * @param $comment The comment |
||
6914 | * @param $name The new dico's name |
||
6915 | * |
||
6916 | * @return bool|int 0 on success |
||
6917 | */ |
||
6918 | public function updateFtsDictionary($dictname, $comment, $name) |
||
6919 | { |
||
6920 | $status = $this->beginTransaction(); |
||
6921 | if ($status != 0) { |
||
6922 | $this->rollbackTransaction(); |
||
6923 | |||
6924 | return -1; |
||
6925 | } |
||
6926 | |||
6927 | $this->fieldClean($dictname); |
||
6928 | $status = $this->setComment('TEXT SEARCH DICTIONARY', $dictname, '', $comment); |
||
6929 | if ($status != 0) { |
||
6930 | $this->rollbackTransaction(); |
||
6931 | |||
6932 | return -1; |
||
6933 | } |
||
6934 | |||
6935 | // Only if the name has changed |
||
6936 | if ($name != $dictname) { |
||
6937 | $f_schema = $this->_schema; |
||
6938 | $this->fieldClean($f_schema); |
||
6939 | $this->fieldClean($name); |
||
6940 | |||
6941 | $sql = "ALTER TEXT SEARCH DICTIONARY \"{$f_schema}\".\"{$dictname}\" RENAME TO \"{$name}\""; |
||
6942 | $status = $this->execute($sql); |
||
6943 | if ($status != 0) { |
||
6944 | $this->rollbackTransaction(); |
||
6945 | |||
6946 | return -1; |
||
6947 | } |
||
6948 | } |
||
6949 | |||
6950 | return $this->endTransaction(); |
||
6951 | } |
||
6952 | |||
6953 | /** |
||
6954 | * Return all information relating to a FTS dictionary. |
||
6955 | * |
||
6956 | * @param $ftsdict The name of the FTS dictionary |
||
6957 | * |
||
6958 | * @return \PHPPgAdmin\ADORecordSet recordset of FTS dictionary information |
||
6959 | */ |
||
6960 | public function getFtsDictionaryByName($ftsdict) |
||
6961 | { |
||
6962 | $c_schema = $this->_schema; |
||
6963 | $this->clean($c_schema); |
||
6964 | $this->clean($ftsdict); |
||
6965 | |||
6966 | $sql = "SELECT |
||
6967 | n.nspname as schema, |
||
6968 | d.dictname as name, |
||
6969 | ( SELECT COALESCE(nt.nspname, '(null)')::pg_catalog.text || '.' || t.tmplname FROM |
||
6970 | pg_catalog.pg_ts_template t |
||
6971 | LEFT JOIN pg_catalog.pg_namespace nt ON nt.oid = t.tmplnamespace |
||
6972 | WHERE d.dicttemplate = t.oid ) AS template, |
||
6973 | d.dictinitoption as init, |
||
6974 | pg_catalog.obj_description(d.oid, 'pg_ts_dict') as comment |
||
6975 | FROM pg_catalog.pg_ts_dict d |
||
6976 | LEFT JOIN pg_catalog.pg_namespace n ON n.oid = d.dictnamespace |
||
6977 | WHERE d.dictname = '{$ftsdict}' |
||
6978 | AND pg_catalog.pg_ts_dict_is_visible(d.oid) |
||
6979 | AND n.nspname='{$c_schema}' |
||
6980 | ORDER BY name"; |
||
6981 | |||
6982 | return $this->selectSet($sql); |
||
6983 | } |
||
6984 | |||
6985 | /** |
||
6986 | * Creates/updates/deletes FTS mapping. |
||
6987 | * |
||
6988 | * @param $ftscfg |
||
6989 | * @param array $mapping Array of tokens' names |
||
6990 | * @param string $action What to do with the mapping: add, alter or drop |
||
6991 | * @param string $dictname Dictionary that will process tokens given or null in case of drop action |
||
6992 | * |
||
6993 | * @return int 0 if operation was successful |
||
6994 | * |
||
6995 | * @internal param string $cfgname The name of the FTS configuration to alter |
||
6996 | */ |
||
6997 | public function changeFtsMapping($ftscfg, $mapping, $action, $dictname = null) |
||
6998 | { |
||
6999 | if (count($mapping) > 0) { |
||
7000 | $f_schema = $this->_schema; |
||
7001 | $this->fieldClean($f_schema); |
||
7002 | $this->fieldClean($ftscfg); |
||
7003 | $this->fieldClean($dictname); |
||
7004 | $this->arrayClean($mapping); |
||
7005 | |||
7006 | switch ($action) { |
||
7007 | case 'alter': |
||
7008 | $whatToDo = 'ALTER'; |
||
7009 | |||
7010 | break; |
||
7011 | case 'drop': |
||
7012 | $whatToDo = 'DROP'; |
||
7013 | |||
7014 | break; |
||
7015 | default: |
||
7016 | $whatToDo = 'ADD'; |
||
7017 | |||
7018 | break; |
||
7019 | } |
||
7020 | |||
7021 | $sql = "ALTER TEXT SEARCH CONFIGURATION \"{$f_schema}\".\"{$ftscfg}\" {$whatToDo} MAPPING FOR "; |
||
7022 | $sql .= implode(',', $mapping); |
||
7023 | if ($action != 'drop' && !empty($dictname)) { |
||
7024 | $sql .= " WITH {$dictname}"; |
||
7025 | } |
||
7026 | |||
7027 | return $this->execute($sql); |
||
7028 | } |
||
7029 | |||
7030 | return -1; |
||
7031 | } |
||
7032 | |||
7033 | /** |
||
7034 | * Return all information related to a given FTS configuration's mapping. |
||
7035 | * |
||
7036 | * @param $ftscfg The name of the FTS configuration |
||
7037 | * @param $mapping The name of the mapping |
||
7038 | * |
||
7039 | * @return FTS configuration information |
||
7040 | */ |
||
7041 | public function getFtsMappingByName($ftscfg, $mapping) |
||
7042 | { |
||
7043 | $c_schema = $this->_schema; |
||
7044 | $this->clean($c_schema); |
||
7045 | $this->clean($ftscfg); |
||
7046 | $this->clean($mapping); |
||
7047 | |||
7048 | $oidSet = $this->selectSet("SELECT c.oid, cfgparser |
||
1 ignored issue
–
show
|
|||
7049 | FROM pg_catalog.pg_ts_config AS c |
||
7050 | LEFT JOIN pg_catalog.pg_namespace AS n ON n.oid = c.cfgnamespace |
||
7051 | WHERE c.cfgname = '{$ftscfg}' |
||
7052 | AND n.nspname='{$c_schema}'"); |
||
1 ignored issue
–
show
|
|||
7053 | |||
7054 | $oid = $oidSet->fields['oid']; |
||
7055 | $cfgparser = $oidSet->fields['cfgparser']; |
||
7056 | |||
7057 | $tokenIdSet = $this->selectSet("SELECT tokid |
||
1 ignored issue
–
show
|
|||
7058 | FROM pg_catalog.ts_token_type({$cfgparser}) |
||
7059 | WHERE alias = '{$mapping}'"); |
||
1 ignored issue
–
show
|
|||
7060 | |||
7061 | $tokid = $tokenIdSet->fields['tokid']; |
||
7062 | |||
7063 | $sql = "SELECT |
||
7064 | (SELECT t.alias FROM pg_catalog.ts_token_type(c.cfgparser) AS t WHERE t.tokid = m.maptokentype) AS name, |
||
7065 | d.dictname as dictionaries |
||
7066 | FROM pg_catalog.pg_ts_config AS c, pg_catalog.pg_ts_config_map AS m, pg_catalog.pg_ts_dict d |
||
7067 | WHERE c.oid = {$oid} AND m.mapcfg = c.oid AND m.maptokentype = {$tokid} AND m.mapdict = d.oid |
||
7068 | LIMIT 1;"; |
||
7069 | |||
7070 | return $this->selectSet($sql); |
||
7071 | } |
||
7072 | |||
7073 | /** |
||
7074 | * Return list of FTS mappings possible for given parser |
||
7075 | * (specified by given configuration since configuration can only have 1 parser). |
||
7076 | * |
||
7077 | * @param $ftscfg The config's name that use the parser |
||
7078 | * |
||
7079 | * @return integer 0 if operation was successful |
||
7080 | */ |
||
7081 | public function getFtsMappings($ftscfg) |
||
7082 | { |
||
7083 | $cfg = $this->getFtsConfigurationByName($ftscfg); |
||
7084 | |||
7085 | $sql = "SELECT alias AS name, description |
||
7086 | FROM pg_catalog.ts_token_type({$cfg->fields['parser_id']}) |
||
7087 | ORDER BY name"; |
||
7088 | |||
7089 | return $this->selectSet($sql); |
||
7090 | } |
||
7091 | |||
7092 | /** |
||
7093 | * Return all information related to a FTS configuration. |
||
7094 | * |
||
7095 | * @param $ftscfg The name of the FTS configuration |
||
7096 | * |
||
7097 | * @return FTS configuration information |
||
7098 | */ |
||
7099 | public function getFtsConfigurationByName($ftscfg) |
||
7100 | { |
||
7101 | $c_schema = $this->_schema; |
||
7102 | $this->clean($c_schema); |
||
7103 | $this->clean($ftscfg); |
||
7104 | $sql = " |
||
7105 | SELECT |
||
7106 | n.nspname as schema, |
||
7107 | c.cfgname as name, |
||
7108 | p.prsname as parser, |
||
7109 | c.cfgparser as parser_id, |
||
7110 | pg_catalog.obj_description(c.oid, 'pg_ts_config') as comment |
||
7111 | FROM pg_catalog.pg_ts_config c |
||
7112 | LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.cfgnamespace |
||
7113 | LEFT JOIN pg_catalog.pg_ts_parser p ON p.oid = c.cfgparser |
||
7114 | WHERE pg_catalog.pg_ts_config_is_visible(c.oid) |
||
7115 | AND c.cfgname = '{$ftscfg}' |
||
7116 | AND n.nspname='{$c_schema}'"; |
||
7117 | |||
7118 | return $this->selectSet($sql); |
||
7119 | } |
||
7120 | |||
7121 | /** |
||
7122 | * Gets all languages. |
||
7123 | * |
||
7124 | * @param bool|true $all True to get all languages, regardless of show_system |
||
7125 | * |
||
7126 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
7127 | */ |
||
7128 | public function getLanguages($all = false) |
||
7129 | { |
||
7130 | $conf = $this->conf; |
||
7131 | |||
7132 | if ($conf['show_system'] || $all) { |
||
7133 | $where = ''; |
||
7134 | } else { |
||
7135 | $where = 'WHERE lanispl'; |
||
7136 | } |
||
7137 | |||
7138 | $sql = " |
||
7139 | SELECT |
||
7140 | lanname, lanpltrusted, |
||
7141 | lanplcallfoid::pg_catalog.regproc AS lanplcallf |
||
7142 | FROM |
||
7143 | pg_catalog.pg_language |
||
7144 | {$where} |
||
7145 | ORDER BY lanname |
||
7146 | "; |
||
7147 | |||
7148 | return $this->selectSet($sql); |
||
7149 | } |
||
7150 | |||
7151 | /** |
||
7152 | * Creates a new aggregate in the database. |
||
7153 | * |
||
7154 | * @param $name The name of the aggregate |
||
7155 | * @param $basetype The input data type of the aggregate |
||
7156 | * @param $sfunc The name of the state transition function for the aggregate |
||
7157 | * @param $stype The data type for the aggregate's state value |
||
7158 | * @param $ffunc The name of the final function for the aggregate |
||
7159 | * @param $initcond The initial setting for the state value |
||
7160 | * @param $sortop The sort operator for the aggregate |
||
7161 | * @param $comment Aggregate comment |
||
7162 | * |
||
7163 | * @return bool|int 0 success |
||
7164 | */ |
||
7165 | public function createAggregate($name, $basetype, $sfunc, $stype, $ffunc, $initcond, $sortop, $comment) |
||
7166 | { |
||
7167 | $f_schema = $this->_schema; |
||
7168 | $this->fieldClean($f_schema); |
||
7169 | $this->fieldClean($name); |
||
7170 | $this->fieldClean($basetype); |
||
7171 | $this->fieldClean($sfunc); |
||
7172 | $this->fieldClean($stype); |
||
7173 | $this->fieldClean($ffunc); |
||
7174 | $this->fieldClean($initcond); |
||
7175 | $this->fieldClean($sortop); |
||
7176 | |||
7177 | $this->beginTransaction(); |
||
7178 | |||
7179 | $sql = "CREATE AGGREGATE \"{$f_schema}\".\"{$name}\" (BASETYPE = \"{$basetype}\", SFUNC = \"{$sfunc}\", STYPE = \"{$stype}\""; |
||
7180 | if (trim($ffunc) != '') { |
||
7181 | $sql .= ", FINALFUNC = \"{$ffunc}\""; |
||
7182 | } |
||
7183 | |||
7184 | if (trim($initcond) != '') { |
||
7185 | $sql .= ", INITCOND = \"{$initcond}\""; |
||
7186 | } |
||
7187 | |||
7188 | if (trim($sortop) != '') { |
||
7189 | $sql .= ", SORTOP = \"{$sortop}\""; |
||
7190 | } |
||
7191 | |||
7192 | $sql .= ')'; |
||
7193 | |||
7194 | $status = $this->execute($sql); |
||
7195 | if ($status) { |
||
7196 | $this->rollbackTransaction(); |
||
7197 | |||
7198 | return -1; |
||
7199 | } |
||
7200 | |||
7201 | if (trim($comment) != '') { |
||
7202 | $status = $this->setComment('AGGREGATE', $name, '', $comment, $basetype); |
||
7203 | if ($status) { |
||
7204 | $this->rollbackTransaction(); |
||
7205 | |||
7206 | return -1; |
||
7207 | } |
||
7208 | } |
||
7209 | |||
7210 | return $this->endTransaction(); |
||
7211 | } |
||
7212 | |||
7213 | /** |
||
7214 | * Removes an aggregate function from the database. |
||
7215 | * |
||
7216 | * @param $aggrname The name of the aggregate |
||
7217 | * @param $aggrtype The input data type of the aggregate |
||
7218 | * @param $cascade True to cascade drop, false to restrict |
||
7219 | * |
||
7220 | * @return integer 0 if operation was successful |
||
7221 | */ |
||
7222 | public function dropAggregate($aggrname, $aggrtype, $cascade) |
||
7223 | { |
||
7224 | $f_schema = $this->_schema; |
||
7225 | $this->fieldClean($f_schema); |
||
7226 | $this->fieldClean($aggrname); |
||
7227 | $this->fieldClean($aggrtype); |
||
7228 | |||
7229 | $sql = "DROP AGGREGATE \"{$f_schema}\".\"{$aggrname}\" (\"{$aggrtype}\")"; |
||
7230 | if ($cascade) { |
||
7231 | $sql .= ' CASCADE'; |
||
7232 | } |
||
7233 | |||
7234 | return $this->execute($sql); |
||
7235 | } |
||
7236 | |||
7237 | /** |
||
7238 | * Gets all information for an aggregate. |
||
7239 | * |
||
7240 | * @param $name The name of the aggregate |
||
7241 | * @param $basetype The input data type of the aggregate |
||
7242 | * |
||
7243 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
7244 | */ |
||
7245 | public function getAggregate($name, $basetype) |
||
7246 | { |
||
7247 | $c_schema = $this->_schema; |
||
7248 | $this->clean($c_schema); |
||
7249 | $this->fieldClean($name); |
||
7250 | $this->fieldClean($basetype); |
||
7251 | |||
7252 | $sql = " |
||
7253 | SELECT p.proname, CASE p.proargtypes[0] |
||
7254 | WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype THEN NULL |
||
7255 | ELSE pg_catalog.format_type(p.proargtypes[0], NULL) END AS proargtypes, |
||
7256 | a.aggtransfn, format_type(a.aggtranstype, NULL) AS aggstype, a.aggfinalfn, |
||
7257 | a.agginitval, a.aggsortop, u.usename, pg_catalog.obj_description(p.oid, 'pg_proc') AS aggrcomment |
||
7258 | FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n, pg_catalog.pg_user u, pg_catalog.pg_aggregate a |
||
7259 | WHERE n.oid = p.pronamespace AND p.proowner=u.usesysid AND p.oid=a.aggfnoid |
||
7260 | AND p.proisagg AND n.nspname='{$c_schema}' |
||
7261 | AND p.proname='" . $name . "' |
||
7262 | AND CASE p.proargtypes[0] |
||
7263 | WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype THEN '' |
||
7264 | ELSE pg_catalog.format_type(p.proargtypes[0], NULL) |
||
7265 | END ='" . $basetype . "'"; |
||
7266 | |||
7267 | return $this->selectSet($sql); |
||
7268 | } |
||
7269 | |||
7270 | /** |
||
7271 | * Gets all aggregates. |
||
7272 | * |
||
7273 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
7274 | */ |
||
7275 | public function getAggregates() |
||
7276 | { |
||
7277 | $c_schema = $this->_schema; |
||
7278 | $this->clean($c_schema); |
||
7279 | $sql = "SELECT p.proname, CASE p.proargtypes[0] WHEN 'pg_catalog.\"any\"'::pg_catalog.regtype THEN NULL ELSE |
||
7280 | pg_catalog.format_type(p.proargtypes[0], NULL) END AS proargtypes, a.aggtransfn, u.usename, |
||
7281 | pg_catalog.obj_description(p.oid, 'pg_proc') AS aggrcomment |
||
7282 | FROM pg_catalog.pg_proc p, pg_catalog.pg_namespace n, pg_catalog.pg_user u, pg_catalog.pg_aggregate a |
||
7283 | WHERE n.oid = p.pronamespace AND p.proowner=u.usesysid AND p.oid=a.aggfnoid |
||
7284 | AND p.proisagg AND n.nspname='{$c_schema}' ORDER BY 1, 2"; |
||
7285 | |||
7286 | return $this->selectSet($sql); |
||
7287 | } |
||
7288 | |||
7289 | /** |
||
7290 | * Alters an aggregate. |
||
7291 | * |
||
7292 | * @param $aggrname The actual name of the aggregate |
||
7293 | * @param $aggrtype The actual input data type of the aggregate |
||
7294 | * @param $aggrowner The actual owner of the aggregate |
||
7295 | * @param $aggrschema The actual schema the aggregate belongs to |
||
7296 | * @param $aggrcomment The actual comment for the aggregate |
||
7297 | * @param $newaggrname The new name of the aggregate |
||
7298 | * @param $newaggrowner The new owner of the aggregate |
||
7299 | * @param $newaggrschema The new schema where the aggregate will belong to |
||
7300 | * @param $newaggrcomment The new comment for the aggregate |
||
7301 | * |
||
7302 | * @return bool|int 0 success |
||
7303 | */ |
||
7304 | public function alterAggregate( |
||
7305 | $aggrname, |
||
7306 | $aggrtype, |
||
7307 | $aggrowner, |
||
7308 | $aggrschema, |
||
7309 | $aggrcomment, |
||
7310 | $newaggrname, |
||
7311 | $newaggrowner, |
||
7312 | $newaggrschema, |
||
7313 | $newaggrcomment |
||
7314 | ) { |
||
7315 | // Clean fields |
||
7316 | $this->fieldClean($aggrname); |
||
7317 | $this->fieldClean($aggrtype); |
||
7318 | $this->fieldClean($aggrowner); |
||
7319 | $this->fieldClean($aggrschema); |
||
7320 | $this->fieldClean($newaggrname); |
||
7321 | $this->fieldClean($newaggrowner); |
||
7322 | $this->fieldClean($newaggrschema); |
||
7323 | |||
7324 | $this->beginTransaction(); |
||
7325 | |||
7326 | // Change the owner, if it has changed |
||
7327 | if ($aggrowner != $newaggrowner) { |
||
7328 | $status = $this->changeAggregateOwner($aggrname, $aggrtype, $newaggrowner); |
||
7329 | if ($status != 0) { |
||
7330 | $this->rollbackTransaction(); |
||
7331 | |||
7332 | return -1; |
||
7333 | } |
||
7334 | } |
||
7335 | |||
7336 | // Set the comment, if it has changed |
||
7337 | if ($aggrcomment != $newaggrcomment) { |
||
7338 | $status = $this->setComment('AGGREGATE', $aggrname, '', $newaggrcomment, $aggrtype); |
||
7339 | if ($status) { |
||
7340 | $this->rollbackTransaction(); |
||
7341 | |||
7342 | return -2; |
||
7343 | } |
||
7344 | } |
||
7345 | |||
7346 | // Change the schema, if it has changed |
||
7347 | if ($aggrschema != $newaggrschema) { |
||
7348 | $status = $this->changeAggregateSchema($aggrname, $aggrtype, $newaggrschema); |
||
7349 | if ($status != 0) { |
||
7350 | $this->rollbackTransaction(); |
||
7351 | |||
7352 | return -3; |
||
7353 | } |
||
7354 | } |
||
7355 | |||
7356 | // Rename the aggregate, if it has changed |
||
7357 | if ($aggrname != $newaggrname) { |
||
7358 | $status = $this->renameAggregate($newaggrschema, $aggrname, $aggrtype, $newaggrname); |
||
7359 | if ($status != 0) { |
||
7360 | $this->rollbackTransaction(); |
||
7361 | |||
7362 | return -4; |
||
7363 | } |
||
7364 | } |
||
7365 | |||
7366 | return $this->endTransaction(); |
||
7367 | } |
||
7368 | |||
7369 | /** |
||
7370 | * Changes the owner of an aggregate function. |
||
7371 | * |
||
7372 | * @param $aggrname The name of the aggregate |
||
7373 | * @param $aggrtype The input data type of the aggregate |
||
7374 | * @param $newaggrowner The new owner of the aggregate |
||
7375 | * |
||
7376 | * @return integer 0 if operation was successful |
||
7377 | */ |
||
7378 | public function changeAggregateOwner($aggrname, $aggrtype, $newaggrowner) |
||
7379 | { |
||
7380 | $f_schema = $this->_schema; |
||
7381 | $this->fieldClean($f_schema); |
||
7382 | $this->fieldClean($aggrname); |
||
7383 | $this->fieldClean($newaggrowner); |
||
7384 | $sql = "ALTER AGGREGATE \"{$f_schema}\".\"{$aggrname}\" (\"{$aggrtype}\") OWNER TO \"{$newaggrowner}\""; |
||
7385 | |||
7386 | return $this->execute($sql); |
||
7387 | } |
||
7388 | |||
7389 | /** |
||
7390 | * Changes the schema of an aggregate function. |
||
7391 | * |
||
7392 | * @param $aggrname The name of the aggregate |
||
7393 | * @param $aggrtype The input data type of the aggregate |
||
7394 | * @param $newaggrschema The new schema for the aggregate |
||
7395 | * |
||
7396 | * @return integer 0 if operation was successful |
||
7397 | */ |
||
7398 | public function changeAggregateSchema($aggrname, $aggrtype, $newaggrschema) |
||
7399 | { |
||
7400 | $f_schema = $this->_schema; |
||
7401 | $this->fieldClean($f_schema); |
||
7402 | $this->fieldClean($aggrname); |
||
7403 | $this->fieldClean($newaggrschema); |
||
7404 | $sql = "ALTER AGGREGATE \"{$f_schema}\".\"{$aggrname}\" (\"{$aggrtype}\") SET SCHEMA \"{$newaggrschema}\""; |
||
7405 | |||
7406 | return $this->execute($sql); |
||
7407 | } |
||
7408 | |||
7409 | /** |
||
7410 | * Renames an aggregate function. |
||
7411 | * |
||
7412 | * @param $aggrschema |
||
7413 | * @param $aggrname The actual name of the aggregate |
||
7414 | * @param $aggrtype The actual input data type of the aggregate |
||
7415 | * @param $newaggrname The new name of the aggregate |
||
7416 | * |
||
7417 | * @return integer 0 if operation was successful |
||
7418 | */ |
||
7419 | public function renameAggregate($aggrschema, $aggrname, $aggrtype, $newaggrname) |
||
7420 | { |
||
7421 | /* this function is called from alterAggregate where params are cleaned */ |
||
7422 | $sql = "ALTER AGGREGATE \"{$aggrschema}\"" . '.' . "\"{$aggrname}\" (\"{$aggrtype}\") RENAME TO \"{$newaggrname}\""; |
||
7423 | |||
7424 | return $this->execute($sql); |
||
7425 | } |
||
7426 | |||
7427 | /** |
||
7428 | * Returns all roles in the database cluster. |
||
7429 | * |
||
7430 | * @param $rolename (optional) The role name to exclude from the select |
||
7431 | * |
||
7432 | * @return All roles |
||
7433 | */ |
||
7434 | public function getRoles($rolename = '') |
||
7435 | { |
||
7436 | $sql = ' |
||
7437 | SELECT rolname, rolsuper, rolcreatedb, rolcreaterole, rolinherit, |
||
7438 | rolcanlogin, rolconnlimit, rolvaliduntil, rolconfig |
||
7439 | FROM pg_catalog.pg_roles'; |
||
7440 | if ($rolename) { |
||
7441 | $sql .= " WHERE rolname!='{$rolename}'"; |
||
7442 | } |
||
7443 | |||
7444 | $sql .= ' ORDER BY rolname'; |
||
7445 | |||
7446 | return $this->selectSet($sql); |
||
7447 | } |
||
7448 | |||
7449 | /** |
||
7450 | * Returns information about a single role. |
||
7451 | * |
||
7452 | * @param $rolename The name of the role to retrieve |
||
7453 | * |
||
7454 | * @return The role's data |
||
7455 | */ |
||
7456 | public function getRole($rolename) |
||
7457 | { |
||
7458 | $this->clean($rolename); |
||
7459 | |||
7460 | $sql = " |
||
7461 | SELECT rolname, rolsuper, rolcreatedb, rolcreaterole, rolinherit, |
||
7462 | rolcanlogin, rolconnlimit, rolvaliduntil, rolconfig |
||
7463 | FROM pg_catalog.pg_roles WHERE rolname='{$rolename}'"; |
||
7464 | |||
7465 | return $this->selectSet($sql); |
||
7466 | } |
||
7467 | |||
7468 | /** |
||
7469 | * Returns all users in the database cluster. |
||
7470 | * |
||
7471 | * @return All users |
||
7472 | */ |
||
7473 | public function getUsers() |
||
7474 | { |
||
7475 | $sql = 'SELECT usename, usesuper, usecreatedb, valuntil AS useexpires, useconfig |
||
7476 | FROM pg_user |
||
7477 | ORDER BY usename'; |
||
7478 | |||
7479 | return $this->selectSet($sql); |
||
7480 | } |
||
7481 | |||
7482 | /** |
||
7483 | * Returns information about a single user. |
||
7484 | * |
||
7485 | * @param $username The username of the user to retrieve |
||
7486 | * |
||
7487 | * @return The user's data |
||
7488 | */ |
||
7489 | public function getUser($username) |
||
7490 | { |
||
7491 | $this->clean($username); |
||
7492 | |||
7493 | $sql = "SELECT usename, usesuper, usecreatedb, valuntil AS useexpires, useconfig |
||
7494 | FROM pg_user |
||
7495 | WHERE usename='{$username}'"; |
||
7496 | |||
7497 | return $this->selectSet($sql); |
||
7498 | } |
||
7499 | |||
7500 | /** |
||
7501 | * Creates a new role. |
||
7502 | * |
||
7503 | * @param $rolename The name of the role to create |
||
7504 | * @param $password A password for the role |
||
7505 | * @param $superuser Boolean whether or not the role is a superuser |
||
7506 | * @param $createdb Boolean whether or not the role can create databases |
||
7507 | * @param $createrole Boolean whether or not the role can create other roles |
||
7508 | * @param $inherits Boolean whether or not the role inherits the privileges from parent roles |
||
7509 | * @param $login Boolean whether or not the role will be allowed to login |
||
7510 | * @param $connlimit Number of concurrent connections the role can make |
||
7511 | * @param $expiry String Format 'YYYY-MM-DD HH:MM:SS'. '' means never expire |
||
7512 | * @param $memberof (array) Roles to which the new role will be immediately added as a new member |
||
7513 | * @param $members (array) Roles which are automatically added as members of the new role |
||
7514 | * @param $adminmembers (array) Roles which are automatically added as admin members of the new role |
||
7515 | * |
||
7516 | * @return integer 0 if operation was successful |
||
7517 | */ |
||
7518 | public function createRole( |
||
7519 | $rolename, |
||
7520 | $password, |
||
7521 | $superuser, |
||
7522 | $createdb, |
||
7523 | $createrole, |
||
7524 | $inherits, |
||
7525 | $login, |
||
7526 | $connlimit, |
||
7527 | $expiry, |
||
7528 | $memberof, |
||
7529 | $members, |
||
7530 | $adminmembers |
||
7531 | ) { |
||
7532 | $enc = $this->_encryptPassword($rolename, $password); |
||
7533 | $this->fieldClean($rolename); |
||
7534 | $this->clean($enc); |
||
7535 | $this->clean($connlimit); |
||
7536 | $this->clean($expiry); |
||
7537 | $this->fieldArrayClean($memberof); |
||
7538 | $this->fieldArrayClean($members); |
||
7539 | $this->fieldArrayClean($adminmembers); |
||
7540 | |||
7541 | $sql = "CREATE ROLE \"{$rolename}\""; |
||
7542 | if ($password != '') { |
||
7543 | $sql .= " WITH ENCRYPTED PASSWORD '{$enc}'"; |
||
7544 | } |
||
7545 | |||
7546 | $sql .= $superuser ? ' SUPERUSER' : ' NOSUPERUSER'; |
||
7547 | $sql .= $createdb ? ' CREATEDB' : ' NOCREATEDB'; |
||
7548 | $sql .= $createrole ? ' CREATEROLE' : ' NOCREATEROLE'; |
||
7549 | $sql .= $inherits ? ' INHERIT' : ' NOINHERIT'; |
||
7550 | $sql .= $login ? ' LOGIN' : ' NOLOGIN'; |
||
7551 | if ($connlimit != '') { |
||
7552 | $sql .= " CONNECTION LIMIT {$connlimit}"; |
||
7553 | } else { |
||
7554 | $sql .= ' CONNECTION LIMIT -1'; |
||
7555 | } |
||
7556 | |||
7557 | if ($expiry != '') { |
||
7558 | $sql .= " VALID UNTIL '{$expiry}'"; |
||
7559 | } else { |
||
7560 | $sql .= " VALID UNTIL 'infinity'"; |
||
7561 | } |
||
7562 | |||
7563 | if (is_array($memberof) && sizeof($memberof) > 0) { |
||
7564 | $sql .= ' IN ROLE "' . join('", "', $memberof) . '"'; |
||
7565 | } |
||
7566 | |||
7567 | if (is_array($members) && sizeof($members) > 0) { |
||
7568 | $sql .= ' ROLE "' . join('", "', $members) . '"'; |
||
7569 | } |
||
7570 | |||
7571 | if (is_array($adminmembers) && sizeof($adminmembers) > 0) { |
||
7572 | $sql .= ' ADMIN "' . join('", "', $adminmembers) . '"'; |
||
7573 | } |
||
7574 | |||
7575 | return $this->execute($sql); |
||
7576 | } |
||
7577 | |||
7578 | /** |
||
7579 | * Helper function that computes encypted PostgreSQL passwords. |
||
7580 | * |
||
7581 | * @param $username The username |
||
7582 | * @param $password The password |
||
7583 | * |
||
7584 | * @return string |
||
7585 | */ |
||
7586 | public function _encryptPassword($username, $password) |
||
1 ignored issue
–
show
|
|||
7587 | { |
||
7588 | return 'md5' . md5($password . $username); |
||
7589 | } |
||
7590 | |||
7591 | /** |
||
7592 | * Adjusts a role's info and renames it. |
||
7593 | * |
||
7594 | * @param $rolename The name of the role to adjust |
||
7595 | * @param $password A password for the role |
||
7596 | * @param $superuser Boolean whether or not the role is a superuser |
||
7597 | * @param $createdb Boolean whether or not the role can create databases |
||
7598 | * @param $createrole Boolean whether or not the role can create other roles |
||
7599 | * @param $inherits Boolean whether or not the role inherits the privileges from parent roles |
||
7600 | * @param $login Boolean whether or not the role will be allowed to login |
||
7601 | * @param $connlimit Number of concurrent connections the role can make |
||
7602 | * @param $expiry string Format 'YYYY-MM-DD HH:MM:SS'. '' means never expire |
||
7603 | * @param $memberof (array) Roles to which the role will be immediately added as a new member |
||
7604 | * @param $members (array) Roles which are automatically added as members of the role |
||
7605 | * @param $adminmembers (array) Roles which are automatically added as admin members of the role |
||
7606 | * @param $memberofold (array) Original roles whose the role belongs to |
||
7607 | * @param $membersold (array) Original roles that are members of the role |
||
7608 | * @param $adminmembersold (array) Original roles that are admin members of the role |
||
7609 | * @param $newrolename The new name of the role |
||
7610 | * |
||
7611 | * @return bool|int 0 success |
||
7612 | */ |
||
7613 | public function setRenameRole( |
||
7614 | $rolename, |
||
7615 | $password, |
||
7616 | $superuser, |
||
7617 | $createdb, |
||
7618 | $createrole, |
||
7619 | $inherits, |
||
7620 | $login, |
||
7621 | $connlimit, |
||
7622 | $expiry, |
||
7623 | $memberof, |
||
7624 | $members, |
||
7625 | $adminmembers, |
||
7626 | $memberofold, |
||
7627 | $membersold, |
||
7628 | $adminmembersold, |
||
7629 | $newrolename |
||
7630 | ) { |
||
7631 | $status = $this->beginTransaction(); |
||
7632 | if ($status != 0) { |
||
7633 | return -1; |
||
7634 | } |
||
7635 | |||
7636 | if ($rolename != $newrolename) { |
||
7637 | $status = $this->renameRole($rolename, $newrolename); |
||
7638 | if ($status != 0) { |
||
7639 | $this->rollbackTransaction(); |
||
7640 | |||
7641 | return -3; |
||
7642 | } |
||
7643 | $rolename = $newrolename; |
||
7644 | } |
||
7645 | |||
7646 | $status = |
||
7647 | $this->setRole( |
||
7648 | $rolename, |
||
7649 | $password, |
||
7650 | $superuser, |
||
7651 | $createdb, |
||
7652 | $createrole, |
||
7653 | $inherits, |
||
7654 | $login, |
||
7655 | $connlimit, |
||
7656 | $expiry, |
||
7657 | $memberof, |
||
7658 | $members, |
||
7659 | $adminmembers, |
||
7660 | $memberofold, |
||
7661 | $membersold, |
||
7662 | $adminmembersold |
||
7663 | ); |
||
7664 | if ($status != 0) { |
||
7665 | $this->rollbackTransaction(); |
||
7666 | |||
7667 | return -2; |
||
7668 | } |
||
7669 | |||
7670 | return $this->endTransaction(); |
||
7671 | } |
||
7672 | |||
7673 | /** |
||
7674 | * Renames a role. |
||
7675 | * |
||
7676 | * @param $rolename The name of the role to rename |
||
7677 | * @param $newrolename The new name of the role |
||
7678 | * |
||
7679 | * @return integer 0 if operation was successful |
||
7680 | */ |
||
7681 | public function renameRole($rolename, $newrolename) |
||
7682 | { |
||
7683 | $this->fieldClean($rolename); |
||
7684 | $this->fieldClean($newrolename); |
||
7685 | |||
7686 | $sql = "ALTER ROLE \"{$rolename}\" RENAME TO \"{$newrolename}\""; |
||
7687 | |||
7688 | return $this->execute($sql); |
||
7689 | } |
||
7690 | |||
7691 | /** |
||
7692 | * Adjusts a role's info. |
||
7693 | * |
||
7694 | * @param $rolename The name of the role to adjust |
||
7695 | * @param $password A password for the role |
||
7696 | * @param $superuser Boolean whether or not the role is a superuser |
||
7697 | * @param $createdb Boolean whether or not the role can create databases |
||
7698 | * @param $createrole Boolean whether or not the role can create other roles |
||
7699 | * @param $inherits Boolean whether or not the role inherits the privileges from parent roles |
||
7700 | * @param $login Boolean whether or not the role will be allowed to login |
||
7701 | * @param $connlimit Number of concurrent connections the role can make |
||
7702 | * @param $expiry string Format 'YYYY-MM-DD HH:MM:SS'. '' means never expire |
||
7703 | * @param $memberof (array) Roles to which the role will be immediately added as a new member |
||
7704 | * @param $members (array) Roles which are automatically added as members of the role |
||
7705 | * @param $adminmembers (array) Roles which are automatically added as admin members of the role |
||
7706 | * @param $memberofold (array) Original roles whose the role belongs to |
||
7707 | * @param $membersold (array) Original roles that are members of the role |
||
7708 | * @param $adminmembersold (array) Original roles that are admin members of the role |
||
7709 | * |
||
7710 | * @return int 0 if operation was successful |
||
7711 | */ |
||
7712 | public function setRole( |
||
7713 | $rolename, |
||
7714 | $password, |
||
7715 | $superuser, |
||
7716 | $createdb, |
||
7717 | $createrole, |
||
7718 | $inherits, |
||
7719 | $login, |
||
7720 | $connlimit, |
||
7721 | $expiry, |
||
7722 | $memberof, |
||
7723 | $members, |
||
7724 | $adminmembers, |
||
7725 | $memberofold, |
||
7726 | $membersold, |
||
7727 | $adminmembersold |
||
7728 | ) { |
||
7729 | $enc = $this->_encryptPassword($rolename, $password); |
||
7730 | $this->fieldClean($rolename); |
||
7731 | $this->clean($enc); |
||
7732 | $this->clean($connlimit); |
||
7733 | $this->clean($expiry); |
||
7734 | $this->fieldArrayClean($memberof); |
||
7735 | $this->fieldArrayClean($members); |
||
7736 | $this->fieldArrayClean($adminmembers); |
||
7737 | |||
7738 | $sql = "ALTER ROLE \"{$rolename}\""; |
||
7739 | if ($password != '') { |
||
7740 | $sql .= " WITH ENCRYPTED PASSWORD '{$enc}'"; |
||
7741 | } |
||
7742 | |||
7743 | $sql .= $superuser ? ' SUPERUSER' : ' NOSUPERUSER'; |
||
7744 | $sql .= $createdb ? ' CREATEDB' : ' NOCREATEDB'; |
||
7745 | $sql .= $createrole ? ' CREATEROLE' : ' NOCREATEROLE'; |
||
7746 | $sql .= $inherits ? ' INHERIT' : ' NOINHERIT'; |
||
7747 | $sql .= $login ? ' LOGIN' : ' NOLOGIN'; |
||
7748 | if ($connlimit != '') { |
||
7749 | $sql .= " CONNECTION LIMIT {$connlimit}"; |
||
7750 | } else { |
||
7751 | $sql .= ' CONNECTION LIMIT -1'; |
||
7752 | } |
||
7753 | |||
7754 | if ($expiry != '') { |
||
7755 | $sql .= " VALID UNTIL '{$expiry}'"; |
||
7756 | } else { |
||
7757 | $sql .= " VALID UNTIL 'infinity'"; |
||
7758 | } |
||
7759 | |||
7760 | $status = $this->execute($sql); |
||
7761 | |||
7762 | if ($status != 0) { |
||
7763 | return -1; |
||
7764 | } |
||
7765 | |||
7766 | //memberof |
||
7767 | $old = explode(',', $memberofold); |
||
7768 | foreach ($memberof as $m) { |
||
7769 | if (!in_array($m, $old, true)) { |
||
7770 | $status = $this->grantRole($m, $rolename); |
||
7771 | if ($status != 0) { |
||
7772 | return -1; |
||
7773 | } |
||
7774 | } |
||
7775 | } |
||
7776 | if ($memberofold) { |
||
7777 | foreach ($old as $o) { |
||
7778 | if (!in_array($o, $memberof, true)) { |
||
7779 | $status = $this->revokeRole($o, $rolename, 0, 'CASCADE'); |
||
7780 | if ($status != 0) { |
||
7781 | return -1; |
||
7782 | } |
||
7783 | } |
||
7784 | } |
||
7785 | } |
||
7786 | |||
7787 | //members |
||
7788 | $old = explode(',', $membersold); |
||
7789 | foreach ($members as $m) { |
||
7790 | if (!in_array($m, $old, true)) { |
||
7791 | $status = $this->grantRole($rolename, $m); |
||
7792 | if ($status != 0) { |
||
7793 | return -1; |
||
7794 | } |
||
7795 | } |
||
7796 | } |
||
7797 | if ($membersold) { |
||
7798 | foreach ($old as $o) { |
||
7799 | if (!in_array($o, $members, true)) { |
||
7800 | $status = $this->revokeRole($rolename, $o, 0, 'CASCADE'); |
||
7801 | if ($status != 0) { |
||
7802 | return -1; |
||
7803 | } |
||
7804 | } |
||
7805 | } |
||
7806 | } |
||
7807 | |||
7808 | //adminmembers |
||
7809 | $old = explode(',', $adminmembersold); |
||
7810 | foreach ($adminmembers as $m) { |
||
7811 | if (!in_array($m, $old, true)) { |
||
7812 | $status = $this->grantRole($rolename, $m, 1); |
||
7813 | if ($status != 0) { |
||
7814 | return -1; |
||
7815 | } |
||
7816 | } |
||
7817 | } |
||
7818 | if ($adminmembersold) { |
||
7819 | foreach ($old as $o) { |
||
7820 | if (!in_array($o, $adminmembers, true)) { |
||
7821 | $status = $this->revokeRole($rolename, $o, 1, 'CASCADE'); |
||
7822 | if ($status != 0) { |
||
7823 | return -1; |
||
7824 | } |
||
7825 | } |
||
7826 | } |
||
7827 | } |
||
7828 | |||
7829 | return $status; |
||
7830 | } |
||
7831 | |||
7832 | /** |
||
7833 | * Grants membership in a role. |
||
7834 | * |
||
7835 | * @param $role The name of the target role |
||
7836 | * @param $rolename The name of the role that will belong to the target role |
||
7837 | * @param int $admin (optional) Flag to grant the admin option |
||
7838 | * |
||
7839 | * @return integer 0 if operation was successful |
||
7840 | */ |
||
7841 | public function grantRole($role, $rolename, $admin = 0) |
||
7842 | { |
||
7843 | $this->fieldClean($role); |
||
7844 | $this->fieldClean($rolename); |
||
7845 | |||
7846 | $sql = "GRANT \"{$role}\" TO \"{$rolename}\""; |
||
7847 | if ($admin == 1) { |
||
7848 | $sql .= ' WITH ADMIN OPTION'; |
||
7849 | } |
||
7850 | |||
7851 | return $this->execute($sql); |
||
7852 | } |
||
7853 | |||
7854 | /** |
||
7855 | * Revokes membership in a role. |
||
7856 | * |
||
7857 | * @param $role The name of the target role |
||
7858 | * @param $rolename The name of the role that will not belong to the target role |
||
7859 | * @param int $admin (optional) Flag to revoke only the admin option |
||
7860 | * @param string $type (optional) Type of revoke: RESTRICT | CASCADE |
||
7861 | * |
||
7862 | * @return integer 0 if operation was successful |
||
7863 | */ |
||
7864 | public function revokeRole($role, $rolename, $admin = 0, $type = 'RESTRICT') |
||
7865 | { |
||
7866 | $this->fieldClean($role); |
||
7867 | $this->fieldClean($rolename); |
||
7868 | |||
7869 | $sql = 'REVOKE '; |
||
7870 | if ($admin == 1) { |
||
7871 | $sql .= 'ADMIN OPTION FOR '; |
||
7872 | } |
||
7873 | |||
7874 | $sql .= "\"{$role}\" FROM \"{$rolename}\" {$type}"; |
||
7875 | |||
7876 | return $this->execute($sql); |
||
7877 | } |
||
7878 | |||
7879 | /** |
||
7880 | * Removes a role. |
||
7881 | * |
||
7882 | * @param $rolename The name of the role to drop |
||
7883 | * |
||
7884 | * @return integer 0 if operation was successful |
||
7885 | */ |
||
7886 | public function dropRole($rolename) |
||
7887 | { |
||
7888 | $this->fieldClean($rolename); |
||
7889 | |||
7890 | $sql = "DROP ROLE \"{$rolename}\""; |
||
7891 | |||
7892 | return $this->execute($sql); |
||
7893 | } |
||
7894 | |||
7895 | /** |
||
7896 | * Creates a new user. |
||
7897 | * |
||
7898 | * @param $username The username of the user to create |
||
7899 | * @param $password A password for the user |
||
7900 | * @param $createdb boolean Whether or not the user can create databases |
||
7901 | * @param $createuser boolean Whether or not the user can create other users |
||
7902 | * @param $expiry string Format 'YYYY-MM-DD HH:MM:SS'. '' means never expire |
||
7903 | * @param $groups |
||
7904 | * |
||
7905 | * @return integer 0 if operation was successful |
||
7906 | * |
||
7907 | * @internal param $group (array) The groups to create the user in |
||
7908 | */ |
||
7909 | public function createUser($username, $password, $createdb, $createuser, $expiry, $groups) |
||
7910 | { |
||
7911 | $enc = $this->_encryptPassword($username, $password); |
||
7912 | $this->fieldClean($username); |
||
7913 | $this->clean($enc); |
||
7914 | $this->clean($expiry); |
||
7915 | $this->fieldArrayClean($groups); |
||
7916 | |||
7917 | $sql = "CREATE USER \"{$username}\""; |
||
7918 | if ($password != '') { |
||
7919 | $sql .= " WITH ENCRYPTED PASSWORD '{$enc}'"; |
||
7920 | } |
||
7921 | |||
7922 | $sql .= $createdb ? ' CREATEDB' : ' NOCREATEDB'; |
||
7923 | $sql .= $createuser ? ' CREATEUSER' : ' NOCREATEUSER'; |
||
7924 | if (is_array($groups) && sizeof($groups) > 0) { |
||
7925 | $sql .= ' IN GROUP "' . join('", "', $groups) . '"'; |
||
7926 | } |
||
7927 | |||
7928 | if ($expiry != '') { |
||
7929 | $sql .= " VALID UNTIL '{$expiry}'"; |
||
7930 | } else { |
||
7931 | $sql .= " VALID UNTIL 'infinity'"; |
||
7932 | } |
||
7933 | |||
7934 | return $this->execute($sql); |
||
7935 | } |
||
7936 | |||
7937 | /** |
||
7938 | * Adjusts a user's info and renames the user. |
||
7939 | * |
||
7940 | * @param $username The username of the user to modify |
||
7941 | * @param $password A new password for the user |
||
7942 | * @param $createdb boolean Whether or not the user can create databases |
||
7943 | * @param $createuser boolean Whether or not the user can create other users |
||
7944 | * @param $expiry string Format 'YYYY-MM-DD HH:MM:SS'. '' means never expire. |
||
7945 | * @param $newname The new name of the user |
||
7946 | * |
||
7947 | * @return bool|int 0 success |
||
7948 | */ |
||
7949 | public function setRenameUser($username, $password, $createdb, $createuser, $expiry, $newname) |
||
7950 | { |
||
7951 | $status = $this->beginTransaction(); |
||
7952 | if ($status != 0) { |
||
7953 | return -1; |
||
7954 | } |
||
7955 | |||
7956 | if ($username != $newname) { |
||
7957 | $status = $this->renameUser($username, $newname); |
||
7958 | if ($status != 0) { |
||
7959 | $this->rollbackTransaction(); |
||
7960 | |||
7961 | return -3; |
||
7962 | } |
||
7963 | $username = $newname; |
||
7964 | } |
||
7965 | |||
7966 | $status = $this->setUser($username, $password, $createdb, $createuser, $expiry); |
||
7967 | if ($status != 0) { |
||
7968 | $this->rollbackTransaction(); |
||
7969 | |||
7970 | return -2; |
||
7971 | } |
||
7972 | |||
7973 | return $this->endTransaction(); |
||
7974 | } |
||
7975 | |||
7976 | /** |
||
7977 | * Renames a user. |
||
7978 | * |
||
7979 | * @param $username The username of the user to rename |
||
7980 | * @param $newname The new name of the user |
||
7981 | * |
||
7982 | * @return integer 0 if operation was successful |
||
7983 | */ |
||
7984 | public function renameUser($username, $newname) |
||
7985 | { |
||
7986 | $this->fieldClean($username); |
||
7987 | $this->fieldClean($newname); |
||
7988 | |||
7989 | $sql = "ALTER USER \"{$username}\" RENAME TO \"{$newname}\""; |
||
7990 | |||
7991 | return $this->execute($sql); |
||
7992 | } |
||
7993 | |||
7994 | // Tablespace functions |
||
7995 | |||
7996 | /** |
||
7997 | * Adjusts a user's info. |
||
7998 | * |
||
7999 | * @param $username The username of the user to modify |
||
8000 | * @param $password A new password for the user |
||
8001 | * @param $createdb boolean Whether or not the user can create databases |
||
8002 | * @param $createuser boolean Whether or not the user can create other users |
||
8003 | * @param $expiry string Format 'YYYY-MM-DD HH:MM:SS'. '' means never expire. |
||
8004 | * |
||
8005 | * @return integer 0 if operation was successful |
||
8006 | */ |
||
8007 | public function setUser($username, $password, $createdb, $createuser, $expiry) |
||
8028 | } |
||
8029 | |||
8030 | /** |
||
8031 | * Removes a user. |
||
8032 | * |
||
8033 | * @param $username The username of the user to drop |
||
8034 | * |
||
8035 | * @return integer 0 if operation was successful |
||
8036 | */ |
||
8037 | public function dropUser($username) |
||
8038 | { |
||
8039 | $this->fieldClean($username); |
||
8040 | |||
8041 | $sql = "DROP USER \"{$username}\""; |
||
8042 | |||
8043 | return $this->execute($sql); |
||
8044 | } |
||
8045 | |||
8046 | /** |
||
8047 | * Changes a role's password. |
||
8048 | * |
||
8049 | * @param $rolename The role name |
||
8050 | * @param $password The new password |
||
8051 | * |
||
8052 | * @return integer 0 if operation was successful |
||
8053 | */ |
||
8054 | public function changePassword($rolename, $password) |
||
8063 | } |
||
8064 | |||
8065 | /** |
||
8066 | * Adds a group member. |
||
8067 | * |
||
8068 | * @param $groname The name of the group |
||
8069 | * @param $user The name of the user to add to the group |
||
8070 | * |
||
8071 | * @return integer 0 if operation was successful |
||
8072 | */ |
||
8073 | public function addGroupMember($groname, $user) |
||
8074 | { |
||
8075 | $this->fieldClean($groname); |
||
8076 | $this->fieldClean($user); |
||
8077 | |||
8078 | $sql = "ALTER GROUP \"{$groname}\" ADD USER \"{$user}\""; |
||
8079 | |||
8080 | return $this->execute($sql); |
||
8081 | } |
||
8082 | |||
8083 | /** |
||
8084 | * Returns all role names which the role belongs to. |
||
8085 | * |
||
8086 | * @param $rolename The role name |
||
8087 | * |
||
8088 | * @return All role names |
||
8089 | */ |
||
8090 | public function getMemberOf($rolename) |
||
8091 | { |
||
8092 | $this->clean($rolename); |
||
8093 | |||
8094 | $sql = " |
||
8095 | SELECT rolname FROM pg_catalog.pg_roles R, pg_auth_members M |
||
8096 | WHERE R.oid=M.roleid |
||
8097 | AND member IN ( |
||
8098 | SELECT oid FROM pg_catalog.pg_roles |
||
8099 | WHERE rolname='{$rolename}') |
||
8100 | ORDER BY rolname"; |
||
8101 | |||
8102 | return $this->selectSet($sql); |
||
8103 | } |
||
8104 | |||
8105 | // Administration functions |
||
8106 | |||
8107 | /** |
||
8108 | * Returns all role names that are members of a role. |
||
8109 | * |
||
8110 | * @param $rolename The role name |
||
8111 | * @param $admin (optional) Find only admin members |
||
8112 | * |
||
8113 | * @return All role names |
||
8114 | */ |
||
8115 | public function getMembers($rolename, $admin = 'f') |
||
8116 | { |
||
8117 | $this->clean($rolename); |
||
8118 | |||
8119 | $sql = " |
||
8120 | SELECT rolname FROM pg_catalog.pg_roles R, pg_auth_members M |
||
8121 | WHERE R.oid=M.member AND admin_option='{$admin}' |
||
8122 | AND roleid IN (SELECT oid FROM pg_catalog.pg_roles |
||
8123 | WHERE rolname='{$rolename}') |
||
8124 | ORDER BY rolname"; |
||
8125 | |||
8126 | return $this->selectSet($sql); |
||
8127 | } |
||
8128 | |||
8129 | /** |
||
8130 | * Removes a group member. |
||
8131 | * |
||
8132 | * @param $groname The name of the group |
||
8133 | * @param $user The name of the user to remove from the group |
||
8134 | * |
||
8135 | * @return integer 0 if operation was successful |
||
8136 | */ |
||
8137 | public function dropGroupMember($groname, $user) |
||
8138 | { |
||
8139 | $this->fieldClean($groname); |
||
8140 | $this->fieldClean($user); |
||
8141 | |||
8142 | $sql = "ALTER GROUP \"{$groname}\" DROP USER \"{$user}\""; |
||
8143 | |||
8144 | return $this->execute($sql); |
||
8145 | } |
||
8146 | |||
8147 | /** |
||
8148 | * Return users in a specific group. |
||
8149 | * |
||
8150 | * @param $groname The name of the group |
||
8151 | * |
||
8152 | * @return All users in the group |
||
8153 | */ |
||
8154 | public function getGroup($groname) |
||
8164 | } |
||
8165 | |||
8166 | /** |
||
8167 | * Returns all groups in the database cluser. |
||
8168 | * |
||
8169 | * @return All groups |
||
8170 | */ |
||
8171 | public function getGroups() |
||
8176 | } |
||
8177 | |||
8178 | /** |
||
8179 | * Creates a new group. |
||
8180 | * |
||
8181 | * @param $groname The name of the group |
||
8182 | * @param $users An array of users to add to the group |
||
8183 | * |
||
8184 | * @return integer 0 if operation was successful |
||
8185 | */ |
||
8186 | public function createGroup($groname, $users) |
||
8198 | } |
||
8199 | |||
8200 | /** |
||
8201 | * Removes a group. |
||
8202 | * |
||
8203 | * @param $groname The name of the group to drop |
||
8204 | * |
||
8205 | * @return integer 0 if operation was successful |
||
8206 | */ |
||
8207 | public function dropGroup($groname) |
||
8208 | { |
||
8209 | $this->fieldClean($groname); |
||
8210 | |||
8211 | $sql = "DROP GROUP \"{$groname}\""; |
||
8212 | |||
8213 | return $this->execute($sql); |
||
8214 | } |
||
8215 | |||
8216 | /** |
||
8217 | * Grants a privilege to a user, group or public. |
||
8218 | * |
||
8219 | * @param $mode 'GRANT' or 'REVOKE'; |
||
8220 | * @param $type The type of object |
||
8221 | * @param $object The name of the object |
||
8222 | * @param $public True to grant to public, false otherwise |
||
8223 | * @param $usernames the array of usernames to grant privs to |
||
8224 | * @param $groupnames the array of group names to grant privs to |
||
8225 | * @param $privileges The array of privileges to grant (eg. ('SELECT', 'ALL PRIVILEGES', etc.) ) |
||
8226 | * @param $grantoption True if has grant option, false otherwise |
||
8227 | * @param $cascade True for cascade revoke, false otherwise |
||
8228 | * @param $table the column's table if type=column |
||
8229 | * |
||
8230 | * @return integer 0 if operation was successful |
||
8231 | */ |
||
8232 | public function setPrivileges( |
||
8233 | $mode, |
||
8234 | $type, |
||
8235 | $object, |
||
8236 | $public, |
||
8237 | $usernames, |
||
8238 | $groupnames, |
||
8239 | $privileges, |
||
8240 | $grantoption, |
||
8241 | $cascade, |
||
8242 | $table |
||
8243 | ) { |
||
8244 | $f_schema = $this->_schema; |
||
8245 | $this->fieldClean($f_schema); |
||
8246 | $this->fieldArrayClean($usernames); |
||
8247 | $this->fieldArrayClean($groupnames); |
||
8248 | |||
8249 | // Input checking |
||
8250 | if (!is_array($privileges) || sizeof($privileges) == 0) { |
||
8251 | return -3; |
||
8252 | } |
||
8253 | |||
8254 | if (!is_array($usernames) || !is_array($groupnames) || |
||
8255 | (!$public && sizeof($usernames) == 0 && sizeof($groupnames) == 0)) { |
||
2 ignored issues
–
show
|
|||
8256 | return -4; |
||
8257 | } |
||
8258 | |||
8259 | if ($mode != 'GRANT' && $mode != 'REVOKE') { |
||
8260 | return -5; |
||
8261 | } |
||
8262 | |||
8263 | $sql = $mode; |
||
8264 | |||
8265 | // Grant option |
||
8266 | if ($this->hasGrantOption() && $mode == 'REVOKE' && $grantoption) { |
||
8267 | $sql .= ' GRANT OPTION FOR'; |
||
8268 | } |
||
8269 | |||
8270 | if (in_array('ALL PRIVILEGES', $privileges, true)) { |
||
8271 | $sql .= ' ALL PRIVILEGES'; |
||
8272 | } else { |
||
8273 | if ($type == 'column') { |
||
8274 | $this->fieldClean($object); |
||
8275 | $sql .= ' ' . join(" (\"{$object}\"), ", $privileges); |
||
8276 | } else { |
||
8277 | $sql .= ' ' . join(', ', $privileges); |
||
8278 | } |
||
8279 | } |
||
8280 | |||
8281 | switch ($type) { |
||
8282 | case 'column': |
||
8283 | $sql .= " (\"{$object}\")"; |
||
8284 | $object = $table; |
||
8285 | // no break |
||
8286 | case 'table': |
||
8287 | case 'view': |
||
8288 | case 'sequence': |
||
8289 | $this->fieldClean($object); |
||
8290 | $sql .= " ON \"{$f_schema}\".\"{$object}\""; |
||
8291 | |||
8292 | break; |
||
8293 | case 'database': |
||
8294 | $this->fieldClean($object); |
||
8295 | $sql .= " ON DATABASE \"{$object}\""; |
||
8296 | |||
8297 | break; |
||
8298 | case 'function': |
||
8299 | // Function comes in with $object as function OID |
||
8300 | $fn = $this->getFunction($object); |
||
8301 | $this->fieldClean($fn->fields['proname']); |
||
8302 | $sql .= " ON FUNCTION \"{$f_schema}\".\"{$fn->fields['proname']}\"({$fn->fields['proarguments']})"; |
||
8303 | |||
8304 | break; |
||
8305 | case 'language': |
||
8306 | $this->fieldClean($object); |
||
8307 | $sql .= " ON LANGUAGE \"{$object}\""; |
||
8308 | |||
8309 | break; |
||
8310 | case 'schema': |
||
8311 | $this->fieldClean($object); |
||
8312 | $sql .= " ON SCHEMA \"{$object}\""; |
||
8313 | |||
8314 | break; |
||
8315 | case 'tablespace': |
||
8316 | $this->fieldClean($object); |
||
8317 | $sql .= " ON TABLESPACE \"{$object}\""; |
||
8318 | |||
8319 | break; |
||
8320 | default: |
||
8321 | return -1; |
||
8322 | } |
||
8323 | |||
8324 | // Dump PUBLIC |
||
8325 | $first = true; |
||
8326 | $sql .= ($mode == 'GRANT') ? ' TO ' : ' FROM '; |
||
8327 | if ($public) { |
||
8328 | $sql .= 'PUBLIC'; |
||
8329 | $first = false; |
||
8330 | } |
||
8331 | // Dump users |
||
8332 | foreach ($usernames as $v) { |
||
8333 | if ($first) { |
||
8334 | $sql .= "\"{$v}\""; |
||
8335 | $first = false; |
||
8336 | } else { |
||
8337 | $sql .= ", \"{$v}\""; |
||
8338 | } |
||
8339 | } |
||
8340 | // Dump groups |
||
8341 | foreach ($groupnames as $v) { |
||
8342 | if ($first) { |
||
8343 | $sql .= "GROUP \"{$v}\""; |
||
8344 | $first = false; |
||
8345 | } else { |
||
8346 | $sql .= ", GROUP \"{$v}\""; |
||
8347 | } |
||
8348 | } |
||
8349 | |||
8350 | // Grant option |
||
8351 | if ($this->hasGrantOption() && $mode == 'GRANT' && $grantoption) { |
||
8352 | $sql .= ' WITH GRANT OPTION'; |
||
8353 | } |
||
8354 | |||
8355 | // Cascade revoke |
||
8356 | if ($this->hasGrantOption() && $mode == 'REVOKE' && $cascade) { |
||
8357 | $sql .= ' CASCADE'; |
||
8358 | } |
||
8359 | |||
8360 | return $this->execute($sql); |
||
8361 | } |
||
8362 | |||
8363 | /** |
||
8364 | * Retrieves information for all tablespaces. |
||
8365 | * |
||
8366 | * @param bool $all Include all tablespaces (necessary when moving objects back to the default space) |
||
8367 | * |
||
8368 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
8369 | */ |
||
8370 | public function getTablespaces($all = false) |
||
8371 | { |
||
8372 | $conf = $this->conf; |
||
8373 | |||
8374 | $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, pg_catalog.pg_tablespace_location(oid) as spclocation, |
||
8375 | (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pg_tablespace.oid=pd.objoid AND pd.classoid='pg_tablespace'::regclass) AS spccomment |
||
8376 | FROM pg_catalog.pg_tablespace"; |
||
8377 | |||
8378 | if (!$conf['show_system'] && !$all) { |
||
8379 | $sql .= ' WHERE spcname NOT LIKE $$pg\_%$$'; |
||
8380 | } |
||
8381 | |||
8382 | $sql .= ' ORDER BY spcname'; |
||
8383 | |||
8384 | return $this->selectSet($sql); |
||
8385 | } |
||
8386 | |||
8387 | // Misc functions |
||
8388 | |||
8389 | /** |
||
8390 | * Retrieves a tablespace's information. |
||
8391 | * |
||
8392 | * @param $spcname |
||
8393 | * |
||
8394 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
8395 | */ |
||
8396 | public function getTablespace($spcname) |
||
8397 | { |
||
8398 | $this->clean($spcname); |
||
8399 | |||
8400 | $sql = "SELECT spcname, pg_catalog.pg_get_userbyid(spcowner) AS spcowner, pg_catalog.pg_tablespace_location(oid) as spclocation, |
||
8401 | (SELECT description FROM pg_catalog.pg_shdescription pd WHERE pg_tablespace.oid=pd.objoid AND pd.classoid='pg_tablespace'::regclass) AS spccomment |
||
8402 | FROM pg_catalog.pg_tablespace WHERE spcname='{$spcname}'"; |
||
8403 | |||
8404 | return $this->selectSet($sql); |
||
8405 | } |
||
8406 | |||
8407 | /** |
||
8408 | * Creates a tablespace. |
||
8409 | * |
||
8410 | * @param $spcname The name of the tablespace to create |
||
8411 | * @param $spcowner The owner of the tablespace. '' for current |
||
8412 | * @param $spcloc The directory in which to create the tablespace |
||
8413 | * @param string $comment |
||
8414 | * |
||
8415 | * @return int 0 success |
||
8416 | */ |
||
8417 | public function createTablespace($spcname, $spcowner, $spcloc, $comment = '') |
||
8418 | { |
||
8419 | $this->fieldClean($spcname); |
||
8420 | $this->clean($spcloc); |
||
8421 | |||
8422 | $sql = "CREATE TABLESPACE \"{$spcname}\""; |
||
8423 | |||
8424 | if ($spcowner != '') { |
||
8425 | $this->fieldClean($spcowner); |
||
8426 | $sql .= " OWNER \"{$spcowner}\""; |
||
8427 | } |
||
8428 | |||
8429 | $sql .= " LOCATION '{$spcloc}'"; |
||
8430 | |||
8431 | $status = $this->execute($sql); |
||
8432 | if ($status != 0) { |
||
8433 | return -1; |
||
8434 | } |
||
8435 | |||
8436 | if ($comment != '' && $this->hasSharedComments()) { |
||
8437 | $status = $this->setComment('TABLESPACE', $spcname, '', $comment); |
||
8438 | if ($status != 0) { |
||
8439 | return -2; |
||
8440 | } |
||
8441 | } |
||
8442 | |||
8443 | return 0; |
||
8444 | } |
||
8445 | |||
8446 | /** |
||
8447 | * Alters a tablespace. |
||
8448 | * |
||
8449 | * @param $spcname The name of the tablespace |
||
8450 | * @param $name The new name for the tablespace |
||
8451 | * @param $owner The new owner for the tablespace |
||
8452 | * @param string $comment |
||
8453 | * |
||
8454 | * @return bool|int 0 success |
||
8455 | */ |
||
8456 | public function alterTablespace($spcname, $name, $owner, $comment = '') |
||
8457 | { |
||
8458 | $this->fieldClean($spcname); |
||
8459 | $this->fieldClean($name); |
||
8460 | $this->fieldClean($owner); |
||
8461 | |||
8462 | // Begin transaction |
||
8463 | $status = $this->beginTransaction(); |
||
8464 | if ($status != 0) { |
||
8465 | return -1; |
||
8466 | } |
||
8467 | |||
8468 | // Owner |
||
8469 | $sql = "ALTER TABLESPACE \"{$spcname}\" OWNER TO \"{$owner}\""; |
||
8470 | $status = $this->execute($sql); |
||
8471 | if ($status != 0) { |
||
8472 | $this->rollbackTransaction(); |
||
8473 | |||
8474 | return -2; |
||
8475 | } |
||
8476 | |||
8477 | // Rename (only if name has changed) |
||
8478 | if ($name != $spcname) { |
||
8479 | $sql = "ALTER TABLESPACE \"{$spcname}\" RENAME TO \"{$name}\""; |
||
8480 | $status = $this->execute($sql); |
||
8481 | if ($status != 0) { |
||
8482 | $this->rollbackTransaction(); |
||
8483 | |||
8484 | return -3; |
||
8485 | } |
||
8486 | |||
8487 | $spcname = $name; |
||
8488 | } |
||
8489 | |||
8490 | // Set comment if it has changed |
||
8491 | if (trim($comment) != '' && $this->hasSharedComments()) { |
||
8492 | $status = $this->setComment('TABLESPACE', $spcname, '', $comment); |
||
8493 | if ($status != 0) { |
||
8494 | return -4; |
||
8495 | } |
||
8496 | } |
||
8497 | |||
8498 | return $this->endTransaction(); |
||
8499 | } |
||
8500 | |||
8501 | /** |
||
8502 | * Drops a tablespace. |
||
8503 | * |
||
8504 | * @param $spcname The name of the domain to drop |
||
8505 | * |
||
8506 | * @return integer 0 if operation was successful |
||
8507 | */ |
||
8508 | public function dropTablespace($spcname) |
||
8509 | { |
||
8510 | $this->fieldClean($spcname); |
||
8511 | |||
8512 | $sql = "DROP TABLESPACE \"{$spcname}\""; |
||
8513 | |||
8514 | return $this->execute($sql); |
||
8515 | } |
||
8516 | |||
8517 | /** |
||
8518 | * Analyze a database. |
||
8519 | * |
||
8520 | * @param string $table (optional) The table to analyze |
||
8521 | * |
||
8522 | * @return bool 0 if successful |
||
8523 | */ |
||
8524 | public function analyzeDB($table = '') |
||
8525 | { |
||
8526 | if ($table != '') { |
||
8527 | $f_schema = $this->_schema; |
||
8528 | $this->fieldClean($f_schema); |
||
8529 | $this->fieldClean($table); |
||
8530 | |||
8531 | $sql = "ANALYZE \"{$f_schema}\".\"{$table}\""; |
||
8532 | } else { |
||
8533 | $sql = 'ANALYZE'; |
||
8534 | } |
||
8535 | |||
8536 | return $this->execute($sql); |
||
8537 | } |
||
8538 | |||
8539 | /** |
||
8540 | * Vacuums a database. |
||
8541 | * |
||
8542 | * @param string $table The table to vacuum |
||
8543 | * @param bool $analyze If true, also does analyze |
||
8544 | * @param bool $full If true, selects "full" vacuum |
||
8545 | * @param bool $freeze If true, selects aggressive "freezing" of tuples |
||
8546 | * |
||
8547 | * @return bool 0 if successful |
||
8548 | */ |
||
8549 | public function vacuumDB($table = '', $analyze = false, $full = false, $freeze = false) |
||
8550 | { |
||
8551 | $sql = 'VACUUM'; |
||
8552 | if ($full) { |
||
8553 | $sql .= ' FULL'; |
||
8554 | } |
||
8555 | |||
8556 | if ($freeze) { |
||
8557 | $sql .= ' FREEZE'; |
||
8558 | } |
||
8559 | |||
8560 | if ($analyze) { |
||
8561 | $sql .= ' ANALYZE'; |
||
8562 | } |
||
8563 | |||
8564 | if ($table != '') { |
||
8565 | $f_schema = $this->_schema; |
||
8566 | $this->fieldClean($f_schema); |
||
8567 | $this->fieldClean($table); |
||
8568 | $sql .= " \"{$f_schema}\".\"{$table}\""; |
||
8569 | } |
||
8570 | |||
8571 | return $this->execute($sql); |
||
8572 | } |
||
8573 | |||
8574 | /** |
||
8575 | * Returns all autovacuum global configuration. |
||
8576 | * |
||
8577 | * @return array associative array array( param => value, ...) |
||
8578 | */ |
||
8579 | public function getAutovacuum() |
||
8580 | { |
||
8581 | $_defaults = $this->selectSet( |
||
8582 | "SELECT name, setting |
||
8583 | FROM pg_catalog.pg_settings |
||
8584 | WHERE |
||
8585 | name = 'autovacuum' |
||
8586 | OR name = 'autovacuum_vacuum_threshold' |
||
8587 | OR name = 'autovacuum_vacuum_scale_factor' |
||
8588 | OR name = 'autovacuum_analyze_threshold' |
||
8589 | OR name = 'autovacuum_analyze_scale_factor' |
||
8590 | OR name = 'autovacuum_vacuum_cost_delay' |
||
8591 | OR name = 'autovacuum_vacuum_cost_limit' |
||
8592 | OR name = 'vacuum_freeze_min_age' |
||
8593 | OR name = 'autovacuum_freeze_max_age' |
||
8594 | " |
||
8595 | ); |
||
8596 | |||
8597 | $ret = []; |
||
8598 | while (!$_defaults->EOF) { |
||
8599 | $ret[$_defaults->fields['name']] = $_defaults->fields['setting']; |
||
8600 | $_defaults->moveNext(); |
||
8601 | } |
||
8602 | |||
8603 | return $ret; |
||
8604 | } |
||
8605 | |||
8606 | /** |
||
8607 | * Returns all available autovacuum per table information. |
||
8608 | * |
||
8609 | * @param string $table |
||
8610 | * @param bool $vacenabled |
||
8611 | * @param int $vacthreshold |
||
8612 | * @param int $vacscalefactor |
||
8613 | * @param int $anathresold |
||
8614 | * @param int $anascalefactor |
||
8615 | * @param int $vaccostdelay |
||
8616 | * @param int $vaccostlimit |
||
8617 | * |
||
8618 | * @return bool 0 if successful |
||
8619 | */ |
||
8620 | public function saveAutovacuum( |
||
8621 | $table, |
||
8622 | $vacenabled, |
||
8623 | $vacthreshold, |
||
8624 | $vacscalefactor, |
||
8625 | $anathresold, |
||
8626 | $anascalefactor, |
||
8627 | $vaccostdelay, |
||
8628 | $vaccostlimit |
||
8629 | ) { |
||
8630 | $f_schema = $this->_schema; |
||
8631 | $this->fieldClean($f_schema); |
||
8632 | $this->fieldClean($table); |
||
8633 | |||
8634 | $sql = "ALTER TABLE \"{$f_schema}\".\"{$table}\" SET ("; |
||
8635 | |||
8636 | if (!empty($vacenabled)) { |
||
8637 | $this->clean($vacenabled); |
||
8638 | $params[] = "autovacuum_enabled='{$vacenabled}'"; |
||
8639 | } |
||
8640 | if (!empty($vacthreshold)) { |
||
8641 | $this->clean($vacthreshold); |
||
8642 | $params[] = "autovacuum_vacuum_threshold='{$vacthreshold}'"; |
||
8643 | } |
||
8644 | if (!empty($vacscalefactor)) { |
||
8645 | $this->clean($vacscalefactor); |
||
8646 | $params[] = "autovacuum_vacuum_scale_factor='{$vacscalefactor}'"; |
||
8647 | } |
||
8648 | if (!empty($anathresold)) { |
||
8649 | $this->clean($anathresold); |
||
8650 | $params[] = "autovacuum_analyze_threshold='{$anathresold}'"; |
||
8651 | } |
||
8652 | if (!empty($anascalefactor)) { |
||
8653 | $this->clean($anascalefactor); |
||
8654 | $params[] = "autovacuum_analyze_scale_factor='{$anascalefactor}'"; |
||
8655 | } |
||
8656 | if (!empty($vaccostdelay)) { |
||
8657 | $this->clean($vaccostdelay); |
||
8658 | $params[] = "autovacuum_vacuum_cost_delay='{$vaccostdelay}'"; |
||
8659 | } |
||
8660 | if (!empty($vaccostlimit)) { |
||
8661 | $this->clean($vaccostlimit); |
||
8662 | $params[] = "autovacuum_vacuum_cost_limit='{$vaccostlimit}'"; |
||
8663 | } |
||
8664 | |||
8665 | $sql = $sql . implode(',', $params) . ');'; |
||
8666 | |||
8667 | return $this->execute($sql); |
||
8668 | } |
||
8669 | |||
8670 | // Type conversion routines |
||
8671 | |||
8672 | /** |
||
8673 | * Drops autovacuum config for a table |
||
8674 | * |
||
8675 | * @param string $table The table |
||
8676 | * |
||
8677 | * @return bool 0 if successful |
||
8678 | */ |
||
8679 | public function dropAutovacuum($table) |
||
8680 | { |
||
8681 | $f_schema = $this->_schema; |
||
8682 | $this->fieldClean($f_schema); |
||
8683 | $this->fieldClean($table); |
||
8684 | |||
8685 | return $this->execute( |
||
8686 | " |
||
8687 | ALTER TABLE \"{$f_schema}\".\"{$table}\" RESET (autovacuum_enabled, autovacuum_vacuum_threshold, |
||
8688 | autovacuum_vacuum_scale_factor, autovacuum_analyze_threshold, autovacuum_analyze_scale_factor, |
||
8689 | autovacuum_vacuum_cost_delay, autovacuum_vacuum_cost_limit |
||
8690 | );" |
||
8691 | ); |
||
8692 | } |
||
8693 | |||
8694 | /** |
||
8695 | * Returns all available process information. |
||
8696 | * |
||
8697 | * @param $database (optional) Find only connections to specified database |
||
8698 | * |
||
8699 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
8700 | */ |
||
8701 | public function getProcesses($database = null) |
||
8702 | { |
||
8703 | if ($database === null) { |
||
8704 | $sql = "SELECT datname, usename, pid, waiting, state_change as query_start, |
||
8705 | case when state='idle in transaction' then '<IDLE> in transaction' when state = 'idle' then '<IDLE>' else query end as query |
||
8706 | FROM pg_catalog.pg_stat_activity |
||
8707 | ORDER BY datname, usename, pid"; |
||
8708 | } else { |
||
8709 | $this->clean($database); |
||
8710 | $sql = "SELECT datname, usename, pid, waiting, state_change as query_start, |
||
8711 | case when state='idle in transaction' then '<IDLE> in transaction' when state = 'idle' then '<IDLE>' else query end as query |
||
8712 | FROM pg_catalog.pg_stat_activity |
||
8713 | WHERE datname='{$database}' |
||
8714 | ORDER BY usename, pid"; |
||
8715 | } |
||
8716 | |||
8717 | return $this->selectSet($sql); |
||
8718 | } |
||
8719 | |||
8720 | // interfaces Statistics collector functions |
||
8721 | |||
8722 | /** |
||
8723 | * Returns table locks information in the current database. |
||
8724 | * |
||
8725 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
8726 | */ |
||
8727 | public function getLocks() |
||
8728 | { |
||
8729 | $conf = $this->conf; |
||
8730 | |||
8731 | if (!$conf['show_system']) { |
||
8732 | $where = 'AND pn.nspname NOT LIKE $$pg\_%$$'; |
||
8733 | } else { |
||
8734 | $where = "AND nspname !~ '^pg_t(emp_[0-9]+|oast)$'"; |
||
8735 | } |
||
8736 | |||
8737 | $sql = " |
||
8738 | SELECT |
||
8739 | pn.nspname, pc.relname AS tablename, pl.pid, pl.mode, pl.granted, pl.virtualtransaction, |
||
8740 | (select transactionid from pg_catalog.pg_locks l2 where l2.locktype='transactionid' |
||
8741 | and l2.mode='ExclusiveLock' and l2.virtualtransaction=pl.virtualtransaction) as transaction |
||
8742 | FROM |
||
8743 | pg_catalog.pg_locks pl, |
||
8744 | pg_catalog.pg_class pc, |
||
8745 | pg_catalog.pg_namespace pn |
||
8746 | WHERE |
||
8747 | pl.relation = pc.oid AND pc.relnamespace=pn.oid |
||
8748 | {$where} |
||
8749 | ORDER BY pid,nspname,tablename"; |
||
8750 | |||
8751 | return $this->selectSet($sql); |
||
8752 | } |
||
8753 | |||
8754 | /** |
||
8755 | * Sends a cancel or kill command to a process. |
||
8756 | * |
||
8757 | * @param $pid The ID of the backend process |
||
8758 | * @param $signal 'CANCEL' |
||
8759 | * |
||
8760 | * @return int 0 success |
||
8761 | */ |
||
8762 | public function sendSignal($pid, $signal) |
||
8763 | { |
||
8764 | // Clean |
||
8765 | $pid = (int) $pid; |
||
8766 | |||
8767 | if ($signal == 'CANCEL') { |
||
8768 | $sql = "SELECT pg_catalog.pg_cancel_backend({$pid}) AS val"; |
||
8769 | } elseif ($signal == 'KILL') { |
||
8770 | $sql = "SELECT pg_catalog.pg_terminate_backend({$pid}) AS val"; |
||
8771 | } else { |
||
8772 | return -1; |
||
8773 | } |
||
8774 | |||
8775 | // Execute the query |
||
8776 | $val = $this->selectField($sql, 'val'); |
||
8777 | |||
8778 | if ($val === 'f') { |
||
8779 | return -1; |
||
8780 | } |
||
8781 | |||
8782 | if ($val === 't') { |
||
8783 | return 0; |
||
8784 | } |
||
8785 | |||
8786 | return -1; |
||
8787 | } |
||
8788 | |||
8789 | /** |
||
8790 | * Executes an SQL script as a series of SQL statements. Returns |
||
8791 | * the result of the final step. This is a very complicated lexer |
||
8792 | * based on the REL7_4_STABLE src/bin/psql/mainloop.c lexer in |
||
8793 | * the PostgreSQL source code. |
||
8794 | * XXX: It does not handle multibyte languages properly. |
||
8795 | * |
||
8796 | * @param string $name Entry in $_FILES to use |
||
8797 | * @param function|null $callback (optional) Callback function to call with each query, its result and line number |
||
8798 | * |
||
8799 | * @return bool true for general success, false on any failure |
||
8800 | */ |
||
8801 | public function executeScript($name, $callback = null) |
||
8802 | { |
||
8803 | // This whole function isn't very encapsulated, but hey... |
||
8804 | $conn = $this->conn->_connectionID; |
||
8805 | if (!is_uploaded_file($_FILES[$name]['tmp_name'])) { |
||
8806 | return false; |
||
8807 | } |
||
8808 | |||
8809 | $fd = fopen($_FILES[$name]['tmp_name'], 'rb'); |
||
8810 | if (!$fd) { |
||
8811 | return false; |
||
8812 | } |
||
8813 | |||
8814 | // Build up each SQL statement, they can be multiline |
||
8815 | $query_buf = null; |
||
8816 | $query_start = 0; |
||
1 ignored issue
–
show
|
|||
8817 | $in_quote = 0; |
||
8818 | $in_xcomment = 0; |
||
8819 | $bslash_count = 0; |
||
8820 | $dol_quote = null; |
||
8821 | $paren_level = 0; |
||
8822 | $len = 0; |
||
1 ignored issue
–
show
|
|||
8823 | $i = 0; |
||
1 ignored issue
–
show
|
|||
8824 | $prevlen = 0; |
||
1 ignored issue
–
show
|
|||
8825 | $thislen = 0; |
||
1 ignored issue
–
show
|
|||
8826 | $lineno = 0; |
||
8827 | |||
8828 | // Loop over each line in the file |
||
8829 | while (!feof($fd)) { |
||
8830 | $line = fgets($fd); |
||
8831 | ++$lineno; |
||
8832 | |||
8833 | // Nothing left on line? Then ignore... |
||
8834 | if (trim($line) == '') { |
||
8835 | continue; |
||
8836 | } |
||
8837 | |||
8838 | $len = strlen($line); |
||
8839 | $query_start = 0; |
||
8840 | |||
8841 | /* |
||
8842 | * Parse line, looking for command separators. |
||
8843 | * |
||
8844 | * The current character is at line[i], the prior character at line[i |
||
8845 | * - prevlen], the next character at line[i + thislen]. |
||
8846 | */ |
||
8847 | $prevlen = 0; |
||
8848 | $thislen = ($len > 0) ? 1 : 0; |
||
8849 | |||
8850 | for ($i = 0; $i < $len; $this->advance_1($i, $prevlen, $thislen)) { |
||
8851 | /* was the previous character a backslash? */ |
||
8852 | if ($i > 0 && substr($line, $i - $prevlen, 1) == '\\') { |
||
8853 | ++$bslash_count; |
||
8854 | } else { |
||
8855 | $bslash_count = 0; |
||
8856 | } |
||
8857 | |||
8858 | /* |
||
8859 | * It is important to place the in_* test routines before the |
||
8860 | * in_* detection routines. i.e. we have to test if we are in |
||
8861 | * a quote before testing for comments. |
||
8862 | */ |
||
8863 | |||
8864 | /* in quote? */ |
||
8865 | if ($in_quote !== 0) { |
||
8866 | /* |
||
8867 | * end of quote if matching non-backslashed character. |
||
8868 | * backslashes don't count for double quotes, though. |
||
8869 | */ |
||
8870 | if (substr($line, $i, 1) == $in_quote && |
||
8871 | ($bslash_count % 2 == 0 || $in_quote == '"')) { |
||
2 ignored issues
–
show
|
|||
8872 | $in_quote = 0; |
||
8873 | } |
||
8874 | } /* in or end of $foo$ type quote? */ |
||
8875 | else { |
||
8876 | if ($dol_quote) { |
||
8877 | if (strncmp(substr($line, $i), $dol_quote, strlen($dol_quote)) == 0) { |
||
8878 | $this->advance_1($i, $prevlen, $thislen); |
||
8879 | while (substr($line, $i, 1) != '$') { |
||
8880 | $this->advance_1($i, $prevlen, $thislen); |
||
8881 | } |
||
8882 | |||
8883 | $dol_quote = null; |
||
8884 | } |
||
8885 | } /* start of extended comment? */ |
||
8886 | else { |
||
8887 | if (substr($line, $i, 2) == '/*') { |
||
8888 | ++$in_xcomment; |
||
8889 | if ($in_xcomment == 1) { |
||
8890 | $this->advance_1($i, $prevlen, $thislen); |
||
8891 | } |
||
8892 | } /* in or end of extended comment? */ |
||
8893 | else { |
||
8894 | if ($in_xcomment) { |
||
8895 | if (substr($line, $i, 2) == '*/' && !--$in_xcomment) { |
||
8896 | $this->advance_1($i, $prevlen, $thislen); |
||
8897 | } |
||
8898 | } /* start of quote? */ |
||
8899 | else { |
||
8900 | if (substr($line, $i, 1) == '\'' || substr($line, $i, 1) == '"') { |
||
8901 | $in_quote = substr($line, $i, 1); |
||
8902 | } /* |
||
8903 | * start of $foo$ type quote? |
||
8904 | */ |
||
8905 | else { |
||
8906 | if (!$dol_quote && $this->valid_dolquote(substr($line, $i))) { |
||
8907 | $dol_end = strpos(substr($line, $i + 1), '$'); |
||
8908 | $dol_quote = substr($line, $i, $dol_end + 1); |
||
8909 | $this->advance_1($i, $prevlen, $thislen); |
||
8910 | while (substr($line, $i, 1) != '$') { |
||
8911 | $this->advance_1($i, $prevlen, $thislen); |
||
8912 | } |
||
8913 | } /* single-line comment? truncate line */ |
||
8914 | else { |
||
8915 | if (substr($line, $i, 2) == '--') { |
||
8916 | $line = substr($line, 0, $i); /* remove comment */ |
||
8917 | break; |
||
8918 | } /* count nested parentheses */ |
||
8919 | |||
8920 | if (substr($line, $i, 1) == '(') { |
||
8921 | ++$paren_level; |
||
8922 | } else { |
||
8923 | if (substr($line, $i, 1) == ')' && $paren_level > 0) { |
||
8924 | --$paren_level; |
||
8925 | } /* semicolon? then send query */ |
||
8926 | else { |
||
8927 | if (substr($line, $i, 1) == ';' && !$bslash_count && !$paren_level) { |
||
8928 | $subline = substr(substr($line, 0, $i), $query_start); |
||
8929 | /* |
||
8930 | * insert a cosmetic newline, if this is not the first |
||
8931 | * line in the buffer |
||
8932 | */ |
||
8933 | if (strlen($query_buf) > 0) { |
||
8934 | $query_buf .= "\n"; |
||
8935 | } |
||
8936 | |||
8937 | /* append the line to the query buffer */ |
||
8938 | $query_buf .= $subline; |
||
8939 | /* is there anything in the query_buf? */ |
||
8940 | if (trim($query_buf)) { |
||
8941 | $query_buf .= ';'; |
||
8942 | |||
8943 | // Execute the query. PHP cannot execute |
||
8944 | // empty queries, unlike libpq |
||
8945 | $res = @pg_query($conn, $query_buf); |
||
8946 | |||
8947 | // Call the callback function for display |
||
8948 | if ($callback !== null) { |
||
8949 | $callback($query_buf, $res, $lineno); |
||
8950 | } |
||
8951 | |||
8952 | // Check for COPY request |
||
8953 | if (pg_result_status($res) == 4) { |
||
8954 | // 4 == PGSQL_COPY_FROM |
||
8955 | while (!feof($fd)) { |
||
8956 | $copy = fgets($fd, 32768); |
||
8957 | ++$lineno; |
||
8958 | pg_put_line($conn, $copy); |
||
8959 | if ($copy == "\\.\n" || $copy == "\\.\r\n") { |
||
8960 | pg_end_copy($conn); |
||
8961 | |||
8962 | break; |
||
8963 | } |
||
8964 | } |
||
8965 | } |
||
8966 | } |
||
8967 | $query_buf = null; |
||
8968 | $query_start = $i + $thislen; |
||
8969 | } |
||
8970 | |||
8971 | /* |
||
8972 | * keyword or identifier? |
||
8973 | * We grab the whole string so that we don't |
||
8974 | * mistakenly see $foo$ inside an identifier as the start |
||
8975 | * of a dollar quote. |
||
8976 | */ |
||
8977 | // XXX: multibyte here |
||
8978 | else { |
||
8979 | if (preg_match('/^[_[:alpha:]]$/', substr($line, $i, 1))) { |
||
8980 | $sub = substr($line, $i, $thislen); |
||
8981 | while (preg_match('/^[\$_A-Za-z0-9]$/', $sub)) { |
||
8982 | /* keep going while we still have identifier chars */ |
||
8983 | $this->advance_1($i, $prevlen, $thislen); |
||
8984 | $sub = substr($line, $i, $thislen); |
||
8985 | } |
||
8986 | // Since we're now over the next character to be examined, it is necessary |
||
8987 | // to move back one space. |
||
8988 | $i -= $prevlen; |
||
8989 | } |
||
8990 | } |
||
8991 | } |
||
8992 | } |
||
8993 | } |
||
8994 | } |
||
8995 | } |
||
8996 | } |
||
8997 | } |
||
8998 | } |
||
8999 | } // end for |
||
9000 | |||
9001 | /* Put the rest of the line in the query buffer. */ |
||
9002 | $subline = substr($line, $query_start); |
||
9003 | if ($in_quote || $dol_quote || strspn($subline, " \t\n\r") != strlen($subline)) { |
||
9004 | if (strlen($query_buf) > 0) { |
||
9005 | $query_buf .= "\n"; |
||
9006 | } |
||
9007 | |||
9008 | $query_buf .= $subline; |
||
9009 | } |
||
9010 | |||
9011 | $line = null; |
||
1 ignored issue
–
show
|
|||
9012 | } // end while |
||
9013 | |||
9014 | /* |
||
9015 | * Process query at the end of file without a semicolon, so long as |
||
9016 | * it's non-empty. |
||
9017 | */ |
||
9018 | if (strlen($query_buf) > 0 && strspn($query_buf, " \t\n\r") != strlen($query_buf)) { |
||
9019 | // Execute the query |
||
9020 | $res = @pg_query($conn, $query_buf); |
||
9021 | |||
9022 | // Call the callback function for display |
||
9023 | if ($callback !== null) { |
||
9024 | $callback($query_buf, $res, $lineno); |
||
9025 | } |
||
9026 | |||
9027 | // Check for COPY request |
||
9028 | if (pg_result_status($res) == 4) { |
||
9029 | // 4 == PGSQL_COPY_FROM |
||
9030 | while (!feof($fd)) { |
||
9031 | $copy = fgets($fd, 32768); |
||
9032 | ++$lineno; |
||
9033 | pg_put_line($conn, $copy); |
||
9034 | if ($copy == "\\.\n" || $copy == "\\.\r\n") { |
||
9035 | pg_end_copy($conn); |
||
9036 | |||
9037 | break; |
||
9038 | } |
||
9039 | } |
||
9040 | } |
||
9041 | } |
||
9042 | |||
9043 | fclose($fd); |
||
9044 | |||
9045 | return true; |
||
9046 | } |
||
9047 | |||
9048 | /** |
||
9049 | * A private helper method for executeScript that advances the |
||
9050 | * character by 1. In psql this is careful to take into account |
||
9051 | * multibyte languages, but we don't at the moment, so this function |
||
9052 | * is someone redundant, since it will always advance by 1. |
||
9053 | * |
||
9054 | * @param int &$i The current character position in the line |
||
9055 | * @param int &$prevlen Length of previous character (ie. 1) |
||
9056 | * @param int &$thislen Length of current character (ie. 1) |
||
9057 | */ |
||
9058 | private function advance_1(&$i, &$prevlen, &$thislen) |
||
1 ignored issue
–
show
|
|||
9059 | { |
||
9060 | $prevlen = $thislen; |
||
9061 | $i += $thislen; |
||
9062 | $thislen = 1; |
||
9063 | } |
||
9064 | |||
9065 | /** |
||
9066 | * Private helper method to detect a valid $foo$ quote delimiter at |
||
9067 | * the start of the parameter dquote. |
||
9068 | * |
||
9069 | * @param string $dquote |
||
9070 | * |
||
9071 | * @return true if valid, false otherwise |
||
9072 | */ |
||
9073 | private function valid_dolquote($dquote) |
||
1 ignored issue
–
show
|
|||
9074 | { |
||
9075 | // XXX: support multibyte |
||
9076 | return preg_match('/^[$][$]/', $dquote) || preg_match('/^[$][_[:alpha:]][_[:alnum:]]*[$]/', $dquote); |
||
9077 | } |
||
9078 | |||
9079 | // Capabilities |
||
9080 | |||
9081 | /** |
||
9082 | * Returns a recordset of all columns in a query. Supports paging. |
||
9083 | * |
||
9084 | * @param string $type Either 'QUERY' if it is an SQL query, or 'TABLE' if it is a table identifier, |
||
9085 | * or 'SELECT" if it's a select query |
||
9086 | * @param string $table The base table of the query. NULL for no table. |
||
9087 | * @param string $query The query that is being executed. NULL for no query. |
||
9088 | * @param string $sortkey The column number to sort by, or '' or null for no sorting |
||
9089 | * @param string $sortdir The direction in which to sort the specified column ('asc' or 'desc') |
||
9090 | * @param int $page The page of the relation to retrieve |
||
9091 | * @param int $page_size The number of rows per page |
||
9092 | * @param int &$max_pages (return-by-ref) The max number of pages in the relation |
||
9093 | * |
||
9094 | * @return A recordset on success |
||
9095 | * @return -1 transaction error |
||
9096 | * @return -2 counting error |
||
9097 | * @return -3 page or page_size invalid |
||
9098 | * @return -4 unknown type |
||
9099 | * @return -5 failed setting transaction read only |
||
9100 | */ |
||
9101 | public function browseQuery($type, $table, $query, $sortkey, $sortdir, $page, $page_size, &$max_pages) |
||
9102 | { |
||
9103 | // Check that we're not going to divide by zero |
||
9104 | if (!is_numeric($page_size) || $page_size != (int) $page_size || $page_size <= 0) { |
||
9105 | return -3; |
||
9106 | } |
||
9107 | |||
9108 | // If $type is TABLE, then generate the query |
||
9109 | switch ($type) { |
||
9110 | case 'TABLE': |
||
9111 | if (preg_match('/^[0-9]+$/', $sortkey) && $sortkey > 0) { |
||
9112 | $orderby = [$sortkey => $sortdir]; |
||
9113 | } else { |
||
9114 | $orderby = []; |
||
9115 | } |
||
9116 | |||
9117 | $query = $this->getSelectSQL($table, [], [], [], $orderby); |
||
9118 | |||
9119 | break; |
||
9120 | case 'QUERY': |
||
9121 | case 'SELECT': |
||
9122 | // Trim query |
||
9123 | $query = trim($query); |
||
9124 | // Trim off trailing semi-colon if there is one |
||
9125 | if (substr($query, strlen($query) - 1, 1) == ';') { |
||
9126 | $query = substr($query, 0, strlen($query) - 1); |
||
9127 | } |
||
9128 | |||
9129 | break; |
||
9130 | default: |
||
9131 | return -4; |
||
9132 | } |
||
9133 | |||
9134 | // Generate count query |
||
9135 | $count = "SELECT COUNT(*) AS total FROM (${query}) AS sub"; |
||
9136 | |||
9137 | // Open a transaction |
||
9138 | $status = $this->beginTransaction(); |
||
9139 | if ($status != 0) { |
||
9140 | return -1; |
||
9141 | } |
||
9142 | |||
9143 | // If backend supports read only queries, then specify read only mode |
||
9144 | // to avoid side effects from repeating queries that do writes. |
||
9145 | if ($this->hasReadOnlyQueries()) { |
||
9146 | $status = $this->execute('SET TRANSACTION READ ONLY'); |
||
9147 | if ($status != 0) { |
||
9148 | $this->rollbackTransaction(); |
||
9149 | |||
9150 | return -5; |
||
9151 | } |
||
9152 | } |
||
9153 | |||
9154 | // Count the number of rows |
||
9155 | $total = $this->browseQueryCount($query, $count); |
||
9156 | if ($total < 0) { |
||
9157 | $this->rollbackTransaction(); |
||
9158 | |||
9159 | return -2; |
||
9160 | } |
||
9161 | |||
9162 | // Calculate max pages |
||
9163 | $max_pages = ceil($total / $page_size); |
||
9164 | |||
9165 | // Check that page is less than or equal to max pages |
||
9166 | if (!is_numeric($page) || $page != (int) $page || $page > $max_pages || $page < 1) { |
||
9167 | $this->rollbackTransaction(); |
||
9168 | |||
9169 | return -3; |
||
9170 | } |
||
9171 | |||
9172 | // Set fetch mode to NUM so that duplicate field names are properly returned |
||
9173 | // for non-table queries. Since the SELECT feature only allows selecting one |
||
9174 | // table, duplicate fields shouldn't appear. |
||
9175 | if ($type == 'QUERY') { |
||
9176 | $this->conn->setFetchMode(ADODB_FETCH_NUM); |
||
9177 | } |
||
9178 | |||
9179 | // Figure out ORDER BY. Sort key is always the column number (based from one) |
||
9180 | // of the column to order by. Only need to do this for non-TABLE queries |
||
9181 | if ($type != 'TABLE' && preg_match('/^[0-9]+$/', $sortkey) && $sortkey > 0) { |
||
9182 | $orderby = " ORDER BY {$sortkey}"; |
||
9183 | // Add sort order |
||
9184 | if ($sortdir == 'desc') { |
||
9185 | $orderby .= ' DESC'; |
||
9186 | } else { |
||
9187 | $orderby .= ' ASC'; |
||
9188 | } |
||
9189 | } else { |
||
9190 | $orderby = ''; |
||
9191 | } |
||
9192 | |||
9193 | // Actually retrieve the rows, with offset and limit |
||
9194 | $rs = $this->selectSet("SELECT * FROM ({$query}) AS sub {$orderby} LIMIT {$page_size} OFFSET " . ($page - 1) * $page_size); |
||
9195 | $status = $this->endTransaction(); |
||
9196 | if ($status != 0) { |
||
9197 | $this->rollbackTransaction(); |
||
9198 | |||
9199 | return -1; |
||
9200 | } |
||
9201 | |||
9202 | return $rs; |
||
9203 | } |
||
9204 | |||
9205 | /** |
||
9206 | * Generates the SQL for the 'select' function. |
||
9207 | * |
||
9208 | * @param $table The table from which to select |
||
9209 | * @param $show An array of columns to show. Empty array means all columns. |
||
9210 | * @param $values An array mapping columns to values |
||
9211 | * @param $ops An array of the operators to use |
||
9212 | * @param $orderby (optional) An array of column numbers or names (one based) |
||
9213 | * mapped to sort direction (asc or desc or '' or null) to order by |
||
9214 | * |
||
9215 | * @return The SQL query |
||
9216 | */ |
||
9217 | public function getSelectSQL($table, $show, $values, $ops, $orderby = []) |
||
9218 | { |
||
9219 | $this->fieldArrayClean($show); |
||
9220 | |||
9221 | // If an empty array is passed in, then show all columns |
||
9222 | if (sizeof($show) == 0) { |
||
9223 | if ($this->hasObjectID($table)) { |
||
9224 | $sql = "SELECT \"{$this->id}\", * FROM "; |
||
9225 | } else { |
||
9226 | $sql = 'SELECT * FROM '; |
||
9227 | } |
||
9228 | } else { |
||
9229 | // Add oid column automatically to results for editing purposes |
||
9230 | if (!in_array($this->id, $show, true) && $this->hasObjectID($table)) { |
||
9231 | $sql = "SELECT \"{$this->id}\", \""; |
||
9232 | } else { |
||
9233 | $sql = 'SELECT "'; |
||
9234 | } |
||
9235 | |||
9236 | $sql .= join('","', $show) . '" FROM '; |
||
9237 | } |
||
9238 | |||
9239 | $this->fieldClean($table); |
||
9240 | |||
9241 | if (isset($_REQUEST['schema'])) { |
||
9242 | $f_schema = $_REQUEST['schema']; |
||
9243 | $this->fieldClean($f_schema); |
||
9244 | $sql .= "\"{$f_schema}\"."; |
||
9245 | } |
||
9246 | $sql .= "\"{$table}\""; |
||
9247 | |||
9248 | // If we have values specified, add them to the WHERE clause |
||
9249 | $first = true; |
||
9250 | if (is_array($values) && sizeof($values) > 0) { |
||
9251 | foreach ($values as $k => $v) { |
||
9252 | if ($v != '' || $this->selectOps[$ops[$k]] == 'p') { |
||
9253 | $this->fieldClean($k); |
||
9254 | if ($first) { |
||
9255 | $sql .= ' WHERE '; |
||
9256 | $first = false; |
||
9257 | } else { |
||
9258 | $sql .= ' AND '; |
||
9259 | } |
||
9260 | // Different query format depending on operator type |
||
9261 | switch ($this->selectOps[$ops[$k]]) { |
||
9262 | case 'i': |
||
9263 | // Only clean the field for the inline case |
||
9264 | // this is because (x), subqueries need to |
||
9265 | // to allow 'a','b' as input. |
||
9266 | $this->clean($v); |
||
9267 | $sql .= "\"{$k}\" {$ops[$k]} '{$v}'"; |
||
9268 | |||
9269 | break; |
||
9270 | case 'p': |
||
9271 | $sql .= "\"{$k}\" {$ops[$k]}"; |
||
9272 | |||
9273 | break; |
||
9274 | case 'x': |
||
9275 | $sql .= "\"{$k}\" {$ops[$k]} ({$v})"; |
||
9276 | |||
9277 | break; |
||
9278 | case 't': |
||
9279 | $sql .= "\"{$k}\" {$ops[$k]}('{$v}')"; |
||
9280 | |||
9281 | break; |
||
9282 | default: |
||
9283 | // Shouldn't happen |
||
9284 | } |
||
9285 | } |
||
9286 | } |
||
9287 | } |
||
9288 | |||
9289 | // ORDER BY |
||
9290 | if (is_array($orderby) && sizeof($orderby) > 0) { |
||
9291 | $sql .= ' ORDER BY '; |
||
9292 | $first = true; |
||
9293 | foreach ($orderby as $k => $v) { |
||
9294 | if ($first) { |
||
9295 | $first = false; |
||
9296 | } else { |
||
9297 | $sql .= ', '; |
||
9298 | } |
||
9299 | |||
9300 | if (preg_match('/^[0-9]+$/', $k)) { |
||
9301 | $sql .= $k; |
||
9302 | } else { |
||
9303 | $this->fieldClean($k); |
||
9304 | $sql .= '"' . $k . '"'; |
||
9305 | } |
||
9306 | if (strtoupper($v) == 'DESC') { |
||
9307 | $sql .= ' DESC'; |
||
9308 | } |
||
9309 | } |
||
9310 | } |
||
9311 | |||
9312 | return $sql; |
||
9313 | } |
||
9314 | |||
9315 | /** |
||
9316 | * Finds the number of rows that would be returned by a |
||
9317 | * query. |
||
9318 | * |
||
9319 | * @param $query The SQL query |
||
9320 | * @param $count The count query |
||
9321 | * |
||
9322 | * @return The count of rows |
||
9323 | * @return -1 error |
||
9324 | */ |
||
9325 | public function browseQueryCount($query, $count) |
||
9326 | { |
||
9327 | return $this->selectField($count, 'total'); |
||
9328 | } |
||
9329 | |||
9330 | /** |
||
9331 | * Returns a recordset of all columns in a table. |
||
9332 | * |
||
9333 | * @param $table The name of a table |
||
9334 | * @param $key The associative array holding the key to retrieve |
||
9335 | * |
||
9336 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
9337 | */ |
||
9338 | public function browseRow($table, $key) |
||
9339 | { |
||
9340 | $f_schema = $this->_schema; |
||
9341 | $this->fieldClean($f_schema); |
||
9342 | $this->fieldClean($table); |
||
9343 | |||
9344 | $sql = "SELECT * FROM \"{$f_schema}\".\"{$table}\""; |
||
9345 | if (is_array($key) && sizeof($key) > 0) { |
||
9346 | $sql .= ' WHERE true'; |
||
9347 | foreach ($key as $k => $v) { |
||
9348 | $this->fieldClean($k); |
||
9349 | $this->clean($v); |
||
9350 | $sql .= " AND \"{$k}\"='{$v}'"; |
||
9351 | } |
||
9352 | } |
||
9353 | |||
9354 | return $this->selectSet($sql); |
||
9355 | } |
||
9356 | |||
9357 | /** |
||
9358 | * Change the value of a parameter to 't' or 'f' depending on whether it evaluates to true or false. |
||
9359 | * |
||
9360 | * @param $parameter the parameter |
||
9361 | * |
||
9362 | * @return string |
||
9363 | */ |
||
9364 | public function dbBool(&$parameter) |
||
9365 | { |
||
9366 | if ($parameter) { |
||
9367 | $parameter = 't'; |
||
9368 | } else { |
||
9369 | $parameter = 'f'; |
||
9370 | } |
||
9371 | |||
9372 | return $parameter; |
||
9373 | } |
||
9374 | |||
9375 | /** |
||
9376 | * Fetches statistics for a database. |
||
9377 | * |
||
9378 | * @param $database The database to fetch stats for |
||
9379 | * |
||
9380 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
9381 | */ |
||
9382 | public function getStatsDatabase($database) |
||
9383 | { |
||
9384 | $this->clean($database); |
||
9385 | |||
9386 | $sql = "SELECT * FROM pg_stat_database WHERE datname='{$database}'"; |
||
9387 | |||
9388 | return $this->selectSet($sql); |
||
9389 | } |
||
9390 | |||
9391 | /** |
||
9392 | * Fetches tuple statistics for a table. |
||
9393 | * |
||
9394 | * @param $table The table to fetch stats for |
||
9395 | * |
||
9396 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
9397 | */ |
||
9398 | public function getStatsTableTuples($table) |
||
9399 | { |
||
9400 | $c_schema = $this->_schema; |
||
9401 | $this->clean($c_schema); |
||
9402 | $this->clean($table); |
||
9403 | |||
9404 | $sql = "SELECT * FROM pg_stat_all_tables |
||
9405 | WHERE schemaname='{$c_schema}' AND relname='{$table}'"; |
||
9406 | |||
9407 | return $this->selectSet($sql); |
||
9408 | } |
||
9409 | |||
9410 | /** |
||
9411 | * Fetches I/0 statistics for a table. |
||
9412 | * |
||
9413 | * @param $table The table to fetch stats for |
||
9414 | * |
||
9415 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
9416 | */ |
||
9417 | public function getStatsTableIO($table) |
||
9427 | } |
||
9428 | |||
9429 | /** |
||
9430 | * Fetches tuple statistics for all indexes on a table. |
||
9431 | * |
||
9432 | * @param $table The table to fetch index stats for |
||
9433 | * |
||
9434 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
9435 | */ |
||
9436 | public function getStatsIndexTuples($table) |
||
9437 | { |
||
9438 | $c_schema = $this->_schema; |
||
9439 | $this->clean($c_schema); |
||
9440 | $this->clean($table); |
||
9441 | |||
9442 | $sql = "SELECT * FROM pg_stat_all_indexes |
||
9443 | WHERE schemaname='{$c_schema}' AND relname='{$table}' ORDER BY indexrelname"; |
||
9444 | |||
9445 | return $this->selectSet($sql); |
||
9446 | } |
||
9447 | |||
9448 | /** |
||
9449 | * Fetches I/0 statistics for all indexes on a table. |
||
9450 | * |
||
9451 | * @param $table The table to fetch index stats for |
||
9452 | * |
||
9453 | * @return \PHPPgAdmin\ADORecordSet A recordset |
||
9454 | */ |
||
9455 | public function getStatsIndexIO($table) |
||
9456 | { |
||
9457 | $c_schema = $this->_schema; |
||
9458 | $this->clean($c_schema); |
||
9459 | $this->clean($table); |
||
9460 | |||
9461 | $sql = "SELECT * FROM pg_statio_all_indexes |
||
9462 | WHERE schemaname='{$c_schema}' AND relname='{$table}' |
||
9463 | ORDER BY indexrelname"; |
||
9464 | |||
9465 | return $this->selectSet($sql); |
||
9466 | } |
||
9467 | |||
9468 | } |
||
9469 |