1996-07-09 08:22:35 +02:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
2004-10-07 20:38:51 +02:00
|
|
|
* execJunk.c
|
1997-09-07 07:04:48 +02:00
|
|
|
* Junk attribute support stuff....
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
2004-08-29 06:13:13 +02:00
|
|
|
* Portions Copyright (c) 1996-2004, PostgreSQL Global Development Group
|
2000-01-26 06:58:53 +01:00
|
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
2004-10-11 04:02:41 +02:00
|
|
|
* $PostgreSQL: pgsql/src/backend/executor/execJunk.c,v 1.45 2004/10/11 02:02:41 neilc Exp $
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
1996-10-31 11:12:26 +01:00
|
|
|
#include "postgres.h"
|
|
|
|
|
1996-11-08 07:02:30 +01:00
|
|
|
#include "access/heapam.h"
|
1996-07-09 08:22:35 +02:00
|
|
|
#include "executor/executor.h"
|
1998-07-20 22:48:54 +02:00
|
|
|
#include "nodes/makefuncs.h"
|
1996-07-09 08:22:35 +02:00
|
|
|
|
|
|
|
/*-------------------------------------------------------------------------
|
1997-09-07 07:04:48 +02:00
|
|
|
* XXX this stuff should be rewritten to take advantage
|
|
|
|
* of ExecProject() and the ProjectionInfo node.
|
|
|
|
* -cim 6/3/91
|
|
|
|
*
|
1996-07-09 08:22:35 +02:00
|
|
|
* An attribute of a tuple living inside the executor, can be
|
|
|
|
* either a normal attribute or a "junk" attribute. "junk" attributes
|
|
|
|
* never make it out of the executor, i.e. they are never printed,
|
2004-10-11 04:02:41 +02:00
|
|
|
* returned or stored on disk. Their only purpose in life is to
|
1996-07-09 08:22:35 +02:00
|
|
|
* store some information useful only to the executor, mainly the values
|
|
|
|
* of some system attributes like "ctid" or rule locks.
|
1997-09-07 07:04:48 +02:00
|
|
|
*
|
1996-07-09 08:22:35 +02:00
|
|
|
* The general idea is the following: A target list consists of a list of
|
|
|
|
* Resdom nodes & expression pairs. Each Resdom node has an attribute
|
1999-05-17 19:03:51 +02:00
|
|
|
* called 'resjunk'. If the value of this attribute is true then the
|
1996-07-09 08:22:35 +02:00
|
|
|
* corresponding attribute is a "junk" attribute.
|
1997-09-07 07:04:48 +02:00
|
|
|
*
|
2001-01-29 01:39:20 +01:00
|
|
|
* When we initialize a plan we call 'ExecInitJunkFilter' to create
|
1996-07-09 08:22:35 +02:00
|
|
|
* and store the appropriate information in the 'es_junkFilter' attribute of
|
|
|
|
* EState.
|
1997-09-07 07:04:48 +02:00
|
|
|
*
|
1996-07-09 08:22:35 +02:00
|
|
|
* We then execute the plan ignoring the "resjunk" attributes.
|
1997-09-07 07:04:48 +02:00
|
|
|
*
|
1996-07-09 08:22:35 +02:00
|
|
|
* Finally, when at the top level we get back a tuple, we can call
|
|
|
|
* 'ExecGetJunkAttribute' to retrieve the value of the junk attributes we
|
|
|
|
* are interested in, and 'ExecRemoveJunk' to remove all the junk attributes
|
|
|
|
* from a tuple. This new "clean" tuple is then printed, replaced, deleted
|
|
|
|
* or inserted.
|
1997-09-07 07:04:48 +02:00
|
|
|
*
|
1996-07-09 08:22:35 +02:00
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
|
|
|
|
2004-10-07 20:38:51 +02:00
|
|
|
/*
|
1996-07-09 08:22:35 +02:00
|
|
|
* ExecInitJunkFilter
|
|
|
|
*
|
|
|
|
* Initialize the Junk filter.
|
1999-10-31 01:13:30 +02:00
|
|
|
*
|
2004-10-07 20:38:51 +02:00
|
|
|
* The source targetlist is passed in. The output tuple descriptor is
|
|
|
|
* built from the non-junk tlist entries, plus the passed specification
|
|
|
|
* of whether to include room for an OID or not.
|
2001-05-27 22:48:51 +02:00
|
|
|
* An optional resultSlot can be passed as well.
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
1997-09-08 04:41:22 +02:00
|
|
|
JunkFilter *
|
2004-10-07 20:38:51 +02:00
|
|
|
ExecInitJunkFilter(List *targetList, bool hasoid, TupleTableSlot *slot)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
1997-09-08 04:41:22 +02:00
|
|
|
JunkFilter *junkfilter;
|
1999-10-31 01:13:30 +02:00
|
|
|
TupleDesc cleanTupType;
|
2004-10-07 20:38:51 +02:00
|
|
|
int cleanLength;
|
|
|
|
AttrNumber *cleanMap;
|
2004-05-26 06:41:50 +02:00
|
|
|
ListCell *t;
|
1997-09-08 04:41:22 +02:00
|
|
|
AttrNumber cleanResno;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2004-10-07 20:38:51 +02:00
|
|
|
* Compute the tuple descriptor for the cleaned tuple.
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
2004-10-07 20:38:51 +02:00
|
|
|
cleanTupType = ExecCleanTypeFromTL(targetList, hasoid);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2004-10-07 20:38:51 +02:00
|
|
|
/*
|
|
|
|
* Now calculate the mapping between the original tuple's attributes and
|
|
|
|
* the "clean" tuple's attributes.
|
|
|
|
*
|
|
|
|
* The "map" is an array of "cleanLength" attribute numbers, i.e. one
|
|
|
|
* entry for every attribute of the "clean" tuple. The value of this
|
|
|
|
* entry is the attribute number of the corresponding attribute of the
|
|
|
|
* "original" tuple. (Zero indicates a NULL output attribute, but we
|
|
|
|
* do not use that feature in this routine.)
|
|
|
|
*/
|
|
|
|
cleanLength = cleanTupType->natts;
|
|
|
|
if (cleanLength > 0)
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
2004-10-07 20:38:51 +02:00
|
|
|
cleanMap = (AttrNumber *) palloc(cleanLength * sizeof(AttrNumber));
|
|
|
|
cleanResno = 1;
|
|
|
|
foreach(t, targetList)
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
2004-10-07 20:38:51 +02:00
|
|
|
TargetEntry *tle = lfirst(t);
|
|
|
|
Resdom *resdom = tle->resdom;
|
|
|
|
|
|
|
|
if (!resdom->resjunk)
|
|
|
|
{
|
|
|
|
cleanMap[cleanResno - 1] = resdom->resno;
|
|
|
|
cleanResno++;
|
|
|
|
}
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|
2004-10-07 20:38:51 +02:00
|
|
|
else
|
|
|
|
cleanMap = NULL;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2004-10-07 20:38:51 +02:00
|
|
|
* Finally create and initialize the JunkFilter struct.
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
2004-10-07 20:38:51 +02:00
|
|
|
junkfilter = makeNode(JunkFilter);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2004-10-07 20:38:51 +02:00
|
|
|
junkfilter->jf_targetList = targetList;
|
|
|
|
junkfilter->jf_cleanTupType = cleanTupType;
|
|
|
|
junkfilter->jf_cleanMap = cleanMap;
|
|
|
|
junkfilter->jf_resultSlot = slot;
|
|
|
|
|
|
|
|
if (slot)
|
|
|
|
ExecSetSlotDescriptor(slot, cleanTupType, false);
|
|
|
|
|
|
|
|
return junkfilter;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* ExecInitJunkFilterConversion
|
|
|
|
*
|
|
|
|
* Initialize a JunkFilter for rowtype conversions.
|
|
|
|
*
|
|
|
|
* Here, we are given the target "clean" tuple descriptor rather than
|
|
|
|
* inferring it from the targetlist. The target descriptor can contain
|
|
|
|
* deleted columns. It is assumed that the caller has checked that the
|
|
|
|
* non-deleted columns match up with the non-junk columns of the targetlist.
|
|
|
|
*/
|
|
|
|
JunkFilter *
|
|
|
|
ExecInitJunkFilterConversion(List *targetList,
|
|
|
|
TupleDesc cleanTupType,
|
|
|
|
TupleTableSlot *slot)
|
|
|
|
{
|
|
|
|
JunkFilter *junkfilter;
|
|
|
|
int cleanLength;
|
|
|
|
AttrNumber *cleanMap;
|
|
|
|
ListCell *t;
|
|
|
|
int i;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2004-10-07 20:38:51 +02:00
|
|
|
* Calculate the mapping between the original tuple's attributes and
|
2001-03-22 07:16:21 +01:00
|
|
|
* the "clean" tuple's attributes.
|
1997-09-07 07:04:48 +02:00
|
|
|
*
|
2001-03-22 07:16:21 +01:00
|
|
|
* The "map" is an array of "cleanLength" attribute numbers, i.e. one
|
|
|
|
* entry for every attribute of the "clean" tuple. The value of this
|
|
|
|
* entry is the attribute number of the corresponding attribute of the
|
2004-10-07 20:38:51 +02:00
|
|
|
* "original" tuple. We store zero for any deleted attributes, marking
|
|
|
|
* that a NULL is needed in the output tuple.
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
2004-10-07 20:38:51 +02:00
|
|
|
cleanLength = cleanTupType->natts;
|
1997-09-07 07:04:48 +02:00
|
|
|
if (cleanLength > 0)
|
|
|
|
{
|
2004-10-07 20:38:51 +02:00
|
|
|
cleanMap = (AttrNumber *) palloc0(cleanLength * sizeof(AttrNumber));
|
|
|
|
t = list_head(targetList);
|
|
|
|
for (i = 0; i < cleanLength; i++)
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
2004-10-07 20:38:51 +02:00
|
|
|
if (cleanTupType->attrs[i]->attisdropped)
|
|
|
|
continue; /* map entry is already zero */
|
|
|
|
for (;;)
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
2004-10-07 20:38:51 +02:00
|
|
|
TargetEntry *tle = lfirst(t);
|
|
|
|
Resdom *resdom = tle->resdom;
|
|
|
|
|
|
|
|
t = lnext(t);
|
|
|
|
if (!resdom->resjunk)
|
|
|
|
{
|
|
|
|
cleanMap[i] = resdom->resno;
|
|
|
|
break;
|
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
cleanMap = NULL;
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2001-01-29 01:39:20 +01:00
|
|
|
* Finally create and initialize the JunkFilter struct.
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
|
|
|
junkfilter = makeNode(JunkFilter);
|
|
|
|
|
|
|
|
junkfilter->jf_targetList = targetList;
|
|
|
|
junkfilter->jf_cleanTupType = cleanTupType;
|
|
|
|
junkfilter->jf_cleanMap = cleanMap;
|
2001-05-27 22:48:51 +02:00
|
|
|
junkfilter->jf_resultSlot = slot;
|
|
|
|
|
|
|
|
if (slot)
|
|
|
|
ExecSetSlotDescriptor(slot, cleanTupType, false);
|
2001-01-29 01:39:20 +01:00
|
|
|
|
1998-09-01 05:29:17 +02:00
|
|
|
return junkfilter;
|
2001-01-29 01:39:20 +01:00
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2004-10-07 20:38:51 +02:00
|
|
|
/*
|
1996-07-09 08:22:35 +02:00
|
|
|
* ExecGetJunkAttribute
|
|
|
|
*
|
|
|
|
* Given a tuple (slot), the junk filter and a junk attribute's name,
|
2001-01-29 01:39:20 +01:00
|
|
|
* extract & return the value and isNull flag of this attribute.
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
|
|
|
* It returns false iff no junk attribute with such name was found.
|
|
|
|
*/
|
|
|
|
bool
|
1997-09-08 23:56:23 +02:00
|
|
|
ExecGetJunkAttribute(JunkFilter *junkfilter,
|
|
|
|
TupleTableSlot *slot,
|
1997-09-07 07:04:48 +02:00
|
|
|
char *attrName,
|
1997-09-08 23:56:23 +02:00
|
|
|
Datum *value,
|
|
|
|
bool *isNull)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
1997-09-08 04:41:22 +02:00
|
|
|
List *targetList;
|
2004-05-26 06:41:50 +02:00
|
|
|
ListCell *t;
|
1997-09-08 04:41:22 +02:00
|
|
|
AttrNumber resno;
|
|
|
|
TupleDesc tupType;
|
|
|
|
HeapTuple tuple;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* first look in the junkfilter's target list for an attribute with
|
|
|
|
* the given name
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
|
|
|
resno = InvalidAttrNumber;
|
|
|
|
targetList = junkfilter->jf_targetList;
|
|
|
|
|
|
|
|
foreach(t, targetList)
|
|
|
|
{
|
1997-09-08 04:41:22 +02:00
|
|
|
TargetEntry *tle = lfirst(t);
|
2003-08-11 22:46:47 +02:00
|
|
|
Resdom *resdom = tle->resdom;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2003-08-11 22:46:47 +02:00
|
|
|
if (resdom->resjunk && resdom->resname &&
|
|
|
|
(strcmp(resdom->resname, attrName) == 0))
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
|
|
|
/* We found it ! */
|
|
|
|
resno = resdom->resno;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resno == InvalidAttrNumber)
|
|
|
|
{
|
|
|
|
/* Ooops! We couldn't find this attribute... */
|
1998-09-01 05:29:17 +02:00
|
|
|
return false;
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1997-09-07 07:04:48 +02:00
|
|
|
* Now extract the attribute value from the tuple.
|
|
|
|
*/
|
|
|
|
tuple = slot->val;
|
2004-10-07 20:38:51 +02:00
|
|
|
tupType = slot->ttc_tupleDescriptor;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
1998-01-31 05:39:26 +01:00
|
|
|
*value = heap_getattr(tuple, resno, tupType, isNull);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
return true;
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
|
|
|
|
2004-10-07 20:38:51 +02:00
|
|
|
/*
|
1996-07-09 08:22:35 +02:00
|
|
|
* ExecRemoveJunk
|
|
|
|
*
|
|
|
|
* Construct and return a tuple with all the junk attributes removed.
|
2001-05-27 22:48:51 +02:00
|
|
|
*
|
|
|
|
* Note: for historical reasons, this does not store the constructed
|
|
|
|
* tuple into the junkfilter's resultSlot. The caller should do that
|
|
|
|
* if it wants to.
|
1996-07-09 08:22:35 +02:00
|
|
|
*/
|
|
|
|
HeapTuple
|
1997-09-08 23:56:23 +02:00
|
|
|
ExecRemoveJunk(JunkFilter *junkfilter, TupleTableSlot *slot)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
2004-10-07 20:38:51 +02:00
|
|
|
#define PREALLOC_SIZE 64
|
1997-09-08 04:41:22 +02:00
|
|
|
HeapTuple tuple;
|
|
|
|
HeapTuple cleanTuple;
|
|
|
|
AttrNumber *cleanMap;
|
|
|
|
TupleDesc cleanTupType;
|
|
|
|
TupleDesc tupType;
|
|
|
|
int cleanLength;
|
2004-10-07 20:38:51 +02:00
|
|
|
int oldLength;
|
1997-09-08 04:41:22 +02:00
|
|
|
int i;
|
|
|
|
Datum *values;
|
|
|
|
char *nulls;
|
2004-06-04 22:35:21 +02:00
|
|
|
Datum *old_values;
|
|
|
|
char *old_nulls;
|
2004-10-07 20:38:51 +02:00
|
|
|
Datum values_array[PREALLOC_SIZE];
|
|
|
|
Datum old_values_array[PREALLOC_SIZE];
|
|
|
|
char nulls_array[PREALLOC_SIZE];
|
|
|
|
char old_nulls_array[PREALLOC_SIZE];
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* get info from the slot and the junk filter
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
|
|
|
tuple = slot->val;
|
2004-10-07 20:38:51 +02:00
|
|
|
tupType = slot->ttc_tupleDescriptor;
|
|
|
|
oldLength = tupType->natts + 1; /* +1 for NULL */
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2001-01-29 01:39:20 +01:00
|
|
|
cleanTupType = junkfilter->jf_cleanTupType;
|
2004-10-07 20:38:51 +02:00
|
|
|
cleanLength = cleanTupType->natts;
|
1997-09-07 07:04:48 +02:00
|
|
|
cleanMap = junkfilter->jf_cleanMap;
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* Create the arrays that will hold the attribute values and the null
|
2004-06-04 22:35:21 +02:00
|
|
|
* information for the old tuple and new "clean" tuple.
|
1997-09-07 07:04:48 +02:00
|
|
|
*
|
2001-03-22 07:16:21 +01:00
|
|
|
* Note: we use memory on the stack to optimize things when we are
|
2002-09-04 22:31:48 +02:00
|
|
|
* dealing with a small number of attributes. for large tuples we just
|
|
|
|
* use palloc.
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
2004-10-07 20:38:51 +02:00
|
|
|
if (cleanLength > PREALLOC_SIZE)
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
2001-01-29 01:39:20 +01:00
|
|
|
values = (Datum *) palloc(cleanLength * sizeof(Datum));
|
|
|
|
nulls = (char *) palloc(cleanLength * sizeof(char));
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|
1996-07-09 08:22:35 +02:00
|
|
|
else
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
|
|
|
values = values_array;
|
|
|
|
nulls = nulls_array;
|
|
|
|
}
|
2004-10-07 20:38:51 +02:00
|
|
|
if (oldLength > PREALLOC_SIZE)
|
2004-06-04 22:35:21 +02:00
|
|
|
{
|
2004-10-07 20:38:51 +02:00
|
|
|
old_values = (Datum *) palloc(oldLength * sizeof(Datum));
|
|
|
|
old_nulls = (char *) palloc(oldLength * sizeof(char));
|
2004-06-04 22:35:21 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
old_values = old_values_array;
|
|
|
|
old_nulls = old_nulls_array;
|
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
2004-10-07 20:38:51 +02:00
|
|
|
* Extract all the values of the old tuple, offsetting the arrays
|
|
|
|
* so that old_values[0] is NULL and old_values[1] is the first
|
|
|
|
* source attribute; this exactly matches the numbering convention
|
|
|
|
* in cleanMap.
|
2004-06-04 22:35:21 +02:00
|
|
|
*/
|
2004-10-07 20:38:51 +02:00
|
|
|
heap_deformtuple(tuple, tupType, old_values + 1, old_nulls + 1);
|
|
|
|
old_values[0] = (Datum) 0;
|
|
|
|
old_nulls[0] = 'n';
|
2004-06-04 22:35:21 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Transpose into proper fields of the new tuple.
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
|
|
|
for (i = 0; i < cleanLength; i++)
|
|
|
|
{
|
2004-10-07 20:38:51 +02:00
|
|
|
int j = cleanMap[i];
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2004-06-04 22:35:21 +02:00
|
|
|
values[i] = old_values[j];
|
|
|
|
nulls[i] = old_nulls[j];
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
1997-09-07 07:04:48 +02:00
|
|
|
* Now form the new tuple.
|
|
|
|
*/
|
2004-06-04 22:35:21 +02:00
|
|
|
cleanTuple = heap_formtuple(cleanTupType, values, nulls);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2001-03-22 07:16:21 +01:00
|
|
|
/*
|
|
|
|
* We are done. Free any space allocated for 'values' and 'nulls' and
|
|
|
|
* return the new tuple.
|
1997-09-07 07:04:48 +02:00
|
|
|
*/
|
2004-06-04 22:35:21 +02:00
|
|
|
if (values != values_array)
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
|
|
|
pfree(values);
|
|
|
|
pfree(nulls);
|
|
|
|
}
|
2004-06-04 22:35:21 +02:00
|
|
|
if (old_values != old_values_array)
|
|
|
|
{
|
|
|
|
pfree(old_values);
|
|
|
|
pfree(old_nulls);
|
|
|
|
}
|
1997-09-07 07:04:48 +02:00
|
|
|
|
1998-09-01 05:29:17 +02:00
|
|
|
return cleanTuple;
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|