/* Updates the given frame with information about a table row that was deleted.
 * This is used only during stream replication. */
int update_frame_with_delete(avro_value_t *frame_val, schema_cache_t cache, Relation rel, HeapTuple oldtuple) {
    int err = 0;
    schema_cache_entry *entry;
    bytea *key_bin = NULL, *old_bin = NULL;

    int changed = schema_cache_lookup(cache, rel, &entry);
    if (changed < 0) {
        return EINVAL;
    } else if (changed) {
        check(err, update_frame_with_table_schema(frame_val, entry));
    }

    if (oldtuple) {
        check(err, extract_tuple_key(entry, rel, RelationGetDescr(rel), oldtuple, &key_bin));
        check(err, avro_value_reset(&entry->row_value));
        check(err, tuple_to_avro_row(&entry->row_value, RelationGetDescr(rel), oldtuple));
        check(err, try_writing(&old_bin, &write_avro_binary, &entry->row_value));
    }

    check(err, update_frame_with_delete_raw(frame_val, RelationGetRelid(rel), key_bin, old_bin));

    if (key_bin) pfree(key_bin);
    if (old_bin) pfree(old_bin);
    return err;
}
/* Updates the given frame value with a tuple inserted into a table. The table
 * schema is automatically included in the frame if it's not in the cache. This
 * function is used both during snapshot and during stream replication.
 *
 * The TupleDesc parameter is not redundant. During stream replication, it is just
 * RelationGetDescr(rel), but during snapshot it is taken from the result set.
 * The difference is that the result set tuple has dropped (logically invisible)
 * columns omitted. */
int update_frame_with_insert(avro_value_t *frame_val, schema_cache_t cache, Relation rel, TupleDesc tupdesc, HeapTuple newtuple) {
    int err = 0;
    schema_cache_entry *entry;
    bytea *key_bin = NULL, *new_bin = NULL;

    int changed = schema_cache_lookup(cache, rel, &entry);
    if (changed) {
        check(err, update_frame_with_table_schema(frame_val, entry));
    }

    check(err, extract_tuple_key(entry, rel, tupdesc, newtuple, &key_bin));
    check(err, avro_value_reset(&entry->row_value));
    check(err, tuple_to_avro_row(&entry->row_value, tupdesc, newtuple));
    check(err, try_writing(&new_bin, &write_avro_binary, &entry->row_value));
    check(err, update_frame_with_insert_raw(frame_val, RelationGetRelid(rel), key_bin, new_bin));

    if (key_bin) pfree(key_bin);
    pfree(new_bin);
    return err;
}
/* Updates the given frame with information about a table row that was modified.
 * This is used only during stream replication. */
int update_frame_with_update(avro_value_t *frame_val, schema_cache_t cache, Relation rel, HeapTuple oldtuple, HeapTuple newtuple) {
    int err = 0;
    schema_cache_entry *entry;
    bytea *old_bin = NULL, *new_bin = NULL, *old_key_bin = NULL, *new_key_bin = NULL;

    int changed = schema_cache_lookup(cache, rel, &entry);
    if (changed < 0) {
        return EINVAL;
    } else if (changed) {
        check(err, update_frame_with_table_schema(frame_val, entry));
    }

    /* oldtuple is non-NULL when replident = FULL, or when replident = DEFAULT and there is no
     * primary key, or replident = DEFAULT and the primary key was not modified by the update. */
    if (oldtuple) {
        check(err, extract_tuple_key(entry, rel, RelationGetDescr(rel), oldtuple, &old_key_bin));
        check(err, avro_value_reset(&entry->row_value));
        check(err, tuple_to_avro_row(&entry->row_value, RelationGetDescr(rel), oldtuple));
        check(err, try_writing(&old_bin, &write_avro_binary, &entry->row_value));
    }

    check(err, extract_tuple_key(entry, rel, RelationGetDescr(rel), newtuple, &new_key_bin));
    check(err, avro_value_reset(&entry->row_value));
    check(err, tuple_to_avro_row(&entry->row_value, RelationGetDescr(rel), newtuple));
    check(err, try_writing(&new_bin, &write_avro_binary, &entry->row_value));

    if (old_key_bin != NULL && (VARSIZE(old_key_bin) != VARSIZE(new_key_bin) ||
            memcmp(VARDATA(old_key_bin), VARDATA(new_key_bin), VARSIZE(new_key_bin) - VARHDRSZ) != 0)) {
        /* If the primary key changed, turn the update into a delete and an insert. */
        check(err, update_frame_with_delete_raw(frame_val, RelationGetRelid(rel), old_key_bin, old_bin));
        check(err, update_frame_with_insert_raw(frame_val, RelationGetRelid(rel), new_key_bin, new_bin));
    } else {
        check(err, update_frame_with_update_raw(frame_val, RelationGetRelid(rel), new_key_bin, old_bin, new_bin));
    }

    if (old_key_bin) pfree(old_key_bin);
    if (new_key_bin) pfree(new_key_bin);
    if (old_bin) pfree(old_bin);
    pfree(new_bin);
    return err;
}