/*
 * libdbi - database independent abstraction layer for C.
 * Copyright (C) 2001-2003, David Parker and Mark Tobenkin.
 * http://libdbi.sourceforge.net
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 * $Id: dbi_result.c,v 1.57 2013/01/08 23:54:30 mhoenicka Exp $
 *
 * (anything that has to do with row seeking or fetching fields goes in this file)
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
  #include <unistd.h>
#endif
#include <math.h>
#include <limits.h>
#include <time.h>

#include <dbi/dbi.h>
#include <dbi/dbi-dev.h>

#ifdef __MINGW32__
/*
 * Per MSDN documentation, the buffers used by the MS C runtime are
 * thread-local.
 * (mingw-w64's pthread.h would also just add a forward strtok_r -> strtok)
 */
#define strtok_r(s1,s2,s3) strtok(s1,s2)
#define gmtime_r(s1,s2) gmtime(s1)
#endif

// cast the opaque parameter to our struct pointer
#define RESULT ((dbi_result_t*)Result)

/* declarations for internal functions -- anything declared as static won't be accessible by name from client programs */
static _field_binding_t *_find_or_create_binding_node(dbi_result_t *result, const char *fieldname);
static void _remove_binding_node(dbi_result_t *result, _field_binding_t *deadbinding);
static unsigned int _find_field(dbi_result_t *result, const char *fieldname, dbi_error_flag* errflag);
static int _is_row_fetched(dbi_result_t *result, unsigned long long row);
static int _setup_binding(dbi_result_t *result, const char *fieldname, void *bindto, void *helperfunc);
static void _activate_bindings(dbi_result_t *result);
static unsigned int _parse_field_formatstr(const char *format, char ***tokens_dest, char ***fieldnames_dest);
static void _free_string_list(char **ptrs, int total);
static void _free_result_rows(dbi_result_t *result);
int _disjoin_from_conn(dbi_result_t *result);

static void _bind_helper_char(_field_binding_t *binding);
static void _bind_helper_uchar(_field_binding_t *binding);
static void _bind_helper_short(_field_binding_t *binding);
static void _bind_helper_ushort(_field_binding_t *binding);
static void _bind_helper_int(_field_binding_t *binding);
static void _bind_helper_uint(_field_binding_t *binding);
static void _bind_helper_longlong(_field_binding_t *binding);
static void _bind_helper_ulonglong(_field_binding_t *binding);
static void _bind_helper_float(_field_binding_t *binding);
static void _bind_helper_double(_field_binding_t *binding);
static void _bind_helper_string(_field_binding_t *binding);
static void _bind_helper_binary(_field_binding_t *binding);
static void _bind_helper_string_copy(_field_binding_t *binding);
static void _bind_helper_binary_copy(_field_binding_t *binding);
static void _bind_helper_datetime(_field_binding_t *binding);

/* XXX ROW SEEKING AND FETCHING XXX */

/* returns 1 if ok, 0 on error */
int dbi_result_seek_row(dbi_result Result, unsigned long long rowidx) {
  int retval;

  if (!RESULT) {
    _error_handler(/*RESULT->conn*/ NULL, DBI_ERROR_BADPTR);
    return 0;
  }	

  _reset_conn_error(RESULT->conn);

  if (RESULT->result_state == NOTHING_RETURNED 
      || rowidx <= 0 || rowidx > RESULT->numrows_matched) {
    _error_handler(RESULT->conn, DBI_ERROR_BADIDX);
    return 0;
  }

  if (_is_row_fetched(RESULT, rowidx) == 1) {
    /* jump right to it */
    RESULT->currowidx = rowidx;
    _activate_bindings(RESULT);
    return 1;
  }
	
  /* row is one-based for the user, but zero-based to the dbd conn */
  retval = RESULT->conn->driver->functions->goto_row(RESULT, rowidx-1, RESULT->dbd_currowidx-1);
  if (retval == -1) {
    _error_handler(RESULT->conn, DBI_ERROR_DBD);
    return 0;
  }
  retval = RESULT->conn->driver->functions->fetch_row(RESULT, rowidx-1);
  if (retval == 0) {
    _error_handler(RESULT->conn, DBI_ERROR_DBD);
    return 0;
  }

  RESULT->currowidx = rowidx;
  RESULT->dbd_currowidx = rowidx;
  _activate_bindings(RESULT);
  return retval;
}

int dbi_result_first_row(dbi_result Result) {
  return dbi_result_seek_row(Result, 1);
}

int dbi_result_last_row(dbi_result Result) {
  return dbi_result_seek_row(Result, dbi_result_get_numrows(Result));
}

int dbi_result_has_prev_row(dbi_result Result) {
  if (!RESULT) {
    _error_handler(/*RESULT->conn*/ NULL, DBI_ERROR_BADPTR);
    return 0;
  }	

  _reset_conn_error(RESULT->conn);

  return RESULT->result_state != NOTHING_RETURNED && RESULT->currowidx > 1;
}

int dbi_result_prev_row(dbi_result Result) {
  if (!RESULT) {
    _error_handler(/*RESULT->conn*/ NULL, DBI_ERROR_BADPTR);
    return 0;
  }	

  _reset_conn_error(RESULT->conn);

  if (!dbi_result_has_prev_row(Result)) {
    _error_handler(RESULT->conn, DBI_ERROR_BADIDX);
    return 0;
  }
  return dbi_result_seek_row(Result, RESULT->currowidx-1);
}

int dbi_result_has_next_row(dbi_result Result) {
  if (!RESULT) {
    _error_handler(/*RESULT->conn*/ NULL, DBI_ERROR_BADPTR);
    return 0;
  }	

  _reset_conn_error(RESULT->conn);

  return RESULT->result_state != NOTHING_RETURNED
  		 && RESULT->currowidx < dbi_result_get_numrows(Result);
}

int dbi_result_next_row(dbi_result Result) {
  if (!RESULT) {
    _error_handler(/*RESULT->conn*/ NULL, DBI_ERROR_BADPTR);
    return 0;
  }	

  _reset_conn_error(RESULT->conn);

  if (!dbi_result_has_next_row(Result)) {
/*     _error_handler(RESULT->conn, DBI_ERROR_BADIDX); */
    return 0;
  }
  return dbi_result_seek_row(Result, RESULT->currowidx+1);
}

unsigned long long dbi_result_get_currow(dbi_result Result) {
  if (!RESULT) {
    _error_handler(/*RESULT->conn*/ NULL, DBI_ERROR_BADPTR);
    return 0;
  }	

  _reset_conn_error(RESULT->conn);

  return RESULT->currowidx;
}

unsigned long long dbi_result_get_numrows(dbi_result Result) {
  if (!RESULT) {
    _error_handler(/*RESULT->conn*/ NULL, DBI_ERROR_BADPTR);
    return DBI_ROW_ERROR;
  }	

  _reset_conn_error(RESULT->conn);

  return RESULT->numrows_matched;
}

unsigned long long dbi_result_get_numrows_affected(dbi_result Result) {
  if (!RESULT) {
    _error_handler(/*RESULT->conn*/ NULL, DBI_ERROR_BADPTR);
    return DBI_ROW_ERROR;
  }	

  _reset_conn_error(RESULT->conn);

  return RESULT->numrows_affected;
}

/* returns the length of the string or binary, excluding a trailing \0 */
size_t dbi_result_get_field_length(dbi_result Result, const char *fieldname) {
  unsigned int fieldidx = 0;
  dbi_error_flag errflag = DBI_ERROR_NONE;

  if (!RESULT) {
    _error_handler(/*RESULT->conn*/ NULL, DBI_ERROR_BADPTR);
    return DBI_LENGTH_ERROR;
  }

  _reset_conn_error(RESULT->conn);

  fieldidx = _find_field(RESULT, fieldname, &errflag);
  if (errflag != DBI_ERROR_NONE) {
    _error_handler(RESULT->conn, errflag);
    return DBI_LENGTH_ERROR;
  }
  
  return dbi_result_get_field_length_idx(Result, fieldidx+1);
}

size_t dbi_result_get_field_length_idx(dbi_result Result, unsigned int fieldidx) {
  unsigned long long currowidx;
  
  /* user-visible indexes start at 1 */
  fieldidx--;
  
  if (!RESULT || !RESULT->rows) {
    _error_handler(RESULT ? RESULT->conn : NULL, DBI_ERROR_BADPTR);
    return DBI_LENGTH_ERROR;
  }

  _reset_conn_error(RESULT->conn);

  currowidx = RESULT->currowidx;
  if (!RESULT->rows[currowidx] || !RESULT->rows[currowidx]->field_sizes) {
    _error_handler(RESULT->conn, DBI_ERROR_BADOBJECT);
    return DBI_LENGTH_ERROR;
  }
  if (fieldidx >= RESULT->numfields) {
    _error_handler(RESULT->conn, DBI_ERROR_BADIDX);
    return DBI_LENGTH_ERROR;
  }
  
  return RESULT->rows[currowidx]->field_sizes[fieldidx];
}

/* the "field_size" functions are merely kept to support legacy
   code. Their purpose was, er, unclear, their implementations were,
   er, broke, and the name "field_length" is more appropriate to what
   these functions do anyway. Support for the "field_size" functions
   is likely to be dropped in the next release */
size_t dbi_result_get_field_size(dbi_result Result, const char *fieldname) {
  return dbi_result_get_field_length(Result, fieldname);
}

size_t dbi_result_get_field_size_idx(dbi_result Result, unsigned int fieldidx) {
  return dbi_result_get_field_length_idx(Result, fieldidx);
}

unsigned int dbi_result_get_field_idx(dbi_result Result, const char *fieldname) {
  unsigned int fieldidx = 0;
  dbi_error_flag errflag = DBI_ERROR_NONE;

  if (!RESULT) {
    _error_handler(/*RESULT->conn*/ NULL, DBI_ERROR_BADPTR);
    return 0;
  }	

  _reset_conn_error(RESULT->conn);

  /* user-visible indexes start at 1 */
  fieldidx = _find_field(RESULT, fieldname, &errflag)+1;
  if (errflag != DBI_ERROR_NONE) {
    _error_handler(RESULT->conn, errflag);
    return 0;
  }
  
  return fieldidx;
}

const char *dbi_result_get_field_name(dbi_result Result, unsigned int fieldidx) {
	
  if (!RESULT) {
    _error_handler(/*RESULT->conn*/ NULL, DBI_ERROR_BADPTR);
    return NULL;
  }

  _reset_conn_error(RESULT->conn);

  if (fieldidx > RESULT->numfields) {
    _error_handler(RESULT->conn, DBI_ERROR_BADIDX);
    return NULL;
  }
  else if (RESULT->field_names == NULL) {
    _error_handler(RESULT->conn, DBI_ERROR_BADOBJECT);
    return NULL;
  }
	
  return (const char *) RESULT->field_names[fieldidx-1];
}

unsigned int dbi_result_get_numfields(dbi_result Result) {
  if (!RESULT) return DBI_FIELD_ERROR;

  _reset_conn_error(RESULT->conn);

  return RESULT->numfields;
}

unsigned short dbi_result_get_field_type(dbi_result Result, const char *fieldname) {
  unsigned int fieldidx = 0;
  dbi_error_flag errflag = DBI_ERROR_NONE;
	
  if (!RESULT) {
    _error_handler(/*RESULT->conn*/ NULL, DBI_ERROR_BADPTR);
    return DBI_TYPE_ERROR;
  }

  _reset_conn_error(RESULT->conn);

  fieldidx = _find_field(RESULT, fieldname, &errflag);
  if (errflag != DBI_ERROR_NONE) {
    _error_handler(RESULT->conn, errflag);
    return DBI_TYPE_ERROR;
  }

  return dbi_result_get_field_type_idx(Result, fieldidx+1);
}

unsigned short dbi_result_get_field_type_idx(dbi_result Result, unsigned int fieldidx) {
  fieldidx--;
	
  if (!RESULT) {
    _error_handler(/*RESULT->conn*/ NULL, DBI_ERROR_BADPTR);
    return DBI_TYPE_ERROR;
  }

  _reset_conn_error(RESULT->conn);

  if (!RESULT->field_types) {
    _error_handler(RESULT->conn, DBI_ERROR_BADOBJECT);
    return DBI_TYPE_ERROR;
  }
  else if (fieldidx >= RESULT->numfields) {
    _error_handler(RESULT->conn, DBI_ERROR_BADIDX);
    return DBI_TYPE_ERROR;
  }

  return RESULT->field_types[fieldidx];
}

unsigned int dbi_result_get_field_attrib(dbi_result Result, const char *fieldname, unsigned int attribmin, unsigned int attribmax) {
  unsigned int fieldidx = 0;
  dbi_error_flag errflag = DBI_ERROR_NONE;
	
  if (!RESULT) {
    _error_handler(/*RESULT->conn*/ NULL, DBI_ERROR_BADPTR);
    return DBI_ATTRIBUTE_ERROR;
  }

  _reset_conn_error(RESULT->conn);

  fieldidx = _find_field(RESULT, fieldname, &errflag);
  if (errflag != DBI_ERROR_NONE) {
    _error_handler(RESULT->conn, errflag);
    return DBI_ATTRIBUTE_ERROR;
  }

  return dbi_result_get_field_attrib_idx(Result, fieldidx+1, attribmin, attribmax);
}

unsigned int dbi_result_get_field_attrib_idx(dbi_result Result, 
		unsigned int fieldidx, unsigned int attribmin, unsigned int attribmax) {
  fieldidx--;
	
  if (!RESULT) {
    _error_handler(/*RESULT->conn*/ NULL, DBI_ERROR_BADPTR);
    return DBI_ATTRIBUTE_ERROR;
  }

  _reset_conn_error(RESULT->conn);

  if (!RESULT->field_attribs) {
    _error_handler(RESULT->conn, DBI_ERROR_BADOBJECT);
    return DBI_ATTRIBUTE_ERROR;
  }
  else if (fieldidx >= RESULT->numfields) {
    _error_handler(RESULT->conn, DBI_ERROR_BADIDX);
    return DBI_ATTRIBUTE_ERROR;
  }

  return _isolate_attrib(RESULT->field_attribs[fieldidx], attribmin, attribmax);
}

unsigned int dbi_result_get_field_attribs(dbi_result Result, const char *fieldname) {
  unsigned int fieldidx = 0;
  dbi_error_flag errflag = DBI_ERROR_NONE;
	
  if (!RESULT) {
    _error_handler(/*RESULT->conn*/ NULL, DBI_ERROR_BADPTR);
    return DBI_ATTRIBUTE_ERROR;
  }

  _reset_conn_error(RESULT->conn);

  fieldidx = _find_field(RESULT, fieldname, &errflag);
  if (errflag != DBI_ERROR_NONE) {
    _error_handler(RESULT->conn, errflag);
    return DBI_ATTRIBUTE_ERROR;
  }

  return dbi_result_get_field_attribs_idx(Result, fieldidx+1);
}

unsigned int dbi_result_get_field_attribs_idx(dbi_result Result, unsigned int fieldidx) {
  fieldidx--;
	
  if (!RESULT) {
    _error_handler(/*RESULT->conn*/ NULL, DBI_ERROR_BADPTR);
    return DBI_ATTRIBUTE_ERROR;
  }

  _reset_conn_error(RESULT->conn);

  if (!RESULT->field_attribs) {
    _error_handler(RESULT->conn, DBI_ERROR_BADOBJECT);
    return DBI_ATTRIBUTE_ERROR;
  }
  else if (fieldidx >= RESULT->numfields) {
    _error_handler(RESULT->conn, DBI_ERROR_BADIDX);
    return DBI_ATTRIBUTE_ERROR;
  }

  return RESULT->field_attribs[fieldidx];
}

void _set_field_flag(dbi_row_t *row, unsigned int fieldidx, unsigned char flag, unsigned char value) {
  unsigned char *flags = &row->field_flags[fieldidx];
  if (value)
    *flags |= flag; // if value = 1, set the flag
  else
    *flags &= ~flag; // set that bit to 0
}

int _get_field_flag(dbi_row_t *row, unsigned int fieldidx, unsigned char flag) {
  return (row->field_flags[fieldidx] & flag) != 0;
}

int dbi_result_field_is_null(dbi_result Result, const char *fieldname) {
  unsigned int fieldidx = 0;
  dbi_error_flag errflag = DBI_ERROR_NONE;

  if (!RESULT) {
    _error_handler(/*RESULT->conn*/ NULL, DBI_ERROR_BADPTR);
    return DBI_FIELD_FLAG_ERROR;
  }

  _reset_conn_error(RESULT->conn);

  fieldidx = _find_field(RESULT, fieldname, &errflag);
  if (errflag != DBI_ERROR_NONE) {
    _error_handler(RESULT->conn, errflag);
    return DBI_FIELD_FLAG_ERROR;
  }

  return dbi_result_field_is_null_idx(Result, fieldidx+1);
}

int dbi_result_field_is_null_idx(dbi_result Result, unsigned int fieldidx) {
  unsigned long long currowidx;

  fieldidx--;
	
  if (!RESULT || !RESULT->rows) {
    _error_handler(RESULT ? RESULT->conn : NULL, DBI_ERROR_BADPTR);
    return DBI_FIELD_FLAG_ERROR;
  }

  _reset_conn_error(RESULT->conn);

  currowidx = RESULT->currowidx;
  if (!RESULT->rows[currowidx] || !RESULT->rows[currowidx]->field_flags) {
    _error_handler(RESULT->conn, DBI_ERROR_BADOBJECT);
    return DBI_FIELD_FLAG_ERROR;
  }
  if (fieldidx >= RESULT->numfields) {
    _error_handler(RESULT->conn, DBI_ERROR_BADIDX);
    return DBI_FIELD_FLAG_ERROR;
  }

  return _get_field_flag(RESULT->rows[currowidx], fieldidx, DBI_VALUE_NULL);
}

int _disjoin_from_conn(dbi_result_t *result) {
  /* todo: is int enough? */
  int idx;
  int found = -1;
  int retval;

  retval = result->conn->driver->functions->free_query(result);

  for (idx = 0; idx < result->conn->results_used; idx++) {
    if (found < 0) {
      /* keep looking */
      if (result->conn->results[idx] == result) {
        found = idx;
        result->conn->results[idx] = NULL;
      }
    }
    else {
      /* already found, shift remaining elements back one */
      result->conn->results[idx-1] = result->conn->results[idx];
    }
  }
  if (found >= 0) {
    result->conn->results[result->conn->results_used-1] = NULL;
    result->conn->results_used--;
  }

  result->conn = NULL;
	
  return retval;
}

int dbi_result_disjoin(dbi_result Result) {
  return RESULT ? _disjoin_from_conn(RESULT) : -1;
}

int dbi_result_free(dbi_result Result) {
  int retval = 0;

  if (!RESULT) return -1;
	
  if (RESULT->conn) {
    retval = _disjoin_from_conn(RESULT);
  }

  _reset_conn_error(RESULT->conn);

  while (RESULT->field_bindings) {
    _remove_binding_node(RESULT, RESULT->field_bindings);
  }
	
  if (RESULT->rows) {
    _free_result_rows(RESULT);
  }
		
  if (RESULT->numfields) {
    _free_string_list(RESULT->field_names, RESULT->numfields);
    free(RESULT->field_types);
    free(RESULT->field_attribs);
  }

  if (retval == -1) {
    _error_handler(RESULT->conn, DBI_ERROR_DBD);
  }

  free(RESULT);
  return retval;
}

dbi_conn dbi_result_get_conn(dbi_result Result) {

  if (!RESULT) {
    _error_handler(/*RESULT->conn*/ NULL, DBI_ERROR_BADPTR);
    return NULL;
  }

  return RESULT->conn;
}

/* RESULT: mass retrieval functions */

static unsigned int _parse_field_formatstr(const char *format, char ***tokens_dest, char ***fieldnames_dest) {
  unsigned int found = 0;
  unsigned int cur = 0;
  char **tokens = NULL;
  char **fieldnames = NULL;
  char *chunk;
  char *fieldtype;
  char *fieldname;
  char *temp1;
  char *temp2;
  char *line = strdup(format);
	
  temp1 = line;
  while (temp1 && (temp1 = strchr(temp1, '.')) != NULL) {
    temp1++;
    found++;
  }
	
  tokens = calloc(found, sizeof(char *));
  fieldnames = calloc(found, sizeof(char *));
  if (!tokens || !fieldnames) return DBI_FIELD_ERROR;
	
  chunk = strtok_r(line, " ", &temp1);
  do {
    temp2 = strchr(chunk, '.');
    if (!temp2) continue;
    fieldname = chunk;
    *temp2 = '\0';
    fieldtype = (temp2+2); /* ignore the % */
    tokens[cur] = strdup(fieldtype);
    fieldnames[cur] = strdup(fieldname);
    cur++;
  } while ((chunk = strtok_r(NULL, " ", &temp1)));

  *tokens_dest = tokens;
  *fieldnames_dest = fieldnames;
	
  free(line);
  return found;
}

static void _free_string_list(char **ptrs, int total) {
  int i;

  if (ptrs == NULL) return;
  for (i = 0; i < total; ++i)
    if (ptrs[i]) free(ptrs[i]);
  free(ptrs);
  return;
}

static void _free_result_rows(dbi_result_t *result) {
  unsigned long long rowidx = 0;
  unsigned int fieldidx = 0;

  for (rowidx = 0; rowidx <= result->numrows_matched; rowidx++) {
    if (!result->rows[rowidx]) continue;
			
    for (fieldidx = 0; fieldidx < result->numfields; fieldidx++) {
      if ((result->field_types[fieldidx] == DBI_TYPE_STRING ||
           result->field_types[fieldidx] == DBI_TYPE_XDECIMAL ||
           result->field_types[fieldidx] == DBI_TYPE_BINARY) 
        && result->rows[rowidx]->field_values[fieldidx].d_string)
      {
        free(result->rows[rowidx]->field_values[fieldidx].d_string);
      }
    }
		
    free(result->rows[rowidx]->field_values);
    free(result->rows[rowidx]->field_sizes);
    free(result->rows[rowidx]->field_flags);
    free(result->rows[rowidx]);
  }
	
  free(result->rows);
}

unsigned int dbi_result_get_fields(dbi_result Result, const char *format, ...) {
  char **tokens, **fieldnames;
  unsigned int curidx = 0, numtokens = 0, uflag;
  va_list ap;

  if (!RESULT) return DBI_FIELD_ERROR;

  numtokens = _parse_field_formatstr(format, &tokens, &fieldnames);

  if (numtokens == DBI_FIELD_ERROR) {
    return numtokens;
  }
	
  va_start(ap, format);
  while (curidx < numtokens) {
    uflag = strlen(tokens[curidx]) > 1 && tokens[curidx][0] == 'u';
    switch (tokens[curidx][strlen(tokens[curidx])-1]) {
    case 'c': /* char */
      if (uflag) /* unsigned */
        *va_arg(ap, unsigned char *) = dbi_result_get_uchar(Result, fieldnames[curidx]);
      else
        *va_arg(ap, char *) = dbi_result_get_char(Result, fieldnames[curidx]);
      break;
    case 'h': /* sHort ("S"tring was taken) */
      if (uflag) /* unsigned */
        *va_arg(ap, unsigned short *) = dbi_result_get_ushort(Result, fieldnames[curidx]);
      else
        *va_arg(ap, short *) = dbi_result_get_short(Result, fieldnames[curidx]);
      break;
    case 'l': /* 4-byte integer (both l and i work) */
    case 'i':
      if (uflag) /* unsigned */
        *va_arg(ap, unsigned int *) = dbi_result_get_uint(Result, fieldnames[curidx]);
      else
        *va_arg(ap, int *) = dbi_result_get_int(Result, fieldnames[curidx]);
      break;
    case 'L': /* long long */
      if (uflag) /* unsigned */
        *va_arg(ap, unsigned long long *) = dbi_result_get_ulonglong(Result, fieldnames[curidx]);
      else
        *va_arg(ap, long long *) = dbi_result_get_longlong(Result, fieldnames[curidx]);
      break;
    case 'f': /* float */
      *va_arg(ap, float *) = dbi_result_get_float(Result, fieldnames[curidx]); 
      break;
    case 'd': /* double */
      *va_arg(ap, double *) = dbi_result_get_double(Result, fieldnames[curidx]); 
      break;
    case 's': /* string */
      *va_arg(ap, const char **) = dbi_result_get_string(Result, fieldnames[curidx]); 
      break;
    case 'b': /* binary */
      *va_arg(ap, const unsigned char **) = dbi_result_get_binary(Result, fieldnames[curidx]); 
      break;
    case 'S': /* string copy */
      *va_arg(ap, char **) = dbi_result_get_string_copy(Result, fieldnames[curidx]); 
      break;
    case 'B': /* binary copy */
      *va_arg(ap, unsigned char **) = dbi_result_get_binary_copy(Result, fieldnames[curidx]); 
      break;
    case 'm': /* datetiMe (what... you have any better ideas?? */
      *va_arg(ap, time_t *) = dbi_result_get_datetime(Result, fieldnames[curidx]);
      break;
    }
    curidx++;
  }
  va_end(ap);

  _free_string_list(tokens, numtokens);
  _free_string_list(fieldnames, numtokens);
  return numtokens;
}

unsigned int dbi_result_bind_fields(dbi_result Result, const char *format, ...) {
  char **tokens, **fieldnames;
  unsigned int curidx = 0, numtokens = 0, uflag;
  va_list ap;

  if (!RESULT) return DBI_FIELD_ERROR;

  numtokens = _parse_field_formatstr(format, &tokens, &fieldnames);

  if (numtokens == DBI_FIELD_ERROR) {
    return numtokens;
  }

  va_start(ap, format);
  while (curidx < numtokens) {
    uflag = strlen(tokens[curidx]) > 1 && tokens[curidx][0] == 'u';
    switch (tokens[curidx][strlen(tokens[curidx])-1]) {
    case 'c': /* char */
      if (uflag) /* unsigned */
        dbi_result_bind_uchar(Result, fieldnames[curidx], va_arg(ap, unsigned char *));
      else
        dbi_result_bind_char(Result, fieldnames[curidx], va_arg(ap, char *));
      break;
    case 'h': /* sHort ("S"tring was taken) */
      if (uflag) /* unsigned */
        dbi_result_bind_ushort(Result, fieldnames[curidx], va_arg(ap, unsigned short *));
      else
        dbi_result_bind_short(Result, fieldnames[curidx], va_arg(ap, short *));
      break;
    case 'l': /* 4-byte integer (both l and i work) */
    case 'i':
      if (uflag) /* unsigned */
        dbi_result_bind_uint(Result, fieldnames[curidx], va_arg(ap, unsigned int *));
      else
        dbi_result_bind_int(Result, fieldnames[curidx], va_arg(ap, int *));
      break;
    case 'L': /* long long */
      if (uflag) /* unsigned */
        dbi_result_bind_ulonglong(Result, fieldnames[curidx], va_arg(ap, unsigned long long *));
      else
        dbi_result_bind_longlong(Result, fieldnames[curidx], va_arg(ap, long long *));
      break;
    case 'f': /* float */
      dbi_result_bind_float(Result, fieldnames[curidx], va_arg(ap, float *));
      break;
    case 'd': /* double */
      dbi_result_bind_double(Result, fieldnames[curidx], va_arg(ap, double *));
      break;
    case 's': /* string */
      dbi_result_bind_string(Result, fieldnames[curidx], va_arg(ap, const char **));
      break;
    case 'b': /* binary */
      dbi_result_bind_binary(Result, fieldnames[curidx], va_arg(ap, const unsigned char **));
      break;
    case 'S': /* string copy */
      dbi_result_bind_string_copy(Result, fieldnames[curidx], va_arg(ap, char **));
      break;
    case 'B': /* binary copy */
      dbi_result_bind_binary_copy(Result, fieldnames[curidx], va_arg(ap, unsigned char **));
      break;
    case 'm': /* datetiMe (what... you have any better ideas?? */
      dbi_result_bind_datetime(Result, fieldnames[curidx], va_arg(ap, time_t *));
      break;
    }
    curidx++;
  }
  va_end(ap);

  _free_string_list(tokens, numtokens);
  _free_string_list(fieldnames, numtokens);
  return numtokens;
}

/* RESULT: get_* functions */

signed char dbi_result_get_char(dbi_result Result, const char *fieldname) {
  signed char my_ERROR = 0;
  unsigned int fieldidx;
  dbi_error_flag errflag = DBI_ERROR_NONE;

  _reset_conn_error(RESULT->conn);

  fieldidx = _find_field(RESULT, fieldname, &errflag);
  if (errflag != DBI_ERROR_NONE) {
    dbi_conn_t *conn = RESULT->conn;
    _error_handler(conn, errflag);
    return my_ERROR;
  }
  return dbi_result_get_char_idx(Result, fieldidx+1);
}

signed char dbi_result_get_char_idx(dbi_result Result, unsigned int fieldidx) {
  signed char my_ERROR = 0;

  fieldidx--;

  _reset_conn_error(RESULT->conn);

  if (fieldidx >= RESULT->numfields) {
    _error_handler(RESULT->conn, DBI_ERROR_BADIDX);
    return my_ERROR;
  }
  if (RESULT->field_types[fieldidx] != DBI_TYPE_INTEGER) {
    _verbose_handler(RESULT->conn, "%s: field `%s` is not integer type\n",
                     __func__, dbi_result_get_field_name(Result, fieldidx+1));
    _error_handler(RESULT->conn, DBI_ERROR_BADTYPE);
    return my_ERROR;
  }

  switch (RESULT->field_attribs[fieldidx] & DBI_INTEGER_SIZEMASK) {
  case DBI_INTEGER_SIZE1:
    return RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_char;
  case DBI_INTEGER_SIZE2:
  case DBI_INTEGER_SIZE3:
  case DBI_INTEGER_SIZE4:
  case DBI_INTEGER_SIZE8:
    _verbose_handler(RESULT->conn, "%s: field `%s` is more than 1 byte wide\n",
                     __func__, dbi_result_get_field_name(Result, fieldidx+1));
  default:
    _error_handler(RESULT->conn, DBI_ERROR_BADTYPE);
    return my_ERROR;
  }
}

short dbi_result_get_short(dbi_result Result, const char *fieldname) {
  short my_ERROR = 0;
  unsigned int fieldidx;
  dbi_error_flag errflag = DBI_ERROR_NONE;

  _reset_conn_error(RESULT->conn);

  fieldidx = _find_field(RESULT, fieldname, &errflag);
  if (errflag != DBI_ERROR_NONE) {
    dbi_conn_t *conn = RESULT->conn;
    _error_handler(conn, DBI_ERROR_BADNAME);
    return my_ERROR;
  }
  return dbi_result_get_short_idx(Result, fieldidx+1);
}

short dbi_result_get_short_idx(dbi_result Result, unsigned int fieldidx) {
  short my_ERROR = 0;
  fieldidx--;

  _reset_conn_error(RESULT->conn);

  if (fieldidx >= RESULT->numfields) {
    _error_handler(RESULT->conn, DBI_ERROR_BADIDX);
    return my_ERROR;
  }
  if (RESULT->field_types[fieldidx] != DBI_TYPE_INTEGER) {
    _verbose_handler(RESULT->conn, "%s: field `%s` is not integer type\n",
                     __func__, dbi_result_get_field_name(Result, fieldidx+1));
    _error_handler(RESULT->conn, DBI_ERROR_BADTYPE);
    return my_ERROR;
  }

  switch (RESULT->field_attribs[fieldidx] & DBI_INTEGER_SIZEMASK) {
  case DBI_INTEGER_SIZE1:
    return RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_char;
  case DBI_INTEGER_SIZE2:
    return RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_short;
  case DBI_INTEGER_SIZE3:
  case DBI_INTEGER_SIZE4:
  case DBI_INTEGER_SIZE8:
    _verbose_handler(RESULT->conn, "%s: field `%s` is more than 2 bytes wide\n",
                     __func__, dbi_result_get_field_name(Result, fieldidx+1));
  default:
    _error_handler(RESULT->conn, DBI_ERROR_BADTYPE);
    return my_ERROR;
  }
}

int dbi_result_get_long(dbi_result Result, const char *fieldname) {
  return dbi_result_get_int(Result, fieldname);
}

int dbi_result_get_int(dbi_result Result, const char *fieldname) {
  long my_ERROR = 0;
  unsigned int fieldidx;
  dbi_error_flag errflag = DBI_ERROR_NONE;

  _reset_conn_error(RESULT->conn);

  fieldidx = _find_field(RESULT, fieldname, &errflag);
  if (errflag != DBI_ERROR_NONE) {
    dbi_conn_t *conn = RESULT->conn;
    _error_handler(conn, DBI_ERROR_BADNAME);
    return my_ERROR;
  }
  return dbi_result_get_int_idx(Result, fieldidx+1);
}

int dbi_result_get_long_idx(dbi_result Result, unsigned int fieldidx) {
  return dbi_result_get_int_idx(Result, fieldidx);
}

int dbi_result_get_int_idx(dbi_result Result, unsigned int fieldidx) {
  long my_ERROR = 0;
  fieldidx--;
	
  _reset_conn_error(RESULT->conn);

  if (fieldidx >= RESULT->numfields) {
    _error_handler(RESULT->conn, DBI_ERROR_BADIDX);
    return my_ERROR;
  }
  if (RESULT->field_types[fieldidx] != DBI_TYPE_INTEGER) {
    _verbose_handler(RESULT->conn, "%s: field `%s` is not integer type\n",
                     __func__, dbi_result_get_field_name(Result, fieldidx+1));
    _error_handler(RESULT->conn, DBI_ERROR_BADTYPE);
    return my_ERROR;
  }

  switch (RESULT->field_attribs[fieldidx] & DBI_INTEGER_SIZEMASK) {
  case DBI_INTEGER_SIZE1:
    return RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_char;
  case DBI_INTEGER_SIZE2:
    return RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_short;
  case DBI_INTEGER_SIZE3:
  case DBI_INTEGER_SIZE4:
    return RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_long;
  case DBI_INTEGER_SIZE8:
    _verbose_handler(RESULT->conn, "%s: field `%s` is more than 4 bytes wide\n",
                     __func__, dbi_result_get_field_name(Result, fieldidx+1));
  default:
    _error_handler(RESULT->conn, DBI_ERROR_BADTYPE);
    return my_ERROR;
  }
}

long long dbi_result_get_longlong(dbi_result Result, const char *fieldname) {
  long long my_ERROR = 0;
  unsigned int fieldidx;
  dbi_error_flag errflag = DBI_ERROR_NONE;
    
  _reset_conn_error(RESULT->conn);

  fieldidx = _find_field(RESULT, fieldname, &errflag);
  if (errflag != DBI_ERROR_NONE) {
    dbi_conn_t *conn = RESULT->conn;
    _error_handler(conn, DBI_ERROR_BADNAME);
    return my_ERROR;
  }
  return dbi_result_get_longlong_idx(Result, fieldidx+1);
}

long long dbi_result_get_longlong_idx(dbi_result Result, unsigned int fieldidx) {
  long long my_ERROR = 0;
  fieldidx--;
	
  _reset_conn_error(RESULT->conn);

  if (fieldidx >= RESULT->numfields) {
    _error_handler(RESULT->conn, DBI_ERROR_BADIDX);
    return my_ERROR;
  }
  if (RESULT->field_types[fieldidx] != DBI_TYPE_INTEGER) {
    _verbose_handler(RESULT->conn, "%s: field `%s` is not integer type\n",
                     __func__, dbi_result_get_field_name(Result, fieldidx+1));
    _error_handler(RESULT->conn, DBI_ERROR_BADTYPE);
    return my_ERROR;
  }

  switch (RESULT->field_attribs[fieldidx] & DBI_INTEGER_SIZEMASK) {
  case DBI_INTEGER_SIZE1:
    return RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_char;
  case DBI_INTEGER_SIZE2:
    return RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_short;
  case DBI_INTEGER_SIZE3:
  case DBI_INTEGER_SIZE4:
    return RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_long;
  case DBI_INTEGER_SIZE8:
    return RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_longlong;
  default:
    _error_handler(RESULT->conn, DBI_ERROR_BADTYPE);
    return my_ERROR;
  }
}

unsigned char dbi_result_get_uchar(dbi_result Result, const char *fieldname) {
  return (unsigned char)dbi_result_get_char(Result, fieldname);
}

unsigned char dbi_result_get_uchar_idx(dbi_result Result, unsigned int fieldidx) {
  return (unsigned char)dbi_result_get_char_idx(Result, fieldidx);
}

unsigned short dbi_result_get_ushort(dbi_result Result, const char *fieldname) {
  return (unsigned short)dbi_result_get_short(Result, fieldname);
}

unsigned short dbi_result_get_ushort_idx(dbi_result Result, unsigned int fieldidx) {
  return (unsigned short)dbi_result_get_short_idx(Result, fieldidx);
}

unsigned int dbi_result_get_uint(dbi_result Result, const char *fieldname) {
  return (unsigned int)dbi_result_get_int(Result, fieldname);
}

unsigned int dbi_result_get_ulong(dbi_result Result, const char *fieldname) {
  return dbi_result_get_uint(Result, fieldname);
}

unsigned int dbi_result_get_uint_idx(dbi_result Result, unsigned int fieldidx) {
  return (unsigned int)dbi_result_get_int_idx(Result, fieldidx);
}

unsigned int dbi_result_get_ulong_idx(dbi_result Result, unsigned int fieldidx) {
  return dbi_result_get_uint_idx(Result, fieldidx);
}

unsigned long long dbi_result_get_ulonglong(dbi_result Result, const char *fieldname) {
  return (unsigned long long)dbi_result_get_longlong(Result, fieldname);
}

unsigned long long dbi_result_get_ulonglong_idx(dbi_result Result, unsigned int fieldidx) {
  return (unsigned long long)dbi_result_get_longlong_idx(Result, fieldidx);
}

float dbi_result_get_float(dbi_result Result, const char *fieldname) {
  float my_ERROR = 0.0;
  dbi_error_flag errflag = DBI_ERROR_NONE;
  unsigned int fieldidx;

  _reset_conn_error(RESULT->conn);

  fieldidx = _find_field(RESULT, fieldname, &errflag);
  if (errflag != DBI_ERROR_NONE) {
    dbi_conn_t *conn = RESULT->conn;
    _error_handler(conn, DBI_ERROR_BADNAME);
    return my_ERROR;
  }
  return dbi_result_get_float_idx(Result, fieldidx+1);
}

float dbi_result_get_float_idx(dbi_result Result, unsigned int fieldidx) {
  float my_ERROR = 0.0;
  fieldidx--;
	
  _reset_conn_error(RESULT->conn);


  if (fieldidx >= RESULT->numfields) {
    _error_handler(RESULT->conn, DBI_ERROR_BADIDX);
    return my_ERROR;
  }
  if (RESULT->field_types[fieldidx] == DBI_TYPE_XDECIMAL)
    return dbi_result_get_double_idx(Result, fieldidx);
  if (RESULT->field_types[fieldidx] != DBI_TYPE_DECIMAL) {
    _verbose_handler(RESULT->conn, "%s: field `%s` is not float type\n",
                     __func__, dbi_result_get_field_name(Result, fieldidx+1));
    _error_handler(RESULT->conn, DBI_ERROR_BADTYPE);
    return my_ERROR;
  }

  switch (RESULT->field_attribs[fieldidx] & DBI_DECIMAL_SIZEMASK) {
  case DBI_DECIMAL_SIZE4:
    return RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_float;
  case DBI_DECIMAL_SIZE8:
    _verbose_handler(RESULT->conn, "%s: field `%s` is double, not float\n",
                     __func__, dbi_result_get_field_name(Result, fieldidx+1));
  default:
    _error_handler(RESULT->conn, DBI_ERROR_BADTYPE);
    return my_ERROR;
  }
}

double dbi_result_get_double(dbi_result Result, const char *fieldname) {
  double my_ERROR = 0.0;
  unsigned int fieldidx;
  dbi_error_flag errflag = DBI_ERROR_NONE;

  _reset_conn_error(RESULT->conn);

  fieldidx = _find_field(RESULT, fieldname, &errflag);
  if (errflag != DBI_ERROR_NONE) {
    dbi_conn_t *conn = RESULT->conn;
    _error_handler(conn, DBI_ERROR_BADNAME);
    return my_ERROR;
  }
  return dbi_result_get_double_idx(Result, fieldidx+1);
}
	
double dbi_result_get_double_idx(dbi_result Result, unsigned int fieldidx) {
  double my_ERROR = 0.0;
  fieldidx--;
	
  _reset_conn_error(RESULT->conn);

  if (fieldidx >= RESULT->numfields) {
    _error_handler(RESULT->conn, DBI_ERROR_BADIDX);
    return my_ERROR;
  }
  if (RESULT->field_types[fieldidx] == DBI_TYPE_XDECIMAL) {
	const char *s = RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_string;
	double v = 0, xpow;
	if (s == NULL)
		return 0;
	for (; isdigit(*s); ++s) {
		v *= 10;
		v += *s - '0';
	}
	if (*s++ != '.')
		/* I hear SQL mandates the dot as a separator */
		return v;
	xpow = 0.1;
	for (; isdigit(*s); ++s) {
		v += (*s - '0') * xpow;
		xpow /= 10;
	}
	return v;
  }
  if (RESULT->field_types[fieldidx] != DBI_TYPE_DECIMAL) {
    _verbose_handler(RESULT->conn, "%s: field `%s` is not double type\n",
                     __func__, dbi_result_get_field_name(Result, fieldidx+1));
    _error_handler(RESULT->conn, DBI_ERROR_BADTYPE);
    return my_ERROR;
  }

  switch (RESULT->field_attribs[fieldidx] & DBI_DECIMAL_SIZEMASK) {
  case DBI_DECIMAL_SIZE4:
    return RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_float;
  case DBI_DECIMAL_SIZE8:
    return RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_double;
  default:
    _error_handler(RESULT->conn, DBI_ERROR_BADTYPE);
    return my_ERROR;
  }
}

const char *dbi_result_get_string(dbi_result Result, const char *fieldname) {
  const char *my_ERROR = "ERROR";
  unsigned int fieldidx;
  dbi_error_flag errflag = DBI_ERROR_NONE;

  _reset_conn_error(RESULT->conn);

  fieldidx = _find_field(RESULT, fieldname, &errflag);
  if (errflag != DBI_ERROR_NONE) {
    dbi_conn_t *conn = RESULT->conn;
    _error_handler(conn, DBI_ERROR_BADNAME);
    return my_ERROR;
  }
  return dbi_result_get_string_idx(Result, fieldidx+1);
}
	
const char *dbi_result_get_string_idx(dbi_result Result, unsigned int fieldidx) {
  const char *my_ERROR = "ERROR";
  fieldidx--;
	
  _reset_conn_error(RESULT->conn);


  if (fieldidx >= RESULT->numfields) {
    _error_handler(RESULT->conn, DBI_ERROR_BADIDX);
    return my_ERROR;
  }

  if (RESULT->field_types[fieldidx] != DBI_TYPE_STRING &&
      RESULT->field_types[fieldidx] != DBI_TYPE_XDECIMAL) {
    dbi_conn_t *conn = RESULT->conn;
    _verbose_handler(conn, "%s: field `%s` is not string type\n",
                     __func__, dbi_result_get_field_name(Result, fieldidx+1));
    _error_handler(conn, DBI_ERROR_BADTYPE);
    return my_ERROR;
  }
  if (RESULT->rows[RESULT->currowidx]->field_sizes[fieldidx] == 0
      && _get_field_flag(RESULT->rows[RESULT->currowidx], fieldidx, DBI_VALUE_NULL)) {
    /* string does not exist */
    return NULL;
  }
  /* else if field size == 0: empty string */
	
  return (const char *)(RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_string);
}

const unsigned char *dbi_result_get_binary(dbi_result Result, const char *fieldname) {
  const char *my_ERROR = "ERROR";
  unsigned int fieldidx;
  dbi_error_flag errflag = DBI_ERROR_NONE;

  _reset_conn_error(RESULT->conn);

  fieldidx = _find_field(RESULT, fieldname, &errflag);
  if (errflag != DBI_ERROR_NONE) {
    dbi_conn_t *conn = RESULT->conn;
    _error_handler(conn, DBI_ERROR_BADNAME);
    return (const unsigned char*)my_ERROR;
  }
  return dbi_result_get_binary_idx(Result, fieldidx+1);
}
	
const unsigned char *dbi_result_get_binary_idx(dbi_result Result, unsigned int fieldidx) {
  const char *my_ERROR = "ERROR";
  fieldidx--;
	
  _reset_conn_error(RESULT->conn);


  if (fieldidx >= RESULT->numfields) {
    _error_handler(RESULT->conn, DBI_ERROR_BADIDX);
    return (const unsigned char*)my_ERROR;
  }
  if (RESULT->field_types[fieldidx] != DBI_TYPE_BINARY) {
    _verbose_handler(RESULT->conn, "%s: field `%s` is not binary type\n",
                     __func__, dbi_result_get_field_name(Result, fieldidx+1));
    _error_handler(RESULT->conn, DBI_ERROR_BADTYPE);
    return (const unsigned char*)my_ERROR;
  }
  if (RESULT->rows[RESULT->currowidx]->field_sizes[fieldidx] == 0) return NULL;

  return (const unsigned char *)(RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_string);
}

char *dbi_result_get_string_copy(dbi_result Result, const char *fieldname) {
  char *my_ERROR = "ERROR";
  unsigned int fieldidx;
  dbi_error_flag errflag = DBI_ERROR_NONE;

  _reset_conn_error(RESULT->conn);

  fieldidx = _find_field(RESULT, fieldname, &errflag);
  if (errflag != DBI_ERROR_NONE) {
    dbi_conn_t *conn = RESULT->conn;
    _error_handler(conn, DBI_ERROR_BADNAME);
    return strdup(my_ERROR);
  }
  return dbi_result_get_string_copy_idx(Result, fieldidx+1);
}
	
char *dbi_result_get_string_copy_idx(dbi_result Result, unsigned int fieldidx) {
  char *my_ERROR = "ERROR";
  char *newstring = NULL;
  fieldidx--;
	
  _reset_conn_error(RESULT->conn);


  if (fieldidx >= RESULT->numfields) {
    _error_handler(RESULT->conn, DBI_ERROR_BADIDX);
    return strdup(my_ERROR);
  }
  if (RESULT->field_types[fieldidx] != DBI_TYPE_STRING &&
      RESULT->field_types[fieldidx] != DBI_TYPE_XDECIMAL) {
    _verbose_handler(RESULT->conn, "%s: field `%s` is not string type\n",
                     __func__, dbi_result_get_field_name(Result, fieldidx+1));
    _error_handler(RESULT->conn, DBI_ERROR_BADTYPE);
    return strdup(my_ERROR);
  }
  if (RESULT->rows[RESULT->currowidx]->field_sizes[fieldidx] == 0
   && RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_string == NULL) {
    // mysql returns 0 for the field size of an empty string, so size==0
    // doesn't necessarily mean NULL
    return NULL;
  }

  newstring = strdup(RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_string);

  if (newstring) {
    return newstring;
  }
  else {
    _error_handler(RESULT->conn, DBI_ERROR_NOMEM);
    return strdup(my_ERROR);
  }
}

unsigned char *dbi_result_get_binary_copy(dbi_result Result, const char *fieldname) {
  char *my_ERROR = "ERROR";
  unsigned int fieldidx;
  dbi_error_flag errflag = DBI_ERROR_NONE;

  _reset_conn_error(RESULT->conn);

  fieldidx = _find_field(RESULT, fieldname, &errflag);
  if (errflag != DBI_ERROR_NONE) {
    dbi_conn_t *conn = RESULT->conn;
    _error_handler(conn, DBI_ERROR_BADNAME);
    return (unsigned char *)strdup(my_ERROR);
  }
  return dbi_result_get_binary_copy_idx(Result, fieldidx+1);
}
	
unsigned char *dbi_result_get_binary_copy_idx(dbi_result Result, unsigned int fieldidx) {
  char *my_ERROR = "ERROR";
  unsigned char *newblob = NULL;
  unsigned long long size;
  fieldidx--;

  _reset_conn_error(RESULT->conn);


  if (fieldidx >= RESULT->numfields) {
    _error_handler(RESULT->conn, DBI_ERROR_BADIDX);
    return (unsigned char *)strdup(my_ERROR);
  }
  if (RESULT->field_types[fieldidx] != DBI_TYPE_BINARY) {
    _verbose_handler(RESULT->conn, "%s: field `%s` is not binary type\n",
                     __func__, dbi_result_get_field_name(Result, fieldidx+1));
    _error_handler(RESULT->conn, DBI_ERROR_BADTYPE);
    return (unsigned char *)strdup(my_ERROR);
  }
  if (RESULT->rows[RESULT->currowidx]->field_sizes[fieldidx] == 0) return NULL;

  /* API function must use 1-based index */
  size = dbi_result_get_field_length_idx(Result, fieldidx+1);
  newblob = malloc(size);
  if (!newblob) {
    _error_handler(RESULT->conn, DBI_ERROR_NOMEM);
    return (unsigned char *)strdup(my_ERROR);
  }
  memcpy(newblob, RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_string, size);
  return newblob;
}

static time_t _dbi_make_datetime(const dbi_datetimex *dtx)
{
	struct tm copy = dtx->tm;

	/* timegm reserves the right to modify it by not taking a const tm */
	return timegm(&copy) - dtx->utc_offset;
}

time_t dbi_result_get_datetime(dbi_result Result, const char *fieldname) {
  time_t my_ERROR = 0;
  unsigned int fieldidx;
  dbi_error_flag errflag = DBI_ERROR_NONE;

  _reset_conn_error(RESULT->conn);

  fieldidx = _find_field(RESULT, fieldname, &errflag);
  if (errflag != DBI_ERROR_NONE) {
    dbi_conn_t *conn = RESULT->conn;
    _error_handler(conn, DBI_ERROR_BADNAME);
    return my_ERROR;
  }
  return dbi_result_get_datetime_idx(Result, fieldidx+1);
}
	
time_t dbi_result_get_datetime_idx(dbi_result Result, unsigned int fieldidx) {
  time_t my_ERROR = 0;
  fieldidx--;

  _reset_conn_error(RESULT->conn);

  if (fieldidx >= RESULT->numfields) {
    _error_handler(RESULT->conn, DBI_ERROR_BADIDX);
    return my_ERROR;
  }
  if (RESULT->field_types[fieldidx] != DBI_TYPE_DATETIME) {
    _verbose_handler(RESULT->conn, "%s: field `%s` is not datetime type\n",
                     __func__, dbi_result_get_field_name(Result, fieldidx+1));
    _error_handler(RESULT->conn, DBI_ERROR_BADTYPE);
    return my_ERROR;
  }
	
  return _dbi_make_datetime(&RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_datetimex);
}

static const dbi_datetimex _dbi_datetimex_ERROR = {
	.tm = {
		.tm_sec = -1, .tm_min = -1, .tm_hour = -1,
		.tm_mday = -1, .tm_mon = -1, .tm_year = -1,
		.tm_wday = -1, .tm_yday = -1, .tm_isdst = -1,
	},
};

const dbi_datetimex *
dbi_result_get_datetimex(dbi_result Result, const char *fieldname)
{
	unsigned int fieldidx;
	dbi_error_flag errflag = DBI_ERROR_NONE;

	_reset_conn_error(RESULT->conn);
	fieldidx = _find_field(RESULT, fieldname, &errflag);
	if (errflag != DBI_ERROR_NONE) {
		dbi_conn_t *conn = RESULT->conn;
		_error_handler(conn, DBI_ERROR_BADNAME);
		return &_dbi_datetimex_ERROR;
	}
	return dbi_result_get_datetimex_idx(Result, fieldidx + 1);
}

const dbi_datetimex *
dbi_result_get_datetimex_idx(dbi_result Result, unsigned int fieldidx)
{
	--fieldidx;
	_reset_conn_error(RESULT->conn);
	if (fieldidx >= RESULT->numfields) {
		_error_handler(RESULT->conn, DBI_ERROR_BADIDX);
		return &_dbi_datetimex_ERROR;
	}
	if (RESULT->field_types[fieldidx] != DBI_TYPE_DATETIME) {
		_verbose_handler(RESULT->conn,
			"%s: field `%s` is not datetime type\n",
			__func__,
			dbi_result_get_field_name(Result, fieldidx + 1));
		_error_handler(RESULT->conn, DBI_ERROR_BADTYPE);
		return &_dbi_datetimex_ERROR;
	}
	return &RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_datetimex;
}

/* RESULT: get_as* functions */

long long dbi_result_get_as_longlong(dbi_result Result, const char *fieldname) {
  long long my_ERROR = 0;
  unsigned int fieldidx;
  dbi_error_flag errflag = DBI_ERROR_NONE;
    
  _reset_conn_error(RESULT->conn);

  fieldidx = _find_field(RESULT, fieldname, &errflag);
  if (errflag != DBI_ERROR_NONE) {
    dbi_conn_t *conn = RESULT->conn;
    _error_handler(conn, DBI_ERROR_BADNAME);
    return my_ERROR;
  }
  return dbi_result_get_as_longlong_idx(Result, fieldidx+1);
}

long long dbi_result_get_as_longlong_idx(dbi_result Result, unsigned int fieldidx) {
  long long my_ERROR = 0;
  fieldidx--;

  switch (RESULT->field_types[fieldidx]) {
  case DBI_TYPE_INTEGER:
    switch (RESULT->field_attribs[fieldidx] & DBI_INTEGER_SIZEMASK) {
    case DBI_INTEGER_SIZE1:
      return (long long)RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_char;
    case DBI_INTEGER_SIZE2:
      return (long long)RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_short;
    case DBI_INTEGER_SIZE3:
    case DBI_INTEGER_SIZE4:
      return (long long)RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_long;
    case DBI_INTEGER_SIZE8:
      return RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_longlong;
    default:
      _error_handler(RESULT->conn, DBI_ERROR_BADTYPE);
      return my_ERROR;
    }
  case DBI_TYPE_DECIMAL:
    switch (RESULT->field_attribs[fieldidx] & DBI_DECIMAL_SIZEMASK) {
    case DBI_DECIMAL_SIZE4:
      return (long long)RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_float;
    case DBI_DECIMAL_SIZE8:
      return (long long)RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_double;
    default:
      _error_handler(RESULT->conn, DBI_ERROR_BADTYPE);
      return my_ERROR;
    }
  case DBI_TYPE_STRING:
  case DBI_TYPE_XDECIMAL:
    if (RESULT->rows[RESULT->currowidx]->field_sizes[fieldidx] == 0
	&& RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_string == NULL) {
      /* string does not exist */
      return 0; /* do not raise an error */
    }
    /* else if field size == 0: empty string */
    /* todo: do we need strtoll() error handling? */
    return strtoll((const char *)(RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_string), NULL, 10);
  case DBI_TYPE_BINARY:
    return 0; /* do not raise an error */
  case DBI_TYPE_DATETIME:
    return _dbi_make_datetime(&RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_datetimex);
  default:
    _error_handler(RESULT->conn, DBI_ERROR_BADTYPE);
    return my_ERROR;
  }
}

char *dbi_result_get_as_string_copy(dbi_result Result, const char *fieldname) {
  char *my_ERROR = "ERROR";
  unsigned int fieldidx;
  dbi_error_flag errflag = DBI_ERROR_NONE;

  _reset_conn_error(RESULT->conn);

  fieldidx = _find_field(RESULT, fieldname, &errflag);
  if (errflag != DBI_ERROR_NONE) {
    dbi_conn_t *conn = RESULT->conn;
    _error_handler(conn, DBI_ERROR_BADNAME);
    return strdup(my_ERROR);
  }
  return dbi_result_get_as_string_copy_idx(Result, fieldidx+1);
}

char *dbi_result_get_as_string_copy_idx(dbi_result Result, unsigned int fieldidx) {
  char *my_ERROR = "ERROR";
  char *newstring = NULL;
  char *oldstring = NULL;

  fieldidx--;

  newstring = malloc(32); /* sufficient for integers, decimal, and datetime */

  if (!newstring) {
    _error_handler(RESULT->conn, DBI_ERROR_NOMEM);
    return my_ERROR;
  }
  *newstring = '\0';

  /* output depends on: type, size, sign */
  switch (RESULT->field_types[fieldidx]) {
  case DBI_TYPE_INTEGER:
    switch (RESULT->field_attribs[fieldidx] & DBI_INTEGER_SIZEMASK) {
    case DBI_INTEGER_SIZE1:
      if (RESULT->field_attribs[fieldidx] & DBI_INTEGER_UNSIGNED) {
	snprintf(newstring, 32, "%hu", RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_char);
      }
      else {
	snprintf(newstring, 32, "%hd", RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_char);
      }
      break;
    case DBI_INTEGER_SIZE2:
      if (RESULT->field_attribs[fieldidx] & DBI_INTEGER_UNSIGNED) {
	snprintf(newstring, 32, "%hu", RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_short);
      }
      else {
	snprintf(newstring, 32, "%hd", RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_short);
      }
      break;
    case DBI_INTEGER_SIZE3:
    case DBI_INTEGER_SIZE4:
      if (RESULT->field_attribs[fieldidx] & DBI_INTEGER_UNSIGNED) {
	snprintf(newstring, 32, "%u", RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_long);
      }
      else {
	snprintf(newstring, 32, "%d", RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_long);
      }
      break;
    case DBI_INTEGER_SIZE8:
      if (RESULT->field_attribs[fieldidx] & DBI_INTEGER_UNSIGNED) {
	snprintf(newstring, 32, "%llu", RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_longlong);
      }
      else {
	snprintf(newstring, 32, "%lld", RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_longlong);
      }
      break;
    default:
      _error_handler(RESULT->conn, DBI_ERROR_BADTYPE);
    }
    break;
  case DBI_TYPE_DECIMAL:
    switch (RESULT->field_attribs[fieldidx] & DBI_DECIMAL_SIZEMASK) {
    case DBI_DECIMAL_SIZE4:
      snprintf(newstring, 32, "%e", RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_float);
      break;
    case DBI_DECIMAL_SIZE8:
      snprintf(newstring, 32, "%e", RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_double);
      break;
    default:
      _error_handler(RESULT->conn, DBI_ERROR_BADTYPE);
    }
    break;
  case DBI_TYPE_STRING:
  case DBI_TYPE_XDECIMAL:
    if (RESULT->rows[RESULT->currowidx]->field_sizes[fieldidx] == 0
	&& RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_string == NULL) {
      /* string does not exist */
      /* return an empty string instead, no error */
    }
    else {
      /* else if field size == 0: empty string */

      oldstring = newstring;
      if ((newstring = strdup(RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_string)) == NULL) {
	_error_handler(RESULT->conn, DBI_ERROR_NOMEM);
	newstring = oldstring;
      }
      else {
	/* don't free in case of an error as we'd return a dangling pointer */
	free(oldstring);
      }
    }
    break;
  case DBI_TYPE_BINARY:
    break; /* return empty string, do not raise an error */
  case DBI_TYPE_DATETIME: {
    const struct tm *tm = &RESULT->rows[RESULT->currowidx]->field_values[fieldidx].d_datetimex.tm;
    snprintf(newstring, 32, "%04d-%02d-%02d %02d:%02d:%02d",
             tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
             tm->tm_hour, tm->tm_min, tm->tm_sec);
    break;
  }
  default:
    _error_handler(RESULT->conn, DBI_ERROR_BADTYPE);
  }

  return newstring; /* is still empty string in case of an error */
}


/* RESULT: bind_* functions */

static int _setup_binding(dbi_result_t *result, const char *fieldname, void *bindto, void *helperfunc) {
  _field_binding_t *binding;
  if (!result) {
    _error_handler(/*RESULT->conn*/ NULL, DBI_ERROR_BADPTR);
    return DBI_BIND_ERROR;
  }

  _reset_conn_error(result->conn);

  if (!fieldname) {
    _error_handler(result->conn, DBI_ERROR_BADNAME);
    return DBI_BIND_ERROR;
  }
  binding = _find_or_create_binding_node(result, fieldname);
  if (!binding) {
    _error_handler(result->conn, DBI_ERROR_NOMEM);
    return DBI_BIND_ERROR;
  }

  if (bindto == NULL) {
    _remove_binding_node(result, binding);
  }
  else {
    binding->bindto = bindto;
    binding->helper_function = (void*)(_field_binding_t *)helperfunc;
  }

  return 0;
}

static void _activate_bindings(dbi_result_t *result) {
  _field_binding_t *binding = result->field_bindings;
  void (*helper_function)(_field_binding_t *);

  while (binding) {
    helper_function = binding->helper_function;
    helper_function(binding);
    binding = binding->next;
  }
  return;
}

int dbi_result_bind_char(dbi_result Result, const char *fieldname, char *bindto) {
  return _setup_binding(RESULT, fieldname, bindto, _bind_helper_char);
}

int dbi_result_bind_uchar(dbi_result Result, const char *fieldname, unsigned char *bindto) {
  return _setup_binding(RESULT, fieldname, bindto, _bind_helper_uchar);
}

int dbi_result_bind_short(dbi_result Result, const char *fieldname, short *bindto) {
  return _setup_binding(RESULT, fieldname, bindto, _bind_helper_short);
}

int dbi_result_bind_ushort(dbi_result Result, const char *fieldname, unsigned short *bindto) {
  return _setup_binding(RESULT, fieldname, bindto, _bind_helper_ushort);
}

int dbi_result_bind_int(dbi_result Result, const char *fieldname, int *bindto) {
  return _setup_binding(RESULT, fieldname, bindto, _bind_helper_int);
}

int dbi_result_bind_long(dbi_result Result, const char *fieldname, int *bindto) {
  return dbi_result_bind_int(Result, fieldname, bindto);
}

int dbi_result_bind_uint(dbi_result Result, const char *fieldname, unsigned int *bindto) {
  return _setup_binding(RESULT, fieldname, bindto, _bind_helper_uint);
}

int dbi_result_bind_ulong(dbi_result Result, const char *fieldname, unsigned int *bindto) {
  return dbi_result_bind_uint(Result, fieldname, bindto);
}

int dbi_result_bind_longlong(dbi_result Result, const char *fieldname, long long *bindto) {
  return _setup_binding(RESULT, fieldname, bindto, _bind_helper_longlong);
}

int dbi_result_bind_ulonglong(dbi_result Result, const char *fieldname, unsigned long long *bindto) {
  return _setup_binding(RESULT, fieldname, bindto, _bind_helper_ulonglong);
}

int dbi_result_bind_float(dbi_result Result, const char *fieldname, float *bindto) {
  return _setup_binding(RESULT, fieldname, bindto, _bind_helper_float);
}

int dbi_result_bind_double(dbi_result Result, const char *fieldname, double *bindto) {
  return _setup_binding(RESULT, fieldname, bindto, _bind_helper_double);
}

int dbi_result_bind_string(dbi_result Result, const char *fieldname, const char **bindto) {
  return _setup_binding(RESULT, fieldname, (char **)bindto, _bind_helper_string);
}

int dbi_result_bind_binary(dbi_result Result, const char *fieldname, const unsigned char **bindto) {
  return _setup_binding(RESULT, fieldname, (unsigned char **)bindto, _bind_helper_binary);
}

int dbi_result_bind_string_copy(dbi_result Result, const char *fieldname, char **bindto) {
  return _setup_binding(RESULT, fieldname, bindto, _bind_helper_string_copy);
}

int dbi_result_bind_binary_copy(dbi_result Result, const char *fieldname, unsigned char **bindto) {
  return _setup_binding(RESULT, fieldname, bindto, _bind_helper_binary_copy);
}

int dbi_result_bind_datetime(dbi_result Result, const char *fieldname, time_t *bindto) {
  return _setup_binding(RESULT, fieldname, (time_t *)bindto, _bind_helper_datetime);
}

static _field_binding_t *_find_or_create_binding_node(dbi_result_t *result, const char *fieldname) {
  _field_binding_t *prevbinding = NULL;
  _field_binding_t *binding = result->field_bindings;

  while (binding && strcasecmp(fieldname, binding->fieldname)) {
    prevbinding = binding;
    binding = binding->next;
  }
  if (!binding) {
    /* allocate a new option node */
    binding = malloc(sizeof(_field_binding_t));
    if (!binding) {
      return NULL;
    }
    binding->result = result;
    binding->fieldname = strdup(fieldname);
    binding->next = NULL;
    if (result->field_bindings == NULL) {
      result->field_bindings = binding;
    }
    else {
      prevbinding->next = binding;
    }
  }

  return binding;
}

static void _remove_binding_node(dbi_result_t *result, _field_binding_t *deadbinding) {
  _field_binding_t *prevbinding = NULL;
  _field_binding_t *binding = result->field_bindings;

  while (binding && (binding != deadbinding)) {
    prevbinding = binding;
    binding = binding->next;
  }
  if (!binding) {
    /* this should never ever happen. silently pretend it never did. */
    return;
  }
  free((char *)deadbinding->fieldname);
  if (result->field_bindings == deadbinding) {
    result->field_bindings = deadbinding->next;
  }
  else {
    prevbinding->next = deadbinding->next;
  }
  free(deadbinding);
}

/* returns the field index (>= 1), or 0 if no such field */
static unsigned int _find_field(dbi_result_t *result, const char *fieldname, dbi_error_flag *errflag) {
  unsigned long i = 0;
  if (!result || !result->field_names) return DBI_FIELD_ERROR;
  while (i < result->numfields) {
    if (strcasecmp(result->field_names[i], fieldname) == 0) {
      *errflag = DBI_ERROR_NONE;
      return i;
    }
    i++;
  }
  _verbose_handler(result->conn, "result row has no field `%s`\n", fieldname);
  *errflag = DBI_ERROR_BADNAME;
  return 0;
}

static int _is_row_fetched(dbi_result_t *result, unsigned long long row) {
  if (!result->rows || (row > result->numrows_matched)) return -1;
  return !(result->rows[row] == NULL);
}


/* PRIVATE: bind helpers */

static void _bind_helper_char(_field_binding_t *binding) {
  *(char *)binding->bindto = dbi_result_get_char((dbi_result)binding->result, binding->fieldname);
}

static void _bind_helper_uchar(_field_binding_t *binding) {
  *(unsigned char *)binding->bindto = dbi_result_get_uchar((dbi_result)binding->result, binding->fieldname);
}

static void _bind_helper_short(_field_binding_t *binding) {
  *(short *)binding->bindto = dbi_result_get_short((dbi_result)binding->result, binding->fieldname);
}

static void _bind_helper_ushort(_field_binding_t *binding) {
  *(unsigned short *)binding->bindto = dbi_result_get_ushort((dbi_result)binding->result, binding->fieldname);
}

static void _bind_helper_int(_field_binding_t *binding) {
  *(int *)binding->bindto = dbi_result_get_int((dbi_result)binding->result, binding->fieldname);
}

static void _bind_helper_uint(_field_binding_t *binding) {
  *(unsigned int *)binding->bindto = dbi_result_get_uint((dbi_result)binding->result, binding->fieldname);
}

static void _bind_helper_longlong(_field_binding_t *binding) {
  *(long long *)binding->bindto = dbi_result_get_longlong((dbi_result)binding->result, binding->fieldname);
}

static void _bind_helper_ulonglong(_field_binding_t *binding) {
  *(unsigned long long *)binding->bindto = dbi_result_get_ulonglong((dbi_result)binding->result, binding->fieldname);
}

static void _bind_helper_float(_field_binding_t *binding) {
  *(float *)binding->bindto = dbi_result_get_float((dbi_result)binding->result, binding->fieldname);
}

static void _bind_helper_double(_field_binding_t *binding) {
  *(double *)binding->bindto = dbi_result_get_double((dbi_result)binding->result, binding->fieldname);
}

static void _bind_helper_string(_field_binding_t *binding) {
  *(const char **)binding->bindto = dbi_result_get_string((dbi_result)binding->result, binding->fieldname);
}

static void _bind_helper_binary(_field_binding_t *binding) {
  *(const unsigned char **)binding->bindto = dbi_result_get_binary((dbi_result)binding->result, binding->fieldname);
}

static void _bind_helper_string_copy(_field_binding_t *binding) {
  *(char **)binding->bindto = dbi_result_get_string_copy((dbi_result)binding->result, binding->fieldname);
}

static void _bind_helper_binary_copy(_field_binding_t *binding) {
  *(unsigned char **)binding->bindto = dbi_result_get_binary_copy((dbi_result)binding->result, binding->fieldname);
}

static void _bind_helper_datetime(_field_binding_t *binding) {
  *(time_t *)binding->bindto = dbi_result_get_datetime((dbi_result)binding->result, binding->fieldname);
}

