HuasoFoundries /
phpPgAdmin6
| 1 | <?php |
||
| 2 | |||
| 3 | /** |
||
| 4 | * PHPPgAdmin 6.1.3 |
||
| 5 | */ |
||
| 6 | |||
| 7 | namespace PHPPgAdmin\Database\Traits; |
||
| 8 | |||
| 9 | /** |
||
| 10 | * Common trait for full text search manipulation. |
||
| 11 | */ |
||
| 12 | trait FunctionTrait |
||
| 13 | { |
||
| 14 | /** |
||
| 15 | * Returns a list of all functions in the database. |
||
| 16 | * |
||
| 17 | * @param bool $all If true, will find all available functions, if false just those in search path |
||
| 18 | * @param mixed $type If truthy, will return functions of type trigger |
||
| 19 | * |
||
| 20 | * @return int|\PHPPgAdmin\ADORecordSet |
||
| 21 | */ |
||
| 22 | public function getFunctions($all = false, $type = null) |
||
| 23 | { |
||
| 24 | if ($all) { |
||
| 25 | $where = 'pg_catalog.pg_function_is_visible(p.oid)'; |
||
| 26 | $distinct = 'DISTINCT ON (p.proname)'; |
||
| 27 | |||
| 28 | if ($type) { |
||
| 29 | $where .= " AND p.prorettype = (select oid from pg_catalog.pg_type p where p.typname = 'trigger') "; |
||
| 30 | } |
||
| 31 | } else { |
||
| 32 | $c_schema = $this->_schema; |
||
| 33 | $this->clean($c_schema); |
||
| 34 | $where = "n.nspname = '{$c_schema}'"; |
||
| 35 | $distinct = ''; |
||
| 36 | } |
||
| 37 | |||
| 38 | $sql = " |
||
| 39 | SELECT |
||
| 40 | {$distinct} |
||
| 41 | p.oid AS prooid, |
||
| 42 | p.proname, |
||
| 43 | p.proretset, |
||
| 44 | pg_catalog.format_type(p.prorettype, NULL) AS proresult, |
||
| 45 | pg_catalog.oidvectortypes(p.proargtypes) AS proarguments, |
||
| 46 | pl.lanname AS prolanguage, |
||
| 47 | pg_catalog.obj_description(p.oid, 'pg_proc') AS procomment, |
||
| 48 | p.proname || ' (' || pg_catalog.oidvectortypes(p.proargtypes) || ')' AS proproto, |
||
| 49 | CASE WHEN p.proretset THEN 'setof ' ELSE '' END || pg_catalog.format_type(p.prorettype, NULL) AS proreturns, |
||
| 50 | coalesce(u.usename::text,p.proowner::text) AS proowner |
||
| 51 | |||
| 52 | FROM pg_catalog.pg_proc p |
||
| 53 | INNER JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace |
||
| 54 | INNER JOIN pg_catalog.pg_language pl ON pl.oid = p.prolang |
||
| 55 | LEFT JOIN pg_catalog.pg_user u ON u.usesysid = p.proowner |
||
| 56 | WHERE NOT p.proisagg |
||
| 57 | AND {$where} |
||
| 58 | ORDER BY p.proname, proresult |
||
| 59 | "; |
||
| 60 | |||
| 61 | return $this->selectSet($sql); |
||
| 62 | } |
||
| 63 | |||
| 64 | /** |
||
| 65 | * Returns a list of all functions that can be used in triggers. |
||
| 66 | * |
||
| 67 | * @return \PHPPgAdmin\ADORecordSet Functions that can be used in a trigger |
||
| 68 | */ |
||
| 69 | public function getTriggerFunctions() |
||
| 70 | { |
||
| 71 | return $this->getFunctions(true, 'trigger'); |
||
|
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||
| 72 | } |
||
| 73 | |||
| 74 | /** |
||
| 75 | * Returns an array containing a function's properties. |
||
| 76 | * |
||
| 77 | * @param array $f The array of data for the function |
||
| 78 | * |
||
| 79 | * @return array|int An array containing the properties, or -1 in case of error |
||
| 80 | */ |
||
| 81 | public function getFunctionProperties($f) |
||
| 82 | { |
||
| 83 | $temp = []; |
||
| 84 | |||
| 85 | // Volatility |
||
| 86 | if ('v' === $f['provolatile']) { |
||
| 87 | $temp[] = 'VOLATILE'; |
||
| 88 | } elseif ('i' === $f['provolatile']) { |
||
| 89 | $temp[] = 'IMMUTABLE'; |
||
| 90 | } elseif ('s' === $f['provolatile']) { |
||
| 91 | $temp[] = 'STABLE'; |
||
| 92 | } else { |
||
| 93 | return -1; |
||
| 94 | } |
||
| 95 | |||
| 96 | // Null handling |
||
| 97 | $f['proisstrict'] = $this->phpBool($f['proisstrict']); |
||
| 98 | |||
| 99 | if ($f['proisstrict']) { |
||
| 100 | $temp[] = 'RETURNS NULL ON NULL INPUT'; |
||
| 101 | } else { |
||
| 102 | $temp[] = 'CALLED ON NULL INPUT'; |
||
| 103 | } |
||
| 104 | |||
| 105 | // Security |
||
| 106 | $f['prosecdef'] = $this->phpBool($f['prosecdef']); |
||
| 107 | |||
| 108 | if ($f['prosecdef']) { |
||
| 109 | $temp[] = 'SECURITY DEFINER'; |
||
| 110 | } else { |
||
| 111 | $temp[] = 'SECURITY INVOKER'; |
||
| 112 | } |
||
| 113 | |||
| 114 | return $temp; |
||
| 115 | } |
||
| 116 | |||
| 117 | /** |
||
| 118 | * Updates (replaces) a function. |
||
| 119 | * |
||
| 120 | * @param string $funcname The name of the function to create |
||
| 121 | * @param string $newname The new name for the function |
||
| 122 | * @param string $args imploded array of argument types |
||
| 123 | * @param string $returns The return type |
||
| 124 | * @param string $definition The definition for the new function |
||
| 125 | * @param string $language The language the function is written for |
||
| 126 | * @param array $flags An array of optional flags |
||
| 127 | * @param bool $setof True if returns a set, false otherwise |
||
| 128 | * @param string $funcown |
||
| 129 | * @param string $newown |
||
| 130 | * @param string $funcschema |
||
| 131 | * @param string $newschema |
||
| 132 | * @param float $cost |
||
| 133 | * @param int $rows |
||
| 134 | * @param string $comment The comment on the function |
||
| 135 | * |
||
| 136 | * @return bool|int 0 success |
||
| 137 | */ |
||
| 138 | public function setFunction( |
||
| 139 | $funcname, |
||
| 140 | $newname, |
||
| 141 | $args, |
||
| 142 | $returns, |
||
| 143 | $definition, |
||
| 144 | $language, |
||
| 145 | $flags, |
||
| 146 | $setof, |
||
| 147 | $funcown, |
||
| 148 | $newown, |
||
| 149 | $funcschema, |
||
| 150 | $newschema, |
||
| 151 | $cost, |
||
| 152 | $rows, |
||
| 153 | $comment |
||
| 154 | ) { |
||
| 155 | // Begin a transaction |
||
| 156 | $status = $this->beginTransaction(); |
||
| 157 | |||
| 158 | if (0 !== $status) { |
||
| 159 | $this->rollbackTransaction(); |
||
| 160 | |||
| 161 | return -1; |
||
| 162 | } |
||
| 163 | |||
| 164 | // Replace the existing function |
||
| 165 | $status = $this->createFunction($funcname, $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, $comment, true); |
||
| 166 | |||
| 167 | if (0 !== $status) { |
||
| 168 | $this->rollbackTransaction(); |
||
| 169 | |||
| 170 | return $status; |
||
| 171 | } |
||
| 172 | |||
| 173 | $f_schema = $this->_schema; |
||
| 174 | $this->fieldClean($f_schema); |
||
| 175 | |||
| 176 | // Rename the function, if necessary |
||
| 177 | $this->fieldClean($newname); |
||
| 178 | /* $funcname is escaped in createFunction */ |
||
| 179 | if ($funcname !== $newname) { |
||
| 180 | $sql = "ALTER FUNCTION \"{$f_schema}\".\"{$funcname}\"({$args}) RENAME TO \"{$newname}\""; |
||
| 181 | $status = $this->execute($sql); |
||
| 182 | |||
| 183 | if (0 !== $status) { |
||
| 184 | $this->rollbackTransaction(); |
||
| 185 | |||
| 186 | return -5; |
||
| 187 | } |
||
| 188 | |||
| 189 | $funcname = $newname; |
||
| 190 | } |
||
| 191 | |||
| 192 | // Alter the owner, if necessary |
||
| 193 | if ($this->hasFunctionAlterOwner()) { |
||
| 194 | $this->fieldClean($newown); |
||
| 195 | |||
| 196 | if ($funcown !== $newown) { |
||
| 197 | $sql = "ALTER FUNCTION \"{$f_schema}\".\"{$funcname}\"({$args}) OWNER TO \"{$newown}\""; |
||
| 198 | $status = $this->execute($sql); |
||
| 199 | |||
| 200 | if (0 !== $status) { |
||
| 201 | $this->rollbackTransaction(); |
||
| 202 | |||
| 203 | return -6; |
||
| 204 | } |
||
| 205 | } |
||
| 206 | } |
||
| 207 | |||
| 208 | // Alter the schema, if necessary |
||
| 209 | if ($this->hasFunctionAlterSchema()) { |
||
| 210 | $this->fieldClean($newschema); |
||
| 211 | /* $funcschema is escaped in createFunction */ |
||
| 212 | if ($funcschema !== $newschema) { |
||
| 213 | $sql = "ALTER FUNCTION \"{$f_schema}\".\"{$funcname}\"({$args}) SET SCHEMA \"{$newschema}\""; |
||
| 214 | $status = $this->execute($sql); |
||
| 215 | |||
| 216 | if (0 !== $status) { |
||
| 217 | $this->rollbackTransaction(); |
||
| 218 | |||
| 219 | return -7; |
||
| 220 | } |
||
| 221 | } |
||
| 222 | } |
||
| 223 | |||
| 224 | return $this->endTransaction(); |
||
| 225 | } |
||
| 226 | |||
| 227 | /** |
||
| 228 | * Creates a new function. |
||
| 229 | * |
||
| 230 | * @param string $funcname The name of the function to create |
||
| 231 | * @param string $args A comma separated string of types |
||
| 232 | * @param string $returns The return type |
||
| 233 | * @param string $definition The definition for the new function |
||
| 234 | * @param string $language The language the function is written for |
||
| 235 | * @param array $flags An array of optional flags |
||
| 236 | * @param bool $setof True if it returns a set, false otherwise |
||
| 237 | * @param string $cost cost the planner should use in the function execution step |
||
| 238 | * @param int $rows number of rows planner should estimate will be returned |
||
| 239 | * @param string $comment Comment for the function |
||
| 240 | * @param bool $replace (optional) True if OR REPLACE, false for |
||
| 241 | * normal |
||
| 242 | * |
||
| 243 | * @return bool|int 0 success |
||
| 244 | */ |
||
| 245 | public function createFunction($funcname, $args, $returns, $definition, $language, $flags, $setof, $cost, $rows, $comment, $replace = false) |
||
| 246 | { |
||
| 247 | // Begin a transaction |
||
| 248 | $status = $this->beginTransaction(); |
||
| 249 | |||
| 250 | if (0 !== $status) { |
||
| 251 | $this->rollbackTransaction(); |
||
| 252 | |||
| 253 | return -1; |
||
| 254 | } |
||
| 255 | |||
| 256 | $this->fieldClean($funcname); |
||
| 257 | $this->clean($args); |
||
| 258 | $this->fieldClean($language); |
||
| 259 | $this->arrayClean($flags); |
||
| 260 | $this->clean($cost); |
||
| 261 | $this->clean($rows); |
||
| 262 | $f_schema = $this->_schema; |
||
| 263 | $this->fieldClean($f_schema); |
||
| 264 | |||
| 265 | $sql = 'CREATE'; |
||
| 266 | |||
| 267 | if ($replace) { |
||
| 268 | $sql .= ' OR REPLACE'; |
||
| 269 | } |
||
| 270 | |||
| 271 | $sql .= " FUNCTION \"{$f_schema}\".\"{$funcname}\" ("; |
||
| 272 | |||
| 273 | if ('' !== $args) { |
||
| 274 | $sql .= $args; |
||
| 275 | } |
||
| 276 | |||
| 277 | // For some reason, the returns field cannot have quotes... |
||
| 278 | $sql .= ') RETURNS '; |
||
| 279 | |||
| 280 | if ($setof) { |
||
| 281 | $sql .= 'SETOF '; |
||
| 282 | } |
||
| 283 | |||
| 284 | $sql .= "{$returns} AS "; |
||
| 285 | |||
| 286 | if (\is_array($definition)) { |
||
|
0 ignored issues
–
show
|
|||
| 287 | $this->arrayClean($definition); |
||
| 288 | $sql .= "'" . $definition[0] . "'"; |
||
| 289 | |||
| 290 | if ($definition[1]) { |
||
| 291 | $sql .= ",'" . $definition[1] . "'"; |
||
| 292 | } |
||
| 293 | } else { |
||
| 294 | $this->clean($definition); |
||
| 295 | $sql .= "'" . $definition . "'"; |
||
| 296 | } |
||
| 297 | |||
| 298 | $sql .= " LANGUAGE \"{$language}\""; |
||
| 299 | |||
| 300 | // Add costs |
||
| 301 | if (!empty($cost)) { |
||
| 302 | $sql .= " COST {$cost}"; |
||
| 303 | } |
||
| 304 | |||
| 305 | if (0 !== $rows) { |
||
| 306 | $sql .= " ROWS {$rows}"; |
||
| 307 | } |
||
| 308 | |||
| 309 | // Add flags |
||
| 310 | foreach ($flags as $v) { |
||
| 311 | // Skip default flags |
||
| 312 | if ('' === $v) { |
||
| 313 | continue; |
||
| 314 | } |
||
| 315 | |||
| 316 | $sql .= "\n{$v}"; |
||
| 317 | } |
||
| 318 | |||
| 319 | $status = $this->execute($sql); |
||
| 320 | |||
| 321 | if (0 !== $status) { |
||
| 322 | $this->rollbackTransaction(); |
||
| 323 | |||
| 324 | return -3; |
||
| 325 | } |
||
| 326 | |||
| 327 | /* set the comment */ |
||
| 328 | $status = $this->setComment('FUNCTION', "\"{$funcname}\"({$args})", null, $comment); |
||
| 329 | |||
| 330 | if (0 !== $status) { |
||
| 331 | $this->rollbackTransaction(); |
||
| 332 | |||
| 333 | return -4; |
||
| 334 | } |
||
| 335 | |||
| 336 | return $this->endTransaction(); |
||
| 337 | } |
||
| 338 | |||
| 339 | /** |
||
| 340 | * Drops a function. |
||
| 341 | * |
||
| 342 | * @param int $function_oid The OID of the function to drop |
||
| 343 | * @param bool $cascade True to cascade drop, false to restrict |
||
| 344 | * |
||
| 345 | * @return int|\PHPPgAdmin\ADORecordSet |
||
| 346 | */ |
||
| 347 | public function dropFunction($function_oid, $cascade) |
||
| 348 | { |
||
| 349 | // Function comes in with $object as function OID |
||
| 350 | $fn = $this->getFunction($function_oid); |
||
| 351 | $f_schema = $this->_schema; |
||
| 352 | $this->fieldClean($f_schema); |
||
| 353 | $this->fieldClean($fn->fields['proname']); |
||
| 354 | |||
| 355 | $sql = "DROP FUNCTION \"{$f_schema}\".\"{$fn->fields['proname']}\"({$fn->fields['proarguments']})"; |
||
| 356 | |||
| 357 | if ($cascade) { |
||
| 358 | $sql .= ' CASCADE'; |
||
| 359 | } |
||
| 360 | |||
| 361 | return $this->execute($sql); |
||
| 362 | } |
||
| 363 | |||
| 364 | /** |
||
| 365 | * Returns all details for a particular function. |
||
| 366 | * |
||
| 367 | * @param int $function_oid |
||
| 368 | * |
||
| 369 | * @return int|\PHPPgAdmin\ADORecordSet |
||
| 370 | * |
||
| 371 | * @internal param string The $func name of the function to retrieve |
||
| 372 | */ |
||
| 373 | public function getFunction($function_oid) |
||
| 374 | { |
||
| 375 | $this->clean($function_oid); |
||
| 376 | |||
| 377 | $sql = " |
||
| 378 | SELECT |
||
| 379 | pc.oid AS prooid, proname, |
||
| 380 | pg_catalog.pg_get_userbyid(proowner) AS proowner, |
||
| 381 | nspname as proschema, lanname as prolanguage, procost, prorows, |
||
| 382 | pg_catalog.format_type(prorettype, NULL) as proresult, prosrc, |
||
| 383 | probin, proretset, proisstrict, provolatile, prosecdef, |
||
| 384 | pg_catalog.oidvectortypes(pc.proargtypes) AS proarguments, |
||
| 385 | proargnames AS proargnames, |
||
| 386 | pg_catalog.obj_description(pc.oid, 'pg_proc') AS procomment, |
||
| 387 | proconfig, |
||
| 388 | (select array_agg( (select typname from pg_type pt |
||
| 389 | where pt.oid = p.oid) ) from unnest(proallargtypes) p) |
||
| 390 | AS proallarguments, |
||
| 391 | proargmodes |
||
| 392 | FROM |
||
| 393 | pg_catalog.pg_proc pc, pg_catalog.pg_language pl, |
||
| 394 | pg_catalog.pg_namespace pn |
||
| 395 | WHERE |
||
| 396 | pc.oid = '{$function_oid}'::oid AND pc.prolang = pl.oid |
||
| 397 | AND pc.pronamespace = pn.oid |
||
| 398 | "; |
||
| 399 | |||
| 400 | return $this->selectSet($sql); |
||
| 401 | } |
||
| 402 | |||
| 403 | /** |
||
| 404 | * Returns plain definition for a particular function. |
||
| 405 | * |
||
| 406 | * @param int $function_oid |
||
| 407 | * |
||
| 408 | * @return int|\PHPPgAdmin\ADORecordSet |
||
| 409 | */ |
||
| 410 | public function getFunctionDef($function_oid) |
||
| 411 | { |
||
| 412 | $this->clean($function_oid); |
||
| 413 | $sql = " |
||
| 414 | SELECT |
||
| 415 | f.proname as relname, |
||
| 416 | n.nspname, |
||
| 417 | u.usename AS relowner, |
||
| 418 | pg_catalog.obj_description(f.oid, 'pg_proc') as relcomment, |
||
| 419 | (SELECT spcname FROM pg_catalog.pg_tablespace pt WHERE pt.oid=f.pronamespace) AS tablespace, |
||
| 420 | pg_get_functiondef(f.oid), |
||
| 421 | pl.lanname AS prolanguage |
||
| 422 | FROM pg_catalog.pg_proc f |
||
| 423 | JOIN pg_catalog.pg_namespace n ON (f.pronamespace = n.oid) |
||
| 424 | JOIN pg_catalog.pg_language pl ON pl.oid = f.prolang |
||
| 425 | LEFT JOIN pg_catalog.pg_user u ON u.usesysid=f.proowner |
||
| 426 | WHERE f.oid='{$function_oid}' |
||
| 427 | "; |
||
| 428 | |||
| 429 | return $this->selectSet($sql); |
||
| 430 | } |
||
| 431 | |||
| 432 | abstract public function fieldClean(&$str); |
||
| 433 | |||
| 434 | abstract public function beginTransaction(); |
||
| 435 | |||
| 436 | abstract public function rollbackTransaction(); |
||
| 437 | |||
| 438 | abstract public function endTransaction(); |
||
| 439 | |||
| 440 | abstract public function execute($sql); |
||
| 441 | |||
| 442 | abstract public function setComment($obj_type, $obj_name, $table, $comment, $basetype = null); |
||
| 443 | |||
| 444 | abstract public function selectSet($sql); |
||
| 445 | |||
| 446 | abstract public function clean(&$str); |
||
| 447 | |||
| 448 | abstract public function phpBool($parameter); |
||
| 449 | |||
| 450 | abstract public function hasFunctionAlterOwner(); |
||
| 451 | |||
| 452 | abstract public function hasFunctionAlterSchema(); |
||
| 453 | |||
| 454 | abstract public function arrayClean(&$arr); |
||
| 455 | } |
||
| 456 |