Issues (217)

src/database/databasetraits/FunctionTrait.php (2 issues)

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
The expression return $this->getFunctions(true, 'trigger') also could return the type integer which is incompatible with the documented return type PHPPgAdmin\ADORecordSet.
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
The condition is_array($definition) is always false.
Loading history...
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