Example #1
0
static int
btnamefastcmp(Datum x, Datum y, SortSupport ssup)
{
	Name		a = DatumGetName(x);
	Name		b = DatumGetName(y);

	return strncmp(NameStr(*a), NameStr(*b), NAMEDATALEN);
}
Example #2
0
/*
 * Check whether the function is IMMUTABLE.
 */
bool
is_immutable_func(Oid funcid)
{
	HeapTuple		tp;
	bool			isnull;
	Datum			datum;

	tp = SearchSysCache(PROCOID, ObjectIdGetDatum(funcid), 0, 0, 0);
	if (!HeapTupleIsValid(tp))
		elog(ERROR, "cache lookup failed for function %u", funcid);

#ifdef DEBUG_FDW
	/* print function name and its immutability */
	{
		char		   *proname;
		datum = SysCacheGetAttr(PROCOID, tp, Anum_pg_proc_proname, &isnull);
		proname = pstrdup(DatumGetName(datum)->data);
		elog(DEBUG1, "func %s(%u) is%s immutable", proname, funcid,
			(DatumGetChar(datum) == PROVOLATILE_IMMUTABLE) ? "" : " not");
		pfree(proname);
	}
#endif

	datum = SysCacheGetAttr(PROCOID, tp, Anum_pg_proc_provolatile, &isnull);
	ReleaseSysCache(tp);

	return (DatumGetChar(datum) == PROVOLATILE_IMMUTABLE);
}
Example #3
0
static int
InitFsysInterface(FsysName name, FsysInterface fsys) {
	int retval = 0;
	Relation	rel;
	TupleDesc	dsc;
	HeapScanDesc scandesc;
	HeapTuple	tuple;
	ScanKeyData entry[1];
	Datum		funcDatum;
	Datum		libFileDatum;
	char	   *libFile;
	char	   *funcName;
	bool 		isNull;

	/*
	 * Search pg_filesystem.  We use a heapscan here even though there is an
	 * index on oid, on the theory that pg_filesystem will usually have just a
	 * few entries and so an indexed lookup is a waste of effort.
	 */
	rel = heap_open(FileSystemRelationId, AccessShareLock);
	dsc = RelationGetDescr(rel);

	ScanKeyInit(&entry[0],
				Anum_pg_filesystem_fsysname,
				BTEqualStrategyNumber, F_NAMEEQ,
				CStringGetDatum(name));
	scandesc = heap_beginscan(rel, SnapshotNow, 1, entry);
	tuple = heap_getnext(scandesc, ForwardScanDirection);

	/* We assume that there can be at most one matching tuple */
	if (!HeapTupleIsValid(tuple))
		ereport(ERROR,
                (errcode(ERRCODE_UNDEFINED_OBJECT),
                 errmsg("filesystem \"%s\" does not exist", name)));

	/* get libfile */
	libFileDatum = heap_getattr(tuple, Anum_pg_filesystem_fsyslibfile, dsc, &isNull);
	if(isNull)
	{
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("filesystem \"%s\" has no libfile specified", name)));
	}
	libFile = TextDatumGetCString(libFileDatum);

	/* Init all funcs used by filesystem */
	for(int i = 0; i < FSYS_FUNC_TOTALNUM; i++)
	{
		FmgrInfo *finfo = &(fsys->fsysFuncs[i]);
		void	   *libraryhandle;

		funcDatum = heap_getattr(tuple, fsys_func_type_to_attnum(i), dsc, &isNull);

		if(isNull)
		{
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_OBJECT),
					 errmsg("filesystem \"%s\" has no %s function defined", name,
							fsys_func_type_to_name(i))));
		}

		funcName = NameStr(*(DatumGetName(funcDatum)));

		finfo->fn_addr = load_external_function(libFile, funcName, true,
												&libraryhandle);
		finfo->fn_oid = (Oid) 1;
		finfo->fn_nargs = 0;
		finfo->fn_strict = 0;
		finfo->fn_strict = 0;
		finfo->fn_retset = 0;
		finfo->fn_stats = 1;
		finfo->fn_extra = NULL;
		finfo->fn_mcxt = CurrentMemoryContext;
		finfo->fn_expr = NULL;
	}

	heap_endscan(scandesc);
	heap_close(rel, AccessShareLock);

	return retval;
}
Example #4
0
/*
 * Add an attribute to the hash calculation.
 * **IMPORTANT: any new hard coded support for a data type in here
 * must be added to isGreenplumDbHashable() below!
 *
 * Note that the caller should provide the base type if the datum is
 * of a domain type. It is quite expensive to call get_typtype() and
 * getBaseType() here since this function gets called a lot for the
 * same set of Datums.
 *
 * @param hashFn called to update the hash value.
 * @param clientData passed to hashFn.
 */
void
hashDatum(Datum datum, Oid type, datumHashFunction hashFn, void *clientData)
{

	void	   *buf = NULL;		/* pointer to the data */
	size_t		len = 0;		/* length for the data buffer */
	
	int64		intbuf;			/* an 8 byte buffer for all integer sizes */
		
	float4		buf_f4;
	float8		buf_f8;
	Timestamp	tsbuf;			/* timestamp data dype is either a double or
								 * int8 (determined in compile time) */
	TimestampTz tstzbuf;
	DateADT		datebuf;
	TimeADT		timebuf;
	TimeTzADT  *timetzptr;
	Interval   *intervalptr;
	AbsoluteTime abstime_buf;
	RelativeTime reltime_buf;
	TimeInterval tinterval;
	AbsoluteTime tinterval_len;
	
	Numeric		num;
	bool		bool_buf;
	char        char_buf;
	Name		namebuf;
	
	ArrayType  *arrbuf;
	inet		 *inetptr; /* inet/cidr */
	unsigned char inet_hkey[sizeof(inet_struct)];
	macaddr		*macptr; /* MAC address */
	
	VarBit		*vbitptr;
	
	int2vector *i2vec_buf;
	oidvector  *oidvec_buf;
	
	Cash		cash_buf;
	AclItem	   *aclitem_ptr;
	uint32		aclitem_buf;
	
	/*
	 * special case buffers
	 */
	uint32		nanbuf;
	uint32		invalidbuf;

	void *tofree = NULL;

	/*
	 * Select the hash to be performed according to the field type we are adding to the
	 * hash.
	 */
	switch (type)
	{
		/*
		 * ======= NUMERIC TYPES ========
		 */
		case INT2OID:			/* -32 thousand to 32 thousand, 2-byte storage */
			intbuf = (int64) DatumGetInt16(datum);		/* cast to 8 byte before
														 * hashing */
			buf = &intbuf;
			len = sizeof(intbuf);
			break;

		case INT4OID:			/* -2 billion to 2 billion integer, 4-byte
								 * storage */
			intbuf = (int64) DatumGetInt32(datum);		/* cast to 8 byte before
														 * hashing */
			buf = &intbuf;
			len = sizeof(intbuf);
			break;
			
		case INT8OID:			/* ~18 digit integer, 8-byte storage */
			intbuf = DatumGetInt64(datum);		/* cast to 8 byte before
												 * hashing */
			buf = &intbuf;
			len = sizeof(intbuf);
			break;

		case FLOAT4OID: /* single-precision floating point number,
								 * 4-byte storage */
			buf_f4 = DatumGetFloat4(datum);

			/*
			 * On IEEE-float machines, minus zero and zero have different bit
			 * patterns but should compare as equal.  We must ensure that they
			 * have the same hash value, which is most easily done this way:
			 */
			if (buf_f4 == (float4) 0)
				buf_f4 = 0.0;

			buf = &buf_f4;
			len = sizeof(buf_f4);
			break;

		case FLOAT8OID: /* double-precision floating point number,
								 * 8-byte storage */
			buf_f8 = DatumGetFloat8(datum);

			/*
			 * On IEEE-float machines, minus zero and zero have different bit
			 * patterns but should compare as equal.  We must ensure that they
			 * have the same hash value, which is most easily done this way:
			 */
			if (buf_f8 == (float8) 0)
				buf_f8 = 0.0;

			buf = &buf_f8;
			len = sizeof(buf_f8);
			break;

		case NUMERICOID:

			num = DatumGetNumeric(datum);

			if (NUMERIC_IS_NAN(num))
			{
				nanbuf = NAN_VAL;
				buf = &nanbuf;
				len = sizeof(nanbuf);
			}
			else
				/* not a nan */
			{
				buf = num->n_data;
				len = (VARSIZE(num) - NUMERIC_HDRSZ);
			}

            /* 
             * If we did a pg_detoast_datum, we need to remember to pfree, 
             * or we will leak memory.  Because of the 1-byte varlena header stuff.
             */
            if (num != DatumGetPointer(datum)) 
                tofree = num;

			break;
		
		/*
		 * ====== CHARACTER TYPES =======
		 */
		case CHAROID:			/* char(1), single character */
			char_buf = DatumGetChar(datum);
			buf = &char_buf;
			len = 1;
			break;

		case BPCHAROID: /* char(n), blank-padded string, fixed storage */
		case TEXTOID:   /* text */
		case VARCHAROID: /* varchar */ 
		case BYTEAOID:   /* bytea */
			{
				int tmplen;
				varattrib_untoast_ptr_len(datum, (char **) &buf, &tmplen, &tofree);
				/* adjust length to not include trailing blanks */
				if (type != BYTEAOID && tmplen > 1)
					tmplen = ignoreblanks((char *) buf, tmplen);

				len = tmplen;
				break;
			}

		case NAMEOID:
			namebuf = DatumGetName(datum);
			len = NAMEDATALEN;
			buf = NameStr(*namebuf);

			/* adjust length to not include trailing blanks */
			if (len > 1)
				len = ignoreblanks((char *) buf, len);
			break;
		
		/*
		 * ====== OBJECT IDENTIFIER TYPES ======
		 */
		case OIDOID:				/* object identifier(oid), maximum 4 billion */
		case REGPROCOID:			/* function name */
		case REGPROCEDUREOID:		/* function name with argument types */
		case REGOPEROID:			/* operator name */
		case REGOPERATOROID:		/* operator with argument types */
		case REGCLASSOID:			/* relation name */
		case REGTYPEOID:			/* data type name */
			intbuf = (int64) DatumGetUInt32(datum);	/* cast to 8 byte before hashing */
			buf = &intbuf;
			len = sizeof(intbuf);
			break;

        case TIDOID:                /* tuple id (6 bytes) */
            buf = DatumGetPointer(datum);
            len = SizeOfIptrData;
            break;
			
		/*
		 * ====== DATE/TIME TYPES ======
		 */
		case TIMESTAMPOID:		/* date and time */
			tsbuf = DatumGetTimestamp(datum);
			buf = &tsbuf;
			len = sizeof(tsbuf);
			break;

		case TIMESTAMPTZOID:	/* date and time with time zone */
			tstzbuf = DatumGetTimestampTz(datum);
			buf = &tstzbuf;
			len = sizeof(tstzbuf);
			break;

		case DATEOID:			/* ANSI SQL date */
			datebuf = DatumGetDateADT(datum);
			buf = &datebuf;
			len = sizeof(datebuf);
			break;

		case TIMEOID:			/* hh:mm:ss, ANSI SQL time */
			timebuf = DatumGetTimeADT(datum);
			buf = &timebuf;
			len = sizeof(timebuf);
			break;

		case TIMETZOID: /* time with time zone */
			
			/*
			 * will not compare to TIMEOID on equal values.
			 * Postgres never attempts to compare the two as well.
			 */
			timetzptr = DatumGetTimeTzADTP(datum);
			buf = (unsigned char *) timetzptr;
			
			/*
			 * Specify hash length as sizeof(double) + sizeof(int4), not as
			 * sizeof(TimeTzADT), so that any garbage pad bytes in the structure
			 * won't be included in the hash!
			 */
			len = sizeof(timetzptr->time) + sizeof(timetzptr->zone);
			break;

		case INTERVALOID:		/* @ <number> <units>, time interval */
			intervalptr = DatumGetIntervalP(datum);
			buf = (unsigned char *) intervalptr;
			/*
			 * Specify hash length as sizeof(double) + sizeof(int4), not as
			 * sizeof(Interval), so that any garbage pad bytes in the structure
			 * won't be included in the hash!
			 */
			len = sizeof(intervalptr->time) + sizeof(intervalptr->month);
			break;
			
		case ABSTIMEOID:
			abstime_buf = DatumGetAbsoluteTime(datum);
			
			if (abstime_buf == INVALID_ABSTIME)
			{
				/* hash to a constant value */
				invalidbuf = INVALID_VAL;
				len = sizeof(invalidbuf);
				buf = &invalidbuf;
			}
			else
			{
				len = sizeof(abstime_buf);
				buf = &abstime_buf;
			}
					
			break;

		case RELTIMEOID:
			reltime_buf = DatumGetRelativeTime(datum);
			
			if (reltime_buf == INVALID_RELTIME)
			{
				/* hash to a constant value */
				invalidbuf = INVALID_VAL;
				len = sizeof(invalidbuf);
				buf = &invalidbuf;
			}
			else
			{
				len = sizeof(reltime_buf);
				buf = &reltime_buf;
			}
				
			break;
			
		case TINTERVALOID:
			tinterval = DatumGetTimeInterval(datum);
			
			/*
			 * check if a valid interval. the '0' status code
			 * stands for T_INTERVAL_INVAL which is defined in
			 * nabstime.c. We use the actual value instead
			 * of defining it again here.
			 */
			if(tinterval->status == 0 ||
			   tinterval->data[0] == INVALID_ABSTIME ||
			   tinterval->data[1] == INVALID_ABSTIME)
			{
				/* hash to a constant value */
				invalidbuf = INVALID_VAL;
				len = sizeof(invalidbuf);
				buf = &invalidbuf;				
			}
			else
			{
				/* normalize on length of the time interval */
				tinterval_len = tinterval->data[1] -  tinterval->data[0];
				len = sizeof(tinterval_len);
				buf = &tinterval_len;	
			}

			break;
			
		/*
		 * ======= NETWORK TYPES ========
		 */
		case INETOID:
		case CIDROID:
			
			inetptr = DatumGetInetP(datum);
			len = inet_getkey(inetptr, inet_hkey, sizeof(inet_hkey)); /* fill-in inet_key & get len */
			buf = inet_hkey;
			break;
		
		case MACADDROID:
			
			macptr = DatumGetMacaddrP(datum);
			len = sizeof(macaddr);
			buf = (unsigned char *) macptr;
			break;
			
		/*
		 * ======== BIT STRINGS ========
		 */
		case BITOID:
		case VARBITOID:
			
			/*
			 * Note that these are essentially strings.
			 * we don't need to worry about '10' and '010'
			 * to compare, b/c they will not, by design.
			 * (see SQL standard, and varbit.c)
			 */
			vbitptr = DatumGetVarBitP(datum);
			len = VARBITBYTES(vbitptr);
			buf = (char *) VARBITS(vbitptr);
			break;

		/*
		 * ======= other types =======
		 */
		case BOOLOID:			/* boolean, 'true'/'false' */
			bool_buf = DatumGetBool(datum);
			buf = &bool_buf;
			len = sizeof(bool_buf);
			break;
			
		/*
		 * We prepare the hash key for aclitems just like postgresql does.
		 * (see code and comment in acl.c: hash_aclitem() ).
		 */
		case ACLITEMOID:
			aclitem_ptr = DatumGetAclItemP(datum);
			aclitem_buf = (uint32) (aclitem_ptr->ai_privs + aclitem_ptr->ai_grantee + aclitem_ptr->ai_grantor);
			buf = &aclitem_buf;
			len = sizeof(aclitem_buf);
			break;
			
		/*
		 * ANYARRAY is a pseudo-type. We use it to include
		 * any of the array types (OIDs 1007-1033 in pg_type.h).
		 * caller needs to be sure the type is ANYARRAYOID
		 * before calling cdbhash on an array (INSERT and COPY do so).
		 */
		case ANYARRAYOID:	
					
			arrbuf = DatumGetArrayTypeP(datum);
			len = VARSIZE(arrbuf) - VARHDRSZ;
			buf = VARDATA(arrbuf);
			break;
			
		case INT2VECTOROID:
			i2vec_buf = (int2vector *) DatumGetPointer(datum);
			len = i2vec_buf->dim1 * sizeof(int2);
			buf = (void *)i2vec_buf->values;
			break;
			
		case OIDVECTOROID:	
			oidvec_buf = (oidvector *) DatumGetPointer(datum);
			len = oidvec_buf->dim1 * sizeof(Oid);
			buf = oidvec_buf->values;
			break;
			
		case CASHOID: /* cash is stored in int32 internally */
			cash_buf = (* (Cash *)DatumGetPointer(datum));
			len = sizeof(Cash);
			buf = &cash_buf;
			break;
				
		default:
			ereport(ERROR,
					(errcode(ERRCODE_CDB_FEATURE_NOT_YET),
					 errmsg("Type %u is not hashable.", type)));

	}							/* switch(type) */

	/* do the hash using the selected algorithm */
	hashFn(clientData, buf, len);
	if(tofree)
		pfree(tofree);
}
/*
 * Fetch the subscription from the syscache.
 */
Subscription *
GetSubscription(Oid subid, bool missing_ok)
{
	HeapTuple	tup;
	Subscription *sub;
	Form_pg_subscription subform;
	Datum		datum;
	bool		isnull;

	tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));

	if (!HeapTupleIsValid(tup))
	{
		if (missing_ok)
			return NULL;

		elog(ERROR, "cache lookup failed for subscription %u", subid);
	}

	subform = (Form_pg_subscription) GETSTRUCT(tup);

	sub = (Subscription *) palloc(sizeof(Subscription));
	sub->oid = subid;
	sub->dbid = subform->subdbid;
	sub->name = pstrdup(NameStr(subform->subname));
	sub->owner = subform->subowner;
	sub->enabled = subform->subenabled;

	/* Get conninfo */
	datum = SysCacheGetAttr(SUBSCRIPTIONOID,
							tup,
							Anum_pg_subscription_subconninfo,
							&isnull);
	Assert(!isnull);
	sub->conninfo = TextDatumGetCString(datum);

	/* Get slotname */
	datum = SysCacheGetAttr(SUBSCRIPTIONOID,
							tup,
							Anum_pg_subscription_subslotname,
							&isnull);
	if (!isnull)
		sub->slotname = pstrdup(NameStr(*DatumGetName(datum)));
	else
		sub->slotname = NULL;

	/* Get synccommit */
	datum = SysCacheGetAttr(SUBSCRIPTIONOID,
							tup,
							Anum_pg_subscription_subsynccommit,
							&isnull);
	Assert(!isnull);
	sub->synccommit = TextDatumGetCString(datum);

	/* Get publications */
	datum = SysCacheGetAttr(SUBSCRIPTIONOID,
							tup,
							Anum_pg_subscription_subpublications,
							&isnull);
	Assert(!isnull);
	sub->publications = textarray_to_stringlist(DatumGetArrayTypeP(datum));

	ReleaseSysCache(tup);

	return sub;
}
Example #6
0
/*
 * dbms_stats_import
 *   Import exported statistics from stdin or a file.
 *
 *   Order of arguments:
 *     1) schema name
 *     2) relation oid
 *     3) attribute name
 *     4) absolute path of source file, or 'stdin' (case insensitive)
 */
Datum
dbms_stats_import(PG_FUNCTION_ARGS)
{
	char		   *nspname;
	char		   *relname;
	char		   *attname;
	char		   *filename;	/* filename, or NULL for STDIN */
	int				ret;
	int				i;
	uint32			r_num;
	HeapTuple	   *r_tups;
	TupleDesc		r_tupdesc;
	SPIPlanPtr		r_upd_plan = NULL;
	SPIPlanPtr		r_ins_plan = NULL;
	SPIPlanPtr		c_sel_plan = NULL;
	SPIPlanPtr		c_del_plan = NULL;
	SPIPlanPtr		c_ins_plan = NULL;

	/* get validated arguments */
	get_args(fcinfo, &nspname, &relname, &attname, &filename);

	/* for debug use */
	elog(DEBUG3, "%s() f=%s n=%s r=%s a=%s", __FUNCTION__,
		 filename ? filename : "(null)",
		 nspname ? nspname : "(null)",
		 relname ? relname : "(null)",
		 attname ? attname : "(null)");

	/* connect to SPI */
	ret = SPI_connect();
	if (ret != SPI_OK_CONNECT)
		elog(ERROR, "pg_dbms_stats: SPI_connect => %d", ret);

	/* lock dummy statistics tables. */
	spi_exec_utility("LOCK dbms_stats.relation_stats_locked"
						" IN SHARE UPDATE EXCLUSIVE MODE");
	spi_exec_utility("LOCK dbms_stats.column_stats_locked"
						" IN SHARE UPDATE EXCLUSIVE MODE");

	/*
	 * Create a temp table to save the statistics to import.
	 * This table should fit with the content of export files.
	 */
	spi_exec_utility("CREATE TEMP TABLE dbms_stats_work_stats ("
					 "nspname          name   NOT NULL,"
					 "relname          name   NOT NULL,"
					 "relpages         int4   NOT NULL,"
					 "reltuples        float4 NOT NULL,"
#if PG_VERSION_NUM >= 90200
					 "relallvisible    int4   NOT NULL,"
#endif
					 "curpages         int4   NOT NULL,"
					 "last_analyze     timestamp with time zone,"
					 "last_autoanalyze timestamp with time zone,"
					 "attname          name,"
					 "nspname_of_typename name,"
					 "typname name,"
					 "atttypmod int4,"
					 "stainherit       bool,"
					 "stanullfrac      float4,"
					 "stawidth         int4,"
					 "stadistinct      float4,"
					 "stakind1         int2,"
					 "stakind2         int2,"
					 "stakind3         int2,"
					 "stakind4         int2,"
#if PG_VERSION_NUM >= 90200
					 "stakind5         int2,"
#endif
					 "staop1           oid,"
					 "staop2           oid,"
					 "staop3           oid,"
					 "staop4           oid,"
#if PG_VERSION_NUM >= 90200
					 "staop5           oid,"
#endif
					 "stanumbers1      float4[],"
					 "stanumbers2      float4[],"
					 "stanumbers3      float4[],"
					 "stanumbers4      float4[],"
#if PG_VERSION_NUM >= 90200
					 "stanumbers5      float4[],"
#endif
					 "stavalues1       dbms_stats.anyarray,"
					 "stavalues2       dbms_stats.anyarray,"
					 "stavalues3       dbms_stats.anyarray,"
					 "stavalues4       dbms_stats.anyarray"
#if PG_VERSION_NUM >= 90200
					",stavalues5       dbms_stats.anyarray"
#endif
					 ")");

	/* load the statistics from export file to the temp table */
	import_stats_from_file(filename, nspname, relname, attname);

	/* Determine the Oid of local table from the tablename and schemaname. */
	ret = SPI_execute("SELECT DISTINCT w.nspname, w.relname, c.oid, "
							 "w.relpages, w.reltuples, "
							 "w.curpages, w.last_analyze, w.last_autoanalyze "
#if PG_VERSION_NUM >= 90200
							 ",w.relallvisible "
#endif
						"FROM pg_catalog.pg_class c "
						"JOIN pg_catalog.pg_namespace n "
						  "ON (c.relnamespace = n.oid) "
					   "RIGHT JOIN dbms_stats_work_stats w "
						  "ON (w.relname = c.relname AND w.nspname = n.nspname) "
					   "ORDER BY 1, 2", false, 0);
	if (ret != SPI_OK_SELECT)
		elog(ERROR, "pg_dbms_stats: SPI_execute => %d", ret);

	/*
	 * If there is no record in the staging table after loading source and
	 * deleting unnecessary records, we treat it as an error.
	 */
	if (SPI_processed == 0)
		elog(ERROR, "no per-table statistic data to be imported");

	/* */
	r_num = SPI_processed;
	r_tups = SPI_tuptable->vals;
	r_tupdesc = SPI_tuptable->tupdesc;
	for (i = 0; i < r_num; i++)
	{
		bool	isnull;
		Datum	w_nspname;
		Datum	w_relname;
		Datum	w_relid;
		Datum	values[9];
		char	nulls[9] = {'t', 't', 't', 't', 't', 't', 't', 't', 't'};
		Oid		r_types[9] = {NAMEOID, NAMEOID, INT4OID, FLOAT4OID, INT4OID,
							  TIMESTAMPTZOID, TIMESTAMPTZOID, OIDOID, INT4OID};
		Oid		c_types[5] = {OIDOID, INT2OID, NAMEOID, NAMEOID,
							  NAMEOID};
		uint32		c_num;
		TupleDesc	c_tupdesc;
		HeapTuple  *c_tups;
		int			j;

		values[0] = w_nspname = SPI_getbinval(r_tups[i], r_tupdesc, 1, &isnull);
		values[1] = w_relname = SPI_getbinval(r_tups[i], r_tupdesc, 2, &isnull);
		values[7] = w_relid = SPI_getbinval(r_tups[i], r_tupdesc, 3, &isnull);
		if (isnull)
		{
			elog(WARNING, "relation \"%s.%s\" does not exist",
					DatumGetName(w_nspname)->data,
					DatumGetName(w_relname)->data);
			continue;
		}

		values[2] = SPI_getbinval(r_tups[i], r_tupdesc, 4, &isnull);
		values[3] = SPI_getbinval(r_tups[i], r_tupdesc, 5, &isnull);
		values[4] = SPI_getbinval(r_tups[i], r_tupdesc, 6, &isnull);
		values[5] = SPI_getbinval(r_tups[i], r_tupdesc, 7, &isnull);
		nulls[5] = isnull ? 'n' : 't';
		values[6] = SPI_getbinval(r_tups[i], r_tupdesc, 8, &isnull);
		nulls[6] = isnull ? 'n' : 't';
		values[8] = SPI_getbinval(r_tups[i], r_tupdesc, 9, &isnull);

		/*
		 * First we try UPDATE with the oid.  When no record matched, try
		 * INSERT.  We can't use DELETE-then-INSERT method because we have FK
		 * on relation_stats_locked so DELETE would delete child records in
		 * column_stats_locked undesirably.
		 */
		spi_exec_query("UPDATE dbms_stats.relation_stats_locked SET "
				"relname = quote_ident($1) || '.' || quote_ident($2), "
				"relpages = $3, reltuples = $4, "
#if PG_VERSION_NUM >= 90200
				"relallvisible = $9, "
#endif
				"curpages = $5, last_analyze = $6, last_autoanalyze = $7 "
				"WHERE relid = $8",
				RELATION_PARAM_NUM, r_types, &r_upd_plan, values, nulls,
				SPI_OK_UPDATE);
		if (SPI_processed == 0)
		{
			spi_exec_query("INSERT INTO dbms_stats.relation_stats_locked "
					"(relname, relpages, reltuples, curpages, "
					"last_analyze, last_autoanalyze, relid"
#if PG_VERSION_NUM >= 90200
					", relallvisible"
#endif
					") VALUES (quote_ident($1) || '.' || quote_ident($2), "
					"$3, $4, $5, $6, $7, $8"
#if PG_VERSION_NUM >= 90200
					", $9"
#endif
					")",
					RELATION_PARAM_NUM, r_types, &r_ins_plan, values, nulls,
					SPI_OK_INSERT);
			/*  If we failed to insert, we can't proceed. */
			if (SPI_processed != 1)
				elog(ERROR, "failed to insert import data");
		}

		elog(DEBUG2, "\"%s.%s\" relation statistic import",
			DatumGetName(w_nspname)->data, DatumGetName(w_relname)->data);

		/*
		 * Determine the attnum of the attribute with given name, and load
		 * statistics from temp table into dbms.column_stats_locked.
		 */
		spi_exec_query("SELECT w.stainherit, w.attname, a.attnum, "
							  "w.nspname_of_typename, tn.nspname, "
							  "w.typname, t.typname, w.atttypmod, a.atttypmod "
						 "FROM pg_catalog.pg_class c "
						 "JOIN pg_catalog.pg_namespace cn "
						   "ON (cn.oid = c.relnamespace) "
						 "JOIN pg_catalog.pg_attribute a "
						   "ON (a.attrelid = c.oid) "
						 "JOIN pg_catalog.pg_type t "
						   "ON (t.oid = a.atttypid) "
						 "JOIN pg_catalog.pg_namespace tn "
						   "ON (tn.oid = t.typnamespace) "
						"RIGHT JOIN dbms_stats_work_stats w "
						   "ON (w.nspname = cn.nspname AND w.relname = c.relname "
							   "AND (w.attname = a.attname OR w.attname = '')) "
						"WHERE w.nspname = $1 AND w.relname = $2 "
						  "AND a.attnum > 0"
						"ORDER BY 1, 3, 2",
				2, r_types, &c_sel_plan, values, NULL, SPI_OK_SELECT);

		/* This query ought to return at least one record. */
		if (SPI_processed == 0)
			elog(ERROR, "no per-column statistic data to be imported");

		values[0] = w_relid;
		values[2] = w_nspname;
		values[3] = w_relname;

		c_num = SPI_processed;
		c_tups = SPI_tuptable->vals;
		c_tupdesc = SPI_tuptable->tupdesc;
		for (j = 0; j < c_num; j++)
		{
			char   *w_typnamespace;
			char   *a_typnamespace;
			char   *w_typname;
			char   *a_typname;
			int		w_typmod;
			int		a_typmod;

			/*
			 * If we have only per-relation statistics in source, all of
			 * column_stats_effective for per-column statistics are NULL.
			 */
			(void) SPI_getbinval(c_tups[j], c_tupdesc, 1, &isnull);
			if (isnull)
				continue;

			/*
			 * If there is no column with given name, we skip the rest of
			 * import process.
			 */
			values[4] = SPI_getbinval(c_tups[j], c_tupdesc, 2, &isnull);
			values[1] = SPI_getbinval(c_tups[j], c_tupdesc, 3, &isnull);
			if (isnull)
			{
				elog(WARNING, "column \"%s\" of \"%s.%s\" does not exist",
					DatumGetName(values[4])->data,
						DatumGetName(w_nspname)->data,
						DatumGetName(w_relname)->data);
				continue;
			}

			/*
			 * If the destination column has different data type from source
			 * column, we stop importing to avoid corrupted statistics.
			 */
			w_typnamespace = DatumGetName(SPI_getbinval(c_tups[j], c_tupdesc, 4,
						&isnull))->data;
			a_typnamespace = DatumGetName(SPI_getbinval(c_tups[j], c_tupdesc, 5,
						&isnull))->data;
			w_typname = DatumGetName(SPI_getbinval(c_tups[j], c_tupdesc, 6,
						&isnull))->data;
			a_typname = DatumGetName(SPI_getbinval(c_tups[j], c_tupdesc, 7,
						&isnull))->data;
			if (strcmp(w_typnamespace, a_typnamespace) != 0 ||
				strcmp(w_typname, a_typname) != 0)
			{
				ereport(WARNING,
						(errcode(ERRCODE_DATATYPE_MISMATCH),
						 errmsg("column \"%s\" is of type \"%s.%s\""
								" but import data is of type \"%s.%s\"",
								DatumGetName(values[4])->data,
								a_typnamespace, a_typname,
								w_typnamespace, w_typname)));
				continue;
			}

			/*
			 * If the atttypmod of the destination column is different from the
			 * one of source, column, we stop importing to avoid corrupted
			 * statistics.
			 */
			w_typmod = DatumGetInt32(SPI_getbinval(c_tups[j], c_tupdesc, 8,
						&isnull));
			a_typmod = DatumGetInt32(SPI_getbinval(c_tups[j], c_tupdesc, 9,
						&isnull));
			if (w_typmod != a_typmod)
			{
				ereport(WARNING,
						(errcode(ERRCODE_DATATYPE_MISMATCH),
						 errmsg("column \"%s\" is of atttypmod %d"
								" but import data is of atttypmod %d",
								DatumGetName(values[4])->data,
								a_typmod, a_typmod)));
				continue;
			}

			/*
			 * First delete old dummy statistics, and import new one.  We use
			 * DELETE-then-INSERT method here to simplify codes.
			 */
			spi_exec_query("DELETE FROM dbms_stats.column_stats_locked "
					"WHERE starelid = $1 AND staattnum = $2", 2, c_types,
					&c_del_plan, values, NULL, SPI_OK_DELETE);

			spi_exec_query("INSERT INTO dbms_stats.column_stats_locked "
				"SELECT $1, $2, "
				"stainherit, stanullfrac, stawidth, stadistinct, "
				"stakind1, stakind2, stakind3, stakind4, "
#if PG_VERSION_NUM >= 90200
				"stakind5, "
#endif
				"staop1, staop2, staop3, staop4, "
#if PG_VERSION_NUM >= 90200
				"staop5, "
#endif
				"stanumbers1, stanumbers2, stanumbers3, stanumbers4, "
#if PG_VERSION_NUM >= 90200
				"stanumbers5, "
#endif
				"stavalues1, stavalues2, stavalues3, stavalues4 "
#if PG_VERSION_NUM >= 90200
				", stavalues5 "
#endif
				"FROM dbms_stats_work_stats "
				"WHERE nspname = $3 AND relname = $4 "
				"AND attname = $5 "
				"ORDER BY 3",
				5, c_types, &c_ins_plan, values, NULL, SPI_OK_INSERT);

			elog(DEBUG2, "\"%s.%s.%s\" column statistic import",
				DatumGetName(w_nspname)->data,
				DatumGetName(w_relname)->data, DatumGetName(values[4])->data);
		}

		if (c_num == 0)
			elog(DEBUG2, "\"%s.%s\" column statistic no data",
				DatumGetName(w_nspname)->data, DatumGetName(w_relname)->data);
	}

	/* release the cached plan */
	SPI_freeplan(r_upd_plan);
	SPI_freeplan(r_ins_plan);
	SPI_freeplan(c_sel_plan);
	SPI_freeplan(c_del_plan);
	SPI_freeplan(c_ins_plan);

	/* delete the temp table */
	spi_exec_utility("DROP TABLE dbms_stats_work_stats");

	/* disconnect SPI */
	ret = SPI_finish();
	if (ret != SPI_OK_FINISH)
		elog(ERROR, "pg_dbms_stats: SPI_finish => %d", ret);

	/*
	 * Recover the protocol state because it has been invalidated by our
	 * COPY-from-stdin.
	 */
	if (filename == NULL)
		pq_puttextmessage('C', "dbms_stats_import");

	PG_RETURN_VOID();
}
Example #7
0
Datum
_Slony_I_logTrigger(PG_FUNCTION_ARGS)
{
	TransactionId newXid = GetTopTransactionId();
	Slony_I_ClusterStatus *cs;
	TriggerData *tg;
	Datum		argv[4];
	text	   *cmdtype = NULL;
	int			rc;
	Name		cluster_name;
	int32		tab_id;
	char	   *attkind;
	int			attkind_idx;
	int			cmddata_need;

	/*
	 * Don't do any logging if the current session role isn't Origin.
	 */
	if (SessionReplicationRole != SESSION_REPLICATION_ROLE_ORIGIN)
		return PointerGetDatum(NULL);

	/*
	 * Get the trigger call context
	 */
	if (!CALLED_AS_TRIGGER(fcinfo))
		elog(ERROR, "Slony-I: logTrigger() not called as trigger");
	tg = (TriggerData *) (fcinfo->context);

	/*
	 * Check all logTrigger() calling conventions
	 */
	if (!TRIGGER_FIRED_AFTER(tg->tg_event))
		elog(ERROR, "Slony-I: logTrigger() must be fired AFTER");
	if (!TRIGGER_FIRED_FOR_ROW(tg->tg_event))
		elog(ERROR, "Slony-I: logTrigger() must be fired FOR EACH ROW");
	if (tg->tg_trigger->tgnargs != 3)
		elog(ERROR, "Slony-I: logTrigger() must be defined with 3 args");

	/*
	 * Connect to the SPI manager
	 */
	if ((rc = SPI_connect()) < 0)
		elog(ERROR, "Slony-I: SPI_connect() failed in createEvent()");

	/*
	 * Get all the trigger arguments
	 */
	cluster_name = DatumGetName(DirectFunctionCall1(namein,
								CStringGetDatum(tg->tg_trigger->tgargs[0])));
	tab_id = strtol(tg->tg_trigger->tgargs[1], NULL, 10);
	attkind = tg->tg_trigger->tgargs[2];

	/*
	 * Get or create the cluster status information and make sure it has the
	 * SPI plans that we need here.
	 */
	cs = getClusterStatus(cluster_name, PLAN_INSERT_LOG);

	/*
	 * Do the following only once per transaction.
	 */
	if (!TransactionIdEquals(cs->currentXid, newXid))
	{
		int32		log_status;
		bool isnull;

		/*
		 * Determine the currently active log table
		 */
		if (SPI_execp(cs->plan_get_logstatus, NULL, NULL, 0) < 0)
			elog(ERROR, "Slony-I: cannot determine log status");
		if (SPI_processed != 1)
			elog(ERROR, "Slony-I: cannot determine log status");

		log_status = DatumGetInt32(SPI_getbinval(SPI_tuptable->vals[0],
											SPI_tuptable->tupdesc, 1, &isnull));
		SPI_freetuptable(SPI_tuptable);

		switch (log_status)
		{
			case 0:
			case 2:
				cs->plan_active_log = cs->plan_insert_log_1;
				break;

			case 1:
			case 3:
				cs->plan_active_log = cs->plan_insert_log_2;
				break;

			default:
				elog(ERROR, "Slony-I: illegal log status %d", log_status);
				break;
		}

		cs->currentXid = newXid;
	}

	/*
	 * Determine cmdtype and cmddata depending on the command type
	 */
	if (TRIGGER_FIRED_BY_INSERT(tg->tg_event))
	{
		HeapTuple	new_row = tg->tg_trigtuple;
		TupleDesc	tupdesc = tg->tg_relation->rd_att;
		char	   *col_ident;
		char	   *col_value;

		int			len_ident;
		int			len_value;
		int			i;
		int			need_comma = false;
		char	   *OldDateStyle;
		char	   *cp = VARDATA(cs->cmddata_buf);

		/*
		 * INSERT
		 *
		 * cmdtype = 'I' cmddata = ("col" [, ...]) values ('value' [, ...])
		 */
		cmdtype = cs->cmdtype_I;

		/*
		 * Specify all the columns
		 */
		*cp++ = '(';
		for (i = 0; i < tg->tg_relation->rd_att->natts; i++)
		{
			/*
			 * Skip dropped columns
			 */
			if (tupdesc->attrs[i]->attisdropped)
				continue;

			col_ident = (char *) slon_quote_identifier(SPI_fname(tupdesc, i + 1));
			cmddata_need = (cp - (char *) (cs->cmddata_buf)) + 16 +
				(len_ident = strlen(col_ident));
			if (cs->cmddata_size < cmddata_need)
			{
				int			have = (cp - (char *) (cs->cmddata_buf));

				while (cs->cmddata_size < cmddata_need)
					cs->cmddata_size *= 2;
				cs->cmddata_buf = realloc(cs->cmddata_buf, cs->cmddata_size);
				cp = (char *) (cs->cmddata_buf) + have;
			}

			if (need_comma)
				*cp++ = ',';
			else
				need_comma = true;

			memcpy(cp, col_ident, len_ident);
			cp += len_ident;
		}

		/*
		 * Append the string ") values ("
		 */
		*cp++ = ')';
		*cp++ = ' ';
		*cp++ = 'v';
		*cp++ = 'a';
		*cp++ = 'l';
		*cp++ = 'u';
		*cp++ = 'e';
		*cp++ = 's';
		*cp++ = ' ';
		*cp++ = '(';

		/*
		 * Append the values
		 */
		need_comma = false;
		OldDateStyle = GetConfigOptionByName("DateStyle", NULL);
		if (!strstr(OldDateStyle, "ISO"))
			set_config_option("DateStyle", "ISO", PGC_USERSET, PGC_S_SESSION, true, true);
		for (i = 0; i < tg->tg_relation->rd_att->natts; i++)
		{
			/*
			 * Skip dropped columns
			 */
			if (tupdesc->attrs[i]->attisdropped)
				continue;


			if ((col_value = SPI_getvalue(new_row, tupdesc, i + 1)) == NULL)
			{
				col_value = "NULL";
			}
			else
			{
				col_value = slon_quote_literal(col_value);
			}

			cmddata_need = (cp - (char *) (cs->cmddata_buf)) + 16 +
				(len_value = strlen(col_value));
			if (cs->cmddata_size < cmddata_need)
			{
				int			have = (cp - (char *) (cs->cmddata_buf));

				while (cs->cmddata_size < cmddata_need)
					cs->cmddata_size *= 2;
				cs->cmddata_buf = realloc(cs->cmddata_buf, cs->cmddata_size);
				cp = (char *) (cs->cmddata_buf) + have;
			}

			if (need_comma)
				*cp++ = ',';
			else
				need_comma = true;

			memcpy(cp, col_value, len_value);
			cp += len_value;
		}

		if (!strstr(OldDateStyle, "ISO"))
			set_config_option("DateStyle", OldDateStyle, PGC_USERSET, PGC_S_SESSION, true, true);

		/*
		 * Terminate and done
		 */
		*cp++ = ')';
		*cp = '\0';
		SET_VARSIZE(cs->cmddata_buf,
					VARHDRSZ + (cp - VARDATA(cs->cmddata_buf)));
	}
	else if (TRIGGER_FIRED_BY_UPDATE(tg->tg_event))
	{
		HeapTuple	old_row = tg->tg_trigtuple;
		HeapTuple	new_row = tg->tg_newtuple;
		TupleDesc	tupdesc = tg->tg_relation->rd_att;
		Datum		old_value;
		Datum		new_value;
		bool		old_isnull;
		bool		new_isnull;

		char	   *col_ident;
		char	   *col_value;
		int			len_ident;
		int			len_value;
		int			i;
		int			need_comma = false;
		int			need_and = false;
		char	   *OldDateStyle;

		char	   *cp = VARDATA(cs->cmddata_buf);

		/*
		 * UPDATE
		 *
		 * cmdtype = 'U' cmddata = "col_ident"='value' [, ...] where
		 * "pk_ident" = 'value' [ and ...]
		 */
		cmdtype = cs->cmdtype_U;
		for (i = 0; i < tg->tg_relation->rd_att->natts; i++)
		{
			/*
			 * Ignore dropped columns
			 */
			if (tupdesc->attrs[i]->attisdropped)
				continue;

			old_value = SPI_getbinval(old_row, tupdesc, i + 1, &old_isnull);
			new_value = SPI_getbinval(new_row, tupdesc, i + 1, &new_isnull);

			/*
			 * If old and new value are NULL, the column is unchanged
			 */
			if (old_isnull && new_isnull)
				continue;

			/*
			 * If both are NOT NULL, we need to compare the values and skip
			 * setting the column if equal
			 */
			if (!old_isnull && !new_isnull)
			{
				Oid			opr_oid;
				FmgrInfo   *opr_finfo_p;

				/*
				 * Lookup the equal operators function call info using the
				 * typecache if available
				 */
#ifdef HAVE_TYPCACHE
				TypeCacheEntry *type_cache;

				type_cache = lookup_type_cache(
											   SPI_gettypeid(tupdesc, i + 1),
								  TYPECACHE_EQ_OPR | TYPECACHE_EQ_OPR_FINFO);
				opr_oid = type_cache->eq_opr;
				if (opr_oid == ARRAY_EQ_OP)
					opr_oid = InvalidOid;
				else
					opr_finfo_p = &(type_cache->eq_opr_finfo);
#else
				FmgrInfo	opr_finfo;

				opr_oid = compatible_oper_funcid(makeList1(makeString("=")),
											   SPI_gettypeid(tupdesc, i + 1),
										SPI_gettypeid(tupdesc, i + 1), true);
				if (OidIsValid(opr_oid))
				{
					fmgr_info(opr_oid, &opr_finfo);
					opr_finfo_p = &opr_finfo;
				}
#endif

				/*
				 * If we have an equal operator, use that to do binary
				 * comparision. Else get the string representation of both
				 * attributes and do string comparision.
				 */
				if (OidIsValid(opr_oid))
				{
					if (DatumGetBool(FunctionCall2(opr_finfo_p,
												   old_value, new_value)))
						continue;
				}
				else
				{
					char	   *old_strval = SPI_getvalue(old_row, tupdesc, i + 1);
					char	   *new_strval = SPI_getvalue(new_row, tupdesc, i + 1);

					if (strcmp(old_strval, new_strval) == 0)
						continue;
				}
			}

			if (need_comma)
				*cp++ = ',';
			else
				need_comma = true;

			col_ident = (char *) slon_quote_identifier(SPI_fname(tupdesc, i + 1));
			if (new_isnull)
				col_value = "NULL";
			else
			{
				OldDateStyle = GetConfigOptionByName("DateStyle", NULL);
				if (!strstr(OldDateStyle, "ISO"))
					set_config_option("DateStyle", "ISO", PGC_USERSET, PGC_S_SESSION, true, true);
				col_value = slon_quote_literal(SPI_getvalue(new_row, tupdesc, i + 1));
				if (!strstr(OldDateStyle, "ISO"))
					set_config_option("DateStyle", OldDateStyle, PGC_USERSET, PGC_S_SESSION, true, true);
			}
			cmddata_need = (cp - (char *) (cs->cmddata_buf)) + 16 +
				(len_ident = strlen(col_ident)) +
				(len_value = strlen(col_value));
			if (cs->cmddata_size < cmddata_need)
			{
				int			have = (cp - (char *) (cs->cmddata_buf));

				while (cs->cmddata_size < cmddata_need)
					cs->cmddata_size *= 2;
				cs->cmddata_buf = realloc(cs->cmddata_buf, cs->cmddata_size);
				cp = (char *) (cs->cmddata_buf) + have;
			}

			memcpy(cp, col_ident, len_ident);
			cp += len_ident;
			*cp++ = '=';
			memcpy(cp, col_value, len_value);
			cp += len_value;
		}

		/*
		 * It can happen that the only UPDATE an application does is to set a
		 * column to the same value again. In that case, we'd end up here with
		 * no columns in the SET clause yet. We add the first key column here
		 * with it's old value to simulate the same for the replication
		 * engine.
		 */
		if (!need_comma)
		{
			for (i = 0, attkind_idx = -1; i < tg->tg_relation->rd_att->natts; i++)
			{
				if (tupdesc->attrs[i]->attisdropped)
					continue;

				attkind_idx++;
				if (!attkind[attkind_idx])
					elog(ERROR, "Slony-I: no key columns found in logTrigger() attkind parameter");

				if (attkind[attkind_idx] == 'k')
					break;
			}
			col_ident = (char *) slon_quote_identifier(SPI_fname(tupdesc, i + 1));
			col_value = slon_quote_literal(SPI_getvalue(old_row, tupdesc, i + 1));

			cmddata_need = (cp - (char *) (cs->cmddata_buf)) + 16 +
				(len_ident = strlen(col_ident)) +
				(len_value = strlen(col_value));
			if (cs->cmddata_size < cmddata_need)
			{
				int			have = (cp - (char *) (cs->cmddata_buf));

				while (cs->cmddata_size < cmddata_need)
					cs->cmddata_size *= 2;
				cs->cmddata_buf = realloc(cs->cmddata_buf, cs->cmddata_size);
				cp = (char *) (cs->cmddata_buf) + have;
			}

			memcpy(cp, col_ident, len_ident);
			cp += len_ident;
			*cp++ = '=';
			memcpy(cp, col_value, len_value);
			cp += len_value;
		}

		*cp++ = ' ';
		*cp++ = 'w';
		*cp++ = 'h';
		*cp++ = 'e';
		*cp++ = 'r';
		*cp++ = 'e';
		*cp++ = ' ';

		for (i = 0, attkind_idx = -1; i < tg->tg_relation->rd_att->natts; i++)
		{
			/*
			 * Ignore dropped columns
			 */
			if (tupdesc->attrs[i]->attisdropped)
				continue;

			attkind_idx++;
			if (!attkind[attkind_idx])
				break;
			if (attkind[attkind_idx] != 'k')
				continue;
			col_ident = (char *) slon_quote_identifier(SPI_fname(tupdesc, i + 1));
			col_value = slon_quote_literal(SPI_getvalue(old_row, tupdesc, i + 1));
			if (col_value == NULL)
				elog(ERROR, "Slony-I: old key column %s.%s IS NULL on UPDATE",
					 NameStr(tg->tg_relation->rd_rel->relname), col_ident);

			cmddata_need = (cp - (char *) (cs->cmddata_buf)) + 16 +
				(len_ident = strlen(col_ident)) +
				(len_value = strlen(col_value));
			if (cs->cmddata_size < cmddata_need)
			{
				int			have = (cp - (char *) (cs->cmddata_buf));

				while (cs->cmddata_size < cmddata_need)
					cs->cmddata_size *= 2;
				cs->cmddata_buf = realloc(cs->cmddata_buf, cs->cmddata_size);
				cp = (char *) (cs->cmddata_buf) + have;
			}

			if (need_and)
			{
				*cp++ = ' ';
				*cp++ = 'a';
				*cp++ = 'n';
				*cp++ = 'd';
				*cp++ = ' ';
			}
			else
				need_and = true;

			memcpy(cp, col_ident, len_ident);
			cp += len_ident;
			*cp++ = '=';
			memcpy(cp, col_value, len_value);
			cp += len_value;
		}
		*cp = '\0';
		SET_VARSIZE(cs->cmddata_buf,
					VARHDRSZ + (cp - VARDATA(cs->cmddata_buf)));
	}
	else if (TRIGGER_FIRED_BY_DELETE(tg->tg_event))
	{
		HeapTuple	old_row = tg->tg_trigtuple;
		TupleDesc	tupdesc = tg->tg_relation->rd_att;
		char	   *col_ident;
		char	   *col_value;
		int			len_ident;
		int			len_value;
		int			i;
		int			need_and = false;
		char	   *cp = VARDATA(cs->cmddata_buf);

		/*
		 * DELETE
		 *
		 * cmdtype = 'D' cmddata = "pk_ident"='value' [and ...]
		 */
		cmdtype = cs->cmdtype_D;

		for (i = 0, attkind_idx = -1; i < tg->tg_relation->rd_att->natts; i++)
		{
			if (tupdesc->attrs[i]->attisdropped)
				continue;

			attkind_idx++;
			if (!attkind[attkind_idx])
				break;
			if (attkind[attkind_idx] != 'k')
				continue;
			col_ident = (char *) slon_quote_identifier(SPI_fname(tupdesc, i + 1));
			col_value = slon_quote_literal(SPI_getvalue(old_row, tupdesc, i + 1));
			if (col_value == NULL)
				elog(ERROR, "Slony-I: old key column %s.%s IS NULL on DELETE",
					 NameStr(tg->tg_relation->rd_rel->relname), col_ident);

			cmddata_need = (cp - (char *) (cs->cmddata_buf)) + 16 +
				(len_ident = strlen(col_ident)) +
				(len_value = strlen(col_value));
			if (cs->cmddata_size < cmddata_need)
			{
				int			have = (cp - (char *) (cs->cmddata_buf));

				while (cs->cmddata_size < cmddata_need)
					cs->cmddata_size *= 2;
				cs->cmddata_buf = realloc(cs->cmddata_buf, cs->cmddata_size);
				cp = (char *) (cs->cmddata_buf) + have;
			}

			if (need_and)
			{
				*cp++ = ' ';
				*cp++ = 'a';
				*cp++ = 'n';
				*cp++ = 'd';
				*cp++ = ' ';
			}
			else
				need_and = true;

			memcpy(cp, col_ident, len_ident);
			cp += len_ident;
			*cp++ = '=';
			memcpy(cp, col_value, len_value);
			cp += len_value;
		}
		*cp = '\0';
		SET_VARSIZE(cs->cmddata_buf,
					VARHDRSZ + (cp - VARDATA(cs->cmddata_buf)));
	}
	else
		elog(ERROR, "Slony-I: logTrigger() fired for unhandled event");

	/*
	 * Construct the parameter array and insert the log row.
	 */
	argv[0] = Int32GetDatum(tab_id);
	argv[1] = PointerGetDatum(cmdtype);
	argv[2] = PointerGetDatum(cs->cmddata_buf);
	SPI_execp(cs->plan_active_log, argv, NULL, 0);

	SPI_finish();
	return PointerGetDatum(NULL);
}