<commit_before>/* Maintains server-side state relating to conversion of Postgres relations to Avro schemas. */

#include "schema_cache.h"
#include "lib/stringinfo.h"
#include "access/heapam.h"
#include "access/tupdesc.h"
#include "utils/lsyscache.h"

void schema_cache_entry_update(schema_cache_t cache, schema_cache_entry *entry, Relation rel);
bool schema_cache_entry_changed(schema_cache_entry *entry, Relation rel);
void schema_cache_entry_decrefs(schema_cache_entry *entry);
void tupdesc_debug_info(StringInfo msg, TupleDesc tupdesc);

/* Creates a new schema cache. All palloc allocations for this cache will be
 * performed in the given memory context. */
schema_cache_t schema_cache_new(MemoryContext context) {
    MemoryContext oldctx = MemoryContextSwitchTo(context);
    schema_cache_t cache = palloc0(sizeof(schema_cache));
    cache->context = context;

    HASHCTL hash_ctl;
    memset(&hash_ctl, 0, sizeof(hash_ctl));
    hash_ctl.keysize = sizeof(Oid);
    hash_ctl.entrysize = sizeof(schema_cache_entry);
    hash_ctl.hcxt = context;

#ifdef HASH_BLOBS
    /* Postgres 9.5 */
    cache->entries = hash_create("Bottled Water schema cache", 32, &hash_ctl,
            HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
#else
    /* Postgres 9.4 */
    hash_ctl.hash = oid_hash;
    cache->entries = hash_create("Bottled Water schema cache", 32, &hash_ctl,
            HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
#endif

    MemoryContextSwitchTo(oldctx);
    return cache;
}

/* Obtains the schema cache entry for the given relation, creating or updating it if necessary.
 * If the schema hasn't changed since the last invocation, a cached value is used and 0 is returned.
 * If the schema has changed, 1 is returned. If the schema has not been seen before, 2 is returned. */
int schema_cache_lookup(schema_cache_t cache, Relation rel, schema_cache_entry **entry_out) {
    Oid relid = RelationGetRelid(rel);
    bool found_entry;
    schema_cache_entry *entry = (schema_cache_entry *)
        hash_search(cache->entries, &relid, HASH_ENTER, &found_entry);

    if (found_entry) {
        if (!schema_cache_entry_changed(entry, rel)) {
            /* Schema has not changed */
            *entry_out = entry;
            return 0;

        } else {
            /* Schema has changed since we last saw it -- update the cache */
            schema_cache_entry_decrefs(entry);
            schema_cache_entry_update(cache, entry, rel);
            *entry_out = entry;
            return 1;
        }
    } else {
        /* Schema not previously seen -- populate a new cache entry */
        schema_cache_entry_update(cache, entry, rel);
        *entry_out = entry;
        return 2;
    }
}

/* Populates a schema cache entry with the information from a given table. */
void schema_cache_entry_update(schema_cache_t cache, schema_cache_entry *entry, Relation rel) {
    entry->relid = RelationGetRelid(rel);
    entry->ns_id = RelationGetNamespace(rel);
    strcpy(NameStr(entry->relname), RelationGetRelationName(rel));
    strcpy(NameStr(entry->ns_name), get_namespace_name(entry->ns_id));

    Relation index_rel = table_key_index(rel);
    if (index_rel) {
        entry->key_id = RelationGetRelid(index_rel);
        entry->keyns_id = RelationGetNamespace(index_rel);
        strcpy(NameStr(entry->key_name), RelationGetRelationName(index_rel));
        strcpy(NameStr(entry->keyns_name), get_namespace_name(entry->keyns_id));
    } else {
        entry->key_id = InvalidOid;
        entry->keyns_id = InvalidOid;
    }

    /* Make a copy of the tuple descriptors in the cache's memory context */
    MemoryContext oldctx = MemoryContextSwitchTo(cache->context);
    if (index_rel) {
        entry->key_tupdesc = CreateTupleDescCopyConstr(RelationGetDescr(index_rel));
    } else {
        entry->key_tupdesc = NULL;
    }
    entry->row_tupdesc = CreateTupleDescCopyConstr(RelationGetDescr(rel));
    MemoryContextSwitchTo(oldctx);
    relation_close(index_rel, AccessShareLock);

    entry->key_schema = schema_for_table_key(rel);
    entry->row_schema = schema_for_table_row(rel);
    entry->row_iface = avro_generic_class_from_schema(entry->row_schema);
    avro_generic_value_new(entry->row_iface, &entry->row_value);

    if (entry->key_schema) {
        entry->key_iface = avro_generic_class_from_schema(entry->key_schema);
        avro_generic_value_new(entry->key_iface, &entry->key_value);
    }
}

/* Returns false if the schema of the given relation matches the cache entry,
 * and returns true if it has changed. This is detected by keeping a copy of
 * the schema information in the cache entry. An alternative way of implementing
 * this might be to use event triggers:
 * http://www.postgresql.org/docs/9.4/static/event-triggers.html */
bool schema_cache_entry_changed(schema_cache_entry *entry, Relation rel) {
    if (entry->relid != RelationGetRelid(rel)) return true;
    if (entry->ns_id != RelationGetNamespace(rel)) return true;
    if (strcmp(NameStr(entry->relname), RelationGetRelationName(rel)) != 0) return true;
    if (strcmp(NameStr(entry->ns_name), get_namespace_name(entry->ns_id)) != 0) return true;

    Relation index_rel = table_key_index(rel);
    bool changed = false;
    if (index_rel && OidIsValid(entry->key_id)) {
        if (entry->key_id != RelationGetRelid(index_rel)) changed = true;
        if (entry->keyns_id != RelationGetNamespace(index_rel)) changed = true;
        if (strcmp(NameStr(entry->key_name), RelationGetRelationName(index_rel)) != 0) changed = true;
        if (strcmp(NameStr(entry->keyns_name), get_namespace_name(entry->keyns_id)) != 0) changed = true;
        if (!equalTupleDescs(entry->key_tupdesc, RelationGetDescr(index_rel))) changed = true;
    } else if (index_rel || OidIsValid(entry->key_id)) {
        changed = true;
    }
    relation_close(index_rel, AccessShareLock);
    if (changed) return true;

    return !equalTupleDescs(entry->row_tupdesc, RelationGetDescr(rel));
}

/* Decrements the reference counts for a schema cache entry. */
void schema_cache_entry_decrefs(schema_cache_entry *entry) {<commit_msg>Fix segfault on tables without primary key<commit_after>/* Maintains server-side state relating to conversion of Postgres relations to Avro schemas. */

#include "schema_cache.h"
#include "lib/stringinfo.h"
#include "access/heapam.h"
#include "access/tupdesc.h"
#include "utils/lsyscache.h"

void schema_cache_entry_update(schema_cache_t cache, schema_cache_entry *entry, Relation rel);
bool schema_cache_entry_changed(schema_cache_entry *entry, Relation rel);
void schema_cache_entry_decrefs(schema_cache_entry *entry);
void tupdesc_debug_info(StringInfo msg, TupleDesc tupdesc);

/* Creates a new schema cache. All palloc allocations for this cache will be
 * performed in the given memory context. */
schema_cache_t schema_cache_new(MemoryContext context) {
    MemoryContext oldctx = MemoryContextSwitchTo(context);
    schema_cache_t cache = palloc0(sizeof(schema_cache));
    cache->context = context;

    HASHCTL hash_ctl;
    memset(&hash_ctl, 0, sizeof(hash_ctl));
    hash_ctl.keysize = sizeof(Oid);
    hash_ctl.entrysize = sizeof(schema_cache_entry);
    hash_ctl.hcxt = context;

#ifdef HASH_BLOBS
    /* Postgres 9.5 */
    cache->entries = hash_create("Bottled Water schema cache", 32, &hash_ctl,
            HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
#else
    /* Postgres 9.4 */
    hash_ctl.hash = oid_hash;
    cache->entries = hash_create("Bottled Water schema cache", 32, &hash_ctl,
            HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
#endif

    MemoryContextSwitchTo(oldctx);
    return cache;
}

/* Obtains the schema cache entry for the given relation, creating or updating it if necessary.
 * If the schema hasn't changed since the last invocation, a cached value is used and 0 is returned.
 * If the schema has changed, 1 is returned. If the schema has not been seen before, 2 is returned. */
int schema_cache_lookup(schema_cache_t cache, Relation rel, schema_cache_entry **entry_out) {
    Oid relid = RelationGetRelid(rel);
    bool found_entry;
    schema_cache_entry *entry = (schema_cache_entry *)
        hash_search(cache->entries, &relid, HASH_ENTER, &found_entry);

    if (found_entry) {
        if (!schema_cache_entry_changed(entry, rel)) {
            /* Schema has not changed */
            *entry_out = entry;
            return 0;

        } else {
            /* Schema has changed since we last saw it -- update the cache */
            schema_cache_entry_decrefs(entry);
            schema_cache_entry_update(cache, entry, rel);
            *entry_out = entry;
            return 1;
        }
    } else {
        /* Schema not previously seen -- populate a new cache entry */
        schema_cache_entry_update(cache, entry, rel);
        *entry_out = entry;
        return 2;
    }
}

/* Populates a schema cache entry with the information from a given table. */
void schema_cache_entry_update(schema_cache_t cache, schema_cache_entry *entry, Relation rel) {
    entry->relid = RelationGetRelid(rel);
    entry->ns_id = RelationGetNamespace(rel);
    strcpy(NameStr(entry->relname), RelationGetRelationName(rel));
    strcpy(NameStr(entry->ns_name), get_namespace_name(entry->ns_id));

    Relation index_rel = table_key_index(rel);
    if (index_rel) {
        entry->key_id = RelationGetRelid(index_rel);
        entry->keyns_id = RelationGetNamespace(index_rel);
        strcpy(NameStr(entry->key_name), RelationGetRelationName(index_rel));
        strcpy(NameStr(entry->keyns_name), get_namespace_name(entry->keyns_id));
    } else {
        entry->key_id = InvalidOid;
        entry->keyns_id = InvalidOid;
    }

    /* Make a copy of the tuple descriptors in the cache's memory context */
    MemoryContext oldctx = MemoryContextSwitchTo(cache->context);
    if (index_rel) {
        entry->key_tupdesc = CreateTupleDescCopyConstr(RelationGetDescr(index_rel));
        relation_close(index_rel, AccessShareLock);
    } else {
        entry->key_tupdesc = NULL;
    }
    entry->row_tupdesc = CreateTupleDescCopyConstr(RelationGetDescr(rel));
    MemoryContextSwitchTo(oldctx);

    entry->key_schema = schema_for_table_key(rel);
    entry->row_schema = schema_for_table_row(rel);
    entry->row_iface = avro_generic_class_from_schema(entry->row_schema);
    avro_generic_value_new(entry->row_iface, &entry->row_value);

    if (entry->key_schema) {
        entry->key_iface = avro_generic_class_from_schema(entry->key_schema);
        avro_generic_value_new(entry->key_iface, &entry->key_value);
    }
}

/* Returns false if the schema of the given relation matches the cache entry,
 * and returns true if it has changed. This is detected by keeping a copy of
 * the schema information in the cache entry. An alternative way of implementing
 * this might be to use event triggers:
 * http://www.postgresql.org/docs/9.4/static/event-triggers.html */
bool schema_cache_entry_changed(schema_cache_entry *entry, Relation rel) {
    if (entry->relid != RelationGetRelid(rel)) return true;
    if (entry->ns_id != RelationGetNamespace(rel)) return true;
    if (strcmp(NameStr(entry->relname), RelationGetRelationName(rel)) != 0) return true;
    if (strcmp(NameStr(entry->ns_name), get_namespace_name(entry->ns_id)) != 0) return true;

    Relation index_rel = table_key_index(rel);
    bool changed = false;
    if (index_rel && OidIsValid(entry->key_id)) {
        if (entry->key_id != RelationGetRelid(index_rel)) changed = true;
        if (entry->keyns_id != RelationGetNamespace(index_rel)) changed = true;
        if (strcmp(NameStr(entry->key_name), RelationGetRelationName(index_rel)) != 0) changed = true;
        if (strcmp(NameStr(entry->keyns_name), get_namespace_name(entry->keyns_id)) != 0) changed = true;
        if (!equalTupleDescs(entry->key_tupdesc, RelationGetDescr(index_rel))) changed = true;
    } else if (index_rel || OidIsValid(entry->key_id)) {
        changed = true;
    }

    if (index_rel) {
        relation_close(index_rel, AccessShareLock);
    }
    if (changed) return true;

    return !equalTupleDescs(entry->row_tupdesc, RelationGetDescr(rel));
}

/* Decrements the reference counts for a schema cache entry. */
void schema_cache_entry_decrefs(schema_cache_entry *entry) {
{'fraction_non_alphanumeric': 0.10403587443946188, 'fraction_numerical': 0.003587443946188341, 'mean_word_length': 3.8751987281399045, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 93, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '187493', 'n_tokens_mistral': 3910, 'n_tokens_neox': 3847, 'n_words': 932}