New edition by D'Arcy and me.

This commit is contained in:
Vadim B. Mikheev 1997-10-02 18:03:09 +00:00
parent d75206fdf5
commit 91eb34e9eb
2 changed files with 256 additions and 220 deletions

View File

@ -1,46 +1,46 @@
PostgreSQL Server Programming Interface PostgreSQL Server Programming Interface
Server Programming Interface (SPI) is attempt to give users ability run The Server Programming Interface (SPI) is an attempt to give users the
SQL-queries inside user-defined C-function. For lack of Procedural Language ability to run SQL-queries inside user-defined C-functions. Given the lack
(PL) in current version of PostgreSQL, SPI is only way to write server of a proper Procedural Language (PL) in the current version of PostgreSQL,
stored procedures and triggers. In the future, SPI will be used as SPI is only way to write server stored procedures and triggers. In the future
"workhorse" for PL. SPI will be used as the "workhorse" for PL.
Actually, SPI is just set of builtin interface functions to simplify In fact, SPI is just set of builtin interface functions to simplify
access to Parser/Planner/Optimizer and Executor. Also, SPI does some memory access to the Parser, Planner, Optimizer and Executor. SPI also does some
management. memory management.
To avoid misunderstanding we'll use word "function" for SPI interface To avoid misunderstanding we'll use the word "function" for SPI interface
functions and word "procedure" for user-defined C-functions using SPI. functions and the word "procedure" for user-defined C-functions using SPI.
SPI procedures are always called by some (upper) Executor and SPI manager SPI procedures are always called by some (upper) Executor and the SPI
uses Executor to run your queries. Other procedures may be called by manager uses the Executor to run your queries. Other procedures may be
Executor running queries from your procedure. called by the Executor running queries from your procedure.
Note, that if during execution of query from a procedure transaction will Note, that if during execution of a query from a procedure the transaction
be aborted then control will not be returned to your procedure - all work is be aborted then control will not be returned to your procedure - all work
will be rollbacked and server will wait for the next command from client. will be rolled back and the server will wait for the next command from the
It will be changed in the next versions. client. This will be changed in the future versions.
Other restrictions are unability to execute BEGIN, END and ABORT Other restrictions are the inability to execute BEGIN, END and ABORT
(transaction control statements) and cursor operations. (transaction control statements) and cursor operations. This will also be
These are also to be changed in future. changed in future.
Interface functions Interface functions
If successful, SPI functions returns non-negative result (either via If successful, SPI functions return a non-negative result (either via
returned (int) value or in SPI_result global variable, as described below). returned (int) value or in SPI_result global variable, as described below).
Otherwise, negative result will be returned. On error, a negative result will be returned.
int SPI_connect (void) int SPI_connect (void)
Connects your procedure to SPI manager. Initializes SPI internal Connects your procedure to the SPI manager. Initializes the SPI internal
structures for query execution and memory management. structures for query execution and memory management.
You are to call this function if you need in execution of queries. Some You should call this function if you will need to execute queries. Some
utility SPI functions may be called from un-connected procedures. utility SPI functions may be called from un-connected procedures.
Returns: Returns:
@ -48,45 +48,47 @@ int SPI_connect (void)
SPI_OK_CONNECT if connected. SPI_OK_CONNECT if connected.
SPI_ERROR_CONNECT if not. You may get this error if SPI_connect() is SPI_ERROR_CONNECT if not. You may get this error if SPI_connect() is
called from already connected procedure - e.g. if you directly call one called from an already connected procedure - e.g. if you directly call one
procedure from another connected one. Actually, while child procedure procedure from another connected one. Actually, while the child procedure
will be able to use SPI, your parent procedure will not be able continue will be able to use SPI, your parent procedure will not be able to continue
use SPI after child returned (if SPI_finish() called by child). It's bad to use SPI after the child returns (if SPI_finish() is called by the child).
practice. It's bad practice.
int SPI_finish(void) int SPI_finish(void)
Dis-connects your procedure from SPI manager. Frees all memory Disconnects your procedure from the SPI manager and frees all memory
allocations made by your procedure via palloc() after SPI_connect(). allocations made by your procedure via palloc() since the SPI_connect().
These allocations can't be used any more! See Memory management. These allocations can't be used any more! See Memory management.
After SPI_finish() is called your procedure loses ability to run queries. After SPI_finish() is called your procedure loses the ability to run
Server is in the same state as just before call to SPI_connect(). queries. The server is in the same state as just before the call to
SPI_connect().
Returns: Returns:
SPI_OK_FINISH if properly disconnected. SPI_OK_FINISH if properly disconnected.
SPI_ERROR_UNCONNECTED if called from un-connected procedure. No problems SPI_ERROR_UNCONNECTED if called from an un-connected procedure. No problem
with this - it means that nothing was made by SPI manager. with this - it means that nothing was made by the SPI manager.
NOTE! SPI_finish() MUST be called by connected procedure or you may get NOTE! SPI_finish() MUST be called by connected procedure or you may get
unpredictable results! But you are able to don't call SPI_finish() if you unpredictable results! But you are able to skip the call to SPI_finish()
abort transaction (via elog(WARN)). if you abort the transaction (via elog(WARN)).
int SPI_exec(char *query, int tcount) int SPI_exec(char *query, int tcount)
Creates execution plan (parser+planner+optimizer) and executes query for Creates an execution plan (parser+planner+optimizer) and executes query
tcount tuples. Should be called from connected procedure. If tcount eq 0 for tcount tuples. This should only be called from a connected procedure.
then executes query for all tuples returned by query scan. Using tcount > If tcount eq 0 then it executes the query for all tuples returned by the
0 you may restrict number of tuples for which query will be executed: query scan. Using tcount > 0 you may restrict the number of tuples for
which the query will be executed:
SPI_exec ("insert into _table_ select * from _table_", 5); SPI_exec ("insert into _table_ select * from _table_", 5);
- at max 5 tuples will be inserted into _table_. - at max 5 tuples will be inserted into _table_.
If execution of your query was successful then one of the next If execution of your query was successful then one of the following
(non-negative) values will be returned: (non-negative) values will be returned:
SPI_OK_UTILITY if some utility (e.g. CREATE TABLE ...) was executed. SPI_OK_UTILITY if some utility (e.g. CREATE TABLE ...) was executed.
@ -97,13 +99,14 @@ int SPI_exec(char *query, int tcount)
SPI_OK_UPDATE if UPDATE was executed. SPI_OK_UPDATE if UPDATE was executed.
NOTE! You may pass many queries in one string or query string may be NOTE! You may pass many queries in one string or query string may be
re-written by RULEs. SPI_exec() returns result for last query executed. re-written by RULEs. SPI_exec() returns the result for the last query
executed.
Actual number of tuples for which (last) query was executed is returned The actual number of tuples for which the (last) query was executed is
in global variable SPI_processed (if not SPI_OK_UTILITY). returned in the global variable SPI_processed (if not SPI_OK_UTILITY).
If SPI_OK_SELECT returned and SPI_processed > 0 then you may use global If SPI_OK_SELECT returned and SPI_processed > 0 then you may use global
pointer SPITupleTable *SPI_tuptable to access selected tuples: pointer SPITupleTable *SPI_tuptable to access the selected tuples:
Structure SPITupleTable is defined in spi.h: Structure SPITupleTable is defined in spi.h:
@ -115,47 +118,47 @@ int SPI_exec(char *query, int tcount)
HeapTuple *vals; /* tuples */ HeapTuple *vals; /* tuples */
} SPITupleTable; } SPITupleTable;
HeapTuple *vals is array of pointers to tuples. TupleDesc tupdesc is HeapTuple *vals is an array of pointers to tuples. TupleDesc tupdesc is
tuple descriptor which you are to pass to SPI functions dealing with a tuple descriptor which you may pass to SPI functions dealing with
tuples. tuples.
NOTE! Functions SPI_exec(), SPI_execp() and SPI_prepare() change both NOTE! Functions SPI_exec(), SPI_execp() and SPI_prepare() change both
SPI_processed and SPI_tuptable (just pointer, not context of structure)! SPI_processed and SPI_tuptable (just the pointer, not the contents of the
So, save theme in local procedure variables if you need. structure)! So, save them in local procedure variables if you need them.
Also NOTE, that SPI_finish() frees and makes all SPITupleTable-s Also NOTE, that SPI_finish() frees and makes all SPITupleTables
unusable! (See Memory management). unusable! (See Memory management).
SPI_exec() may return one of the next (negative) values: SPI_exec() may return one of the following (negative) values:
SPI_ERROR_ARGUMENT if query is NULL or tcount < 0. SPI_ERROR_ARGUMENT if query is NULL or tcount < 0.
SPI_ERROR_UNCONNECTED if procedure is un-connected. SPI_ERROR_UNCONNECTED if procedure is unconnected.
SPI_ERROR_COPY if COPY TO/FROM stdin. SPI_ERROR_COPY if COPY TO/FROM stdin.
SPI_ERROR_CURSOR if DECLARE/CLOSE CURSOR, FETCH. SPI_ERROR_CURSOR if DECLARE/CLOSE CURSOR, FETCH.
SPI_ERROR_TRANSACTION if BEGIN/ABORT/END. SPI_ERROR_TRANSACTION if BEGIN/ABORT/END.
SPI_ERROR_OPUNKNOWN if type of query is unknown (this shouldn't occure). SPI_ERROR_OPUNKNOWN if type of query is unknown (this shouldn't occur).
void *SPI_prepare(char *query, int nargs, Oid * argtypes) void *SPI_prepare(char *query, int nargs, Oid * argtypes)
Creates and returns execution plan (parser+planner+optimizer) but doesn't Creates and returns an execution plan (parser+planner+optimizer) but doesn't
execute query. Should be called from connected procedure. execute the query. Should only be called from a connected procedure.
nargs is number of parameters ($1 ... $<nargs> - like in SQL-functions), nargs is number of parameters ($1 ... $<nargs> - as in SQL-functions),
*argtypes is array of parameter type OIDs. *argtypes is an array of parameter type OIDs.
nargs may be 0 only if there is no any $1 in query. nargs may be 0 only if there is not any $1 in query.
Execution of prepared execution plans is much faster sometimes... So this Execution of prepared execution plans is sometimes much faster so this
feature may be useful if the same query will be executed may times. feature may be useful if the same query will be executed many times.
NOTE! Plan returned by SPI_prepare() may be used only in current NOTE! The plan returned by SPI_prepare() may be used only in current
invocation of procedure: SPI_finish() frees memory allocated for a plan. invocation of procedure: SPI_finish() frees memory allocated for a plan.
See SPI_saveplan(). See SPI_saveplan().
If successful, NOT NULL pointer will be returned. Otherwise, you'll get If successful, NOT NULL pointer will be returned. Otherwise, you'll get
NULL plan. In both cases SPI_result will be setted like value returned by a NULL plan. In both cases SPI_result will be set like the value returned
SPI_exec, but by SPI_exec, except
SPI_ERROR_ARGUMENT if query is NULL or nargs < 0 or nargs > 0 && argtypes SPI_ERROR_ARGUMENT if query is NULL or nargs < 0 or nargs > 0 && argtypes
is NULL. is NULL.
@ -163,24 +166,24 @@ void *SPI_prepare(char *query, int nargs, Oid * argtypes)
void *SPI_saveplan(void *plan) void *SPI_saveplan(void *plan)
Currently, there is no ability to store prepared plans in system catalog Currently, there is no ability to store prepared plans in the system
and fetch them from there for execution. This will be implemented in catalog and fetch them from there for execution. This will be implemented
future versions. in future versions.
As work arround, there is ability to re-use prepared plans in the As a work arround, there is the ability to reuse prepared plans in the
consequent invocations of your procedure in current session. consequent invocations of your procedure in the current session.
SPI_saveplan() saves passed plan (prepared by SPI_prepare()) in memory SPI_saveplan() saves a passed plan (prepared by SPI_prepare()) in memory
protected from free-ing by SPI_finish() and by transaction manager and protected from freeing by SPI_finish() and by the transaction manager and
returns pointer to saved plan. You may preserve pointer returned in local returns a pointer to the saved plan. You may save the pointer returned in
variable and always check is this pointer NULL or not to either prepare a local variable. Always check if this pointer is NULL or not either when
plan or use already prepared plan in SPI_execp (see below). preparing a plan or using an already prepared plan in SPI_execp (see below).
NOTE! If one of objects (relation, function, ...) referenced by prepared NOTE! If one of objects (relation, function, ...) referenced by prepared
plan will be dropped during your session (by your or another backend) plan is dropped during your session (by your backend or another) then the
then results of SPI_execp (for this plan) will be unpredictable. results of SPI_execp (for this plan) will be unpredictable.
If successful, NOT NULL returned. Otherwise, SPI_result setted to If successful, NOT NULL is returned otherwise, SPI_result is set to
SPI_ERROR_ARGUMENT if plan is NULL. SPI_ERROR_ARGUMENT if plan is NULL.
SPI_ERROR_UNCONNECTED if procedure is un-connected. SPI_ERROR_UNCONNECTED if procedure is un-connected.
@ -188,26 +191,26 @@ void *SPI_saveplan(void *plan)
int SPI_execp(void *plan, Datum * values, char *Nulls, int tcount) int SPI_execp(void *plan, Datum * values, char *Nulls, int tcount)
Executes plan prepared by SPI_prepare() (or returned by SPI_saveplan()). Executes a plan prepared by SPI_prepare() (or returned by SPI_saveplan()).
Should be called from connected procedure. Should only be called from a connected procedure.
plan is pointer to execution plan, values points to actual parameter plan is pointer to an execution plan, values points to actual parameter
values, Nulls - to array describing what parameters get NULLs ('n' - values, Nulls - to array describing what parameters get NULLs ('n' -
NULL, ' ' - NOT NULL), tcount - number of tuples for which plan is to be NULL, ' ' - NOT NULL), tcount - number of tuples for which plan is to be
executed. executed.
If Nulls is NULL then SPI assumes that all values (if any) are NOT NULL. If Nulls is NULL then SPI assumes that all values (if any) are NOT NULL.
Returns value like SPI_exec, but Returns the same value as SPI_exec, except
SPI_ERROR_ARGUMENT if plan is NULL or tcount < 0. SPI_ERROR_ARGUMENT if plan is NULL or tcount < 0.
SPI_ERROR_PARAM if Values is NULL and plan prepared with some parameters. SPI_ERROR_PARAM if Values is NULL and plan prepared with some parameters.
If successful, SPI_tuptable and SPI_processed are initialized like by If successful, SPI_tuptable and SPI_processed are initialized as in
SPI_exec(). SPI_exec().
All functions described below may be used by connected and un-connected All functions described below may be used by connected and unconnected
procedures. procedures.
@ -215,26 +218,26 @@ HeapTuple SPI_copytuple(HeapTuple tuple)
Makes copy of tuple in upper Executor context (see Memory management). Makes copy of tuple in upper Executor context (see Memory management).
If successful, NOT NULL returned. NULL (i.e. - error) will be returned If successful, NOT NULL returned. NULL (i.e. - error) will be returned
only if NULL passed in. only if NULL is passed in.
HeapTuple SPI_modifytuple(Relation rel, HeapTuple tuple, int natts, HeapTuple SPI_modifytuple(Relation rel, HeapTuple tuple, int natts,
int *attnum, Datum * Values, char *Nulls) int *attnum, Datum * Values, char *Nulls)
Modifies tuple of relation rel as described by the rest of arguments. Modifies tuple of relation rel as described by the rest of the arguments.
natts is number of attribute numbers in attnum. natts is the number of attribute numbers in attnum.
attnum is array of numbers of attributes which are to be changed. attnum is an array of numbers of the attributes which are to be changed.
Values are new values for attributes specified. Values are new values for the attributes specified.
Nulls describes what of attributes specified are NULL (if Nulls is Nulls describes which of the attributes specified are NULL (if Nulls is
NULL then no NULLs). NULL then no NULLs).
If successful, NOT NULL pointer to new tuple returned. New tuple is If successful, NOT NULL pointer to new tuple returned. New tuple is
allocated in upper Executor context (see Memory management). Passed tuple allocated in upper Executor context (see Memory management). Passed tuple
is not changed. is not changed.
Returns NULL if failed and cause in SPI_result: Returns NULL if failed with cause in SPI_result:
SPI_ERROR_ARGUMENT if rel is NULL or tuple is NULL or natts le 0 or SPI_ERROR_ARGUMENT if rel is NULL or tuple is NULL or natts le 0 or
attnum is NULL or Values is NULL. attnum is NULL or Values is NULL.
@ -244,26 +247,27 @@ HeapTuple SPI_modifytuple(Relation rel, HeapTuple tuple, int natts,
int SPI_fnumber(TupleDesc tupdesc, char *fname) int SPI_fnumber(TupleDesc tupdesc, char *fname)
Returns attribute number for attribute with name as in fname. Returns the attribute number for the attribute with name in fname.
tupdesc is tuple description. tupdesc is tuple description.
Attribute numbers are 1-based. Attribute numbers are 1 based.
Returns SPI_ERROR_NOATTRIBUTE if attribute not found. Returns SPI_ERROR_NOATTRIBUTE if the named attribute is not found.
char *SPI_fname(TupleDesc tupdesc, int fnumber) char *SPI_fname(TupleDesc tupdesc, int fnumber)
Returns (copy of) name of attribute with number fnumber. Returns (a copy of) the name of the attribute with number fnumber.
Returns NULL and (SPI_ERROR_NOATTRIBUTE in SPI_result) if fnumber is Returns NULL and (SPI_ERROR_NOATTRIBUTE in SPI_result) if fnumber is
greater number of attributes in tupdesc or fnumber le 0. greater than the number of attributes in tupdesc or fnumber le 0.
char *SPI_getvalue(HeapTuple tuple, TupleDesc tupdesc, int fnumber) char *SPI_getvalue(HeapTuple tuple, TupleDesc tupdesc, int fnumber)
Returns external (string) representation of value of attribute fnumber in Returns an external (string) representation of the value of attribute
tuple with descriptor tupdesc. Allocates memory as required by value. fnumber in tuple with descriptor tupdesc. Allocates memory as required
by the value.
Returns NULL if Returns NULL if
@ -275,8 +279,8 @@ char *SPI_getvalue(HeapTuple tuple, TupleDesc tupdesc, int fnumber)
Datum SPI_getbinval(HeapTuple tuple, TupleDesc tupdesc, int fnumber, Datum SPI_getbinval(HeapTuple tuple, TupleDesc tupdesc, int fnumber,
bool *isnull) bool *isnull)
Returns value of attribute fnumber in tuple with descriptor tupdesc. This Returns the value of attribute fnumber in the tuple with descriptor
is binary value in internal form. This is not copy! tupdesc. This is a binary value in internal form. This is not a copy!
Returns NULL indicator in *isnull. Returns NULL indicator in *isnull.
@ -285,7 +289,7 @@ Datum SPI_getbinval(HeapTuple tuple, TupleDesc tupdesc, int fnumber,
char *SPI_gettype(TupleDesc tupdesc, int fnumber) char *SPI_gettype(TupleDesc tupdesc, int fnumber)
Returns (copy of) type name for attribute fnumber. Returns (a copy of) the type name for attribute fnumber.
Returns NULL (and SPI_ERROR_NOATTRIBUTE in SPI_result) if fnumber Returns NULL (and SPI_ERROR_NOATTRIBUTE in SPI_result) if fnumber
is invalid. is invalid.
@ -300,7 +304,7 @@ Oid SPI_gettypeid(TupleDesc tupdesc, int fnumber)
char *SPI_getrelname(Relation rel) char *SPI_getrelname(Relation rel)
Returns (copy of) relation name of relation rel. Returns (a copy of) the name of relation rel.
void *SPI_palloc (Size size) void *SPI_palloc (Size size)
@ -323,67 +327,71 @@ void SPI_pfree(void *pointer)
Server allocates memory in memory contexts in such way that allocations Server allocates memory in memory contexts in such way that allocations
made in one context may be freed by context destruction without affecting made in one context may be freed by context destruction without affecting
allocations made in other contexts. There is way to choose some context as allocations made in other contexts. All allocations (via palloc(), etc) are
current one. All allocations (via palloc(), etc) are made in current made in the context which are chosen as current one. You'll get
context. You'll get unpredictable results if you'll try to free (or unpredictable results if you'll try to free (or reallocate) memory allocated
reallocate) memory allocated not in current context. not in current context.
Creation and switching between memory contexts are subject of SPI manager
memory management.
SPI procedures deal with two memory contexts: upper Executor memory SPI procedures deal with two memory contexts: upper Executor memory
context and procedure memory context (if connected). context and procedure memory context (if connected).
Before a procedure is connected to SPI manager current memory context is Before a procedure is connected to the SPI manager, current memory context
upper Executor context. And so, all allocation made by procedure itself via is upper Executor context so all allocation made by the procedure itself via
palloc()/repalloc() or by SPI utility functions before connection to SPI are palloc()/repalloc() or by SPI utility functions before connecting to SPI are
made in this context. made in this context.
After SPI_connect() is called current context is procedure one. All After SPI_connect() is called current context is the procedure's one. All
allocations made via palloc()/repalloc() or by SPI utility functions (except allocations made via palloc()/repalloc() or by SPI utility functions (except
for SPI_copytuple(), SPI_modifytuple, SPI_palloc() and SPI_repalloc()) are for SPI_copytuple(), SPI_modifytuple, SPI_palloc() and SPI_repalloc()) are
made in this context. made in this context.
When a procedure dis-connects from SPI manager (via SPI_finish()) current When a procedure disconnects from the SPI manager (via SPI_finish()) the
context is restored to upper Executor context and all allocations made in current context is restored to the upper Executor context and all allocations
procedure memory context are freed and can't be used any more! made in the procedure memory context are freed and can't be used any more!
If you want to return something to upper Executor then you have to If you want to return something to the upper Executor then you have to
allocate memory for this in upper context! allocate memory for this in the upper context!
SPI has no ability to automatically free allocations in upper Executor SPI has no ability to automatically free allocations in the upper Executor
context! context!
SPI automatically frees memory allocated during execution of a query when SPI automatically frees memory allocated during execution of a query when
this query is done! this query is done!
Data changes visibility Data changes visibility
PostgreSQL data changes visibility rule: during query execution data PostgreSQL data changes visibility rule: during a query execution, data
changes made by query itself (via SQL-function, SPI-function, triggers) changes made by the query itself (via SQL-function, SPI-function, triggers)
are invisible to the query scan. are invisible to the query scan. For example, in query
For example, in query
INSERT INTO a SELECT * FROM a INSERT INTO a SELECT * FROM a
tuples inserted are invisible for SELECT' scan. tuples inserted are invisible for SELECT' scan. In effect, this
duplicates the database table within itself (subject to unique index
rules, of course) without recursing.
But also note that Changes made by query Q are visible by queries which are started after
query Q, no matter whether they are started inside Q (during the execution
of Q) or after Q is done.
changes made by query Q are visible by queries which are started after The last example of the usage of SPI procedure below demonstrates the
query Q, no matter - are they started inside Q (during execution of Q) or visibility rule.
after Q is done.
Last example of usage SPI function below demonstrates visibility rule.
Examples Examples
There are complex examples in contrib/spi and in There are more complex examples in in src/test/regress/regress.c and
src/test/regress/regress.c. in contrib/spi.
This is very simple example of SPI using. Function execq accepts This is a very simple example of SPI usage. The procedure execq accepts
SQL-query in first arguments and tcount in second, executes query an SQL-query in its first argument and tcount in its second, executes the
using SPI_exec and returns number of tuples for which query executed: query using SPI_exec and returns the number of tuples for which the query
executed:
---------------------------------------------------------------------------- ----------------------------------------------------------------------------
#include "executor/spi.h" /* this is what you need to work with SPI */ #include "executor/spi.h" /* this is what you need to work with SPI */
@ -430,7 +438,7 @@ execq(text *sql, int cnt)
} }
---------------------------------------------------------------------------- ----------------------------------------------------------------------------
Now, compile and create function: Now, compile and create the function:
create function execq (text, int4) returns int4 as '...path_to_so' language 'c'; create function execq (text, int4) returns int4 as '...path_to_so' language 'c';
vac=> select execq('create table a (x int4)', 0); vac=> select execq('create table a (x int4)', 0);

View File

@ -1,62 +1,89 @@
PostgreSQL Trigger Programming Guide PostgreSQL Trigger Programming Guide
For the lack of Procedural Language (PL) in current version of While the current version of PostgreSQL has various client interfaces
PostgreSQL, there is only ability to specify call to a C-function as trigger such as Perl, Tcl, Python and C, it lacks an actual Procedural Language
action. (PL). We hope to have a proper PL one day. In the meantime it is possible
Also, STATEMENT-level trigger events are not supported in current to call C functions as trigger actions. Note that STATEMENT-level trigger
version, and so you are only able to specify BEFORE | AFTER events are not supported in the current version. You can currently specify
INSERT|DELETE|UPDATE of a tuple as trigger event. BEFORE or AFTER on INSERT, DELETE or UPDATE of a tuple as a trigger event.
If trigger event occures, trigger manager (called by Executor) If a trigger event occurs, the trigger manager (called by the Executor)
initializes global structure TriggerData *CurrentTriggerData (described initializes the global structure TriggerData *CurrentTriggerData (described
below) and calls trigger function to handle event. below) and calls the trigger function to handle the event.
Trigger function must be created before trigger creation as function The trigger function must be created before the trigger is created as a
not accepting any arguments and returns opaque. function taking no arguments and returns opaque.
Actually, there are two specific features in triggers handling.
The syntax for creating triggers is as follows.
CREATE TRIGGER <trigger name> <BEFORE|AFTER> <INSERT|DELETE|UPDATE>
ON <relation name> FOR EACH <ROW|STATEMENT>
EXECUTE PROCEDURE <procedure name> (<function args>);
The name of the trigger is used if you ever have to delete the trigger.
It is used as an argument to the DROP TRIGGER command.
The next word determines whether the function is called before or after
the event.
The next element of the command determines on what event(s) will trigger
the function. Multiple events can be specified separated by OR.
The relation name determines which table the event applies to.
The FOR EACH statement determines whether the trigger is fired for each
affected row or before (or after) the entire statement has completed.
The procedure name is the C function called.
The args are passed to the function in the CurrentTriggerData structure.
The purpose of passing arguments to the function is to allow different
triggers with similar requirements to call the same function.
First, in CREATE TRIGGER one may specify arguments for trigger
function (EXECUTE PROCEDURE tfunc (aa,'bb', 1)), and these arguments
will be passed to trigger function in CurrentTriggerData.
It allows to use single function for many triggers and process events in
different ways.
Also, function may be used for triggering different relations (these Also, function may be used for triggering different relations (these
functions are named as "general trigger functions"). functions are named as "general trigger functions").
Second, trigger function has to return HeapTuple to upper Executor. As example of using both features above, there could be a general
No matter for triggers fired AFTER operation (INSERT, DELETE, UPDATE), function that takes as its arguments two field names and puts the current
but it allows to BEFORE triggers: user in one and the current timestamp in the other. This allows triggers to
- return NULL to skip operation for current tuple (and so tuple be written on INSERT events to automatically track creation of records in a
will not be inserted/updated/deleted); transaction table for example. It could also be used as a "last updated"
- return pointer to another tuple (INSERT and UPDATE only) which will be function if used in an UPDATE event.
inserted (as new version of updated tuple if UPDATE) instead of
original tuple.
Note, that there is no initialization performed by CREATE TRIGGER Trigger functions return HeapTuple to the calling Executor. This
handler. It will be changed in the future. is ignored for triggers fired after an INSERT, DELETE or UPDATE operation
but it allows BEFORE triggers to:
Also, if more than one trigger defined for the same event on the same - return NULL to skip the operation for the current tuple (and so the
relation then order of trigger firing is unpredictable. It may be changed in tuple will not be inserted/updated/deleted);
the future. - return a pointer to another tuple (INSERT and UPDATE only) which will
be inserted (as the new version of the updated tuple if UPDATE) instead
of original tuple.
Also, if a trigger function executes SQL-queries (using SPI) then these Note, that there is no initialization performed by the CREATE TRIGGER
queries may fire triggers again. This is known as cascading of triggers. handler. This will be changed in the future. Also, if more than one trigger
There is no explicit limitation for number of cascade levels. is defined for the same event on the same relation, the order of trigger
If a trigger is fired by INSERT and inserts new tuple in the same firing is unpredictable. This may be changed in the future.
relation then this trigger will be fired again. Currently, there is nothing
provided for synchronization (etc) of these cases. It may be changed. At If a trigger function executes SQL-queries (using SPI) then these queries
may fire triggers again. This is known as cascading triggers. There is no
explicit limitation on the number of cascade levels.
If a trigger is fired by INSERT and inserts a new tuple in the same
relation then this trigger will be fired again. Currently, there is nothing
provided for synchronization (etc) of these cases but this may change. At
the moment, there is function funny_dup17() in the regress tests which uses the moment, there is function funny_dup17() in the regress tests which uses
some technics to stop recursion (cascading) of itself... some techniques to stop recursion (cascading) on itself...
Interaction with trigger manager Interaction with the trigger manager
As it's mentioned above when function is called by trigger manager As mentioned above, when function is called by the trigger manager,
structure TriggerData *CurrentTriggerData is NOT NULL and initialized. And structure TriggerData *CurrentTriggerData is NOT NULL and initialized. So
so, it's better to check CurrentTriggerData against being NULL in the it is better to check CurrentTriggerData against being NULL at the start
begining and set it to NULL just after fetching information - to prevent and set it to NULL just after fetching the information to prevent calls to
calls to trigger function not from trigger manager. a trigger function not from the trigger manager.
struct TriggerData is defined in src/include/commands/trigger.h: struct TriggerData is defined in src/include/commands/trigger.h:
@ -70,8 +97,8 @@ typedef struct TriggerData
} TriggerData; } TriggerData;
tg_event tg_event
describes event for what function is called. You may use macros describes event for which the function is called. You may use the
to deal with tg_event: following macros to examine tg_event:
TRIGGER_FIRED_BEFORE(event) returns TRUE if trigger fired BEFORE; TRIGGER_FIRED_BEFORE(event) returns TRUE if trigger fired BEFORE;
TRIGGER_FIRED_AFTER(event) returns TRUE if trigger fired AFTER; TRIGGER_FIRED_AFTER(event) returns TRUE if trigger fired AFTER;
@ -84,23 +111,25 @@ tg_event
TRIGGER_FIRED_BY_UPDATE(event) returns TRUE if trigger fired by UPDATE. TRIGGER_FIRED_BY_UPDATE(event) returns TRUE if trigger fired by UPDATE.
tg_relation tg_relation
is pointer to structure describing triggered relation. Look @ is pointer to structure describing the triggered relation. Look at
src/include/utils/rel.h about this structure. The most interest things src/include/utils/rel.h for details about this structure. The most
are tg_relation->rd_att (descriptor of relation tuples) and interest things are tg_relation->rd_att (descriptor of the relation
tg_relation->rd_rel->relname (relation' name. This is not char*, but tuples) and tg_relation->rd_rel->relname (relation's name. This is not
NameData - use SPI_getrelname(tg_relation) to get char* to copy of name). char*, but NameData. Use SPI_getrelname(tg_relation) to get char* if
you need a copy of name).
tg_trigtuple tg_trigtuple
is tuple (pointer) for which trigger is fired. This is tuple to being is a pointer to the tuple for which the trigger is fired. This is the tuple
inserted (if INSERT), deleted (if DELETE) or updated (if UPDATE). being inserted (if INSERT), deleted (if DELETE) or updated (if UPDATE).
If INSERT/DELETE then this is what you are to return to Executor if If INSERT/DELETE then this is what you are to return to Executor if
you don't want to replace tuple with another one (INSERT) or skip you don't want to replace tuple with another one (INSERT) or skip the
operation. operation.
tg_newtuple tg_newtuple
is pointer to new version of tuple if UPDATE and NULL if INSERT/DELETE. is a pointer to the new version of tuple if UPDATE and NULL if this is
This is what you are to return to Executor if UPDATE and you don't want for an INSERT or a DELETE. This is what you are to return to Executor if
to replace tuple with another one or skip operation. UPDATE and you don't want to replace this tuple with another one or skip
the operation.
tg_trigger tg_trigger
is pointer to structure Trigger defined in src/include/utils/rel.h: is pointer to structure Trigger defined in src/include/utils/rel.h:
@ -116,45 +145,44 @@ typedef struct Trigger
char **tgargs; char **tgargs;
} Trigger; } Trigger;
tgname is trigger' name, tgnargs is number of arguments in tgargs, tgargs tgname is the trigger's name, tgnargs is number of arguments in tgargs,
is array of pointers to arguments specified in CREATE TRIGGER. Other tgargs is an array of pointers to the arguments specified in the CREATE
members are for internal use. TRIGGER statement. Other members are for internal use only.
Data changes visibility Visibility of Data Changes
PostgreSQL data changes visibility rule: during query execution data PostgreSQL data changes visibility rule: during a query execution, data
changes made by query itself (via SQL-function, SPI-function, triggers) changes made by the query itself (via SQL-function, SPI-function, triggers)
are invisible to the query scan. are invisible to the query scan. For example, in query
For example, in query
INSERT INTO a SELECT * FROM a INSERT INTO a SELECT * FROM a
tuples inserted are invisible for SELECT' scan. tuples inserted are invisible for SELECT' scan. In effect, this
duplicates the database table within itself (subject to unique index
rules, of course) without recursing.
But keep in mind notices about visibility in SPI documentation: But keep in mind this notice about visibility in the SPI documentation:
changes made by query Q are visible by queries which are started after Changes made by query Q are visible by queries which are started after
query Q, no matter - are they started inside Q (during execution of Q) or query Q, no matter whether they are started inside Q (during the
after Q is done. execution of Q) or after Q is done.
This is true for triggers as well. And so, though tuple being inserted This is true for triggers as well so, though a tuple being inserted
(tg_trigtuple) is not visible to queries in BEFORE trigger, this tuple (just (tg_trigtuple) is not visible to queries in a BEFORE trigger, this tuple
inserted) is visible to queries in AFTER trigger, and to queries in (just inserted) is visible to queries in an AFTER trigger, and to queries
BEFORE/AFTER triggers fired after this! in BEFORE/AFTER triggers fired after this!
Examples Examples
There are complex examples in contrib/spi and in There are more complex examples in in src/test/regress/regress.c and
src/test/regress/regress.c. in contrib/spi.
This is very simple example of trigger usage. Function trigf reports Here is a very simple example of trigger usage. Function trigf reports
about number of tuples in triggered relation ttest and in trigger fired the number of tuples in the triggered relation ttest and skips the
BEFORE INSERT/UPDATE checks against is attribute x NULL and skips operations operation if the query attempts to insert NULL into x (i.e - it acts as a
for NULLs (ala NOT NULL implementation using triggers without aborting NOT NULL constraint but doesn't abort the transaction).
transaction if NULL).
---------------------------------------------------------------------------- ----------------------------------------------------------------------------
#include "executor/spi.h" /* this is what you need to work with SPI */ #include "executor/spi.h" /* this is what you need to work with SPI */
@ -247,7 +275,7 @@ vac=> insert into ttest values (1);
NOTICE:trigf (fired before): there are 0 tuples in ttest NOTICE:trigf (fired before): there are 0 tuples in ttest
NOTICE:trigf (fired after ): there are 1 tuples in ttest NOTICE:trigf (fired after ): there are 1 tuples in ttest
^^^^^^^^ ^^^^^^^^
remember about visibility remember what we said about visibility.
INSERT 167793 1 INSERT 167793 1
vac=> select * from ttest; vac=> select * from ttest;
x x
@ -259,7 +287,7 @@ vac=> insert into ttest select x * 2 from ttest;
NOTICE:trigf (fired before): there are 1 tuples in ttest NOTICE:trigf (fired before): there are 1 tuples in ttest
NOTICE:trigf (fired after ): there are 2 tuples in ttest NOTICE:trigf (fired after ): there are 2 tuples in ttest
^^^^^^^^ ^^^^^^^^
remember about visibility remember what we said about visibility.
INSERT 167794 1 INSERT 167794 1
vac=> select * from ttest; vac=> select * from ttest;
x x
@ -288,7 +316,7 @@ NOTICE:trigf (fired after ): there are 1 tuples in ttest
NOTICE:trigf (fired before): there are 1 tuples in ttest NOTICE:trigf (fired before): there are 1 tuples in ttest
NOTICE:trigf (fired after ): there are 0 tuples in ttest NOTICE:trigf (fired after ): there are 0 tuples in ttest
^^^^^^^^ ^^^^^^^^
remember about visibility remember what we said about visibility.
DELETE 2 DELETE 2
vac=> select * from ttest; vac=> select * from ttest;
x x