/*
 * Copyright (c) 2003-2015
 * Distributed Systems Software.  All rights reserved.
 * See the file LICENSE for redistribution information.
 */

/*
 * This is a VFS layer for SQLite.
 *
 * We use the item_type as the table name; if there is no item_type we use
 * the default table name "items".
 * Each row has two columns: "key" and "value" (both variable length text).
 * Duplicates keys are not allowed.
 */

#ifndef lint
static const char copyright[] =
"Copyright (c) 2003-2015\n\
Distributed Systems Software.  All rights reserved.";
static const char revid[] =
  "$Id: vfs_sqlite.c 2829 2015-08-10 22:32:35Z brachman $";
#endif

#include "local.h"
#include "vfs.h"

#ifdef ENABLE_SQLITE

#include <sqlite3.h>

static const char *log_module_name = "vfs_sqlite";

static int sqlite_open(Vfs_handle *, char *naming_context);
static int sqlite_close(Vfs_handle *handle);
static int sqlite_control(Vfs_handle *, Vfs_control_op op, va_list ap);
static int sqlite_get(Vfs_handle *handle, char *key, void **buffer,
					  size_t *length);
static int sqlite_getsize(Vfs_handle *handle, char *key, size_t *length);
static int sqlite_put(Vfs_handle *handle, char *key, void *buffer,
					  size_t length);
static int sqlite_delete(Vfs_handle *handle, char *key);
static int sqlite_exists(Vfs_handle *handle, char *key);
static int sqlite_rename(Vfs_handle *handle, char *oldkey, char *newkey);
static int sqlite_list(Vfs_handle *handle, int (*is_valid)(char *),
					   int (*compar)(const void *, const void *),
					   int (*add)(char *, char *, void ***), void ***names);

static Vfs_switch sqlite_conf = {
  "sqlite",
  sqlite_open,
  sqlite_close,
  sqlite_control,
  sqlite_get,
  sqlite_getsize,
  sqlite_put,
  sqlite_delete,
  sqlite_exists,
  sqlite_rename,
  sqlite_list
};

typedef struct {
  sqlite3 *db;
  char *dbfile;
} Handle;

typedef enum {
  OP_READ    = 0,
  OP_REPLACE = 1,
  OP_DELETE  = 2,
  OP_RENAME  = 3,
  OP_LIST    = 4,
  OP_CREATE  = 5
} Sql_op;

/*
 * Generate and prepare a SQL statement to do operation OP on the table
 * named ITEM_TYPE with the row identified by key=KEY.  VALUE and LENGTH are
 * optional arguments that depend on OP.
 */
static sqlite3_stmt *
prepare_sql_stmt(Vfs_handle *handle, Sql_op op, char *item_type, ...)
{
  int st;
  char *it, *key, *newkey, *value;
  const char *sql;
  size_t length;
  sqlite3 *db;
  sqlite3_stmt *stmt;
  va_list ap;
  Handle *h;

  /*
   * Apparently the table name cannot be a parameter value bound using
   * sqlite3_bind_*() ...
   */
  if ((it = item_type) == NULL)
	it = "items";

  va_start(ap, item_type);
  switch (op) {
  case OP_CREATE:
	/* The fields are actually arbitrary text. */
	sql = ds_xprintf("create table %s(key %s unique, value %s)",
					 it, "varchar(32)", "varchar(128)");
	break;

  case OP_READ:
	key = va_arg(ap, char *);
    sql = ds_xprintf("select value from %s where key = '%s'", it, key);
	break;

  case OP_REPLACE:
	key = va_arg(ap, char *);
	value = va_arg(ap, char *);
	length = va_arg(ap, int);
    sql = ds_xprintf("replace into %s values('%s', ?)", it, key);
	break;

  case OP_DELETE:
	key = va_arg(ap, char *);
    sql = ds_xprintf("delete from %s where key = '%s'", it, key);
	break;

  case OP_RENAME:
	key = va_arg(ap, char *);
	newkey = va_arg(ap, char *);
	sql = ds_xprintf("update %s set key = '%s' where key = '%s'",
					 it, newkey, key);
	break;

  case OP_LIST:
    sql = ds_xprintf("select all key from %s", it);
	break;

  default:
	sql = NULL;
	break;
  }

  va_end(ap);

  if (sql == NULL) {
	handle->error_msg = "Internal error: bad op";
	return(NULL);
  }

  h = (Handle *) handle->h;
  db = h->db;

  if (sqlite3_prepare_v2(db, sql, strlen(sql) + 1, &stmt, NULL) != SQLITE_OK) {
  fail:
	handle->error_msg = (char *) sqlite3_errmsg(db);
	if (stmt != NULL)
	  sqlite3_finalize(stmt);
	return(NULL);
  }

  if (op == OP_REPLACE) {
    st = sqlite3_bind_text(stmt, 1, value, length, SQLITE_STATIC);
	if (st != SQLITE_OK)
	  goto fail;
  }

  return(stmt);
}

static int
sqlite_open(Vfs_handle *handle, char *naming_context)
{
  int flags, st;
  sqlite3 *db;
  Handle *h;

  if (handle->delete_flag && unlink(naming_context) == -1
	  && errno != ENOENT) {
	handle->error_num = errno;		/* XXX Not a real DB error message. */
	handle->error_msg = strerror(errno);
	return(-1);
  }

  flags = 0;
  if (access(naming_context, F_OK) == -1 && errno == ENOENT
	  && handle->create_flag)
	flags |= SQLITE_OPEN_CREATE;

  if (access(naming_context, W_OK) == -1 && errno == EACCES)
	flags |= SQLITE_OPEN_READONLY;
  else
	flags |= SQLITE_OPEN_READWRITE;

  if ((st = sqlite3_open_v2(naming_context, &db, flags, NULL)) != SQLITE_OK) {
	handle->error_num = st;
	handle->error_msg = (char *) sqlite3_errmsg(db);
	sqlite3_close(db);
	return(-1);
  }

  if (handle->mode != 0) {
	if (chmod(naming_context, handle->mode) == -1) {
	  handle->error_num = errno;		/* XXX Not a real DB error message. */
	  handle->error_msg = strerror(errno);
	  return(-1);
	}
  }

  h = ALLOC(Handle);
  h->dbfile = strdup(naming_context);
  h->db = db;

  handle->h = (void *) h;
  if (flags & SQLITE_OPEN_CREATE) {
	sqlite3_stmt *stmt;

	stmt = prepare_sql_stmt(handle, OP_CREATE, handle->sd->item_type);

	if ((st = sqlite3_step(stmt)) != SQLITE_DONE) {
	  handle->error_msg = (char *) sqlite3_errmsg(h->db);
	  sqlite3_finalize(stmt);
	  return(-1);
	}
	sqlite3_finalize(stmt);
  }

  return(0);
}

static int
sqlite_close(Vfs_handle *handle)
{
  int st;
  Handle *h;

  h = (Handle *) handle->h;
  if ((st = sqlite3_close(h->db)) != SQLITE_OK) {
	handle->error_num = st;
	handle->error_msg = (char *) sqlite3_errmsg(h->db);
  }

  free(h->dbfile);
  free(h);

  return((st == SQLITE_OK) ? 0 : -1);
}

static int
sqlite_control(Vfs_handle *handle, Vfs_control_op op, va_list ap)
{
  char **pptr;
  Handle *h;

  h = (Handle *) handle->h;
  if (h == NULL || h->dbfile == NULL)
	return(-1);

  switch (op) {
  case VFS_GET_CONTAINER:
	pptr = va_arg(ap, char **);
	*pptr = ds_xprintf("file:%s", h->dbfile);
	break;

  case VFS_SET_FIELD_SEP:
	/* Nothing to do... */
	break;

  case VFS_GET_FIELD_SEP:
	return(-1);
	/*NOTREACHED*/
	break;

  default:
	log_msg((LOG_ERROR_LEVEL, "Invalid control request: %d", op));
	return(-1);
	/*NOTREACHED*/
	break;
  }

  return(0);
}

static int
sqlite_getsize(Vfs_handle *handle, char *key, size_t *length)
{
  int st;
  Handle *h;
  sqlite3_stmt *stmt;

  if (key == NULL) {
	handle->error_msg = "NULL key is invalid";
	return(-1);
  }

  h = (Handle *) handle->h;

  stmt = prepare_sql_stmt(handle, OP_READ, handle->sd->item_type, key);

  if ((st = sqlite3_step(stmt)) != SQLITE_ROW) {
  fail:
	handle->error_msg = (char *) sqlite3_errmsg(h->db);
	sqlite3_finalize(stmt);
	return(-1);
  }

  if ((st = sqlite3_column_bytes(stmt, 0)) <= 0)
	goto fail;

  *length = st;
  sqlite3_finalize(stmt);

  return(0);
}

static int
sqlite_get(Vfs_handle *handle, char *key, void **buffer, size_t *length)
{
  int len, st;
  const char *b;
  Handle *h;
  sqlite3_stmt *stmt;

  if (key == NULL) {
	handle->error_msg = "NULL key is invalid";
	return(-1);
  }

  h = (Handle *) handle->h;

  stmt = prepare_sql_stmt(handle, OP_READ, handle->sd->item_type, key);

  if ((st = sqlite3_step(stmt)) != SQLITE_ROW) {
  fail:
	handle->error_msg = (char *) sqlite3_errmsg(h->db);
	sqlite3_finalize(stmt);
	return(-1);
  }

  if ((len = sqlite3_column_bytes(stmt, 0)) <= 0)
	goto fail;
  *length = (size_t) len;

  if ((b = (char *) sqlite3_column_text(stmt, 0)) == NULL)
	goto fail;
  *buffer = memdupn((void *) b, (size_t) len);

  sqlite3_finalize(stmt);

  return(0);
}

/*
 * By default, multiple data items are not permitted, and each database
 * store operation will overwrite any previous data item for that key. 
 */
static int
sqlite_put(Vfs_handle *handle, char *key, void *buffer, size_t length)
{
  int st;
  Handle *h;
  sqlite3_stmt *stmt;

  if (key == NULL) {
	handle->error_msg = "NULL key is invalid";
	return(-1);
  }

  h = (Handle *) handle->h;

  stmt = prepare_sql_stmt(handle, OP_REPLACE, handle->sd->item_type, key,
						  buffer, length);

  st = sqlite3_step(stmt);
  if (st != SQLITE_DONE) {
	/* SQLITE_BUSY? */
	handle->error_msg = (char *) sqlite3_errmsg(h->db);
	sqlite3_finalize(stmt);
	return(-1);
  }

  sqlite3_finalize(stmt);

  return(0);
}

static int
sqlite_delete(Vfs_handle *handle, char *key)
{
  int rc, st;
  Handle *h;
  sqlite3_stmt *stmt;

  if (key == NULL) {
	handle->error_msg = "NULL key is invalid";
	return(-1);
  }

  h = (Handle *) handle->h;

  stmt = prepare_sql_stmt(handle, OP_DELETE, handle->sd->item_type, key);

  if ((st = sqlite3_step(stmt)) != SQLITE_DONE) {
	handle->error_msg = (char *) sqlite3_errmsg(h->db);
	rc = -1;
  }
  else
	rc = 0;

  sqlite3_finalize(stmt);

  return(rc);
}

static int
sqlite_exists(Vfs_handle *handle, char *key)
{
  int rc, st;
  Handle *h;
  sqlite3_stmt *stmt;

  if (key == NULL) {
	handle->error_msg = "NULL key is invalid";
	return(-1);
  }

  h = (Handle *) handle->h;

  stmt = prepare_sql_stmt(handle, OP_READ, handle->sd->item_type, key);

  if ((st = sqlite3_step(stmt)) == SQLITE_ROW)
	rc = 1;
  else if (st == SQLITE_DONE)
	rc = 0;
  else {
	handle->error_msg = (char *) sqlite3_errmsg(h->db);
	rc = -1;
  }

  sqlite3_finalize(stmt);

  return(rc);
}

static int
sqlite_rename(Vfs_handle *handle, char *oldkey, char *newkey)
{
  int st;
  Handle *h;
  sqlite3_stmt *stmt;

  if (oldkey == NULL || newkey == NULL) {
	handle->error_msg = "NULL key is invalid";
	return(-1);
  }

  h = (Handle *) handle->h;
  stmt = prepare_sql_stmt(handle, OP_RENAME, handle->sd->item_type,
						  oldkey, newkey);

  if ((st = sqlite3_step(stmt)) != SQLITE_DONE) {
	handle->error_msg = (char *) sqlite3_errmsg(h->db);
	sqlite3_finalize(stmt);
	return(-1);
  }

  sqlite3_finalize(stmt);

  return(0);
}

static int
sqlite_list(Vfs_handle *handle, int (*is_valid)(char *),
			int (*compar)(const void *, const void *),
			int (*add)(char *, char *, void ***), void ***names)
{
  int n, st;
  char *key;
  Handle *h;
  sqlite3_stmt *stmt;

  h = (Handle *) handle->h;

  stmt = prepare_sql_stmt(handle, OP_LIST, handle->sd->item_type);

  n = 0;
  while (1) {
	if ((st = sqlite3_step(stmt)) != SQLITE_ROW) {
	  if (st != SQLITE_DONE)
		handle->error_msg = (char *) sqlite3_errmsg(h->db);
	  sqlite3_finalize(stmt);
	  return((st == SQLITE_DONE) ? n : -1);
	}

	if ((key = (char *) sqlite3_column_text(stmt, 0)) == NULL) {
	  handle->error_msg = (char *) sqlite3_errmsg(h->db);
	  sqlite3_finalize(stmt);
	  return(-1);
	}

	if (is_valid == NULL || is_valid(key)) {
	  if (add(handle->sd->item_type, key, names) == 1)
		n++;
	}
  }

  if (compar != NULL) {
	if (handle->list_sort == NULL)
	  qsort(names, n, sizeof(void **), compar);
	else
	  handle->list_sort(names, n, sizeof(void **), compar);
  }

  sqlite3_finalize(stmt);

  return(n);
}

Vfs_switch *
vfs_sqlite_init(char *store_name)
{

  return(&sqlite_conf);
}

#else

Vfs_switch *
vfs_sqlite_init(char *store_name)
{

  return(NULL);
}

#endif
