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); } } }
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, '}'); }
/* need to ignore UPDATE where only ignored columns change */ static int is_interesting_change(PgqTriggerEvent *ev, TriggerData *tg) { 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; int i, attkind_idx = -1; int ignore_count = 0; /* only UPDATE may need to be ignored */ if (!TRIGGER_FIRED_BY_UPDATE(tg->tg_event)) return 1; /* if no columns are ignored, all events are interesting */ if (ev->tgargs->ignore_list == NULL) return 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; } /* a non-ignored column has changed */ return 1; } /* skip if only ignored column had changed */ if (ignore_count) return 0; /* do show NOP updates */ return 1; }
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, ')'); }
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; }