Tell me more ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

I have a sort of database with a many-to-many association between tags and files. For various reasons, I decided to forgo a junction table in favor of having the left and right tables store the associated values in the tables themselves. However I've ended up writing code like this:

GHashTable *tagdb_get_tag_files (tagdb *db, int tag_code)
{
    return g_hash_table_lookup(db->reverse, GINT_TO_POINTER(tag_code));
}

GHashTable *tagdb_get_file_tags (tagdb *db, int file_id)
{
    return g_hash_table_lookup(db->forward, GINT_TO_POINTER(file_id));
}

void tagdb_remove_tag (tagdb *db, int id)
{
    GHashTable *its_files = g_hash_table_lookup(db->reverse, GINT_TO_POINTER(id));
    if (its_files == NULL)
    {
        return;
    }

    GHashTableIter it;
    gpointer key, value;
    g_hash_table_iter_init(&it, its_files);
    if (g_hash_table_iter_next(&it, &key, &value))
    {
        tagdb_remove_tag_from_file(db, id, key);
    }
    g_hash_table_remove(db->reverse, GINT_TO_POINTER(id));
}

void tagdb_remove_file(tagdb *db, int id)
{
    GHashTable *its_tags = g_hash_table_lookup(db->forward, GINT_TO_POINTER(id));
    if (its_tags == NULL)
    {
        return;
    }

    GHashTableIter it;
    gpointer key, value;
    g_hash_table_iter_init(&it, its_tags);
    if (g_hash_table_iter_next(&it, &key, &value))
    {
        tagdb_remove_file_from_tag(db, id, key);
    }
    g_hash_table_remove(db->forward, GINT_TO_POINTER(id));
}

Where only a few identifiers change, but basically the same thing happens, just on different sides. Is there a simpler, less error prone way to do this in c? Maybe with macros?

share|improve this question
What are the types of db->forward and db->reverse – Loki Astari Mar 4 '12 at 3:23
both are HashTable* – 2ck Mar 4 '12 at 3:49

2 Answers

You can pass a function that gets the correct object from tagdb.
AKA Service Locator pattern

typedef HashTable* (*ITEM_GETTER)(tagdb*);

HashTable* getTag(tagdb *db)  { return db->reverse;}
HashTable* getFile(tagdb *db) { return db->forward;}

GHashTable *tagdb_get_item_files (tagdb *db, int itemId, ITEM_GETTER getter)
{
    return g_hash_table_lookup(getter(db), GINT_TO_POINTER(itemId));
}

void tagdb_remove_item (tagdb *db, int id, ITEM_GETTER getter)
{
    GHashTable *its_files = g_hash_table_lookup(getter(db), GINT_TO_POINTER(id));
    if (its_files == NULL)
    {
        return;
    }

    GHashTableIter it;
    gpointer key, value;
    g_hash_table_iter_init(&it, its_files);
    if (g_hash_table_iter_next(&it, &key, &value))
    {
        tagdb_remove_tag_from_file(db, id, key);
    }
    g_hash_table_remove(getter(db), GINT_TO_POINTER(id));
}

Edit for clarity:

The old functions can now be implanted like this:

GHashTable *tagdb_get_tag_files (tagdb *db, int tag_code)
{
    return tagdb_get_item_files(db, tag_code, getTag);
}

GHashTable *tagdb_get_file_tags (tagdb *db, int file_id)
{
    return return tagdb_get_item_files(db, file_id, getFile);
}

void tagdb_remove_tag (tagdb *db, int id)
{
    tagdb_remove_item(db, id, getTag);
}

void tagdb_remove_file(tagdb *db, int id)
{
    tagdb_remove_item(db, id, getFile);
}
share|improve this answer
I think I like this. I'm not sure about the tagdb_remove_tag_from_file since it removes from an item in the opposite table. – 2ck Mar 4 '12 at 4:18
up vote 1 down vote accepted

So what I've done is to index the tables numerically while passing in the index to each function that accesses a particular table. The setup isn't much different to Loki's, but additionally allows me to work with my notion of parity by checking the passed in table id.

GHashTable *tagdb_get_sub (tagdb *db, int item_id, int sub_id, int table_id)
{
    GHashTable *sub_table = tagdb_get_item(db, item_id, table_id);
    return g_hash_table_lookup(sub_table, GINT_TO_POINTER(sub_id));
}

void tagdb_remove_sub (tagdb *db, int item_id, int sub_id, int table_id)
{
    GHashTable *sub_table = tagdb_get_item(db, item_id, table_id);
    if (sub_table != NULL)
    {
        g_hash_table_remove(sub_table, GINT_TO_POINTER(item_id));
    }
}

void tagdb_remove_item (tagdb *db, int item_id, int table_id)
{
    GHashTable *its_associates = tagdb_get_item(db, item_id, table_id);
    if (its_associates == NULL)
    {
        return;
    }

    GHashTableIter it;
    gpointer key, value;
    g_hash_table_iter_init(&it, its_associates);
    int other = (table_id == FILE_TABLE)?TAG_TABLE:FILE_TABLE;
    if (g_hash_table_iter_next(&it, &key, &value))
    {
        printf("removing %d from %s\n", GPOINTER_TO_INT(key), (other == FILE_TABLE)?"File table":"Tag table");
        tagdb_remove_sub(db, item_id, GPOINTER_TO_INT(key), other);
    }
    g_hash_table_remove(db->tables[table_id], GINT_TO_POINTER(item_id));
}

GHashTable *tagdb_get_item (tagdb *db, int item_id, int table_id)
{
    return g_hash_table_lookup(db->tables[table_id], GINT_TO_POINTER(item_id));
}
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.