mirror of
				https://github.com/postgres/postgres.git
				synced 2025-10-31 00:03:57 -04:00 
			
		
		
		
	New edition by D'Arcy and me.
This commit is contained in:
		
							parent
							
								
									d75206fdf5
								
							
						
					
					
						commit
						91eb34e9eb
					
				
							
								
								
									
										280
									
								
								doc/spi.txt
									
									
									
									
									
								
							
							
						
						
									
										280
									
								
								doc/spi.txt
									
									
									
									
									
								
							| @ -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); | ||||||
|  | |||||||
							
								
								
									
										196
									
								
								doc/trigger.txt
									
									
									
									
									
								
							
							
						
						
									
										196
									
								
								doc/trigger.txt
									
									
									
									
									
								
							| @ -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 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user