summaryrefslogblamecommitdiff
path: root/src/common/db.c
blob: f56a511bc2bac9b25b55c595b6849937fc2172f2 (plain) (tree)
1
2
3
4
5
6
7
8
9

               


                   
 
                  

                      
 
                                                                      
 


                                             

 
                                                           
 
                             
               



                                                     



                                        

 
                                      
 
                      
                                  
                            
                           
                 

 
                                                   
 




                  

 
                                        
 
                      

 
                             
 
                      
                                  
                            
                 

 





















                                                                
 
                                                                   
 
             
     
                                                







                           

 
                            
                                                             
 

                             
                










                                  

 
                                                              
 

                            
                 










                                   

 
                                                           
 

                                                 
     













































                                                          

 
                           
                                                                 
 

                         
 
                 
                     
                       



                     
                       


                        
                                
               
     




















                                      
                                     





                                
     









                                      
                        

                                                       









                                                    

                                                            


                                   
                                         


                    
                                                             















                                                    

                                                      







                                                     

                                                              






                                                
                                                            

















                                                     

 
                                                                      
 




                                                      
     
                                           
                   


                                                                   
                               
                                                 



                           

                                    
                  
                        
            
                         
     
                              






                                                      
                 
     


                       
        

                           
     

                                           

             

 
                                                   
 


                                                      
     
                                               








                         
                            
                                             
             
                
 



























































                                                                                         
 
                                                        
 
               
                        

                                       
     




                                                     
                     

                              


                                       

                                     

                             
                                           

                       
                                                           

                             
                                 





                                    



                                  
                

 

                                                        
 
               
                        

                                       
     




                                                    
                     

                              



                                           

                                     

                             
                                           
             
                                               

                             
                                  





                                     
                                  
                     
                   


                                

                 
 
#include "db.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "utils.h"

#define ROOT_SIZE 4096

static int strdb_cmp (struct dbt *table, const char *a, const char* b)
{
    if (table->maxlen)
        return strncmp (a, b, table->maxlen);
    return strcmp (a, b);
}

static hash_t strdb_hash (struct dbt *table, const char *a)
{
    size_t i = table->maxlen;
    if (i == 0)
        i = (size_t)-1;
    hash_t h = 0;
    const unsigned char *p = (const unsigned char*)a;
    while (*p && i--)
    {
        h = (h * 33 + *p++) ^ (h >> 24);
    }
    return h;
}

struct dbt *strdb_init (size_t maxlen)
{
    struct dbt *table;
    CREATE (table, struct dbt, 1);
    table->type = DB_STRING;
    table->maxlen = maxlen;
    return table;
}

static int numdb_cmp (numdb_key_t a, numdb_key_t b)
{
    if (a == b)
        return 0;
    if (a < b)
        return -1;
    return 1;
}

static hash_t numdb_hash (numdb_key_t a)
{
    return (hash_t) a;
}

struct dbt *numdb_init (void)
{
    struct dbt *table;
    CREATE (table, struct dbt, 1);
    table->type = DB_NUMBER;
    return table;
}

static int table_cmp (struct dbt *table, db_key_t a, db_key_t b)
{
    switch(table->type)
    {
    case DB_NUMBER: return numdb_cmp (a.i, b.i);
    case DB_STRING: return strdb_cmp (table, a.s, b.s);
    }
    abort();
}

static hash_t table_hash (struct dbt *table, db_key_t key)
{
    switch(table->type)
    {
    case DB_NUMBER: return numdb_hash (key.i);
    case DB_STRING: return strdb_hash (table, key.s);
    }
    abort();
}

/// Search for a node with the given key
db_val_t db_search (struct dbt *table, db_key_t key)
{
    struct dbn *p = table->ht[table_hash (table, key) % HASH_SIZE];

    while (p)
    {
        int  c = table_cmp (table, key, p->key);
        if (c == 0)
            return p->data;
        if (c < 0)
            p = p->left;
        else
            p = p->right;
    }
    return NULL;
}

// Tree maintainance methods
static void db_rotate_left (struct dbn *p, struct dbn **root)
{
    struct dbn *y = p->right;
    p->right = y->left;
    if (y->left)
        y->left->parent = p;
    y->parent = p->parent;

    if (p == *root)
        *root = y;
    else if (p == p->parent->left)
        p->parent->left = y;
    else
        p->parent->right = y;
    y->left = p;
    p->parent = y;
}

static void db_rotate_right (struct dbn *p, struct dbn **root)
{
    struct dbn *y = p->left;
    p->left = y->right;
    if (y->right)
        y->right->parent = p;
    y->parent = p->parent;

    if (p == *root)
        *root = y;
    else if (p == p->parent->right)
        p->parent->right = y;
    else
        p->parent->left = y;
    y->right = p;
    p->parent = y;
}

static void db_rebalance (struct dbn *p, struct dbn **root)
{
    p->color = RED;
    while (p != *root && p->parent->color == RED)
    {
        if (p->parent == p->parent->parent->left)
        {
            struct dbn *y = p->parent->parent->right;
            if (y && y->color == RED)
            {
                p->parent->color = BLACK;
                y->color = BLACK;
                p->parent->parent->color = RED;
                p = p->parent->parent;
            }
            else
            {
                if (p == p->parent->right)
                {
                    p = p->parent;
                    db_rotate_left (p, root);
                }
                p->parent->color = BLACK;
                p->parent->parent->color = RED;
                db_rotate_right (p->parent->parent, root);
            }
        }
        else
        {
            struct dbn *y = p->parent->parent->left;
            if (y && y->color == RED)
            {
                p->parent->color = BLACK;
                y->color = BLACK;
                p->parent->parent->color = RED;
                p = p->parent->parent;
            }
            else
            {
                if (p == p->parent->left)
                {
                    p = p->parent;
                    db_rotate_right (p, root);
                }
                p->parent->color = BLACK;
                p->parent->parent->color = RED;
                db_rotate_left (p->parent->parent, root);
            }
        }
    }
    (*root)->color = BLACK;
}

// param z = node to remove
static void db_rebalance_erase (struct dbn *z, struct dbn **root)
{
    struct dbn *y = z;
    struct dbn *x = NULL;

    if (!y->left)
        x = y->right;
    else if (!y->right)
        x = y->left;
    else
    {
        y = y->right;
        while (y->left)
            y = y->left;
        x = y->right;
    }
    struct dbn *x_parent = NULL;
    if (y != z)
    {
        z->left->parent = y;
        y->left = z->left;
        if (y != z->right)
        {
            x_parent = y->parent;
            if (x)
                x->parent = y->parent;
            y->parent->left = x;
            y->right = z->right;
            z->right->parent = y;
        }
        else
            x_parent = y;
        if (*root == z)
            *root = y;
        else if (z->parent->left == z)
            z->parent->left = y;
        else
            z->parent->right = y;
        y->parent = z->parent;
        {
            dbn_color tmp = y->color;
            y->color = z->color;
            z->color = tmp;
        }
        y = z;
    }
    else
    {
        x_parent = y->parent;
        if (x)
            x->parent = y->parent;
        if (*root == z)
            *root = x;
        else if (z->parent->left == z)
            z->parent->left = x;
        else
            z->parent->right = x;
    }
    if (y->color != RED)
    {
        while (x != *root && (!x || x->color == BLACK))
            if (x == x_parent->left)
            {
                struct dbn *w = x_parent->right;
                if (w->color == RED)
                {
                    w->color = BLACK;
                    x_parent->color = RED;
                    db_rotate_left (x_parent, root);
                    w = x_parent->right;
                }
                if ((!w->left || w->left->color == BLACK) &&
                    (!w->right || w->right->color == BLACK))
                {
                    w->color = RED;
                    x = x_parent;
                    x_parent = x->parent;
                }
                else
                {
                    if (!w->right|| w->right->color == BLACK)
                    {
                        if (w->left)
                            w->left->color = BLACK;
                        w->color = RED;
                        db_rotate_right (w, root);
                        w = x_parent->right;
                    }
                    w->color = x_parent->color;
                    x_parent->color = BLACK;
                    if (w->right)
                        w->right->color = BLACK;
                    db_rotate_left (x_parent, root);
                    break;
                }
            }
            else
            {
                // same as above, with right <-> left.
                struct dbn *w = x_parent->left;
                if (w->color == RED)
                {
                    w->color = BLACK;
                    x_parent->color = RED;
                    db_rotate_right (x_parent, root);
                    w = x_parent->left;
                }
                if ((!w->right || w->right->color == BLACK) &&
                    (!w->left || w->left->color == BLACK))
                {
                    w->color = RED;
                    x = x_parent;
                    x_parent = x_parent->parent;
                }
                else
                {
                    if (!w->left || w->left->color == BLACK)
                    {
                        if (w->right)
                            w->right->color = BLACK;
                        w->color = RED;
                        db_rotate_left (w, root);
                        w = x_parent->left;
                    }
                    w->color = x_parent->color;
                    x_parent->color = BLACK;
                    if (w->left)
                        w->left->color = BLACK;
                    db_rotate_right (x_parent, root);
                    break;
                }
            }
        if (x)
            x->color = BLACK;
    }
}

struct dbn *db_insert (struct dbt *table, db_key_t key, db_val_t data)
{
    hash_t hash = table_hash (table, key) % HASH_SIZE;
    int c = 0;
    struct dbn *prev = NULL;
    struct dbn *p = table->ht[hash];
    while (p)
    {
        c = table_cmp (table, key, p->key);
        if (c == 0)
        {
            // key found in table, replace
            // Tell the user of the table to free the key and value
            if (table->release)
                table->release (p->key, p->data);
            p->data = data;
            p->key = key;
            return p;
        }
        // prev is always p->parent?
        prev = p;
        if (c < 0)
            p = p->left;
        else
            p = p->right;
    }
    CREATE (p, struct dbn, 1);
    p->key = key;
    p->data = data;
    p->color = RED;
    if (c == 0)
    {                           // hash entry is empty
        table->ht[hash] = p;
        p->color = BLACK;
        return p;
    }
    p->parent = prev;
    if (c < 0)
        prev->left = p;
    else
        prev->right = p;
    if (prev->color == RED)
    {
        // must rebalance
        db_rebalance (p, &table->ht[hash]);
    }
    return p;
}

db_val_t db_erase (struct dbt *table, db_key_t key)
{
    hash_t hash = table_hash (table, key) % HASH_SIZE;
    struct dbn *p = table->ht[hash];
    while (p)
    {
        int c = table_cmp (table, key, p->key);
        if (c == 0)
            break;
        if (c < 0)
            p = p->left;
        else
            p = p->right;
    }
    if (!p)
        return NULL;
    db_val_t data = p->data;
    db_rebalance_erase (p, &table->ht[hash]);
    free (p);
    return data;
}
#ifdef SMART_WALK_TREE
static inline void db_walk_tree (bool dealloc, struct dbn* p, db_func_t func, va_list ap)
{
    if (!p)
        return;
    if (!dealloc && !func)
    {
        fprintf(stderr, "DEBUG: Must walk tree to either free or invoke a function.\n");
        abort();
    }
    if (p->parent)
    {
        fprintf(stderr, "DEBUG: Root nodes must not have parents\n");
        abort();
    }
    while (true)
    {
        // apply_func loop
        if (func)
            func (p->key, p->data, ap);
        if (p->left)
        {
            // continue descending
            p = p->left;
            continue; //goto apply_func;
        }
        if (p->right)
        {
            // descending the other side
            p = p->right;
            continue; //goto apply_func;
        }
        while (true)
        {
            // backtrack loop
            if (!p->parent)
            {
                if (dealloc)
                    free (p);
                // if we have already done both children, there is no more to do
                return;
            }
            if (p->parent->left == p && p->parent->right)
            {
                // finished the left tree, now walk the right tree
                p = p->parent->right;
                if (dealloc)
                    free (p->parent->left);
                break; //goto apply_func;
            }
            // p->parent->right == p
            // or p->parent->left == p but p->parent->right == NULL
            // keep backtracking
            p = p->parent;
            if (dealloc)
                free (p->right?:p->left);
        } //backtrack loop
    } // apply_func loop
}
#endif // SMART_WALK_TREE

void db_foreach (struct dbt *table, db_func_t func, ...)
{
    va_list ap;
    va_start (ap, func);

    for (int i = 0; i < HASH_SIZE; i++)
    {
#ifdef SMART_WALK_TREE
        db_walk_tree (false, table->ht[i], func, ap);
#else
        struct dbn *p = table->ht[i];
        if (!p)
            continue;
        struct dbn *stack[64];
        int sp = 0;
        while (1)
        {
            func (p->key, p->data, ap);
            struct dbn *pn = p->left;
            if (pn)
            {
                if (p->right)
                    stack[sp++] = p->right;
                p = pn;
            }
            else // pn == NULL, time to do the right branch
            {
                if (p->right)
                    p = p->right;
                else
                {
                    if (sp == 0)
                        break;
                    p = stack[--sp];
                }
            } // if pn else if !pn
        } // while true
#endif // else ! SMART_WALK_TREE
    } // for i
    va_end (ap);
}

// This function is suspiciously similar to the previous
void db_final (struct dbt *table, db_func_t func, ...)
{
    va_list ap;
    va_start (ap, func);

    for (int i = 0; i < HASH_SIZE; i++)
    {
#ifdef SMART_WALK_TREE
        db_walk_tree (true, table->ht[i], func, ap);
#else
        struct dbn *p = table->ht[i];
        if (!p)
            continue;
        struct dbn *stack[64];
        int sp = 0;
        while (1)
        {
            if (func)
                func (p->key, p->data, ap);
            struct dbn *pn = p->left;
            if (pn)
            {
                if (p->right)
                    stack[sp++] = p->right;
            }
            else // pn == NULL, check the right
            {
                if (p->right)
                    pn = p->right;
                else
                {
                    if (sp == 0)
                        break;
                    pn = stack[--sp];
                }
            } // if pn else if !pn
            free (p);
            p = pn;
        } // while true
#endif // else ! SMART_WALK_TREE
    } // for i
    free (table);
    va_end (ap);
}