/*
* Copyright 2001-2004 Brandon Long
* All Rights Reserved.
*
* ClearSilver Templating System
*
* This code is made available under the terms of the ClearSilver License.
* http://www.clearsilver.net/license.hdf
*
*/
/*
* The wdb is a wrapper around the sleepycat db library which adds
* a relatively simple data/column definition. In many respects, this
* code is way more complicated than it ever needed to be, but it works,
* so I'm loathe to "fix" it.
*
* One of they key features of this is the ability to update the
* "schema" of the wdb without changing all of the existing rows of
* data.
*/
#include "cs_config.h"
#include <unistd.h>
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <db.h>
#include <ctype.h>
#include "neo_misc.h"
#include "neo_err.h"
#include "dict.h"
#include "ulist.h"
#include "skiplist.h"
#include "wdb.h"
#define DEFN_VERSION_1 "WDB-VERSION-200006301"
#define PACK_VERSION_1 1
static void string_rstrip (char *s)
{
size_t len;
len = strlen(s);
len--;
while (len > 0 && isspace(s[len]))
{
s[len] = '\0';
len--;
}
return;
}
static int to_hex (unsigned char ch, unsigned char *s)
{
unsigned int uvalue = ch;
s[1] = "0123456789ABCDEF"[uvalue % 16];
uvalue = (uvalue / 16); s[0] = "0123456789ABCDEF"[uvalue % 16];
return 0;
}
int Index_hex[128] = {
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1, -1,-1,-1,-1,
-1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
-1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1
};
#define hexval(c) Index_hex[(unsigned int)(c)]
/* Encoding means any non-printable characters and : and % */
static NEOERR *wdb_encode_str_alloc (const char *s, char **o)
{
int x = 0;
int c = 0;
char *out;
unsigned char ch;
while (s[x])
{
ch = (unsigned char) s[x];
if ((ch < 32) || (ch > 127) || (ch == ':') || (ch == '%') || isspace(ch))
c++;
x++;
}
out = (char *) malloc (sizeof (char) * (x + c * 3) + 1);
if (out == NULL)
return nerr_raise (NERR_NOMEM,
"Unable to allocate memory for encoding %s", s);
x = 0;
c = 0;
*o = out;
while (s[x])
{
ch = (unsigned char) s[x];
if ((ch < 32) || (ch > 127) || (ch == ':') || (ch == '%') || isspace(ch))
{
out[c++] = '%';
to_hex (s[x], &out[c]);
c+=2;
}
else
{
out[c++] = s[x];
}
x++;
}
out[c] = '\0';
return STATUS_OK;
}
static NEOERR *wdb_decode_str_alloc (const char *s, char **o)
{
int x = 0;
int c = 0;
char *out;
unsigned char ch;
x = strlen(s);
/* Overkill, the decoded string will be smaller */
out = (char *) malloc (sizeof (char) * (x + 1));
if (out == NULL)
return nerr_raise (NERR_NOMEM,
"Unable to allocate memory for decoding %s", s);
x = 0;
c = 0;
while (s[x])
{
if (s[x] == '%')
{
x++;
ch = hexval(s[x]) << 4;
x++;
ch |= hexval(s[x]);
out[c++] = ch;
}
else
{
out[c++] = s[x];
}
x++;
}
out[c] = '\0';
*o = out;
return STATUS_OK;
}
static void free_cb (void *value, void *rock)
{
free (value);
}
static void free_col_cb (void *value, void *rock)
{
WDBColumn *col;
col = (WDBColumn *)value;
free (col->name);
free (col);
}
static NEOERR *wdb_alloc (WDB **wdb, int flags)
{
WDB *my_wdb;
NEOERR *err = STATUS_OK;
my_wdb = (WDB *) calloc (1, sizeof (WDB));
if (my_wdb == NULL)
return nerr_raise (NERR_NOMEM, "Unable to allocate memory for WDB");
do
{
err = dictCreate (&(my_wdb->attrs), 0, 2, 5, 0, 0, free_cb, NULL);
if (err != STATUS_OK) break;
err = dictCreate (&(my_wdb->cols), 0, 2, 5, 0, 0, free_col_cb, NULL);
if (err != STATUS_OK) break;
err = uListInit (&(my_wdb->cols_l), 0, 0);
if (err != STATUS_OK) break;
err = skipNewList(&(my_wdb->ondisk), 0, 4, 2, 0, NULL, NULL);
if (err != STATUS_OK) break;
*wdb = my_wdb;
return STATUS_OK;
} while (0);
wdb_destroy(&my_wdb);
return nerr_pass (err);
}
#define STATE_REQUIRED 1
#define STATE_ATTRIBUTES 2
#define STATE_COLUMN_DEF 3
static NEOERR *wdb_load_defn_v1 (WDB *wdb, FILE *fp)
{
char line[1024];
int state = 1;
char *k, *v;
NEOERR *err = STATUS_OK;
int colindex = 1;
WDBColumn *col;
while (fgets(line, sizeof(line), fp) != NULL)
{
string_rstrip(line);
switch (state)
{
case STATE_REQUIRED:
if (!strcmp(line, "attributes"))
state = STATE_ATTRIBUTES;
else if (!strcmp(line, "columns"))
state = STATE_COLUMN_DEF;
else
{
k = line;
v = strchr(line, ':');
/* HACK */
if (!strcmp(k, "name") && ((v == NULL) || (v[1] == '\0')))
{
v = "dNone";
}
else
{
if (v == NULL)
return nerr_raise (NERR_PARSE, "Error parsing %s", line);
if (v[1] == '\0')
return nerr_raise (NERR_PARSE, "Error parsing %s", line);
}
v[0] = '\0';
v++;
if (!strcmp(k, "key"))
{
err = wdb_decode_str_alloc (v, &(wdb->key));
if (err) return nerr_pass(err);
}
else if (!strcmp(k, "name"))
{
err = wdb_decode_str_alloc (v, &(wdb->name));
if (err) return nerr_pass(err);
}
else if (!strcmp(k, "ondisk"))
{
wdb->last_ondisk = atoi (v);
}
}
break;
case STATE_ATTRIBUTES:
if (!strcmp(line, "columns"))
state = STATE_COLUMN_DEF;
else
{
k = line;
v = strchr(line, ':');
if (v == NULL)
return nerr_raise (NERR_PARSE, "Error parsing %s", line);
v[0] = '\0';
v++;
err = wdb_decode_str_alloc (k, &k);
if (err) return nerr_pass(err);
err = wdb_decode_str_alloc (v, &v);
if (err) return nerr_pass(err);
err = dictSetValue(wdb->attrs, k, v);
free(k);
if (err)
return nerr_pass_ctx(err, "Error parsing %s", line);
}
break;
case STATE_COLUMN_DEF:
k = line;
v = strchr(line, ':');
if (v == NULL)
return nerr_raise (NERR_PARSE, "Error parsing %s", line);
if (v[1] == '\0')
return nerr_raise (NERR_PARSE, "Error parsing %s", line);
v[0] = '\0';
v++;
err = wdb_decode_str_alloc (k, &k);
if (err) return nerr_pass(err);
col = (WDBColumn *) calloc (1, sizeof (WDBColumn));
col->name = k;
col->inmem_index = colindex++;
col->type = *v;
v+=2;
col->ondisk_index = atoi(v);
err = dictSetValue(wdb->cols, k, col);
if (err)
return nerr_raise (NERR_PARSE, "Error parsing %s", line);
err = uListAppend(wdb->cols_l, col);
if (err) return nerr_pass(err);
/* stupid skiplist will assert */
if (col->ondisk_index == 0)
{
return nerr_raise (NERR_ASSERT, "Invalid ondisk mapping for %s", k);
}
err = skipInsert (wdb->ondisk, col->ondisk_index,
(void *)(col->inmem_index), 0);
if (err)
return nerr_pass_ctx(err, "Unable to update ondisk mapping for %s", k);
break;
default:
return nerr_raise (NERR_ASSERT, "Invalid state %d", state);
}
}
return STATUS_OK;
}
static NEOERR *wdb_save_defn_v1 (WDB *wdb, FILE *fp)
{
NEOERR *err = STATUS_OK;
WDBColumn *col;
char *s = NULL;
char *key = NULL;
int r, x, len;
char *k = NULL;
char *v = NULL;
/* Write version string */
r = fprintf (fp, "%s\n", DEFN_VERSION_1);
if (!r) goto save_err;
err = wdb_encode_str_alloc (wdb->name, &s);
if (err) goto save_err;
r = fprintf (fp, "name:%s\n", s);
if (!r) goto save_err;
free (s);
err = wdb_encode_str_alloc (wdb->key, &s);
if (err != STATUS_OK) goto save_err;
r = fprintf (fp, "key:%s\n", s);
if (!r) goto save_err;
free (s);
s = NULL;
r = fprintf (fp, "ondisk:%d\n", wdb->last_ondisk);
if (!r) goto save_err;
r = fprintf (fp, "attributes\n");
if (!r) goto save_err;
key = NULL;
s = (char *) dictNext (wdb->attrs, &key, NULL);
while (s)
{
err = wdb_encode_str_alloc (key, &k);
if (err != STATUS_OK) goto save_err;
err = wdb_encode_str_alloc (s, &v);
if (err != STATUS_OK) goto save_err;
r = fprintf (fp, "%s:%s\n", k, v);
if (!r) goto save_err;
free (k);
free (v);
k = NULL;
v = NULL;
s = (char *) dictNext (wdb->attrs, &key, NULL);
}
s = NULL;
r = fprintf (fp, "columns\n");
if (!r) goto save_err;
len = uListLength(wdb->cols_l);
for (x = 0; x < len; x++)
{
err = uListGet (wdb->cols_l, x, (void *)&col);
if (err) goto save_err;
err = wdb_encode_str_alloc (col->name, &s);
if (err != STATUS_OK) goto save_err;
r = fprintf (fp, "%s:%c:%d\n", s, col->type, col->ondisk_index);
if (!r) goto save_err;
free(s);
s = NULL;
}
return STATUS_OK;
save_err:
if (s != NULL) free (s);
if (k != NULL) free (k);
if (v != NULL) free (v);
if (err == STATUS_OK) return nerr_pass(err);
return nerr_raise (r, "Unable to save defn");
}
static NEOERR *wdb_load_defn (WDB *wdb, const char *name)
{
char path[_POSIX_PATH_MAX];
char line[1024];
FILE *fp;
NEOERR *err = STATUS_OK;
snprintf (path, sizeof(path), "%s.wdf", name);
fp = fopen (path, "r");
if (fp == NULL)
{
if (errno == ENOENT)
return nerr_raise (NERR_NOT_FOUND, "Unable to open defn %s", name);
return nerr_raise_errno (NERR_IO, "Unable to open defn %s", name);
}
/* Read Version string */
if (fgets (line, sizeof(line), fp) == NULL)
{
fclose(fp);
return nerr_raise_errno (NERR_IO, "Unable to read defn %s", name);
}
string_rstrip(line);
if (!strcmp(line, DEFN_VERSION_1))
{
err = wdb_load_defn_v1(wdb, fp);
fclose(fp);
if (err) return nerr_pass(err);
}
else
{
fclose(fp);
return nerr_raise (NERR_ASSERT, "Unknown defn version %s: %s", line, name);
}
wdb->table_version = rand();
return STATUS_OK;
}
static NEOERR *wdb_save_defn (WDB *wdb, const char *name)
{
char path[_POSIX_PATH_MAX];
char path2[_POSIX_PATH_MAX];
FILE *fp;
NEOERR *err = STATUS_OK;
int r;
snprintf (path, sizeof(path), "%s.wdf.new", name);
snprintf (path2, sizeof(path2), "%s.wdf", name);
fp = fopen (path, "w");
if (fp == NULL)
return nerr_raise_errno (NERR_IO, "Unable to open defn %s", name);
err = wdb_save_defn_v1 (wdb, fp);
fclose (fp);
if (err != STATUS_OK)
{
unlink (path);
return nerr_pass (err);
}
r = unlink (path2);
if (r == -1 && errno != ENOENT)
return nerr_raise_errno (NERR_IO, "Unable to unlink %s", path2);
r = link (path, path2);
if (r == -1)
return nerr_raise_errno (NERR_IO, "Unable to link %s to %s", path, path2);
r = unlink (path);
wdb->defn_dirty = 0;
wdb->table_version = rand();
return STATUS_OK;
}
NEOERR *wdb_open (WDB **wdb, const char *name, int flags)
{
WDB *my_wdb;
char path[_POSIX_PATH_MAX];
NEOERR *err = STATUS_OK;
int r;
*wdb = NULL;
err = wdb_alloc (&my_wdb, flags);
if (err) return nerr_pass(err);
my_wdb->path = strdup (name);
if (err)
{
wdb_destroy (&my_wdb);
return nerr_pass(err);
}
err = wdb_load_defn (my_wdb, name);
if (err)
{
wdb_destroy (&my_wdb);
return nerr_pass(err);
}
snprintf (path, sizeof(path), "%s.wdb", name);
r = db_open(path, DB_BTREE, 0, 0, NULL, NULL, &(my_wdb->db));
if (r)
{
wdb_destroy (&my_wdb);
return nerr_raise (NERR_DB, "Unable to open database %s: %d", name, r);
}
*wdb = my_wdb;
return STATUS_OK;
}
NEOERR *wdb_save (WDB *wdb)
{
if (wdb->defn_dirty)
{
wdb_save_defn (wdb, wdb->path);
}
return STATUS_OK;
}
void wdb_destroy (WDB **wdb)
{
WDB *my_wdb;
my_wdb = *wdb;
if (my_wdb == NULL) return;
if (my_wdb->defn_dirty)
{
wdb_save_defn (my_wdb, my_wdb->path);
}
if (my_wdb->attrs != NULL)
{
dictDestroy (my_wdb->attrs);
}
if (my_wdb->cols != NULL)
{
dictDestroy (my_wdb->cols);
}
if (my_wdb->cols_l != NULL)
{
uListDestroy(&(my_wdb->cols_l), 0);
}
if (my_wdb->ondisk != NULL)
{
skipFreeList(my_wdb->ondisk);
}
if (my_wdb->db != NULL)
{
my_wdb->db->close (my_wdb->db, 0);
my_wdb->db = NULL;
}
if (my_wdb->path != NULL)
{
free(my_wdb->path);
my_wdb->path = NULL;
}
if (my_wdb->name != NULL)
{
free(my_wdb->name);
my_wdb->name = NULL;
}
if (my_wdb->key != NULL)
{
free(my_wdb->key);
my_wdb->key = NULL;
}
free (my_wdb);
*wdb = NULL;
return;
}
#define PACK_UB4(pdata, plen, pmax, pn) \
{ \
if (plen + 4 > pmax) \
{ \
pmax *= 2; \
pdata = realloc ((void *)pdata, pmax); \
if (pdata == NULL) goto pack_err; \
} \
pdata[plen++] = (0x0ff & (pn >> 0)); \
pdata[plen++] = (0x0ff & (pn >> 8)); \
pdata[plen++] = (0x0ff & (pn >> 16)); \
pdata[plen++] = (0x0ff & (pn >> 24)); \
}
#define UNPACK_UB4(pdata, plen, pn, pd) \
{ \
if (pn + 4 > plen) \
goto pack_err; \
pd = ((0x0ff & pdata[pn+0])<<0) | ((0x0ff & pdata[pn+1])<<8) | \
((0x0ff & pdata[pn+2])<<16) | ((0x0ff & pdata[pn+3])<<24); \
pn+=4; \
}
#define PACK_BYTE(pdata, plen, pmax, pn) \
{ \
if (plen + 1 > pmax) \
{ \
pmax *= 2; \
pdata = realloc ((void *)pdata, pmax); \
if (pdata == NULL) goto pack_err; \
} \
pdata[plen++] = (0x0ff & (pn >> 0)); \
}
#define UNPACK_BYTE(pdata, plen, pn, pd) \
{ \
if (pn + 1 > plen) \
goto pack_err; \
pd = pdata[pn++]; \
}
#define PACK_STRING(pdata, plen, pmax, dl, ds) \
{ \
if (plen + 4 + dl > pmax) \
{ \
while (plen + 4 + dl > pmax) \
pmax *= 2; \
pdata = realloc ((void *)pdata, pmax); \
if (pdata == NULL) goto pack_err; \
} \
pdata[plen++] = (0x0ff & (dl >> 0)); \
pdata[plen++] = (0x0ff & (dl >> 8)); \
pdata[plen++] = (0x0ff & (dl >> 16)); \
pdata[plen++] = (0x0ff & (dl >> 24)); \
memcpy (&pdata[plen], ds, dl); \
plen+=dl;\
}
#define UNPACK_STRING(pdata, plen, pn, ps) \
{ \
int pl; \
if (pn + 4 > plen) \
goto pack_err; \
pl = ((0x0ff & pdata[pn+0])<<0) | ((0x0ff & pdata[pn+1])<<8) | \
((0x0ff & pdata[pn+2])<<16) | ((0x0ff & pdata[pn+3])<<24); \
pn+=4; \
if (pl) \
{ \
ps = (char *)malloc(sizeof(char)*(pl+1)); \
if (ps == NULL) \
goto pack_err; \
memcpy (ps, &pdata[pn], pl); \
ps[pl] = '\0'; \
pn += pl; \
} else { \
ps = NULL; \
} \
}
/* A VERSION_1 Row consists of the following data:
* UB4 VERSION
* UB4 DATA COUNT
* DATA where
* UB4 ONDISK INDEX
* UB1 TYPE
* if INT, then UB4
* if STR, then UB4 length and length UB1s
*/
static NEOERR *pack_row (WDB *wdb, WDBRow *row, void **rdata, int *rdlen)
{
char *data;
int x, len, dlen, dmax;
char *s;
int n;
WDBColumn *col;
NEOERR *err;
*rdata = NULL;
*rdlen = 0;
/* allocate */
data = (char *)malloc(sizeof (char) * 1024);
if (data == NULL)
return nerr_raise (NERR_NOMEM, "Unable to allocate memory to pack row");
dmax = 1024;
dlen = 0;
PACK_UB4 (data, dlen, dmax, PACK_VERSION_1);
/* PACK_UB4 (data, dlen, dmax, time(NULL)); */
len = uListLength(wdb->cols_l);
if (len > row->data_count)
len = row->data_count;
PACK_UB4 (data, dlen, dmax, len);
for (x = 0; x < len; x++)
{
err = uListGet (wdb->cols_l, x, (void *)&col);
if (err) goto pack_err;
PACK_UB4 (data, dlen, dmax, col->ondisk_index);
PACK_BYTE (data, dlen, dmax, col->type);
switch (col->type)
{
case WDB_TYPE_INT:
n = (int)(row->data[x]);
PACK_UB4 (data, dlen, dmax, n);
break;
case WDB_TYPE_STR:
s = (char *)(row->data[x]);
if (s == NULL)
{
s = "";
}
n = strlen(s);
PACK_STRING (data, dlen, dmax, n, s);
break;
default:
free (data);
return nerr_raise (NERR_ASSERT, "Unknown type %d", col->type);
}
}
*rdata = data;
*rdlen = dlen;
return STATUS_OK;
pack_err:
if (data != NULL)
free (data);
if (err == STATUS_OK)
return nerr_raise(NERR_NOMEM, "Unable to allocate memory for pack_row");
return nerr_pass(err);
}
static NEOERR *unpack_row (WDB *wdb, void *rdata, int dlen, WDBRow *row)
{
unsigned char *data = rdata;
int version, n;
int count, x, ondisk_index, type, d_int, inmem_index;
char *s;
n = 0;
UNPACK_UB4(data, dlen, n, version);
switch (version)
{
case PACK_VERSION_1:
UNPACK_UB4(data, dlen, n, count);
for (x = 0; x<count; x++)
{
UNPACK_UB4 (data, dlen, n, ondisk_index);
UNPACK_BYTE (data, dlen, n, type);
inmem_index = (int) skipSearch (wdb->ondisk, ondisk_index, NULL);
switch (type)
{
case WDB_TYPE_INT:
UNPACK_UB4 (data, dlen, n, d_int);
if (inmem_index != 0)
row->data[inmem_index-1] = (void *) d_int;
break;
case WDB_TYPE_STR:
UNPACK_STRING (data, dlen, n, s);
if (inmem_index != 0)
row->data[inmem_index-1] = s;
break;
default:
return nerr_raise (NERR_ASSERT, "Unknown type %d for col %d", type, ondisk_index);
}
}
break;
default:
return nerr_raise (NERR_ASSERT, "Unknown version %d", version);
}
return STATUS_OK;
pack_err:
return nerr_raise(NERR_PARSE, "Unable to unpack row %s", row->key_value);
}
NEOERR *wdb_column_insert (WDB *wdb, int loc, const char *key, char type)
{
NEOERR *err;
WDBColumn *col, *ocol;
int x, len;
col = (WDBColumn *) dictSearch (wdb->cols, key, NULL);
if (col != NULL)
return nerr_raise (NERR_DUPLICATE,
"Duplicate key %s:%d", key, col->inmem_index);
col = (WDBColumn *) calloc (1, sizeof (WDBColumn));
if (col == NULL)
{
return nerr_raise (NERR_NOMEM,
"Unable to allocate memory for creation of col %s:%d", key, loc);
}
col->name = strdup(key);
if (col->name == NULL)
{
free(col);
return nerr_raise (NERR_NOMEM,
"Unable to allocate memory for creation of col %s:%d", key, loc);
}
col->type = type;
col->ondisk_index = wdb->last_ondisk++;
/* -1 == append */
if (loc == -1)
{
err = dictSetValue(wdb->cols, key, col);
if (err)
{
free (col->name);
free (col);
return nerr_pass_ctx (err,
"Unable to insert for creation of col %s:%d", key, loc);
}
err = uListAppend (wdb->cols_l, (void *)col);
if (err) return nerr_pass(err);
x = uListLength (wdb->cols_l);
col->inmem_index = x;
err = skipInsert (wdb->ondisk, col->ondisk_index,
(void *)(col->inmem_index), 0);
if (err)
return nerr_pass_ctx (err, "Unable to update ondisk mapping for %s", key);
}
else
{
/* We are inserting this in middle, so the skipList ondisk is now
* invalid, as is the inmem_index for all cols */
err = dictSetValue(wdb->cols, key, col);
if (err)
{
free (col->name);
free (col);
return nerr_pass_ctx (err,
"Unable to insert for creation of col %s:%d", key, loc);
}
err = uListInsert (wdb->cols_l, loc, (void *)col);
if (err) return nerr_pass(err);
len = uListLength (wdb->cols_l);
/* Fix up inmem_index and ondisk skipList */
for (x = 0; x < len; x++)
{
err = uListGet (wdb->cols_l, x, (void *)&ocol);
if (err) return nerr_pass(err);
ocol->inmem_index = x + 1;
err = skipInsert (wdb->ondisk, ocol->ondisk_index,
(void *)(ocol->inmem_index), TRUE);
if (err)
return nerr_pass_ctx (err, "Unable to update ondisk mapping for %s", key);
}
}
wdb->defn_dirty = 1;
wdb->table_version = rand();
return STATUS_OK;
}
NEOERR *wdb_column_update (WDB *wdb, const char *oldkey, const char *newkey)
{
WDBColumn *ocol, *col;
WDBColumn *vcol;
NEOERR *err = STATUS_OK;
int x, len, r;
ocol = (WDBColumn *) dictSearch (wdb->cols, oldkey, NULL);
if (ocol == NULL)
return nerr_raise (NERR_NOT_FOUND,
"Unable to find column for key %s", oldkey);
col = (WDBColumn *) calloc (1, sizeof (WDBColumn));
if (col == NULL)
{
return nerr_raise (NERR_NOMEM,
"Unable to allocate memory for column update %s", newkey);
}
*col = *ocol;
col->name = strdup(newkey);
if (col->name == NULL)
{
free(col);
return nerr_raise (NERR_NOMEM,
"Unable to allocate memory for column update %s", oldkey);
}
len = uListLength(wdb->cols_l);
for (x = 0; x < len; x++)
{
err = uListGet (wdb->cols_l, x, (void *)&vcol);
if (err) return nerr_pass(err);
if (!strcmp(vcol->name, oldkey))
{
err = uListSet (wdb->cols_l, x, (void *)col);
if (err) return nerr_pass(err);
break;
}
}
if (x>len)
{
return nerr_raise (NERR_ASSERT, "Unable to find cols_l for key %s", oldkey);
}
r = dictRemove (wdb->cols, oldkey); /* Only failure is key not found */
err = dictSetValue(wdb->cols, newkey, col);
if (err)
{
free (col->name);
free (col);
return nerr_pass_ctx (err,
"Unable to insert for update of col %s->%s", oldkey, newkey);
}
wdb->defn_dirty = 1;
wdb->table_version = rand();
return STATUS_OK;
}
NEOERR *wdb_column_delete (WDB *wdb, const char *name)
{
WDBColumn *col;
NEOERR *err = STATUS_OK;
int len, x, r;
len = uListLength(wdb->cols_l);
for (x = 0; x < len; x++)
{
err = uListGet (wdb->cols_l, x, (void *)&col);
if (err) return nerr_pass(err);
if (!strcmp(col->name, name))
{
err = uListDelete (wdb->cols_l, x, NULL);
if (err) return nerr_pass(err);
break;
}
}
r = dictRemove (wdb->cols, name); /* Only failure is key not found */
if (!r)
{
return nerr_raise (NERR_NOT_FOUND,
"Unable to find column for key %s", name);
}
wdb->defn_dirty = 1;
wdb->table_version = rand();
return STATUS_OK;
}
NEOERR *wdb_column_exchange (WDB *wdb, const char *key1, const char *key2)
{
return nerr_raise (NERR_ASSERT,
"wdb_column_exchange: Not Implemented");
}
/* Not that there's that much point in changing the key name ... */
NEOERR *wdb_update (WDB *wdb, const char *name, const char *key)
{
if (name != NULL && strcmp(wdb->name, name))
{
if (wdb->name != NULL)
free(wdb->name);
wdb->name = strdup(name);
if (wdb->name == NULL)
return nerr_raise (NERR_NOMEM,
"Unable to allocate memory to update name to %s", name);
wdb->defn_dirty = 1;
wdb->table_version = rand();
}
if (key != NULL && strcmp(wdb->key, key))
{
if (wdb->key != NULL)
free(wdb->key);
wdb->key = strdup(key);
if (wdb->key == NULL)
{
wdb->defn_dirty = 0;
return nerr_raise (NERR_NOMEM,
"Unable to allocate memory to update key to %s", key);
}
wdb->defn_dirty = 1;
wdb->table_version = rand();
}
return STATUS_OK;
}
NEOERR *wdb_create (WDB **wdb, const char *path, const char *name,
const char *key, ULIST *col_def, int flags)
{
WDB *my_wdb;
char d_path[_POSIX_PATH_MAX];
NEOERR *err = STATUS_OK;
int x, len, r;
char *s;
*wdb = NULL;
err = wdb_alloc (&my_wdb, flags);
if (err) return nerr_pass(err);
my_wdb->name = strdup (name);
my_wdb->key = strdup (key);
my_wdb->path = strdup(path);
if (my_wdb->name == NULL || my_wdb->key == NULL || my_wdb->path == NULL)
{
wdb_destroy (&my_wdb);
return nerr_raise (NERR_NOMEM,
"Unable to allocate memory for creation of %s", name);
}
/* ondisk must start at one because of skipList */
my_wdb->last_ondisk = 1;
len = uListLength(col_def);
for (x = 0; x < len; x++)
{
err = uListGet (col_def, x, (void *)&s);
if (err)
{
wdb_destroy (&my_wdb);
return nerr_pass(err);
}
err = wdb_column_insert (my_wdb, -1, s, WDB_TYPE_STR);
my_wdb->defn_dirty = 0; /* So we don't save on error destroy */
if (err)
{
wdb_destroy (&my_wdb);
return nerr_pass(err);
}
}
err = wdb_save_defn (my_wdb, path);
if (err)
{
wdb_destroy (&my_wdb);
return nerr_pass(err);
}
snprintf (d_path, sizeof(d_path), "%s.wdb", path);
r = db_open(d_path, DB_BTREE, DB_CREATE | DB_TRUNCATE, 0, NULL, NULL, &(my_wdb->db));
if (r)
{
wdb_destroy (&my_wdb);
return nerr_raise (NERR_DB, "Unable to create db file %s: %d", d_path, r);
}
*wdb = my_wdb;
return STATUS_OK;
}
NEOERR *wdb_attr_next (WDB *wdb, char **key, char **value)
{
*value = (char *) dictNext (wdb->attrs, key, NULL);
return STATUS_OK;
}
NEOERR *wdb_attr_get (WDB *wdb, const char *key, char **value)
{
void *v;
v = dictSearch (wdb->attrs, key, NULL);
if (v == NULL)
return nerr_raise (NERR_NOT_FOUND, "Unable to find attr %s", key);
*value = (char *)v;
return STATUS_OK;
}
NEOERR *wdb_attr_set (WDB *wdb, const char *key, const char *value)
{
NEOERR *err = STATUS_OK;
char *v;
v = strdup(value);
if (v == NULL)
return nerr_raise (NERR_NOMEM, "No memory for new attr");
err = dictSetValue(wdb->attrs, key, v);
if (err)
return nerr_pass_ctx (err, "Unable to set attr %s", key);
wdb->defn_dirty = 1;
return STATUS_OK;
}
NEOERR *wdbr_get (WDB *wdb, WDBRow *row, const char *key, void **value)
{
WDBColumn *col;
void *v;
col = (WDBColumn *) dictSearch (wdb->cols, key, NULL);
if (col == NULL)
return nerr_raise (NERR_NOT_FOUND, "Unable to find key %s", key);
if (col->inmem_index-1 > row->data_count)
return nerr_raise (NERR_ASSERT, "Index for key %s is greater than row data, was table altered?", key);
v = row->data[col->inmem_index-1];
*value = v;
return STATUS_OK;
}
NEOERR *wdbr_set (WDB *wdb, WDBRow *row, const char *key, void *value)
{
WDBColumn *col;
col = (WDBColumn *) dictSearch (wdb->cols, key, NULL);
if (col == NULL)
return nerr_raise (NERR_NOT_FOUND, "Unable to find key %s", key);
if (col->inmem_index-1 > row->data_count)
return nerr_raise (NERR_ASSERT, "Index for key %s is greater than row data, was table altered?", key);
if (col->type == WDB_TYPE_STR && row->data[col->inmem_index-1] != NULL)
{
free (row->data[col->inmem_index-1]);
}
row->data[col->inmem_index-1] = value;
return STATUS_OK;
}
static NEOERR *alloc_row (WDB *wdb, WDBRow **row)
{
WDBRow *my_row;
int len;
*row = NULL;
len = uListLength (wdb->cols_l);
my_row = (WDBRow *) calloc (1, sizeof (WDBRow) + len * (sizeof (void *)));
if (my_row == NULL)
return nerr_raise (NERR_NOMEM, "No memory for new row");
my_row->data_count = len;
my_row->table_version = wdb->table_version;
*row = my_row;
return STATUS_OK;
}
NEOERR *wdbr_destroy (WDB *wdb, WDBRow **row)
{
WDBColumn *col;
WDBRow *my_row;
int len, x;
NEOERR *err;
err = STATUS_OK;
if (*row == NULL)
return err;
my_row = *row;
/* Verify this row maps to this table, or else we could do something
* bad */
if (wdb->table_version != my_row->table_version)
return nerr_raise (NERR_ASSERT, "Row %s doesn't match current table", my_row->key_value);
if (my_row->key_value != NULL)
free (my_row->key_value);
len = uListLength(wdb->cols_l);
for (x = 0; x < len; x++)
{
if (my_row->data[x] != NULL)
{
err = uListGet (wdb->cols_l, x, (void *)&col);
if (err) break;
switch (col->type)
{
case WDB_TYPE_INT:
break;
case WDB_TYPE_STR:
free (my_row->data[x]);
break;
default:
return nerr_raise (NERR_ASSERT, "Unknown type %d", col->type);
}
}
}
free (my_row);
*row = NULL;
return nerr_pass(err);
}
NEOERR *wdbr_lookup (WDB *wdb, const char *key, WDBRow **row)
{
DBT dkey, data;
NEOERR *err = STATUS_OK;
WDBRow *my_row;
int r;
*row = NULL;
memset(&dkey, 0, sizeof(dkey));
memset(&data, 0, sizeof(data));
dkey.flags = DB_DBT_USERMEM;
data.flags = DB_DBT_MALLOC;
dkey.data = (void *)key;
dkey.size = strlen(key);
r = wdb->db->get (wdb->db, NULL, &dkey, &data, 0);
if (r == DB_NOTFOUND)
return nerr_raise (NERR_NOT_FOUND, "Unable to find key %s", key);
else if (r)
return nerr_raise (NERR_DB, "Error retrieving key %s: %d", key, r);
/* allocate row */
err = alloc_row (wdb, &my_row);
if (err != STATUS_OK)
{
free (data.data);
return nerr_pass(err);
}
my_row->key_value = strdup(key);
if (my_row->key_value == NULL)
{
free (data.data);
free (my_row);
return nerr_raise (NERR_NOMEM, "No memory for new row");
}
/* unpack row */
err = unpack_row (wdb, data.data, data.size, my_row);
free (data.data);
if (err)
{
free (my_row);
return nerr_pass(err);
}
*row = my_row;
return STATUS_OK;
}
NEOERR *wdbr_create (WDB *wdb, const char *key, WDBRow **row)
{
WDBRow *my_row;
NEOERR *err = STATUS_OK;
*row = NULL;
/* allocate row */
err = alloc_row (wdb, &my_row);
if (err) return nerr_pass(err);
my_row->key_value = strdup(key);
if (my_row->key_value == NULL)
{
wdbr_destroy (wdb, &my_row);
return nerr_raise (NERR_NOMEM, "No memory for new row");
}
*row = my_row;
return STATUS_OK;
}
NEOERR *wdbr_save (WDB *wdb, WDBRow *row, int flags)
{
DBT dkey, data;
int dflags = 0;
NEOERR *err = STATUS_OK;
int r;
memset(&dkey, 0, sizeof(dkey));
memset(&data, 0, sizeof(data));
dkey.data = row->key_value;
dkey.size = strlen(row->key_value);
err = pack_row (wdb, row, &(data.data), &data.size);
if (err != STATUS_OK) return nerr_pass(err);
if (flags & WDBR_INSERT)
{
dflags = DB_NOOVERWRITE;
}
r = wdb->db->put (wdb->db, NULL, &dkey, &data, dflags);
free (data.data);
if (r == DB_KEYEXIST)
return nerr_raise (NERR_DUPLICATE, "Key %s already exists", row->key_value);
if (r)
return nerr_raise (NERR_DB, "Error saving key %s: %d",
row->key_value, r);
return STATUS_OK;
}
NEOERR *wdbr_delete (WDB *wdb, const char *key)
{
DBT dkey;
int r;
memset(&dkey, 0, sizeof(dkey));
dkey.flags = DB_DBT_USERMEM;
dkey.data = (void *)key;
dkey.size = strlen(key);
r = wdb->db->del (wdb->db, NULL, &dkey, 0);
if (r == DB_NOTFOUND)
return nerr_raise (NERR_NOT_FOUND, "Key %s not found", key);
else if (r)
return nerr_raise (NERR_DB, "Error deleting key %s: %d", key, r);
return STATUS_OK;
}
NEOERR *wdbr_dump (WDB *wdb, WDBRow *row)
{
int x;
ne_warn ("version: %d", row->table_version);
ne_warn ("key: %s", row->key_value);
ne_warn ("count: %d", row->data_count);
for (x=0; x < row->data_count; x++)
ne_warn ("data[%d]: %s", x, row->data[x]);
return STATUS_OK;
}
NEOERR *wdbc_create (WDB *wdb, WDBCursor **cursor)
{
DBC *db_cursor;
WDBCursor *new_cursor;
int r;
*cursor = NULL;
#if (DB_VERSION_MINOR==4)
r = (wdb->db)->cursor (wdb->db, NULL, &db_cursor);
#else
r = (wdb->db)->cursor (wdb->db, NULL, &db_cursor, 0);
#endif
if (r)
return nerr_raise (NERR_DB, "Unable to create cursor: %d", r);
new_cursor = (WDBCursor *) calloc (1, sizeof (WDBCursor));
if (new_cursor == NULL)
{
db_cursor->c_close (db_cursor);
return nerr_raise (NERR_NOMEM, "Unable to create cursor");
}
new_cursor->table_version = wdb->table_version;
new_cursor->db_cursor = db_cursor;
*cursor = new_cursor;
return STATUS_OK;
}
NEOERR *wdbc_destroy (WDB *wdb, WDBCursor **cursor)
{
if (*cursor != NULL)
{
(*cursor)->db_cursor->c_close ((*cursor)->db_cursor);
free (*cursor);
*cursor = NULL;
}
return STATUS_OK;
}
NEOERR *wdbr_next (WDB *wdb, WDBCursor *cursor, WDBRow **row, int flags)
{
DBT dkey, data;
WDBRow *my_row;
NEOERR *err = STATUS_OK;
int r;
*row = NULL;
if (wdb->table_version != cursor->table_version)
{
return nerr_raise (NERR_ASSERT, "Cursor doesn't match database");
}
memset(&dkey, 0, sizeof(dkey));
memset(&data, 0, sizeof(data));
dkey.flags = DB_DBT_MALLOC;
data.flags = DB_DBT_MALLOC;
/* First call */
if (flags & WDBC_FIRST)
{
r = cursor->db_cursor->c_get (cursor->db_cursor, &dkey, &data, DB_FIRST);
if (r == DB_NOTFOUND)
return nerr_raise (NERR_NOT_FOUND, "Cursor empty");
else if (r)
return nerr_raise (NERR_DB, "Unable to get first item from cursor: %d",
r);
}
else
{
r = cursor->db_cursor->c_get (cursor->db_cursor, &dkey, &data, DB_NEXT);
if (r == DB_NOTFOUND)
return STATUS_OK;
else if (r)
return nerr_raise (NERR_DB, "Unable to get next item from cursor: %d", r);
}
/* allocate row */
err = alloc_row (wdb, &my_row);
if (err)
{
free (data.data);
return nerr_pass(err);
}
my_row->key_value = (char *) malloc (dkey.size + 1);
if (my_row->key_value == NULL)
{
free (data.data);
free (my_row);
return nerr_raise (NERR_NOMEM, "No memory for new row");
}
memcpy (my_row->key_value, dkey.data, dkey.size);
my_row->key_value[dkey.size] = '\0';
/* unpack row */
err = unpack_row (wdb, data.data, data.size, my_row);
free (data.data);
free (dkey.data);
if (err)
{
free (my_row);
return nerr_pass(err);
}
*row = my_row;
return STATUS_OK;
}
NEOERR *wdbr_find (WDB *wdb, WDBCursor *cursor, const char *key, WDBRow **row)
{
DBT dkey, data;
WDBRow *my_row;
NEOERR *err = STATUS_OK;
int r;
*row = NULL;
if (wdb->table_version != cursor->table_version)
{
return nerr_raise (NERR_ASSERT, "Cursor doesn't match database");
}
memset(&dkey, 0, sizeof(dkey));
memset(&data, 0, sizeof(data));
dkey.flags = DB_DBT_USERMEM;
data.flags = DB_DBT_MALLOC;
dkey.data = (void *)key;
dkey.size = strlen(key);
r = cursor->db_cursor->c_get (cursor->db_cursor, &dkey, &data, DB_SET_RANGE);
if (r == DB_NOTFOUND)
return STATUS_OK;
else if (r)
return nerr_raise (r, "Unable to get find item for key %s", key);
/* allocate row */
err = alloc_row (wdb, &my_row);
if (err)
{
free (data.data);
return nerr_pass(err);
}
my_row->key_value = (char *) malloc (dkey.size + 1);
if (my_row->key_value == NULL)
{
free (data.data);
free (my_row);
return nerr_raise (NERR_NOMEM, "No memory for new row");
}
memcpy (my_row->key_value, dkey.data, dkey.size);
my_row->key_value[dkey.size] = '\0';
/* unpack row */
err = unpack_row (wdb, data.data, data.size, my_row);
free (data.data);
if (err)
{
free (my_row);
return nerr_pass(err);
}
*row = my_row;
return STATUS_OK;
}
NEOERR *wdb_keys (WDB *wdb, char **primary_key, ULIST **data)
{
NEOERR *err;
int x, len;
WDBColumn *col;
ULIST *my_data;
char *my_key = NULL;
char *my_col = NULL;
*data = NULL;
*primary_key = NULL;
my_key = strdup(wdb->key);
if (my_key == NULL)
return nerr_raise (NERR_NOMEM, "Unable to allocate memory for keys");
len = uListLength(wdb->cols_l);
err = uListInit (&my_data, len, 0);
if (err != STATUS_OK)
{
free(my_key);
return nerr_pass(err);
}
for (x = 0; x < len; x++)
{
err = uListGet (wdb->cols_l, x, (void *)&col);
if (err) goto key_err;
my_col = strdup(col->name);
if (my_col == NULL)
{
err = nerr_raise (NERR_NOMEM, "Unable to allocate memory for keys");
goto key_err;
}
err = uListAppend (my_data, my_col);
my_col = NULL;
if (err) goto key_err;
}
*data = my_data;
*primary_key = my_key;
return STATUS_OK;
key_err:
if (my_key != NULL) free (my_key);
if (my_col != NULL) free (my_col);
*primary_key = NULL;
uListDestroy (&my_data, 0);
return nerr_pass(err);
}
/*
* Known Issues:
* - Probably need to store the actual key value in the packed row..
* Maybe not, because any cursor you use on a sleepycat db will
* return the key...
* - um, memory. Especially when dealing with rows, need to keep track
* of when we allocate, when we dealloc, and who owns that memory to
* free it
* - function to delete entry from wdb
*/