You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ffdb/trie.c

321 lines
6.4 KiB
C

#include "trie.h"
#include "json/json.h"
#include <stdlib.h>
#include <string.h>
struct edge {
char* label;
char* value;
int count;
};
/*
{
"k": "test",
"t": 2,
"z": 7
}
*/
struct trie_entry
{
struct {
struct edge* items;
int count;
} edges;
char* filename;
};
static void trie_entry_free( struct trie_entry* e )
{
if( !e ) { return; }
for( int i = 0; i < e->edges.count; ++i ) {
free(e->edges.items[i].label);
free(e->edges.items[i].value);
}
free(e->edges.items);
free(e->filename);
free(e);
}
static bool trie_entry_load( FILE* f, struct trie_entry* e )
{
struct json_pull_parser jpp = {
.f = f,
.curr_state = jpp_initial_state,
};
if( !jpp.f ) { return false; }
int save;
if( !json_pull_parser_begin_object(&jpp,&save) ) { goto failed; }
e->edges.count = 0;
char* edge_label = NULL;
while( edge_label = json_pull_parser_read_object_key(&jpp) ) {
e->edges.count += 1;
e->edges.items = realloc( e->edges.items, sizeof(struct edge) * e->edges.count );
struct edge* ed = &e->edges.items[ e->edges.count-1 ];
memset(ed,0,sizeof(*ed));
ed->label = edge_label;
ed->count = 1;
if( !json_pull_parser_read_int( &jpp, &ed->count ) ) {
char* value = json_pull_parser_read_string(&jpp);
if( !value ) { goto failed; }
ed->value = value;
}
}
if( !json_pull_parser_end_object(&jpp,&save) ) { goto failed; }
return true;
failed:
fclose(jpp.f);
return false;
}
static bool trie_entry_load_from_file( const char* filename, struct trie_entry* e )
{
e->filename = strdup(filename);
FILE* f = fopen( filename, "r" );
if( !f ) { return false; }
bool result = trie_entry_load( f, e );
fclose(f);
return result;
}
static void trie_entry_save( FILE* f, struct trie_entry* e )
{
fprintf( f, "{" );
for( int i = 0; i < e->edges.count; ++i ) {
if( i != 0 ) {
fprintf( f, ",\n\t" );
} else {
fprintf( f, "\n\t" );
}
struct edge* ed = &e->edges.items[i];
json_write_string( f, ed->label );
fprintf( f, ": " );
if( ed->count > 1 ) {
fprintf( f, "%d", ed->count );
} else {
json_write_string( f, ed->value );
}
}
fprintf( f, "\n}\n" );
}
static bool trie_entry_save_to_file( const char* filename, struct trie_entry* e )
{
char tmp_filename[512];
snprintf( tmp_filename, sizeof(tmp_filename), "%s.tmp", filename );
FILE* f = fopen(tmp_filename,"w");
if( !f ) { return false; }
trie_entry_save( f, e );
fclose(f);
rename( tmp_filename, filename );
return true;
}
// Escape '/' as "%|" and '%' as "%%"
static char* escape( const char* str )
{
int size = 0;
for( const char* i = str; *i; ++i ) {
switch( *i ) {
case '/': size += 2; break;
case '%': size += 2; break;
default: size += 1; break;
}
}
char* result = malloc(size+1);
char* o = result;
for( const char* i = str; *i; ++i ) {
switch( *i ) {
case '/':
o[0] = '%';
o[1] = '|';
o += 2;
break;
case '%':
o[0] = '%';
o[1] = '%';
o += 2;
break;
default:
*o = *i;
++o;
}
}
*o = '\0';
return result;
}
static int prefix_match( const char* a, const char* b )
{
int res = 0;
while( *a && *b && *a == *b ) {
a += 1;
b += 1;
res += 1;
}
return res;
}
static bool trie_entry_set( struct trie_entry* e, const char* key, const char* value )
{
int prefix_len = 0;
struct edge* ed;
printf( "key: %s\n", key );
for( int i = 0; i < e->edges.count; ++i ) {
ed = &e->edges.items[i];
printf( "label[%d]: %s\n", i, ed->label );
fflush(stdout);
prefix_len = prefix_match( key, ed->label );
if( prefix_len == strlen( ed->label ) ) {
goto traverse_existing_edge;
} else if( prefix_len != 0 ) {
goto split_existing_edge;
}
}
goto add_new_edge;
add_new_edge:
{
printf( "Adding edge %s\n", key );
e->edges.count += 1;
e->edges.items = realloc( e->edges.items, sizeof(struct edge) * e->edges.count );
struct edge* ed2 = &e->edges.items[e->edges.count-1];
ed2->label = strdup(key);
ed2->value = strdup(value);
ed2->count = 1;
}
return trie_entry_save_to_file( e->filename, e );
traverse_existing_edge:
if( ed->count == 1 ) {
free(ed->value);
ed->value = strdup(value);
return true;
}
printf( "TODO: exact match, go down this node\n" );
return false;
split_existing_edge:
{
char* new_prefix = strndup( key, prefix_len );
char* new_filename = malloc(strlen(e->filename)+prefix_len+1);
strcpy(new_filename,e->filename);
strcat(new_filename,new_prefix);
// Create the new node
struct trie_entry* new_e = malloc(sizeof(struct trie_entry));
memset(new_e,0,sizeof(*new_e));
new_e->filename = new_filename;
new_e->edges.items = malloc( sizeof(struct edge) * 2 );
new_e->edges.items[0].label = strdup(&ed->label[prefix_len]);
new_e->edges.items[0].value = ed->value;
new_e->edges.items[0].count = 1;
new_e->edges.items[1].label = strdup(&key[prefix_len]);
new_e->edges.items[1].value = strdup(value);
new_e->edges.items[1].count = 1;
new_e->edges.count = 2;
// Save to file
trie_entry_save_to_file( new_e->filename, new_e );
trie_entry_free(new_e);
// Update the existing edge
free(ed->label);
ed->label = new_prefix;
ed->value = NULL;
ed->count = 2;
trie_entry_save_to_file( e->filename, e );
}
return true;
}
static struct trie_entry* load_root_node( const char* filename )
{
struct trie_entry* root = malloc(sizeof(*root));
memset(root,0,sizeof(*root));
char buffer[512];
snprintf( buffer, sizeof(buffer), "%s/%ROOT|", filename );
trie_entry_load_from_file( buffer, root );
return root;
}
bool ffdb_trie_set( const char* filename, const char* key, const char* value )
{
struct trie_entry* root = NULL;
char* key_escaped = escape(key);
bool result = false;
root = load_root_node(filename);
if( !trie_entry_set( root, key_escaped, value ) ) {
printf( "Failed to set %s to %s\n", key, value );
goto failed;
}
result = true;
cleanup:
free(key_escaped);
trie_entry_free(root);
return result;
failed:
result = false;
goto cleanup;
}
char* ffdb_trie_get( const char* filename, const char* key )
{
return NULL;
}
bool ffdb_trie_remove( const char* filename, const char* key )
{
return false;
}
int ffdb_trie_count( const char* filename )
{
struct trie_entry* root = NULL;
int result = 0;
root = load_root_node(filename);
if( !root ) { goto failed; }
for( int i = 0; i < root->edges.count; ++i ) {
result += root->edges.items[i].count;
}
cleanup:
trie_entry_free(root);
return result;
failed:
result = -1;
goto cleanup;
}
void ffdb_trie_clean( const char* filename )
{
}