void __table_log_restore_table_update(SPITupleTable *spi_tuptable, char *table_restore, char *table_orig_pkey, char *col_query_start, int col_pkey, int number_columns, int i, char *old_pkey_string) {
  int           size_of_values, j, ret;
  char          *tmp, *tmp2;

  /* memory for dynamic query */
  int           d_query_size;
  char          *d_query;
  char          *d_query_start;

  /* get the size of names and values */
  size_of_values = 0;
  /* go through all columns in this result */
  for (j = 1; j <= number_columns; j++) {
    /* get value */
    tmp = SPI_getvalue(spi_tuptable->vals[i], spi_tuptable->tupdesc, j);
    /* and get name of column */
    tmp2 = SPI_fname(spi_tuptable->tupdesc, j);
    if (tmp == NULL) {
      size_of_values += 6 + strlen(do_quote_ident(tmp2)) + 2;
    } else {
      size_of_values += strlen(do_quote_literal(tmp)) + strlen(do_quote_ident(tmp2)) + 3;
    }
  }
  /* reserve memory */
  d_query_size = 250 + size_of_values + NAMEDATALEN + strlen(do_quote_literal(old_pkey_string));
  d_query_start = (char *) palloc((d_query_size + 1) * sizeof(char));
  d_query = d_query_start;

  /* build query */
  sprintf(d_query, "UPDATE %s SET ", do_quote_ident(table_restore));
  d_query = d_query_start + strlen(d_query_start);

  for (j = 1; j <= number_columns; j++) {
    if (j > 1) {
      strncat(d_query_start, (const char *)", ", d_query_size);
      d_query += 2;
    }
    tmp = SPI_getvalue(spi_tuptable->vals[i], spi_tuptable->tupdesc, j);
    tmp2 = SPI_fname(spi_tuptable->tupdesc, j);
    if (tmp == NULL) {
      snprintf(d_query, d_query_size, "%s=NULL", do_quote_ident(tmp2));
    } else {
      snprintf(d_query, d_query_size, "%s=%s", do_quote_ident(tmp2), do_quote_literal(tmp));
    }
    d_query = d_query_start + strlen(d_query_start);
  }

  snprintf(d_query, d_query_size, " WHERE %s=%s", do_quote_ident(table_orig_pkey), do_quote_literal(old_pkey_string));
  d_query = d_query_start + strlen(d_query_start);

#ifdef TABLE_LOG_DEBUG_QUERY
  elog(NOTICE, "query: %s", d_query_start);
#endif /* TABLE_LOG_DEBUG_QUERY */

  ret = SPI_exec(d_query_start, 0);
  if (ret != SPI_OK_UPDATE) {
    elog(ERROR, "could not update data in: %s", table_restore);
  }
  /* done */
}
示例#2
0
/*
 * Class:     org_postgresql_pljava_internal_TupleDesc
 * Method:    _getColumnName
 * Signature: (JI)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL
Java_org_postgresql_pljava_internal_TupleDesc__1getColumnName(JNIEnv* env, jclass cls, jlong _this, jint index)
{
	jstring result = 0;

	BEGIN_NATIVE
	PG_TRY();
	{
		char* name;
		Ptr2Long p2l;
		p2l.longVal = _this;
		name = SPI_fname((TupleDesc)p2l.ptrVal, (int)index);
		if(name == 0)
		{
			Exception_throw(ERRCODE_INVALID_DESCRIPTOR_INDEX,
				"Invalid attribute index \"%d\"", (int)index);
		}
		else
		{
			result = String_createJavaStringFromNTS(name);
			pfree(name);
		}
	}
	PG_CATCH();
	{
		Exception_throw_ERROR("SPI_fname");
	}
	PG_END_TRY();
	END_NATIVE
	return result;
}
示例#3
0
static void process_delete(PgqTriggerEvent *ev, StringInfo sql)
{
	TriggerData *tg = ev->tgdata;
	HeapTuple old_row = tg->tg_trigtuple;
	TupleDesc tupdesc = tg->tg_relation->rd_att;
	char *col_ident;
	char *col_value;
	int i;
	int need_and = false;
	int attkind_idx;

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

		attkind_idx++;
		if (!pgqtriga_is_pkey(ev, i, attkind_idx))
			continue;
		col_ident = SPI_fname(tupdesc, i + 1);
		col_value = SPI_getvalue(old_row, tupdesc, i + 1);

		if (need_and)
			appendStringInfoString(sql, " and ");
		else
			need_and = true;

		append_key_eq(sql, col_ident, col_value);
	}
}
示例#4
0
文件: plphp_io.c 项目: avsmips/PL-php
/*
 * plphp_htup_from_zval
 * 		Build a HeapTuple from a zval (which must be an array) and a TupleDesc.
 *
 * The return HeapTuple is allocated in the current memory context and must
 * be freed by the caller.
 *
 * If zval doesn't contain any of the element names from the TupleDesc,
 * build a tuple from the first N elements. This allows us to accept
 * arrays in form array(1,2,3) as the result of functions with OUT arguments.
 * XXX -- possible optimization: keep the memory context created and only
 * reset it between calls.
 */
HeapTuple
plphp_htup_from_zval(zval *val, TupleDesc tupdesc)
{
	MemoryContext	oldcxt;
	MemoryContext	tmpcxt;
	HeapTuple		ret;
	AttInMetadata  *attinmeta;
	HashPosition	pos;
	zval		  **element;
	char		  **values;
	int				i;
	bool			allempty = true;

	tmpcxt = AllocSetContextCreate(TopTransactionContext,
								   "htup_from_zval cxt",
								   ALLOCSET_DEFAULT_MINSIZE,
								   ALLOCSET_DEFAULT_INITSIZE,
								   ALLOCSET_DEFAULT_MAXSIZE);
	oldcxt = MemoryContextSwitchTo(tmpcxt);

	values = (char **) palloc(tupdesc->natts * sizeof(char *));

	for (i = 0; i < tupdesc->natts; i++)
	{
		char   *key = SPI_fname(tupdesc, i + 1);
		zval   *scalarval = plphp_array_get_elem(val, key);

		values[i] = plphp_zval_get_cstring(scalarval, true, true);
		/* 
		 * Reset the flag is even one of the keys actually exists,
		 * even if it is NULL.
		 */
		if (scalarval != NULL)
			allempty = false;
	}
	/* None of the names from the tuple exists,
	 * try to get 1st N array elements and assign them to the tuple
	 */
	if (allempty)
		for (i = 0, 
			 zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(val), &pos);
			 (zend_hash_get_current_data_ex(Z_ARRVAL_P(val), 
										   (void **) &element,
											&pos) == SUCCESS) && 
			(i < tupdesc->natts);
			zend_hash_move_forward_ex(Z_ARRVAL_P(val), &pos), i++)
			values[i] = plphp_zval_get_cstring(element[0], true, true);

	attinmeta = TupleDescGetAttInMetadata(tupdesc);

	MemoryContextSwitchTo(oldcxt);
	ret = BuildTupleFromCStrings(attinmeta, values);

	MemoryContextDelete(tmpcxt);

	return ret;
}
示例#5
0
void pgq_urlenc_row(PgqTriggerEvent *ev, HeapTuple row, StringInfo buf)
{
	TriggerData *tg = ev->tgdata;
	TupleDesc tupdesc = tg->tg_relation->rd_att;
	bool first = true;
	int i;
	const char *col_ident, *col_value;
	int attkind_idx = -1;

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

		attkind_idx++;

		if (pgqtriga_skip_col(ev, i, attkind_idx))
			continue;

		if (first)
			first = false;
		else
			appendStringInfoChar(buf, '&');

		/* quote column name */
		col_ident = SPI_fname(tupdesc, i + 1);
		pgq_encode_cstring(buf, col_ident, TBUF_QUOTE_URLENC);

		/* quote column value */
		col_value = SPI_getvalue(row, tupdesc, i + 1);
		if (col_value != NULL) {
			appendStringInfoChar(buf, '=');
			pgq_encode_cstring(buf, col_value, TBUF_QUOTE_URLENC);
		}
	}
}
示例#6
0
int create_sqlite_table(Portal *cur,sqlite3 *db,char *insert_str, char *dataset_name, char *twkb_name, char *id_name, int create)
{
	char create_table_string[SQLSTRLEN];
	char tmp_str[64];
	TupleDesc tupdesc;
	int i, rc;
	int strlengd = 0;
	int strlengd_ins = 0;
	char *err_msg, sqlitetype[15] ;
	char field_name[128];
	char value_list[256];	
	int strlengd_vals = 0;
	/*Get fielads definition by fetching 0 rows*/
	SPI_cursor_fetch(*cur, true,0);
	
if(create)
{
	snprintf(create_table_string, sizeof(create_table_string), " %s%s%s","create table ",dataset_name,"(");
	strlengd = strlen(create_table_string);
	
	
}
	snprintf(insert_str,SQLSTRLEN, " %s%s%s","insert into " ,dataset_name,"(");
	strlengd_ins = strlen(insert_str);
	
	
	snprintf(value_list,sizeof(value_list), " %s","values(");
	strlengd_vals = strlen(value_list);
	
	tupdesc = SPI_tuptable->tupdesc;
	
	for (i = 1; i <= tupdesc->natts; i++)
	{
		 snprintf(field_name, sizeof(field_name), "%s", SPI_fname(tupdesc, i));
		
		//convert type to sqlite type
		getsqlitetype(SPI_gettype(tupdesc, i), sqlitetype);
		
		
		
		if (strcmp(field_name, id_name)==0) 
		{
			snprintf(tmp_str, sizeof(tmp_str), " %s%s",
			" id integer primary key",
			(i == tupdesc->natts) ? " " : ", ");	
		
			//construct the insert string with field names
			snprintf(insert_str+strlengd_ins, SQLSTRLEN-strlengd_ins, "%s%s",
			"id",
			(i == tupdesc->natts) ? " " : ", ");		
			strlengd_ins += 3; //adding 1 for the comma-sign			
		}
		else if (strcmp(field_name, twkb_name)==0) 
		{
			snprintf(tmp_str, sizeof(tmp_str), " %s%s",
			" twkb blob",
			(i == tupdesc->natts) ? " " : ", ");	
		
			//construct the insert string with field names
			snprintf(insert_str+strlengd_ins, SQLSTRLEN-strlengd_ins, "%s%s",
			"twkb",
			(i == tupdesc->natts) ? " " : ", ");		
			strlengd_ins += 5; //adding 1 for the comma-sign			
		}
		else
		{
			//put together field name, type and comma sign if not last column
			snprintf(tmp_str, sizeof(tmp_str), " %s %s%s",
			field_name,
			sqlitetype,
			(i == tupdesc->natts) ? " " : ", ");
			
					
			//construct the insert string with field names
			snprintf(insert_str+strlengd_ins, SQLSTRLEN-strlengd_ins, "%s%s",
			field_name,
			(i == tupdesc->natts) ? " " : ", ");		
			strlengd_ins += strlen(field_name)+1; //adding 1 for the comma-sign
		}
		if(create)
		{
		//put the column name and type in the create-table sql-string
			snprintf(create_table_string+strlengd, sizeof(create_table_string)-strlengd, " %s",tmp_str);
			strlengd += strlen(tmp_str);
		}
		
		//construct the value part of the insert
		snprintf(value_list+strlengd_vals, sizeof(value_list)-strlengd_vals, "%s%s",
		"?",
		(i == tupdesc->natts) ? " " : ", ");		
		strlengd_vals += 2; //adding 1 for the comma-sign
		
//	elog(INFO, "strlength %d, temp: %s",strlengd_ins, insert_str);
	}
	if(create)
		snprintf(create_table_string+strlengd, sizeof(create_table_string)-strlengd, " %s",")");
	
	
	elog(INFO, " SQLSTRLEN-strlengd_ins: %d, insert sql: %s", SQLSTRLEN-strlengd_ins, insert_str);
//	snprintf(insert_str+strlengd_ins, SQLSTRLEN-strlengd_ins, " %s",")");
	
	
	snprintf(insert_str+strlengd_ins, SQLSTRLEN-strlengd_ins, " %s%s%s",")",value_list,")" );
	
	elog(INFO, "sql: %s", create_table_string);
	elog(INFO, "insert sql: %s", insert_str);
	if(create)
	{
	rc = sqlite3_exec(db, create_table_string, NULL, 0, &err_msg);
	    
	    if (rc != SQLITE_OK ) {	
		    sqlite3_free(err_msg);
		sqlite3_close(db);	
		    ereport(ERROR,  ( errmsg("Problem creating table: %s", err_msg)));
		//fprintf(stderr, "SQL error: %s\n", err_msg);
		
	    } 	
    }
		return 0;
}
示例#7
0
Datum
funny_dup17(PG_FUNCTION_ARGS)
{
	TriggerData *trigdata = (TriggerData *) fcinfo->context;
	TransactionId *xid;
	int		   *level;
	bool	   *recursion;
	Relation	rel;
	TupleDesc	tupdesc;
	HeapTuple	tuple;
	char	   *query,
			   *fieldval,
			   *fieldtype;
	char	   *when;
	int			inserted;
	int			selected = 0;
	int			ret;

	if (!CALLED_AS_TRIGGER(fcinfo))
		elog(ERROR, "funny_dup17: not fired by trigger manager");

	tuple = trigdata->tg_trigtuple;
	rel = trigdata->tg_relation;
	tupdesc = rel->rd_att;
	if (TRIGGER_FIRED_BEFORE(trigdata->tg_event))
	{
		xid = &fd17b_xid;
		level = &fd17b_level;
		recursion = &fd17b_recursion;
		when = "BEFORE";
	}
	else
	{
		xid = &fd17a_xid;
		level = &fd17a_level;
		recursion = &fd17a_recursion;
		when = "AFTER ";
	}

	if (!TransactionIdIsCurrentTransactionId(*xid))
	{
		*xid = GetCurrentTransactionId();
		*level = 0;
		*recursion = true;
	}

	if (*level == 17)
	{
		*recursion = false;
		return PointerGetDatum(tuple);
	}

	if (!(*recursion))
		return PointerGetDatum(tuple);

	(*level)++;

	SPI_connect();

	fieldval = SPI_getvalue(tuple, tupdesc, 1);
	fieldtype = SPI_gettype(tupdesc, 1);

	query = (char *) palloc(100 + NAMEDATALEN * 3 +
							strlen(fieldval) + strlen(fieldtype));

	sprintf(query, "insert into %s select * from %s where %s = '%s'::%s",
			SPI_getrelname(rel), SPI_getrelname(rel),
			SPI_fname(tupdesc, 1),
			fieldval, fieldtype);

	if ((ret = SPI_exec(query, 0)) < 0)
		elog(ERROR, "funny_dup17 (fired %s) on level %3d: SPI_exec (insert ...) returned %d",
			 when, *level, ret);

	inserted = SPI_processed;

	sprintf(query, "select count (*) from %s where %s = '%s'::%s",
			SPI_getrelname(rel),
			SPI_fname(tupdesc, 1),
			fieldval, fieldtype);

	if ((ret = SPI_exec(query, 0)) < 0)
		elog(ERROR, "funny_dup17 (fired %s) on level %3d: SPI_exec (select ...) returned %d",
			 when, *level, ret);

	if (SPI_processed > 0)
	{
		selected = DatumGetInt32(DirectFunctionCall1(int4in,
												CStringGetDatum(SPI_getvalue(
													   SPI_tuptable->vals[0],
													   SPI_tuptable->tupdesc,
																			 1
																		))));
	}

	elog(DEBUG4, "funny_dup17 (fired %s) on level %3d: %d/%d tuples inserted/selected",
		 when, *level, inserted, selected);

	SPI_finish();

	(*level)--;

	if (*level == 0)
		*xid = InvalidTransactionId;

	return PointerGetDatum(tuple);
}
示例#8
0
文件: jsontriga.c 项目: pgq/pgq
static void pgq_jsonenc_row(PgqTriggerEvent *ev, HeapTuple row, StringInfo buf)
{
	Oid col_type;
	Datum col_datum;
	bool isnull;
	TriggerData *tg = ev->tgdata;
	TupleDesc tupdesc = tg->tg_relation->rd_att;
	bool first = true;
	int i;
	const char *col_ident, *col_value;
	int attkind_idx = -1;

	if (ev->op_type == 'R') {
		appendStringInfoString(buf, "{}");
		return;
	}

	appendStringInfoChar(buf, '{');
	for (i = 0; i < tg->tg_relation->rd_att->natts; i++) {
		/* Skip dropped columns */
		if (TupleDescAttr(tupdesc, i)->attisdropped)
			continue;

		attkind_idx++;

		if (pgqtriga_skip_col(ev, i, attkind_idx))
			continue;

		if (first)
			first = false;
		else
			appendStringInfoChar(buf, ',');

		/* quote column name */
		col_ident = SPI_fname(tupdesc, i + 1);
		pgq_encode_cstring(buf, col_ident, TBUF_QUOTE_JSON);
		appendStringInfoChar(buf, ':');

		/* quote column value */
		col_type = TupleDescAttr(tupdesc, i)->atttypid;
		col_datum = SPI_getbinval(row, tupdesc, i + 1, &isnull);
		col_value = NULL;
		if (isnull) {
			appendStringInfoString(buf, "null");
			continue;
		}

		switch (col_type) {
		case BOOLOID:
			if (DatumGetBool(col_datum)) {
				appendStringInfoString(buf, "true");
			} else {
				appendStringInfoString(buf, "false");
			}
			break;

		case TIMESTAMPOID:
			timestamp_to_json(col_datum, buf);
			break;

		case TIMESTAMPTZOID:
			timestamptz_to_json(col_datum, buf);
			break;

		case DATEOID:
			date_to_json(col_datum, buf);
			break;

		case INT2OID:
			appendStringInfo(buf, "%d", (int)DatumGetInt16(col_datum));
			break;

		case INT4OID:
			appendStringInfo(buf, "%d", (int)DatumGetInt32(col_datum));
			break;

		case INT8OID:
			col_value = SPI_getvalue(row, tupdesc, i + 1);
			appendStringInfoString(buf, col_value);
			break;

		default:
			col_value = SPI_getvalue(row, tupdesc, i + 1);
			pgq_encode_cstring(buf, col_value, TBUF_QUOTE_JSON);
			break;
		}

		if (col_value)
			pfree((void*)col_value);
	}
	appendStringInfoChar(buf, '}');
}
/*
__table_log()

helper function for table_log()

parameter:
  - trigger data
  - change mode (INSERT, UPDATE, DELETE)
  - tuple to log (old, new)
  - pointer to tuple
  - number columns in table
  - logging table
  - flag for writing session user
return:
  none
*/
static void __table_log (TriggerData *trigdata, char *changed_mode, char *changed_tuple, HeapTuple tuple, int number_columns, char *log_table, int use_session_user, char *log_schema) {
  char     *before_char;
  int      i, col_nr, found_col;
  /* start with 100 bytes */
  int      size_query = 100;
  char     *query;
  char     *query_start;
  int      ret;

#ifdef TABLE_LOG_DEBUG
  elog(NOTICE, "calculate query size");
#endif /* TABLE_LOG_DEBUG */
  /* add all sizes we need and know at this point */
  size_query += strlen(changed_mode) + strlen(changed_tuple) + strlen(log_table) + strlen(log_schema);

  /* calculate size of the columns */
  col_nr = 0;
  for (i = 1; i <= number_columns; i++) {
    col_nr++;
    found_col = 0;
    do {
      if (trigdata->tg_relation->rd_att->attrs[col_nr - 1]->attisdropped) {
        /* this column is dropped, skip it */
        col_nr++;
        continue;
      } else {
        found_col++;
      }
    } while (found_col == 0);
    /* the column name */
    size_query += strlen(do_quote_ident(SPI_fname(trigdata->tg_relation->rd_att, col_nr))) + 3;
    /* the value */
    before_char = SPI_getvalue(tuple, trigdata->tg_relation->rd_att, col_nr);
    /* old size plus this char and 3 bytes for , and so */
    if (before_char == NULL) {
      size_query += 6;
    } else {
      size_query += strlen(do_quote_literal(before_char)) + 3;
    }
  }

  if (use_session_user == 1) {
    /* add memory for session user */
    size_query += NAMEDATALEN + 20;
  }

#ifdef TABLE_LOG_DEBUG
  // elog(NOTICE, "query size: %i", size_query);
#endif /* TABLE_LOG_DEBUG */
#ifdef TABLE_LOG_DEBUG
  elog(NOTICE, "build query");
#endif /* TABLE_LOG_DEBUG */
  /* allocate memory */
  query_start = (char *) palloc(size_query * sizeof(char));
  query = query_start;

  /* build query */
  sprintf(query, "INSERT INTO %s.%s (", do_quote_ident(log_schema), do_quote_ident(log_table));
  query = query_start + strlen(query);

  /* add colum names */
  col_nr = 0;
  for (i = 1; i <= number_columns; i++) {
    col_nr++;
    found_col = 0;
    do {
      if (trigdata->tg_relation->rd_att->attrs[col_nr - 1]->attisdropped) {
        /* this column is dropped, skip it */
        col_nr++;
        continue;
      } else {
        found_col++;
      }
    } while (found_col == 0);
    sprintf(query, "%s, ", do_quote_ident(SPI_fname(trigdata->tg_relation->rd_att, col_nr)));
    query = query_start + strlen(query_start);
  }

  /* add session user */
  if (use_session_user == 1) {
    sprintf(query, "trigger_user, ");
    query = query_start + strlen(query_start);
  }
  /* add the 3 extra colum names */
  sprintf(query, "trigger_mode, trigger_tuple, trigger_changed) VALUES (");
  query = query_start + strlen(query_start);

  /* add values */
  col_nr = 0;
  for (i = 1; i <= number_columns; i++) {
    col_nr++;
    found_col = 0;
    do {
      if (trigdata->tg_relation->rd_att->attrs[col_nr - 1]->attisdropped) {
        /* this column is dropped, skip it */
        col_nr++;
        continue;
      } else {
        found_col++;
      }
    } while (found_col == 0);
    before_char = SPI_getvalue(tuple, trigdata->tg_relation->rd_att, col_nr);
    if (before_char == NULL) {
      sprintf(query, "NULL, ");
    } else {
      sprintf(query, "%s, ", do_quote_literal(before_char));
    }
    query = query_start + strlen(query_start);
  }

  /* add session user */
  if (use_session_user == 1) {
    sprintf(query, "SESSION_USER, ");
    query = query_start + strlen(query_start);
  }
  /* add the 3 extra values */
  sprintf(query, "%s, %s, NOW());", do_quote_literal(changed_mode), do_quote_literal(changed_tuple));
  query = query_start + strlen(query_start);

#ifdef TABLE_LOG_DEBUG_QUERY
  elog(NOTICE, "query: %s", query_start);
#else
#ifdef TABLE_LOG_DEBUG
  elog(NOTICE, "execute query");
#endif /* TABLE_LOG_DEBUG */
#endif /* TABLE_LOG_DEBUG_QUERY */
  /* execute insert */
  ret = SPI_exec(query_start, 0);
  if (ret != SPI_OK_INSERT) {
    elog(ERROR, "could not insert log information into relation %s (error: %d)", log_table, ret);
  }
#ifdef TABLE_LOG_DEBUG
  elog(NOTICE, "done");
#endif /* TABLE_LOG_DEBUG */

  /* clean up */
  pfree(query_start);

}
Datum serialize_record( PG_FUNCTION_ARGS )
{
//	FILE* log;

//	log = fopen("/var/lib/postgresql/serializer.log", "a");
	HeapTupleHeader rec = PG_GETARG_HEAPTUPLEHEADER(0);

	HeapTupleData tuple;
	bool		needComma = false;
	int		 i;
	Datum	  *values;
	bool	   *nulls;
	StringInfoData buf;
	char *conversion_buf;

	/* Extract type info from the tuple itself */
	Oid tupType = HeapTupleHeaderGetTypeId(rec);
	int32 tupTypmod = HeapTupleHeaderGetTypMod(rec);
	TupleDesc tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
	int ncolumns = tupdesc->natts;

	/* Build a temporary HeapTuple control structure */
	tuple.t_len = HeapTupleHeaderGetDatumLength(rec);
	ItemPointerSetInvalid(&(tuple.t_self));
	tuple.t_tableOid = InvalidOid;
	tuple.t_data = rec;

//	fprintf(log, "Doing serialize_record\n");
//	fflush(log);

	values = (Datum *) palloc(ncolumns * sizeof(Datum));
	nulls = (bool *) palloc(ncolumns * sizeof(bool));

	/* Break down the tuple into fields */
	heap_deform_tuple(&tuple, tupdesc, values, nulls);

	/* And build the result string */
	initStringInfo(&buf);

	appendStringInfoChar(&buf, '{');

	for (i = 0; i < ncolumns; i++)
	{
		Oid		 column_type = tupdesc->attrs[ i ]->atttypid;
		char	   *value;
		char	   *column_name;
		char 		type_category;
		HeapTuple 	type_tuple;
		FmgrInfo flinfo;

		/* Ignore dropped columns in datatype */
		if (tupdesc->attrs[i]->attisdropped)
			continue;

		if (nulls[i])
		{
			/* emit nothing... */
			continue;
		}

		if (needComma)
			appendStringInfoChar(&buf, ',');

		needComma = true;


		/* obtain column name */
		column_name = SPI_fname( tupdesc, i + 1 );

		/* obtain type information from pg_catalog */
		type_tuple = SearchSysCache1( TYPEOID, ObjectIdGetDatum(column_type) );
		if (!HeapTupleIsValid( type_tuple ))
			elog(ERROR, "cache lookup failed for relation %u", column_type);

		type_category = ((Form_pg_type) GETSTRUCT( type_tuple ))->typcategory;

		ReleaseSysCache( type_tuple );

		/* append column name */
		appendStringInfoChar(&buf, '"');
		appendStringInfoString(&buf, column_name);
		appendStringInfoString(&buf, "\":");

		switch( type_category )
		{
			// http://www.postgresql.org/docs/current/static/catalog-pg-type.html#CATALOG-TYPCATEGORY-TABLE

			case 'A': //array
				//call to serialize_array( ... )

				MemSet( &flinfo, 0, sizeof( flinfo ) );
				flinfo.fn_addr = serialize_array;
				flinfo.fn_nargs = 1;
				flinfo.fn_mcxt = fcinfo->flinfo->fn_mcxt;

				value = PG_TEXT_DATUM_GET_CSTR( FunctionCall1( &flinfo, values[ i ] ) );

				appendStringInfoString(&buf, value);
			break;

			case 'C': //composite
				//recursive call to serialize_record( ... )
				MemSet( &flinfo, 0, sizeof( flinfo ) );
				flinfo.fn_addr = serialize_record;
				flinfo.fn_nargs = 1;
				flinfo.fn_mcxt = fcinfo->flinfo->fn_mcxt;

				value = PG_TEXT_DATUM_GET_CSTR( FunctionCall1( &flinfo, values[ i ] ) );

				appendStringInfoString(&buf, value);
			break;

			case 'N': //numeric

				conversion_buf = NULL;
				// get column text value
//				fprintf(log, "Calling ConvertToText\n");
//				fflush(log);
				value = ConvertToText( values[ i ], column_type, fcinfo->flinfo->fn_mcxt, &conversion_buf );
//				fprintf(log, "ConvertToText succeded\n");
//				fflush(log);

				appendStringInfoString(&buf, value);
//				fprintf(log, "append.... succeded\n");
//				fflush(log);

				if(conversion_buf != NULL) {
					pfree(conversion_buf);
					conversion_buf = NULL;
				}

			break;

			case 'B': //boolean
				appendStringInfoString(&buf,
					// get column boolean value
					DatumGetBool( values[ i ] ) ? "true" : "false"
				);
			break;

			default: //another

				conversion_buf = NULL;
				// get column text value
//				fprintf(log, "Calling ConvertToText\n");
//				fflush(log);
				value = ConvertToText( values[ i ], column_type, fcinfo->flinfo->fn_mcxt, &conversion_buf );
//				fprintf(log, "ConvertToText succeded\n");
//				fflush(log);

				appendStringInfoQuotedString(&buf, value);
//				fprintf(log, "append.... succeded\n");
//				fflush(log);

				if(conversion_buf != NULL) {
					pfree(conversion_buf);
					conversion_buf = NULL;
				}
		}
	}

	appendStringInfoChar(&buf, '}');

	pfree(values);
	pfree(nulls);
	ReleaseTupleDesc(tupdesc);

//	fclose(log);

	PG_RETURN_TEXT_P( PG_CSTR_GET_TEXT( buf.data ) );
}
示例#11
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);
}
示例#12
0
static void process_insert(PgqTriggerEvent *ev, StringInfo sql)
{
	TriggerData *tg = ev->tgdata;
	HeapTuple new_row = tg->tg_trigtuple;
	TupleDesc tupdesc = tg->tg_relation->rd_att;
	int i;
	int need_comma = false;
	int attkind_idx;

	/*
	 * Specify all the columns
	 */
	appendStringInfoChar(sql, '(');
	attkind_idx = -1;
	for (i = 0; i < tupdesc->natts; i++) {
		char *col_ident;

		/* Skip dropped columns */
		if (tupdesc->attrs[i]->attisdropped)
			continue;

		/* Check if allowed by colstring */
		attkind_idx++;
		if (pgqtriga_skip_col(ev, i, attkind_idx))
			continue;

		if (need_comma)
			appendStringInfoChar(sql, ',');
		else
			need_comma = true;

		/* quote column name */
		col_ident = SPI_fname(tupdesc, i + 1);
		pgq_encode_cstring(sql, col_ident, TBUF_QUOTE_IDENT);
	}

	/*
	 * Append the string ") values ("
	 */
	appendStringInfoString(sql, ") values (");

	/*
	 * Append the values
	 */
	need_comma = false;
	attkind_idx = -1;
	for (i = 0; i < tupdesc->natts; i++) {
		char *col_value;

		/* Skip dropped columns */
		if (tupdesc->attrs[i]->attisdropped)
			continue;

		/* Check if allowed by colstring */
		attkind_idx++;
		if (pgqtriga_skip_col(ev, i, attkind_idx))
			continue;

		if (need_comma)
			appendStringInfoChar(sql, ',');
		else
			need_comma = true;

		/* quote column value */
		col_value = SPI_getvalue(new_row, tupdesc, i + 1);
		if (col_value == NULL)
			appendStringInfoString(sql, "null");
		else
			pgq_encode_cstring(sql, col_value, TBUF_QUOTE_LITERAL);
	}

	/*
	 * Terminate and done
	 */
	appendStringInfoChar(sql, ')');
}
示例#13
0
static int process_update(PgqTriggerEvent *ev, StringInfo sql)
{
	TriggerData *tg = ev->tgdata;
	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 i;
	int need_comma = false;
	int need_and = false;
	int attkind_idx;
	int ignore_count = 0;

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

		attkind_idx++;

		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
			 */
			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);

			/*
			 * 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 (pgqtriga_skip_col(ev, i, attkind_idx)) {
			/* this change should be ignored */
			ignore_count++;
			continue;
		}

		if (need_comma)
			appendStringInfoChar(sql, ',');
		else
			need_comma = true;

		col_ident = SPI_fname(tupdesc, i + 1);
		col_value = SPI_getvalue(new_row, tupdesc, i + 1);

		append_normal_eq(sql, col_ident, col_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) {
		/* there was change in ignored columns, skip whole event */
		if (ignore_count > 0)
			return 0;

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

			attkind_idx++;
			if (pgqtriga_is_pkey(ev, i, attkind_idx))
				break;
		}
		col_ident = SPI_fname(tupdesc, i + 1);
		col_value = SPI_getvalue(old_row, tupdesc, i + 1);

		append_key_eq(sql, col_ident, col_value);
	}

	appendStringInfoString(sql, " where ");

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

		attkind_idx++;
		if (!pgqtriga_is_pkey(ev, i, attkind_idx))
			continue;

		col_ident = SPI_fname(tupdesc, i + 1);
		col_value = SPI_getvalue(old_row, tupdesc, i + 1);

		if (need_and)
			appendStringInfoString(sql, " and ");
		else
			need_and = true;

		append_key_eq(sql, col_ident, col_value);
	}
	return 1;
}