Project

General

Profile

Documentation » History » Version 12

Álvaro Herrera, 12/02/2005 07:32 AM
Update a bit. Include the new SPI functions.

1 6 Álvaro Herrera
= PL/php - PHP Procedural Language for PostgreSQL =
2 1 bford -
3 4 Álvaro Herrera
[[PageOutline(2-3,Contents,inline)]]
4 1 bford -
5 6 Álvaro Herrera
== What is PL/php? ==
6 1 bford -
7 6 Álvaro Herrera
PL/php is a procedural language with hooks into the PostgreSQL
8 8 Álvaro Herrera
database sytem, intended to allow writing of PHP functions for
9
use as functions inside the PostgreSQL database.
10
It was written by Command Prompt, Inc. and has since
11 1 bford -
been open sourced and licensed under the PHP and PostgreSQL (BSD)
12
licenses.
13
14
== Download and Installation ==
15 3 Álvaro Herrera
16 8 Álvaro Herrera
Please see [wiki:InstallDocs10 the installation documentation] for instructions on
17
how to install PL/php 1.0.  To install the new code, which only works with PostgreSQL
18
8.0 and 8.1 and is currently in development, see [wiki:InstallDocs this page instead].
19 7 Álvaro Herrera
20 1 bford -
== Creating the PL/php language ==
21 7 Álvaro Herrera
22 8 Álvaro Herrera
Please see the documentation on [wiki:CreateLang80 how to create the language in a database]
23
once the library is installed.  If you are using PostgreSQL 8.1 you must follow
24
[wiki:CreateLang these other instructions] instead.
25 7 Álvaro Herrera
26 2 Álvaro Herrera
== Functions and Arguments ==
27 3 Álvaro Herrera
28 1 bford -
To create a function, use the standard syntax:
29
30
31
{{{
32 6 Álvaro Herrera
CREATE FUNCTION funcname (argument-types) RETURNS return-type AS $$
33 1 bford -
   # plphp function body here
34 6 Álvaro Herrera
$$ LANGUAGE 'plphp';
35 1 bford -
}}}
36
37
38
Arguments are passed in the $args array and the result value is returned
39
with the 'return' statement.
40
41 2 Álvaro Herrera
42 1 bford -
{{{
43 6 Álvaro Herrera
CREATE FUNCTION plphp_max(integer, integer) RETURNS integer AS $$
44 1 bford -
   if ($args[0] > $args[1]) {
45
      return $args[0];
46
   } else {
47
      return $args[1];
48
   }     
49 6 Álvaro Herrera
$$ STRICT LANGUAGE 'plphp'
50 1 bford -
}}}
51
52 2 Álvaro Herrera
53 1 bford -
NOTE: The use of the STRICT clause saves us from having to
54
think about NULL input values to our function.  If a NULL value is
55 2 Álvaro Herrera
passed, the function will not be run at all, but will rather just
56 1 bford -
return a NULL result automatically.
57
58
In a non-strict function, if the actual value of an argument is NULL,
59
the corresponding $args[n-1] variable will be set to an empty string
60
(unset).
61
62
63
== Data Types and Returns ==
64
65 9 Álvaro Herrera
The arguments passed into your PL/php function are converted to text
66 3 Álvaro Herrera
so you can manipulate them with the loose typing enjoyed in typical
67 1 bford -
PHP scripts.  Conversely, the return command will accept any string
68
that is acceptable input format for the function's declared return
69
type.
70
71
72 3 Álvaro Herrera
== Global Shared Variable ==
73 1 bford -
74
There is a global variable that can be used to store data between
75
function calls, called $_SHARED.
76
77
{{{
78 6 Álvaro Herrera
CREATE FUNCTION set_var(text) RETURNS text AS $$
79 1 bford -
   global $_SHARED;
80 6 Álvaro Herrera
   $_SHARED['first']=$args[0];
81
   return 'ok';
82
$$ LANGUAGE 'plphp';
83 1 bford -
84 6 Álvaro Herrera
CREATE FUNCTION get_var() RETURNS text AS $$
85 1 bford -
   global $_SHARED;
86 6 Álvaro Herrera
   return $_SHARED['first'];
87
$$ LANGUAGE 'plphp';
88 1 bford -
89
SELECT set_var('hello plphp');
90
SELECT get_var(); -- will return 'hello plphp'
91
}}}
92
93
NOTE: The shared global variable is connection-specific.  This is
94
useful for passing information around a single script execution,
95 2 Álvaro Herrera
but it is wiped when the connection is closed.
96
97
98 3 Álvaro Herrera
== PostgreSQL Array Support ==
99 1 bford -
100
There is support for multi-dimensional arrays.
101
102
For example:
103
104
{{{
105 6 Álvaro Herrera
CREATE FUNCTION php_array() RETURNS text[][] AS $$
106 1 bford -
   $return = array(array("Steven", "Klassen"),
107
                   array("Jonathan", "Daugherty"));
108
109
   return $return;
110 6 Álvaro Herrera
$$ LANGUAGE 'plphp';
111 1 bford -
}}}
112
113
114
{{{
115
sklassen=# select php_array();
116
                php_array                
117
-----------------------------------------
118
 {{Steven,Klassen},{Jonathan,Daugherty}}
119
(1 row)
120
}}}
121
122
123 3 Álvaro Herrera
== Polymorphic Arguments and Return Types ==
124 1 bford -
125
Functions may be declared to accept and return the polymorphic types
126
'anyelement', 'anyarray', and 'anyrecord'.  See the PostgreSQL
127
documentation section 33.2.5 for a more detailed explanation of
128
polymorphic functions.
129
130
For example,
131
132
{{{
133
CREATE FUNCTION array_three_values(anyelement, anyelement, anyelement) RETURNS anyarray AS '
134
   $ret[0] = $args[0];
135
   $ret[1] = $args[1];
136
   $ret[2] = $args[2];
137
   return $ret;
138
' LANGUAGE 'plphp';
139
140
SELECT array_three_values(3,2,1);
141
 array_three_values
142
--------------------
143
 {3,2,1}
144
(1 row)
145
146
CREATE OR REPLACE FUNCTION php_row(integer) RETURNS record AS '
147
   $ret[f1]=$args[0];
148
   $ret[f2]="hello";
149
   $ret[f3]="world";
150
   return $ret;
151
' LANGUAGE 'plphp';
152
153
select * FROM php_row(1) AS (f1 integer, f2 text, f3 text);
154
}}}
155
156
157
== Database Access (SPI) ==
158
159 12 Álvaro Herrera
Some functions are provided for database access.
160 1 bford -
161 12 Álvaro Herrera
 1. spi_exec - Execute a query with optional limit.
162 1 bford -
{{{
163 12 Álvaro Herrera
resource spi_exec(string query[, int limit])
164 1 bford -
}}}
165
 1. spi_fetch_row - Return an associative array of the row's results.
166
{{{
167
array spi_fetch_row(resource result)
168
}}}
169 12 Álvaro Herrera
 1. spi_status - Return the status of a previous query.  If this is SPI_OK_SELECT you can obtain tuples from the result using spi_fetch_row.
170
{{{
171
string spi_status(resource result)
172
}}}
173
 1. spi_processed - Return the number of tuples in a result.
174
{{{
175
int spi_processed(resource result)
176
}}}
177
 1. spi_rewind - Put the row cursor at the beginning of the result, so spi_fetch_row will continue fetching tuples from the beginning of the result.
178
{{{
179
void spi_rewind(resource result)
180
}}}
181 1 bford -
182
For example:
183
184
This isn't a particularly useful function, but it will illustrate the
185
above-described access functions.  You provide an integer id and it
186
returns the username text field.
187
188
189
{{{
190 12 Álvaro Herrera
CREATE FUNCTION get_username(integer) RETURNS text AS $$
191 1 bford -
   # Assign the query to a variable.
192
   $query = "SELECT username FROM users WHERE id = " .$args[0];
193
194
   # Run the query and get the $result object.
195
   $result = spi_exec_query($query);
196
197
   # Fetch the row from the $result.
198
   $row = spi_fetch_row($result);
199
200 12 Álvaro Herrera
   return $row['username'];
201
$$ LANGUAGE 'plphp';
202 1 bford -
203
sklassen=# select get_username(1);
204
 get_username 
205
--------------
206
 sklassen
207
(1 row)
208
}}}
209
210
== Triggers ==
211 4 Álvaro Herrera
212 1 bford -
When a function is being used to return a trigger, the associative
213
array $_TD contains trigger-related values.
214
215 4 Álvaro Herrera
 $_TD["new"]::
216 1 bford -
   An associative array containing the values of the new table row for
217
   INSERT/UPDATE actions, or empty for DELETE.  The array is indexed
218
   by field name.  Important note: Fields that are NULL will not
219
   appear in the array!
220 4 Álvaro Herrera
 $_TD["old"]::
221 1 bford -
   An associative array containing the values of the old table row for
222 9 Álvaro Herrera
   UPDATE/DELETE actions, or empty for INSERT.  The array is indexed
223 1 bford -
   by field name.  Important note: Fields that are NULL will not
224
   appear in the array!
225 9 Álvaro Herrera
 $_TD["name"]::
226 1 bford -
   Contains the trigger name itself.
227 4 Álvaro Herrera
 $_TD["event"]::
228 9 Álvaro Herrera
   Contains one of the values: "INSERT", "UPDATE", "DELETE".
229 1 bford -
 $_TD["when"]::
230 4 Álvaro Herrera
   Contains one of the values: "BEFORE", "AFTER".
231 1 bford -
 $_TD["level"]::
232
   Contains one of the values: "ROW", "STATEMENT".
233
 $_TD["relid"]::
234
   Contains the relation ID of the table on which the trigger occured.
235 4 Álvaro Herrera
 $_TD["relname"]::
236 1 bford -
   Contains the relation name.
237 10 Álvaro Herrera
 $_TD["args"]::
238 1 bford -
   An array of arguments passed to the trigger, if any.  They can be
239 12 Álvaro Herrera
   accessed as $_TD["args"][idx].  For example, $_TD["args"]![0].
240 1 bford -
 $_TD["argc"]::
241 12 Álvaro Herrera
  The number of arguments passed to the trigger, 0 if none.
242 4 Álvaro Herrera
243 1 bford -
=== Example of an AFTER INSERT trigger ===
244 2 Álvaro Herrera
245 1 bford -
Suppose you have a users table with the typical columns and an
246 3 Álvaro Herrera
activity table that you're using to track page accesses.  On row
247 1 bford -
248
INSERT to the activity table, you want to update the last_seen field
249
of the appropriate user's record.
250
251
Consider the following table definitions:
252
253
{{{
254
CREATE TABLE users (
255
    id serial PRIMARY KEY NOT NULL,
256
    username text NOT NULL,
257
    email text,
258
    last_seen timestamp without time zone,
259
    active boolean DEFAULT true NOT NULL
260
);
261
262
CREATE TABLE activity (
263
    id serial PRIMARY KEY NOT NULL,
264
    users_id integer NOT NULL,
265
    file_accessed text NOT NULL,
266
    stamp timestamp without time zone DEFAULT now() NOT NULL,
267
    CONSTRAINT users_id_exists FOREIGN KEY (users_id) REFERENCES users(id)
268
);
269
270
CREATE FUNCTION update_lastseen() RETURNS trigger AS '
271
    $new =& $_TD[''new''];
272
273
    if (isset($new[''users_id'']) && isset($new[''stamp''])) {
274
        $query = "UPDATE users SET last_seen = ''" .$new[''stamp''].
275
                 "'' WHERE id = " .$new[''users_id''];
276
277
        $rv = spi_exec_query($query);
278
    }
279
280
    return;
281
' LANGUAGE 'plphp';
282
283
CREATE TRIGGER after_update_lastseen_trigger
284
    AFTER INSERT ON activity FOR EACH ROW EXECUTE PROCEDURE update_lastseen();
285
286
}}}
287
288
'''1. We'll insert a new user row.'''
289
290
291
{{{
292
sklassen=# insert into users (username, email) values ('sklassen','sklassen@commandprompt.com');
293
INSERT 1
294
295
sklassen=# select * from users where username = 'sklassen';
296
297
 id | username |           email            | last_seen | active 
298
----+----------+----------------------------+-----------+--------
299
  1 | sklassen | sklassen@commandprompt.com |           | t
300
(1 row)
301
}}}
302
303
304
'''2. Insert a new row into the activity table.'''
305
306
307
{{{
308
sklassen=# insert into activity (users_id, file_accessed) values (1,'index.html');
309
INSERT 1
310
}}}
311
312
'''3. Check and make sure our trigger fired as expected.'''
313
314
{{{
315
sklassen=# select * from users where username = 'sklassen';
316
 id | username |           email            |         last_seen          | active 
317
----+----------+----------------------------+----------------------------+--------
318
  1 | sklassen | sklassen@commandprompt.com | 2005-01-10 09:48:57.191595 | t
319
(1 row)
320
}}}
321
322
=== Example of a BEFORE INSERT OR UPDATE trigger ===
323
324
Let's say we have a user named admin that we want to prevent the
325 12 Álvaro Herrera
application from modifying.  We'll create a BEFORE DELETE trigger that
326 1 bford -
prevents them from deleting the row and a BEFORE UPDATE trigger that
327
prevents them modifying the username on which the previous trigger
328
depends.
329
330
{{{
331 12 Álvaro Herrera
CREATE TABLE users (username text, email text);
332
INSERT INTO users VALUES ('admin', 'admin@commandprompt.com');
333
INSERT INTO users VALUES ('darcy', 'darcy@example.com');
334
335
CREATE OR REPLACE FUNCTION immortal() RETURNS trigger AS $$
336 1 bford -
   # The record may not be deleted if the username is "admin".
337 12 Álvaro Herrera
   if ($_TD['old']['username'] == 'admin') {
338
      pg_raise('notice', "You cannot delete the admin user");
339
      return 'SKIP';
340
   }
341
   return;
342
$$ LANGUAGE 'plphp';
343 1 bford -
344
CREATE TRIGGER before_delete_immortal_trigger BEFORE DELETE ON users
345
FOR EACH ROW EXECUTE PROCEDURE immortal();
346
347 12 Álvaro Herrera
CREATE OR REPLACE FUNCTION protect_admin() RETURNS trigger AS $$
348 1 bford -
   # Do not let them modify the username of the admin account.
349 12 Álvaro Herrera
   $oldUsername = $_TD['old']['username'];
350
   $newUsername = $_TD['new']['username'];
351 1 bford -
352 12 Álvaro Herrera
   if ($oldUsername == 'admin' && ($oldUsername != $newUsername)) {
353
      pg_raise('notice', "You cannot change the admin username.");
354
      return 'SKIP';
355 1 bford -
   } else {
356 12 Álvaro Herrera
      return;
357 1 bford -
   }
358 12 Álvaro Herrera
$$ LANGUAGE 'plphp';
359 1 bford -
360
CREATE TRIGGER before_update_protect_admin_trigger BEFORE UPDATE ON
361
users FOR EACH ROW EXECUTE PROCEDURE protect_admin();
362 12 Álvaro Herrera
}}}
363 1 bford -
364 12 Álvaro Herrera
Now the user admin cannot be deleted, nor its username can be changed:
365
366
{{{
367
pl_regression=# select * from users;
368
 username |          email          
369
----------+-------------------------
370
 admin    | admin@commandprompt.com
371
 darcy    | darcy@example.com
372 1 bford -
(2 rows)
373
374 12 Álvaro Herrera
pl_regression=# update users set username = 'foobar';
375
NOTICE:  plphp: You cannot change the admin username.
376
UPDATE 1
377 1 bford -
378 12 Álvaro Herrera
pl_regression=# select * from users;
379
 username |          email          
380
----------+-------------------------
381
 admin    | admin@commandprompt.com
382
 foobar   | darcy@example.com
383
(2 rows)
384 1 bford -
385 12 Álvaro Herrera
pl_regression=# delete from users;
386
NOTICE:  plphp: You cannot delete the admin user
387
DELETE 1
388
389
pl_regression=# select * from users;
390
 username |          email          
391
----------+-------------------------
392
 admin    | admin@commandprompt.com
393
(1 row)
394 1 bford -
}}}
395
396
397
== Trusted vs. Untrusted ==
398
399
Normally, PL/php is installed as a "trusted" procedural language named
400
'plphp'.  In this configuration, PHP will run in "safe mode".  Read
401
more about the restrictions here:
402
403
http://www.php.net/manual/en/features.safe-mode.functions.php
404
405
In general, the operations that are restricted are those that interact
406
with the environment.  This includes file operations, require, and use
407
(for external modules).
408
409
Since there is no way to gain access to the internals of the database
410
backend process or the operating system itself, any unprivileged
411
database user may use functions written in this language.
412
413
An example of a NON-working function due to security constraints:
414
415
{{{
416
CREATE FUNCTION read_passwd_file() RETURNS text AS '
417
   readfile("/etc/passwd");
418
   return 0;
419
' LANGUAGE 'plphp';
420
}}}
421
422
It will appear to execute, but depending on your log level, you'll may
423
see something like the following:
424
425
{{{
426
Warning: readfile(): SAFE MODE Restriction in effect. The script
427
whose uid is 500 is not allowed to access /etc/passwd owned by uid 0
428
in Command line code on line 3
429
430
Warning: readfile(/etc/passwd): failed to open stream: Success in
431 4 Álvaro Herrera
plphp function source on line 3
432
}}}
433 1 bford -
434
Sometimes it is desirable to write PHP functions that are not
435
restricted.  In this case, you can create the language as 'plphpu' to
436
enable the previously unavailable functions.
437
  
438
{{{
439 12 Álvaro Herrera
 CREATE LANGUAGE plphpu;
440 1 bford -
}}}
441 4 Álvaro Herrera
442 1 bford -
443
== Composite Type Arguments ==
444
445
Composite-type arguments are passed to the function as associative
446
array. The keys of the array are the attribute names of
447
the composite type. Here is an example:
448
449
{{{
450
CREATE TABLE employee (
451
   name text,
452
   basesalary integer,
453
   bonus integer
454
);
455
456
CREATE FUNCTION empcomp(employee) RETURNS integer AS '
457
   return $args[0][''basesalary''] + $args[0][''bonus''];
458
' LANGUAGE 'plphp';
459
460 12 Álvaro Herrera
INSERT INTO employee values ('Josh', 1000, 10);
461
INSERT INTO employee values ('Darcy', 500, 20);
462
463 1 bford -
SELECT name, empcomp(employee) FROM employee;
464 12 Álvaro Herrera
}}}
465 1 bford -
466 12 Álvaro Herrera
This example results in the following output:
467
468
{{{
469
 name  | empcomp 
470
-------+---------
471
 Josh  |    1010
472
 Darcy |     520
473
(2 rows)
474 1 bford -
}}}
475
476
== Returning A Row (composite type) ==
477
478
To return a row or composite-type value from a PL/php-language
479
function, you can use an indexed array:
480
481
{{{
482 12 Álvaro Herrera
CREATE TYPE php_row AS (f1 integer, f2 text, f3 text);
483 1 bford -
484 12 Álvaro Herrera
CREATE OR REPLACE FUNCTION php_row(integer) RETURNS php_row AS $$
485
   $ret['f1'] = $args[0];
486
   $ret['f3'] = "world";
487
   $ret['f2'] = "hello";
488 1 bford -
   return $ret;
489 12 Álvaro Herrera
$$ LANGUAGE 'plphp';
490 1 bford -
491 12 Álvaro Herrera
SELECT * FROM php_row(1);
492
}}}
493 6 Álvaro Herrera
494 1 bford -
Will return:
495 12 Álvaro Herrera
{{{
496 6 Álvaro Herrera
 f1 |  f2   |  f3
497 2 Álvaro Herrera
----+-------+-------
498 1 bford -
  1 | hello | world
499
}}}
500
501
502
== Returning multiple rows (SRF functions) ==
503
504
SRF stands for "set-returning functions".  This means you can return
505 2 Álvaro Herrera
multiple rows, like in the following example:
506 1 bford -
507
{{{
508
CREATE TYPE __testsetphp AS (f1 integer, f2 text, f3 text);
509 2 Álvaro Herrera
510 1 bford -
CREATE OR REPLACE FUNCTION php_set(integer) RETURNS SETOF __testsetphp AS '
511
   $ret[0][f1]=$args[0];
512
   $ret[0][f2]="hello";
513
   $ret[0][f3]="world";
514
515
   $ret[1][f1]=2*$args[0];
516
   $ret[1][f2]="hello";
517
   $ret[1][f3]="postgres";
518
519
   $ret[2][f1]=3*$args[0];
520
   $ret[2][f2]="hello";
521
   $ret[2][f3]="plphp";
522
   return $ret;
523
' LANGUAGE 'plphp';
524
525
SELECT * FROM php_set(1);
526
527 3 Álvaro Herrera
Will return:
528 1 bford -
 f1 |  f2   |    f3
529
----+-------+----------
530
  1 | hello | world
531 3 Álvaro Herrera
  2 | hello | postgres
532 10 Álvaro Herrera
  3 | hello | plphp
533
}}}
534 1 bford -
535
== Limitations ==
536
537 12 Álvaro Herrera
PL/php functions cannot call each other directly because their names
538
are mangled.