Project

General

Profile

Documentation » History » Version 5

bford -, 11/01/2005 08:45 AM

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