nostrdb

an unfairly fast embedded nostr database backed by lmdb
git clone git://jb55.com/nostrdb
Log | Files | Refs | Submodules | README | LICENSE

scope_table.c (5509B)


      1  /* Note: only one hash table can be implemented a single file. */
      2 
      3 
      4 /*
      5  * The generic hash table is designed to make the key length optional
      6  * and we do not need it because our key is a terminated token list.
      7  *
      8  * The token list avoids having to allocated a new string and the
      9  * associated issues of memory management. In most cases the search key
     10  * is also a similar token list.
     11  *
     12  * However, on occasion we need to look up an unparsed string of dot
     13  * separated scopes (nested_flatbuffer attributes). This is not
     14  * trivially possible without reverting to allocating the strings.
     15  * We could parse the attribute into tokens but it is also non-trivial
     16  * because the token buffer breaks pointers when reallocating and
     17  * the parse output is considered read-only at this point.
     18  *
     19  * We can however, use a trick to overcome this because the hash table
     20  * does not enforce that the search key has same representation as the
     21  * stored key. We can use the key length to switch between key types.
     22  *
     23  * When the key is paresed to a token list:
     24  *
     25  *   enemy: MyGame . Example.Monster
     26  *
     27  * the spaces in dots may be ignored by the parser.
     28  * Spaces must be handled explicitly or disallowed when the key is
     29  * parsed as an attribute string (only the quoted content):
     30  *
     31  *   (nested_flatbuffer:"MyGame.Example.Monster")
     32  *
     33  * vs
     34  *
     35  *   (nested_flatbuffer:"MyGame . Example.Monster")
     36  *
     37  * Googles flatc allows spaces in the token stream where dots are
     38  * operators, but not in attribute strings which are supposed to
     39  * be unique so we follow that convention.
     40  *
     41  * On both key representations, preprocessing must strip the trailing
     42  * symbol stored within the scope before lookup - minding that this
     43  * lookup only finds the scope itself. For token lists this can be
     44  * done by either zero terminating the list early, or by issuing
     45  * a negative length (after cast to int) of elements to consider. For
     46  * string keys the key length should be made to the length to be
     47  * considered.
     48  *
     49  * If the scope string is zero length, a null key should be issued
     50  * with zero length. This is indistinguishly from a null length token
     51  * list - both indicating a global scope - null thus being a valid key.
     52  * 
     53  * Note: it is important to not use a non-null zero length string
     54  * as key.
     55  */
     56 
     57 #include "../symbols.h"
     58 
     59 static inline size_t scope_hash(const void *s, size_t len);
     60 #define HT_HASH_FUNCTION scope_hash
     61 
     62 #include "hash/hash_table_def.h"
     63 DEFINE_HASH_TABLE(fb_scope_table)
     64 #include "hash/hash_table_impl.h"
     65 
     66 /* Null is a valid key used for root scopes. */
     67 static inline int ht_match(const void *key, size_t len, fb_scope_t *scope)
     68 {
     69     const fb_ref_t *name = scope->name;
     70     int count = (int)len;
     71     size_t n1, n2, i;
     72 
     73     /* Note: `name` may be null here - this is the global scope name. */
     74     if (count <= 0) {
     75         const fb_ref_t *keyname = key;
     76         /*
     77          * If count is negative, this is the token count of the key
     78          * which may have suffix to be ignored, otherwise the key is the
     79          * full list.
     80          */
     81         /* `key` is a ref list (a list of tokens). */
     82         while (name && keyname) {
     83             n1 = (size_t)name->ident->len;
     84             n2 = (size_t)keyname->ident->len;
     85             if (n1 != n2 || strncmp(name->ident->text, keyname->ident->text, n1)) {
     86                 return 0;
     87             }
     88             name = name->link;
     89             keyname = keyname->link;
     90             if (++count == 0) {
     91                 return name == 0;
     92             }
     93         }
     94         if (name || keyname) {
     95             return 0;
     96         }
     97         return 1;
     98     } else {
     99         /* `key` is a dotted string. */
    100         const char *s1, *s2 = key;
    101         while (name) {
    102             s1 = name->ident->text;
    103             n1 = (size_t)name->ident->len;
    104             if (n1 > len) {
    105                 return 0;
    106             }
    107             for (i = 0; i < n1; ++i) {
    108                 if (s1[i] != s2[i]) {
    109                     return 0;
    110                 }
    111             }
    112             if (n1 == len) {
    113                 return name->link == 0;
    114             }
    115             if (s2[i] != '.') {
    116                 return 0;
    117             }
    118             len -= n1 + 1;
    119             s2 += n1 + 1;
    120             name = name->link;
    121         }
    122         return 0;
    123     }
    124 }
    125 
    126 static inline const void *ht_key(fb_scope_t *scope)
    127 {
    128     return scope->name;
    129 }
    130 
    131 static inline size_t ht_key_len(fb_scope_t *scope)
    132 {
    133     (void)scope;
    134     /*
    135      * Must be zero because the result is passed to ht_match
    136      * when comparing two stored items for hash conflicts.
    137      * Only external lookup keys can be non-zero.
    138      */
    139     return 0;
    140 }
    141 
    142 static inline size_t scope_hash(const void *key, size_t len)
    143 {
    144     size_t h = 0, i;
    145     int count = (int)len;
    146 
    147     if (count <= 0) {
    148         const fb_ref_t *name = key;
    149 
    150         while (name) {
    151             h ^= ht_strn_hash_function(name->ident->text, (size_t)name->ident->len);
    152             h = ht_int_hash_function((void *)h, 0);
    153             name = name->link;
    154             if (++count == 0) {
    155                 break;
    156             }
    157         }
    158         return h;
    159     } else {
    160         const char *s = key;
    161         for (;;) {
    162             for (i = 0; i < len; ++i) {
    163                 if (s[i] == '.') {
    164                     break;
    165                 }
    166             }
    167             h ^= ht_strn_hash_function(s, i);
    168             h = ht_int_hash_function((void *)h, 0);
    169             if (i == len) {
    170                 break;
    171             }
    172             len -= i + 1;
    173             s += i + 1;
    174         }
    175         return h;
    176     }
    177 }