1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
|
<!--
$Header: /cvsroot/pgsql/doc/src/sgml/pltcl.sgml,v 2.6 2000/09/29 20:21:34 petere Exp $
-->
<chapter id="pltcl">
<title>PL/Tcl - TCL Procedural Language</title>
<para>
PL/Tcl is a loadable procedural language for the
<productname>Postgres</productname> database system
that enables the Tcl language to be used to create functions and
trigger-procedures.
</para>
<para>
This package was originally written by Jan Wieck.
</para>
<!-- **** PL/Tcl overview **** -->
<sect1 id="pltcl-overview">
<title>Overview</title>
<para>
PL/Tcl offers most of the capabilities a function
writer has in the C language, except for some restrictions.
</para>
<para>
The good restriction is, that everything is executed in a safe
Tcl-interpreter. In addition to the limited command set of safe Tcl, only
a few commands are available to access the database over SPI and to raise
messages via elog(). There is no way to access internals of the
database backend or gaining OS-level access under the permissions of the
<productname>Postgres</productname> user ID like in C.
Thus, any unprivileged database user may be
permitted to use this language.
</para>
<para>
The other, internal given, restriction is, that Tcl procedures cannot
be used to create input-/output-functions for new data types.
</para>
<para>
The shared object for the PL/Tcl call handler is automatically built
and installed in the <productname>Postgres</productname>
library directory if the Tcl/Tk support is specified
in the configuration step of the installation procedure.
</para>
</sect1>
<!-- **** PL/Tcl description **** -->
<sect1 id="pltcl-description">
<title>Description</title>
<sect2>
<title><productname>Postgres</productname> Functions and Tcl Procedure Names</title>
<para>
In <productname>Postgres</productname>, one and the
same function name can be used for
different functions as long as the number of arguments or their types
differ. This would collide with Tcl procedure names. To offer the same
flexibility in PL/Tcl, the internal Tcl procedure names contain the object
ID of the procedures pg_proc row as part of their name. Thus, different
argtype versions of the same <productname>Postgres</productname>
function are different for Tcl too.
</para>
</sect2>
<sect2>
<title>Defining Functions in PL/Tcl</title>
<para>
To create a function in the PL/Tcl language, use the known syntax
<programlisting>
CREATE FUNCTION <replaceable>funcname</replaceable> <replaceable>argument-types</replaceable>) RETURNS <replaceable>return-type</replaceable> AS '
# PL/Tcl function body
' LANGUAGE 'pltcl';
</programlisting>
When calling this function in a query, the arguments are given as
variables $1 ... $n to the Tcl procedure body. So a little max function
returning the higher of two int4 values would be created as:
<programlisting>
CREATE FUNCTION tcl_max (int4, int4) RETURNS int4 AS '
if {$1 > $2} {return $1}
return $2
' LANGUAGE 'pltcl';
</programlisting>
Composite type arguments are given to the procedure as Tcl arrays.
The element names
in the array are the attribute names of the composite
type. If an attribute in the actual row
has the NULL value, it will not appear in the array! Here is
an example that defines the overpaid_2 function (as found in the
older <productname>Postgres</productname> documentation) in PL/Tcl
<programlisting>
CREATE FUNCTION overpaid_2 (EMP) RETURNS bool AS '
if {200000.0 < $1(salary)} {
return "t"
}
if {$1(age) < 30 && 100000.0 < $1(salary)} {
return "t"
}
return "f"
' LANGUAGE 'pltcl';
</programlisting>
</para>
</sect2>
<sect2>
<title>Global Data in PL/Tcl</title>
<para>
Sometimes (especially when using the SPI functions described later) it
is useful to have some global status data that is held between two
calls to a procedure.
All PL/Tcl procedures executed in one backend share the same
safe Tcl interpreter.
To help protecting PL/Tcl procedures from side effects,
an array is made available to each procedure via the upvar
command. The global name of this variable is the procedures internal
name and the local name is GD.
</para>
</sect2>
<sect2>
<title>Trigger Procedures in PL/Tcl</title>
<para>
Trigger procedures are defined in <productname>Postgres</productname>
as functions without
arguments and a return type of opaque. And so are they in the PL/Tcl
language.
</para>
<para>
The informations from the trigger manager are given to the procedure body
in the following variables:
<variablelist>
<varlistentry>
<term><replaceable class="Parameter">$TG_name</replaceable></term>
<listitem>
<para>
The name of the trigger from the CREATE TRIGGER statement.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="Parameter">$TG_relid</replaceable></term>
<listitem>
<para>
The object ID of the table that caused the trigger procedure
to be invoked.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="Parameter">$TG_relatts</replaceable></term>
<listitem>
<para>
A Tcl list of the tables field names prefixed with an empty list element.
So looking up an element name in the list with the lsearch Tcl command
returns the same positive number starting from 1 as the fields are numbered
in the pg_attribute system catalog.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="Parameter">$TG_when</replaceable></term>
<listitem>
<para>
The string BEFORE or AFTER depending on the event of the trigger call.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="Parameter">$TG_level</replaceable></term>
<listitem>
<para>
The string ROW or STATEMENT depending on the event of the trigger call.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="Parameter">$TG_op</replaceable></term>
<listitem>
<para>
The string INSERT, UPDATE or DELETE depending on the event of the
trigger call.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="Parameter">$NEW</replaceable></term>
<listitem>
<para>
An array containing the values of the new table row on INSERT/UPDATE
actions, or empty on DELETE.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="Parameter">$OLD</replaceable></term>
<listitem>
<para>
An array containing the values of the old table row on UPDATE/DELETE
actions, or empty on INSERT.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="Parameter">$GD</replaceable></term>
<listitem>
<para>
The global status data array as described above.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="Parameter">$args</replaceable></term>
<listitem>
<para>
A Tcl list of the arguments to the procedure as given in the
CREATE TRIGGER statement. The arguments are also accessible as $1 ... $n
in the procedure body.
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
<para>
The return value from a trigger procedure is one of the strings OK or SKIP,
or a list as returned by the 'array get' Tcl command. If the return value
is OK, the normal operation (INSERT/UPDATE/DELETE) that fired this trigger
will take place. Obviously, SKIP tells the trigger manager to silently
suppress the operation. The list from 'array get' tells PL/Tcl
to return a modified row to the trigger manager that will be inserted instead
of the one given in $NEW (INSERT/UPDATE only). Needless to say that all
this is only meaningful when the trigger is BEFORE and FOR EACH ROW.
</para>
<para>
Here's a little example trigger procedure that forces an integer value
in a table to keep track of the # of updates that are performed on the
row. For new row's inserted, the value is initialized to 0 and then
incremented on every update operation:
<programlisting>
CREATE FUNCTION trigfunc_modcount() RETURNS OPAQUE AS '
switch $TG_op {
INSERT {
set NEW($1) 0
}
UPDATE {
set NEW($1) $OLD($1)
incr NEW($1)
}
default {
return OK
}
}
return [array get NEW]
' LANGUAGE 'pltcl';
CREATE TABLE mytab (num int4, modcnt int4, description text);
CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab
FOR EACH ROW EXECUTE PROCEDURE trigfunc_modcount('modcnt');
</programlisting>
</para>
</sect2>
<sect2>
<title>Database Access from PL/Tcl</title>
<para>
The following commands are available to access the database from
the body of a PL/Tcl procedure:
</para>
<variablelist>
<varlistentry>
<term>elog <replaceable>level</replaceable> <replaceable>msg</replaceable></term>
<listitem>
<para>
Fire a log message. Possible levels are NOTICE, ERROR,
FATAL, DEBUG and NOIND
like for the <function>elog</function> C function.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>quote <replaceable>string</replaceable></term>
<listitem>
<para>
Duplicates all occurences of single quote and backslash characters.
It should be used when variables are used in the query string given
to <function>spi_exec</function> or
<function>spi_prepare</function> (not for the value list on
<function>spi_execp</function>).
Think about a query string like
<programlisting>
"SELECT '$val' AS ret"
</programlisting>
where the Tcl variable val actually contains "doesn't". This would result
in the final query string
<programlisting>
"SELECT 'doesn't' AS ret"
</programlisting>
what would cause a parse error during
<function>spi_exec</function> or
<function>spi_prepare</function>.
It should contain
<programlisting>
"SELECT 'doesn''t' AS ret"
</programlisting>
and has to be written as
<programlisting>
"SELECT '[ quote $val ]' AS ret"
</programlisting>
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>spi_exec ?-count <replaceable>n</replaceable>? ?-array <replaceable>name</replaceable>? <replaceable>query</replaceable> ?<replaceable>loop-body</replaceable>?</term>
<listitem>
<para>
Call parser/planner/optimizer/executor for query.
The optional -count value tells <function>spi_exec</function>
the maximum number of rows
to be processed by the query.
</para>
<para>
If the query is
a SELECT statement and the optional loop-body (a body of Tcl commands
like in a foreach statement) is given, it is evaluated for each
row selected and behaves like expected on continue/break. The values
of selected fields are put into variables named as the column names. So a
<programlisting>
spi_exec "SELECT count(*) AS cnt FROM pg_proc"
</programlisting>
will set the variable $cnt to the number of rows in the pg_proc system
catalog. If the option -array is given, the column values are stored
in the associative array named 'name' indexed by the column name
instead of individual variables.
<programlisting>
spi_exec -array C "SELECT * FROM pg_class" {
elog DEBUG "have table $C(relname)"
}
</programlisting>
will print a DEBUG log message for every row of pg_class. The return value
of <function>spi_exec</function> is the number of rows
affected by the query as found in
the global variable SPI_processed.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>spi_prepare <replaceable>query</replaceable> <replaceable>typelist</replaceable></term>
<listitem>
<para>
Prepares AND SAVES a query plan for later execution. It is a bit different
from the C level SPI_prepare in that the plan is automatically copied to the
toplevel memory context. Thus, there is currently no way of preparing a
plan without saving it.
</para>
<para>
If the query references arguments, the type names must be given as a Tcl
list. The return value from spi_prepare is a query ID to be used in
subsequent calls to spi_execp. See spi_execp for a sample.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>spi_exec ?-count <replaceable>n</replaceable>? ?-array<replaceable>name</replaceable>? ?-nulls<replaceable>string</replaceable>? <replaceable>queryid</replaceable> ?<replaceable>value-list</replaceable>? ?<replaceable>loop-body</replaceable>?</term>
<listitem>
<para>
Execute a prepared plan from spi_prepare with variable substitution.
The optional -count value tells spi_execp the maximum number of rows
to be processed by the query.
</para>
<para>
The optional value for -nulls is a string of spaces and 'n' characters
telling spi_execp which of the values are NULL's. If given, it must
have exactly the length of the number of values.
</para>
<para>
The queryid is the ID returned by the spi_prepare call.
</para>
<para>
If there was a typelist given to spi_prepare, a Tcl list of values of
exactly the same length must be given to spi_execp after the query. If
the type list on spi_prepare was empty, this argument must be omitted.
</para>
<para>
If the query is a SELECT statement, the same as described for spi_exec
happens for the loop-body and the variables for the fields selected.
</para>
<para>
Here's an example for a PL/Tcl function using a prepared plan:
<programlisting>
CREATE FUNCTION t1_count(int4, int4) RETURNS int4 AS '
if {![ info exists GD(plan) ]} {
# prepare the saved plan on the first call
set GD(plan) [ spi_prepare \\
"SELECT count(*) AS cnt FROM t1 WHERE num >= \\$1 AND num <= \\$2" \\
int4 ]
}
spi_execp -count 1 $GD(plan) [ list $1 $2 ]
return $cnt
' LANGUAGE 'pltcl';
</programlisting>
Note that each backslash that Tcl should see must be doubled in
the query creating the function, since the main parser processes
backslashes too on CREATE FUNCTION.
Inside the query string given to spi_prepare should
really be dollar signs to mark the parameter positions and to not let
$1 be substituted by the value given in the first function call.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>
Modules and the unknown command
</term>
<listitem>
<para>
PL/Tcl has a special support for things often used. It recognizes two
magic tables, pltcl_modules and pltcl_modfuncs.
If these exist, the module 'unknown' is loaded into the interpreter
right after creation. Whenever an unknown Tcl procedure is called,
the unknown proc is asked to check if the procedure is defined in one
of the modules. If this is true, the module is loaded on demand.
To enable this behavior, the PL/Tcl call handler must be compiled
with -DPLTCL_UNKNOWN_SUPPORT set.
</para>
<para>
There are support scripts to maintain these tables in the modules
subdirectory of the PL/Tcl source including the source for the
unknown module that must get installed initially.
</para>
</listitem>
</varlistentry>
</variablelist>
</sect2>
</sect1>
</chapter>
<!-- Keep this comment at the end of the file
Local variables:
mode:sgml
sgml-omittag:nil
sgml-shorttag:t
sgml-minimize-attributes:nil
sgml-always-quote-attributes:t
sgml-indent-step:1
sgml-indent-data:t
sgml-parent-document:nil
sgml-default-dtd-file:"./reference.ced"
sgml-exposed-tags:nil
sgml-local-catalogs:("/usr/lib/sgml/catalog")
sgml-local-ecat-files:nil
End:
-->
|