/*
 * 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
 */