/*************************************************************************************************
 * System-dependent configurations of Tokyo Cabinet
 *                                                               Copyright (C) 2006-2012 FAL Labs
 * This file is part of Tokyo Cabinet.
 * Tokyo Cabinet 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 any later version.  Tokyo Cabinet 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 Tokyo
 * Cabinet; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307 USA.
 *************************************************************************************************/


#include "myconf.h"



/*************************************************************************************************
 * common settings
 *************************************************************************************************/


int _tc_dummy_cnt = 0;


int _tc_dummyfunc(void){
  return 0;
}


int _tc_dummyfuncv(int a, ...){
  return 0;
}



/*************************************************************************************************
 * for ZLIB
 *************************************************************************************************/


#if TCUSEZLIB


#include <zlib.h>

#define ZLIBBUFSIZ     8192


static char *_tc_deflate_impl(const char *ptr, int size, int *sp, int mode);
static char *_tc_inflate_impl(const char *ptr, int size, int *sp, int mode);
static unsigned int _tc_getcrc_impl(const char *ptr, int size);


char *(*_tc_deflate)(const char *, int, int *, int) = _tc_deflate_impl;
char *(*_tc_inflate)(const char *, int, int *, int) = _tc_inflate_impl;
unsigned int (*_tc_getcrc)(const char *, int) = _tc_getcrc_impl;


static char *_tc_deflate_impl(const char *ptr, int size, int *sp, int mode){
  assert(ptr && size >= 0 && sp);
  z_stream zs;
  zs.zalloc = Z_NULL;
  zs.zfree = Z_NULL;
  zs.opaque = Z_NULL;
  switch(mode){
    case _TCZMRAW:
      if(deflateInit2(&zs, 5, Z_DEFLATED, -15, 7, Z_DEFAULT_STRATEGY) != Z_OK)
        return NULL;
      break;
    case _TCZMGZIP:
      if(deflateInit2(&zs, 6, Z_DEFLATED, 15 + 16, 9, Z_DEFAULT_STRATEGY) != Z_OK)
        return NULL;
      break;
    default:
      if(deflateInit2(&zs, 6, Z_DEFLATED, 15, 8, Z_DEFAULT_STRATEGY) != Z_OK)
        return NULL;
      break;
  }
  int asiz = size + 16;
  if(asiz < ZLIBBUFSIZ) asiz = ZLIBBUFSIZ;
  char *buf;
  if(!(buf = MYMALLOC(asiz))){
    deflateEnd(&zs);
    return NULL;
  }
  unsigned char obuf[ZLIBBUFSIZ];
  int bsiz = 0;
  zs.next_in = (unsigned char *)ptr;
  zs.avail_in = size;
  zs.next_out = obuf;
  zs.avail_out = ZLIBBUFSIZ;
  int rv;
  while((rv = deflate(&zs, Z_FINISH)) == Z_OK){
    int osiz = ZLIBBUFSIZ - zs.avail_out;
    if(bsiz + osiz > asiz){
      asiz = asiz * 2 + osiz;
      char *swap;
      if(!(swap = MYREALLOC(buf, asiz))){
        MYFREE(buf);
        deflateEnd(&zs);
        return NULL;
      }
      buf = swap;
    }
    memcpy(buf + bsiz, obuf, osiz);
    bsiz += osiz;
    zs.next_out = obuf;
    zs.avail_out = ZLIBBUFSIZ;
  }
  if(rv != Z_STREAM_END){
    MYFREE(buf);
    deflateEnd(&zs);
    return NULL;
  }
  int osiz = ZLIBBUFSIZ - zs.avail_out;
  if(bsiz + osiz + 1 > asiz){
    asiz = asiz * 2 + osiz;
    char *swap;
    if(!(swap = MYREALLOC(buf, asiz))){
      MYFREE(buf);
      deflateEnd(&zs);
      return NULL;
    }
    buf = swap;
  }
  memcpy(buf + bsiz, obuf, osiz);
  bsiz += osiz;
  buf[bsiz] = '\0';
  if(mode == _TCZMRAW) bsiz++;
  *sp = bsiz;
  deflateEnd(&zs);
  return buf;
}


static char *_tc_inflate_impl(const char *ptr, int size, int *sp, int mode){
  assert(ptr && size >= 0 && sp);
  z_stream zs;
  zs.zalloc = Z_NULL;
  zs.zfree = Z_NULL;
  zs.opaque = Z_NULL;
  switch(mode){
    case _TCZMRAW:
      if(inflateInit2(&zs, -15) != Z_OK) return NULL;
      break;
    case _TCZMGZIP:
      if(inflateInit2(&zs, 15 + 16) != Z_OK) return NULL;
      break;
    default:
      if(inflateInit2(&zs, 15) != Z_OK) return NULL;
      break;
  }
  int asiz = size * 2 + 16;
  if(asiz < ZLIBBUFSIZ) asiz = ZLIBBUFSIZ;
  char *buf;
  if(!(buf = MYMALLOC(asiz))){
    inflateEnd(&zs);
    return NULL;
  }
  unsigned char obuf[ZLIBBUFSIZ];
  int bsiz = 0;
  zs.next_in = (unsigned char *)ptr;
  zs.avail_in = size;
  zs.next_out = obuf;
  zs.avail_out = ZLIBBUFSIZ;
  int rv;
  while((rv = inflate(&zs, Z_NO_FLUSH)) == Z_OK){
    int osiz = ZLIBBUFSIZ - zs.avail_out;
    if(bsiz + osiz >= asiz){
      asiz = asiz * 2 + osiz;
      char *swap;
      if(!(swap = MYREALLOC(buf, asiz))){
        MYFREE(buf);
        inflateEnd(&zs);
        return NULL;
      }
      buf = swap;
    }
    memcpy(buf + bsiz, obuf, osiz);
    bsiz += osiz;
    zs.next_out = obuf;
    zs.avail_out = ZLIBBUFSIZ;
  }
  if(rv != Z_STREAM_END){
    MYFREE(buf);
    inflateEnd(&zs);
    return NULL;
  }
  int osiz = ZLIBBUFSIZ - zs.avail_out;
  if(bsiz + osiz >= asiz){
    asiz = asiz * 2 + osiz;
    char *swap;
    if(!(swap = MYREALLOC(buf, asiz))){
      MYFREE(buf);
      inflateEnd(&zs);
      return NULL;
    }
    buf = swap;
  }
  memcpy(buf + bsiz, obuf, osiz);
  bsiz += osiz;
  buf[bsiz] = '\0';
  *sp = bsiz;
  inflateEnd(&zs);
  return buf;
}


static unsigned int _tc_getcrc_impl(const char *ptr, int size){
  assert(ptr && size >= 0);
  int crc = crc32(0, Z_NULL, 0);
  return crc32(crc, (unsigned char *)ptr, size);
}


#else


char *(*_tc_deflate)(const char *, int, int *, int) = NULL;
char *(*_tc_inflate)(const char *, int, int *, int) = NULL;
unsigned int (*_tc_getcrc)(const char *, int) = NULL;


#endif



/*************************************************************************************************
 * for BZIP2
 *************************************************************************************************/


#if TCUSEBZIP


#include <bzlib.h>

#define BZIPBUFSIZ     8192


static char *_tc_bzcompress_impl(const char *ptr, int size, int *sp);
static char *_tc_bzdecompress_impl(const char *ptr, int size, int *sp);


char *(*_tc_bzcompress)(const char *, int, int *) = _tc_bzcompress_impl;
char *(*_tc_bzdecompress)(const char *, int, int *) = _tc_bzdecompress_impl;


static char *_tc_bzcompress_impl(const char *ptr, int size, int *sp){
  assert(ptr && size >= 0 && sp);
  bz_stream zs;
  zs.bzalloc = NULL;
  zs.bzfree = NULL;
  zs.opaque = NULL;
  if(BZ2_bzCompressInit(&zs, 9, 0, 0) != BZ_OK) return NULL;
  int asiz = size + 16;
  if(asiz < BZIPBUFSIZ) asiz = BZIPBUFSIZ;
  char *buf;
  if(!(buf = MYMALLOC(asiz))){
    BZ2_bzCompressEnd(&zs);
    return NULL;
  }
  char obuf[BZIPBUFSIZ];
  int bsiz = 0;
  zs.next_in = (char *)ptr;
  zs.avail_in = size;
  zs.next_out = obuf;
  zs.avail_out = BZIPBUFSIZ;
  int rv;
  while((rv = BZ2_bzCompress(&zs, BZ_FINISH)) == BZ_FINISH_OK){
    int osiz = BZIPBUFSIZ - zs.avail_out;
    if(bsiz + osiz > asiz){
      asiz = asiz * 2 + osiz;
      char *swap;
      if(!(swap = MYREALLOC(buf, asiz))){
        MYFREE(buf);
        BZ2_bzCompressEnd(&zs);
        return NULL;
      }
      buf = swap;
    }
    memcpy(buf + bsiz, obuf, osiz);
    bsiz += osiz;
    zs.next_out = obuf;
    zs.avail_out = BZIPBUFSIZ;
  }
  if(rv != BZ_STREAM_END){
    MYFREE(buf);
    BZ2_bzCompressEnd(&zs);
    return NULL;
  }
  int osiz = BZIPBUFSIZ - zs.avail_out;
  if(bsiz + osiz + 1 > asiz){
    asiz = asiz * 2 + osiz;
    char *swap;
    if(!(swap = MYREALLOC(buf, asiz))){
      MYFREE(buf);
      BZ2_bzCompressEnd(&zs);
      return NULL;
    }
    buf = swap;
  }
  memcpy(buf + bsiz, obuf, osiz);
  bsiz += osiz;
  buf[bsiz] = '\0';
  *sp = bsiz;
  BZ2_bzCompressEnd(&zs);
  return buf;
}


static char *_tc_bzdecompress_impl(const char *ptr, int size, int *sp){
  assert(ptr && size >= 0 && sp);
  bz_stream zs;
  zs.bzalloc = NULL;
  zs.bzfree = NULL;
  zs.opaque = NULL;
  if(BZ2_bzDecompressInit(&zs, 0, 0) != BZ_OK) return NULL;
  int asiz = size * 2 + 16;
  if(asiz < BZIPBUFSIZ) asiz = BZIPBUFSIZ;
  char *buf;
  if(!(buf = MYMALLOC(asiz))){
    BZ2_bzDecompressEnd(&zs);
    return NULL;
  }
  char obuf[BZIPBUFSIZ];
  int bsiz = 0;
  zs.next_in = (char *)ptr;
  zs.avail_in = size;
  zs.next_out = obuf;
  zs.avail_out = BZIPBUFSIZ;
  int rv;
  while((rv = BZ2_bzDecompress(&zs)) == BZ_OK){
    int osiz = BZIPBUFSIZ - zs.avail_out;
    if(bsiz + osiz >= asiz){
      asiz = asiz * 2 + osiz;
      char *swap;
      if(!(swap = MYREALLOC(buf, asiz))){
        MYFREE(buf);
        BZ2_bzDecompressEnd(&zs);
        return NULL;
      }
      buf = swap;
    }
    memcpy(buf + bsiz, obuf, osiz);
    bsiz += osiz;
    zs.next_out = obuf;
    zs.avail_out = BZIPBUFSIZ;
  }
  if(rv != BZ_STREAM_END){
    MYFREE(buf);
    BZ2_bzDecompressEnd(&zs);
    return NULL;
  }
  int osiz = BZIPBUFSIZ - zs.avail_out;
  if(bsiz + osiz >= asiz){
    asiz = asiz * 2 + osiz;
    char *swap;
    if(!(swap = MYREALLOC(buf, asiz))){
      MYFREE(buf);
      BZ2_bzDecompressEnd(&zs);
      return NULL;
    }
    buf = swap;
  }
  memcpy(buf + bsiz, obuf, osiz);
  bsiz += osiz;
  buf[bsiz] = '\0';
  *sp = bsiz;
  BZ2_bzDecompressEnd(&zs);
  return buf;
}


#else


char *(*_tc_bzcompress)(const char *, int, int *) = NULL;
char *(*_tc_bzdecompress)(const char *, int, int *) = NULL;


#endif



/*************************************************************************************************
 * for test of custom codec functions
 *************************************************************************************************/


#if TCUSEEXLZMA


#include <lzmalib.h>


void *_tc_recencode(const void *ptr, int size, int *sp, void *op){
  return lzma_compress(ptr, size, sp);
}


void *_tc_recdecode(const void *ptr, int size, int *sp, void *op){
  return lzma_decompress(ptr, size, sp);
}


#elif TCUSEEXLZO


#include <lzo/lzo1x.h>


bool _tc_lzo_init = false;


void *_tc_recencode(const void *ptr, int size, int *sp, void *op){
  if(!_tc_lzo_init){
    if(lzo_init() != LZO_E_OK) return NULL;
    _tc_lzo_init = false;
  }
  lzo_bytep buf = MYMALLOC(size + (size >> 4) + 80);
  if(!buf) return NULL;
  lzo_uint bsiz;
  char wrkmem[LZO1X_1_MEM_COMPRESS];
  if(lzo1x_1_compress((lzo_bytep)ptr, size, buf, &bsiz, wrkmem) != LZO_E_OK){
    MYFREE(buf);
    return NULL;
  }
  buf[bsiz] = '\0';
  *sp = bsiz;
  return (char *)buf;
}


void *_tc_recdecode(const void *ptr, int size, int *sp, void *op){
  if(!_tc_lzo_init){
    if(lzo_init() != LZO_E_OK) return NULL;
    _tc_lzo_init = false;
  }
  lzo_bytep buf;
  lzo_uint bsiz;
  int rat = 6;
  while(true){
    bsiz = (size + 256) * rat + 3;
    buf = MYMALLOC(bsiz + 1);
    if(!buf) return NULL;
    int rv = lzo1x_decompress_safe((lzo_bytep)ptr, size, buf, &bsiz, NULL);
    if(rv == LZO_E_OK){
      break;
    } else if(rv == LZO_E_OUTPUT_OVERRUN){
      MYFREE(buf);
      rat *= 2;
    } else {
      MYFREE(buf);
      return NULL;
    }
  }
  buf[bsiz] = '\0';
  if(sp) *sp = bsiz;
  return (char *)buf;
}


#else


void *_tc_recencode(const void *ptr, int size, int *sp, void *op){
  char *res = MYMALLOC(size + 1);
  if(!res) return NULL;
  memcpy(res, ptr, size);
  *sp = size;
  return res;
}


void *_tc_recdecode(const void *ptr, int size, int *sp, void *op){
  char *res = MYMALLOC(size + 1);
  if(!res) return NULL;
  memcpy(res, ptr, size);
  *sp = size;
  return res;
}


#endif



// END OF FILE
/*************************************************************************************************
 * The utility API of Tokyo Cabinet
 *                                                               Copyright (C) 2006-2012 FAL Labs
 * This file is part of Tokyo Cabinet.
 * Tokyo Cabinet 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 any later version.  Tokyo Cabinet 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 Tokyo
 * Cabinet; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307 USA.
 *************************************************************************************************/


#include "tcutil.h"
#include "myconf.h"
#include "md5.h"



/*************************************************************************************************
 * basic utilities
 *************************************************************************************************/


/* String containing the version information. */
const char *tcversion = _TC_VERSION;


/* Call back function for handling a fatal error. */
void (*tcfatalfunc)(const char *message) = NULL;


/* Allocate a region on memory. */
void *tcmalloc(size_t size){
  assert(size > 0 && size < INT_MAX);
  char *p;
  TCMALLOC(p, size);
  return p;
}


/* Allocate a nullified region on memory. */
void *tccalloc(size_t nmemb, size_t size){
  assert(nmemb > 0 && nmemb < INT_MAX && size > 0 && size < INT_MAX);
  char *p;
  TCCALLOC(p, nmemb, size);
  return p;
}


/* Re-allocate a region on memory. */
void *tcrealloc(void *ptr, size_t size){
  assert(size >= 0 && size < INT_MAX);
  char *p;
  TCREALLOC(p, ptr, size);
  return p;
}


/* Duplicate a region on memory. */
void *tcmemdup(const void *ptr, size_t size){
  assert(ptr && size >= 0);
  char *p;
  TCMALLOC(p, size + 1);
  memcpy(p, ptr, size);
  p[size] = '\0';
  return p;
}


/* Duplicate a string on memory. */
char *tcstrdup(const void *str){
  assert(str);
  int size = strlen(str);
  char *p;
  TCMALLOC(p, size + 1);
  memcpy(p, str, size);
  p[size] = '\0';
  return p;
}


/* Free a region on memory. */
void tcfree(void *ptr){
  TCFREE(ptr);
}



/*************************************************************************************************
 * extensible string
 *************************************************************************************************/


#define TCXSTRUNIT     12                // allocation unit size of an extensible string


/* private function prototypes */
static void tcvxstrprintf(TCXSTR *xstr, const char *format, va_list ap);


/* Create an extensible string object. */
TCXSTR *tcxstrnew(void){
  TCXSTR *xstr;
  TCMALLOC(xstr, sizeof(*xstr));
  TCMALLOC(xstr->ptr, TCXSTRUNIT);
  xstr->size = 0;
  xstr->asize = TCXSTRUNIT;
  xstr->ptr[0] = '\0';
  return xstr;
}


/* Create an extensible string object from a character string. */
TCXSTR *tcxstrnew2(const char *str){
  assert(str);
  TCXSTR *xstr;
  TCMALLOC(xstr, sizeof(*xstr));
  int size = strlen(str);
  int asize = tclmax(size + 1, TCXSTRUNIT);
  TCMALLOC(xstr->ptr, asize);
  xstr->size = size;
  xstr->asize = asize;
  memcpy(xstr->ptr, str, size + 1);
  return xstr;
}


/* Create an extensible string object with the initial allocation size. */
TCXSTR *tcxstrnew3(int asiz){
  assert(asiz >= 0);
  asiz = tclmax(asiz, TCXSTRUNIT);
  TCXSTR *xstr;
  TCMALLOC(xstr, sizeof(*xstr));
  TCMALLOC(xstr->ptr, asiz);
  xstr->size = 0;
  xstr->asize = asiz;
  xstr->ptr[0] = '\0';
  return xstr;
}


/* Copy an extensible string object. */
TCXSTR *tcxstrdup(const TCXSTR *xstr){
  assert(xstr);
  TCXSTR *nxstr;
  TCMALLOC(nxstr, sizeof(*nxstr));
  int asize = tclmax(xstr->size + 1, TCXSTRUNIT);
  TCMALLOC(nxstr->ptr, asize);
  nxstr->size = xstr->size;
  nxstr->asize = asize;
  memcpy(nxstr->ptr, xstr->ptr, xstr->size + 1);
  return nxstr;
}


/* Delete an extensible string object. */
void tcxstrdel(TCXSTR *xstr){
  assert(xstr);
  TCFREE(xstr->ptr);
  TCFREE(xstr);
}


/* Concatenate a region to the end of an extensible string object. */
void tcxstrcat(TCXSTR *xstr, const void *ptr, int size){
  assert(xstr && ptr && size >= 0);
  int nsize = xstr->size + size + 1;
  if(xstr->asize < nsize){
    while(xstr->asize < nsize){
      xstr->asize *= 2;
      if(xstr->asize < nsize) xstr->asize = nsize;
    }
    TCREALLOC(xstr->ptr, xstr->ptr, xstr->asize);
  }
  memcpy(xstr->ptr + xstr->size, ptr, size);
  xstr->size += size;
  xstr->ptr[xstr->size] = '\0';
}


/* Concatenate a character string to the end of an extensible string object. */
void tcxstrcat2(TCXSTR *xstr, const char *str){
  assert(xstr && str);
  int size = strlen(str);
  int nsize = xstr->size + size + 1;
  if(xstr->asize < nsize){
    while(xstr->asize < nsize){
      xstr->asize *= 2;
      if(xstr->asize < nsize) xstr->asize = nsize;
    }
    TCREALLOC(xstr->ptr, xstr->ptr, xstr->asize);
  }
  memcpy(xstr->ptr + xstr->size, str, size + 1);
  xstr->size += size;
}


/* Get the pointer of the region of an extensible string object. */
const void *tcxstrptr(const TCXSTR *xstr){
  assert(xstr);
  return xstr->ptr;
}


/* Get the size of the region of an extensible string object. */
int tcxstrsize(const TCXSTR *xstr){
  assert(xstr);
  return xstr->size;
}


/* Clear an extensible string object. */
void tcxstrclear(TCXSTR *xstr){
  assert(xstr);
  xstr->size = 0;
  xstr->ptr[0] = '\0';
}


/* Perform formatted output into an extensible string object. */
void tcxstrprintf(TCXSTR *xstr, const char *format, ...){
  assert(xstr && format);
  va_list ap;
  va_start(ap, format);
  tcvxstrprintf(xstr, format, ap);
  va_end(ap);
}


/* Allocate a formatted string on memory. */
char *tcsprintf(const char *format, ...){
  assert(format);
  TCXSTR *xstr = tcxstrnew();
  va_list ap;
  va_start(ap, format);
  tcvxstrprintf(xstr, format, ap);
  va_end(ap);
  return tcxstrtomalloc(xstr);
}


/* Perform formatted output into an extensible string object. */
static void tcvxstrprintf(TCXSTR *xstr, const char *format, va_list ap){
  assert(xstr && format);
  while(*format != '\0'){
    if(*format == '%'){
      char cbuf[TCNUMBUFSIZ];
      cbuf[0] = '%';
      int cblen = 1;
      int lnum = 0;
      format++;
      while(strchr("0123456789 .+-hlLz", *format) && *format != '\0' &&
            cblen < TCNUMBUFSIZ - 1){
        if(*format == 'l' || *format == 'L') lnum++;
        cbuf[cblen++] = *(format++);
      }
      cbuf[cblen++] = *format;
      cbuf[cblen] = '\0';
      int tlen;
      char *tmp, tbuf[TCNUMBUFSIZ*4];
      switch(*format){
        case 's':
          tmp = va_arg(ap, char *);
          if(!tmp) tmp = "(null)";
          tcxstrcat2(xstr, tmp);
          break;
        case 'd':
          if(lnum >= 2){
            tlen = sprintf(tbuf, cbuf, va_arg(ap, long long));
          } else if(lnum >= 1){
            tlen = sprintf(tbuf, cbuf, va_arg(ap, long));
          } else {
            tlen = sprintf(tbuf, cbuf, va_arg(ap, int));
          }
          TCXSTRCAT(xstr, tbuf, tlen);
          break;
        case 'o': case 'u': case 'x': case 'X': case 'c':
          if(lnum >= 2){
            tlen = sprintf(tbuf, cbuf, va_arg(ap, unsigned long long));
          } else if(lnum >= 1){
            tlen = sprintf(tbuf, cbuf, va_arg(ap, unsigned long));
          } else {
            tlen = sprintf(tbuf, cbuf, va_arg(ap, unsigned int));
          }
          TCXSTRCAT(xstr, tbuf, tlen);
          break;
        case 'e': case 'E': case 'f': case 'g': case 'G':
          if(lnum >= 1){
            tlen = snprintf(tbuf, sizeof(tbuf), cbuf, va_arg(ap, long double));
          } else {
            tlen = snprintf(tbuf, sizeof(tbuf), cbuf, va_arg(ap, double));
          }
          if(tlen < 0 || tlen > sizeof(tbuf)){
            tbuf[sizeof(tbuf)-1] = '*';
            tlen = sizeof(tbuf);
          }
          TCXSTRCAT(xstr, tbuf, tlen);
          break;
        case '@':
          tmp = va_arg(ap, char *);
          if(!tmp) tmp = "(null)";
          while(*tmp){
            switch(*tmp){
              case '&': TCXSTRCAT(xstr, "&amp;", 5); break;
              case '<': TCXSTRCAT(xstr, "&lt;", 4); break;
              case '>': TCXSTRCAT(xstr, "&gt;", 4); break;
              case '"': TCXSTRCAT(xstr, "&quot;", 6); break;
              default:
                if(!((*tmp >= 0 && *tmp <= 0x8) || (*tmp >= 0x0e && *tmp <= 0x1f)))
                  TCXSTRCAT(xstr, tmp, 1);
                break;
            }
            tmp++;
          }
          break;
        case '?':
          tmp = va_arg(ap, char *);
          if(!tmp) tmp = "(null)";
          while(*tmp){
            unsigned char c = *(unsigned char *)tmp;
            if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
               (c >= '0' && c <= '9') || (c != '\0' && strchr("_-.", c))){
              TCXSTRCAT(xstr, tmp, 1);
            } else {
              tlen = sprintf(tbuf, "%%%02X", c);
              TCXSTRCAT(xstr, tbuf, tlen);
            }
            tmp++;
          }
          break;
        case 'b':
          if(lnum >= 2){
            tlen = tcnumtostrbin(va_arg(ap, unsigned long long), tbuf,
                                 tcatoi(cbuf + 1), (cbuf[1] == '0') ? '0' : ' ');
          } else if(lnum >= 1){
            tlen = tcnumtostrbin(va_arg(ap, unsigned long), tbuf,
                                 tcatoi(cbuf + 1), (cbuf[1] == '0') ? '0' : ' ');
          } else {
            tlen = tcnumtostrbin(va_arg(ap, unsigned int), tbuf,
                                 tcatoi(cbuf + 1), (cbuf[1] == '0') ? '0' : ' ');
          }
          TCXSTRCAT(xstr, tbuf, tlen);
          break;
        case '%':
          TCXSTRCAT(xstr, "%", 1);
          break;
      }
    } else {
      TCXSTRCAT(xstr, format, 1);
    }
    format++;
  }
}



/*************************************************************************************************
 * extensible string (for experts)
 *************************************************************************************************/


/* Convert an extensible string object into a usual allocated region. */
void *tcxstrtomalloc(TCXSTR *xstr){
  assert(xstr);
  char *ptr;
  ptr = xstr->ptr;
  TCFREE(xstr);
  return ptr;
}


/* Create an extensible string object from an allocated region. */
TCXSTR *tcxstrfrommalloc(void *ptr, int size){
  TCXSTR *xstr;
  TCMALLOC(xstr, sizeof(*xstr));
  TCREALLOC(xstr->ptr, ptr, size + 1);
  xstr->ptr[size] = '\0';
  xstr->size = size;
  xstr->asize = size;
  return xstr;
}



/*************************************************************************************************
 * array list
 *************************************************************************************************/


#define TCLISTUNIT     64                // allocation unit number of a list handle


/* private function prototypes */
static int tclistelemcmp(const void *a, const void *b);
static int tclistelemcmpci(const void *a, const void *b);


/* Create a list object. */
TCLIST *tclistnew(void){
  TCLIST *list;
  TCMALLOC(list, sizeof(*list));
  list->anum = TCLISTUNIT;
  TCMALLOC(list->array, sizeof(list->array[0]) * list->anum);
  list->start = 0;
  list->num = 0;
  return list;
}


/* Create a list object. */
TCLIST *tclistnew2(int anum){
  TCLIST *list;
  TCMALLOC(list, sizeof(*list));
  if(anum < 1) anum = 1;
  list->anum = anum;
  TCMALLOC(list->array, sizeof(list->array[0]) * list->anum);
  list->start = 0;
  list->num = 0;
  return list;
}


/* Create a list object with initial string elements. */
TCLIST *tclistnew3(const char *str, ...){
  TCLIST *list = tclistnew();
  if(str){
    tclistpush2(list, str);
    va_list ap;
    va_start(ap, str);
    const char *elem;
    while((elem = va_arg(ap, char *)) != NULL){
      tclistpush2(list, elem);
    }
    va_end(ap);
  }
  return list;
}


/* Copy a list object. */
TCLIST *tclistdup(const TCLIST *list){
  assert(list);
  int num = list->num;
  if(num < 1) return tclistnew();
  const TCLISTDATUM *array = list->array + list->start;
  TCLIST *nlist;
  TCMALLOC(nlist, sizeof(*nlist));
  TCLISTDATUM *narray;
  TCMALLOC(narray, sizeof(list->array[0]) * num);
  for(int i = 0; i < num; i++){
    int size = array[i].size;
    TCMALLOC(narray[i].ptr, tclmax(size + 1, TCXSTRUNIT));
    memcpy(narray[i].ptr, array[i].ptr, size + 1);
    narray[i].size = array[i].size;
  }
  nlist->anum = num;
  nlist->array = narray;
  nlist->start = 0;
  nlist->num = num;
  return nlist;
}


/* Delete a list object. */
void tclistdel(TCLIST *list){
  assert(list);
  TCLISTDATUM *array = list->array;
  int end = list->start + list->num;
  for(int i = list->start; i < end; i++){
    TCFREE(array[i].ptr);
  }
  TCFREE(list->array);
  TCFREE(list);
}


/* Get the number of elements of a list object. */
int tclistnum(const TCLIST *list){
  assert(list);
  return list->num;
}


/* Get the pointer to the region of an element of a list object. */
const void *tclistval(const TCLIST *list, int index, int *sp){
  assert(list && index >= 0 && sp);
  if(index >= list->num) return NULL;
  index += list->start;
  *sp = list->array[index].size;
  return list->array[index].ptr;
}


/* Get the string of an element of a list object. */
const char *tclistval2(const TCLIST *list, int index){
  assert(list && index >= 0);
  if(index >= list->num) return NULL;
  index += list->start;
  return list->array[index].ptr;
}


/* Add an element at the end of a list object. */
void tclistpush(TCLIST *list, const void *ptr, int size){
  assert(list && ptr && size >= 0);
  int index = list->start + list->num;
  if(index >= list->anum){
    list->anum += list->num + 1;
    TCREALLOC(list->array, list->array, list->anum * sizeof(list->array[0]));
  }
  TCLISTDATUM *array = list->array;
  TCMALLOC(array[index].ptr, tclmax(size + 1, TCXSTRUNIT));
  memcpy(array[index].ptr, ptr, size);
  array[index].ptr[size] = '\0';
  array[index].size = size;
  list->num++;
}


/* Add a string element at the end of a list object. */
void tclistpush2(TCLIST *list, const char *str){
  assert(list && str);
  int index = list->start + list->num;
  if(index >= list->anum){
    list->anum += list->num + 1;
    TCREALLOC(list->array, list->array, list->anum * sizeof(list->array[0]));
  }
  int size = strlen(str);
  TCLISTDATUM *array = list->array;
  TCMALLOC(array[index].ptr, tclmax(size + 1, TCXSTRUNIT));
  memcpy(array[index].ptr, str, size + 1);
  array[index].size = size;
  list->num++;
}


/* Remove an element of the end of a list object. */
void *tclistpop(TCLIST *list, int *sp){
  assert(list && sp);
  if(list->num < 1) return NULL;
  int index = list->start + list->num - 1;
  list->num--;
  *sp = list->array[index].size;
  return list->array[index].ptr;
}


/* Remove a string element of the end of a list object. */
char *tclistpop2(TCLIST *list){
  assert(list);
  if(list->num < 1) return NULL;
  int index = list->start + list->num - 1;
  list->num--;
  return list->array[index].ptr;
}


/* Add an element at the top of a list object. */
void tclistunshift(TCLIST *list, const void *ptr, int size){
  assert(list && ptr && size >= 0);
  if(list->start < 1){
    if(list->start + list->num >= list->anum){
      list->anum += list->num + 1;
      TCREALLOC(list->array, list->array, list->anum * sizeof(list->array[0]));
    }
    list->start = list->anum - list->num;
    memmove(list->array + list->start, list->array, list->num * sizeof(list->array[0]));
  }
  int index = list->start - 1;
  TCMALLOC(list->array[index].ptr, tclmax(size + 1, TCXSTRUNIT));
  memcpy(list->array[index].ptr, ptr, size);
  list->array[index].ptr[size] = '\0';
  list->array[index].size = size;
  list->start--;
  list->num++;
}


/* Add a string element at the top of a list object. */
void tclistunshift2(TCLIST *list, const char *str){
  assert(list && str);
  if(list->start < 1){
    if(list->start + list->num >= list->anum){
      list->anum += list->num + 1;
      TCREALLOC(list->array, list->array, list->anum * sizeof(list->array[0]));
    }
    list->start = list->anum - list->num;
    memmove(list->array + list->start, list->array, list->num * sizeof(list->array[0]));
  }
  int index = list->start - 1;
  int size = strlen(str);
  TCMALLOC(list->array[index].ptr, tclmax(size + 1, TCXSTRUNIT));
  memcpy(list->array[index].ptr, str, size + 1);
  list->array[index].size = size;
  list->start--;
  list->num++;
}


/* Remove an element of the top of a list object. */
void *tclistshift(TCLIST *list, int *sp){
  assert(list && sp);
  if(list->num < 1) return NULL;
  int index = list->start;
  list->start++;
  list->num--;
  *sp = list->array[index].size;
  void *rv = list->array[index].ptr;
  if((list->start & 0xff) == 0 && list->start > (list->num >> 1)){
    memmove(list->array, list->array + list->start, list->num * sizeof(list->array[0]));
    list->start = 0;
  }
  return rv;
}


/* Remove a string element of the top of a list object. */
char *tclistshift2(TCLIST *list){
  assert(list);
  if(list->num < 1) return NULL;
  int index = list->start;
  list->start++;
  list->num--;
  void *rv = list->array[index].ptr;
  if((list->start & 0xff) == 0 && list->start > (list->num >> 1)){
    memmove(list->array, list->array + list->start, list->num * sizeof(list->array[0]));
    list->start = 0;
  }
  return rv;
}


/* Add an element at the specified location of a list object. */
void tclistinsert(TCLIST *list, int index, const void *ptr, int size){
  assert(list && index >= 0 && ptr && size >= 0);
  if(index > list->num) return;
  index += list->start;
  if(list->start + list->num >= list->anum){
    list->anum += list->num + 1;
    TCREALLOC(list->array, list->array, list->anum * sizeof(list->array[0]));
  }
  memmove(list->array + index + 1, list->array + index,
          sizeof(list->array[0]) * (list->start + list->num - index));
  TCMALLOC(list->array[index].ptr, tclmax(size + 1, TCXSTRUNIT));
  memcpy(list->array[index].ptr, ptr, size);
  list->array[index].ptr[size] = '\0';
  list->array[index].size = size;
  list->num++;
}


/* Add a string element at the specified location of a list object. */
void tclistinsert2(TCLIST *list, int index, const char *str){
  assert(list && index >= 0 && str);
  if(index > list->num) return;
  index += list->start;
  if(list->start + list->num >= list->anum){
    list->anum += list->num + 1;
    TCREALLOC(list->array, list->array, list->anum * sizeof(list->array[0]));
  }
  memmove(list->array + index + 1, list->array + index,
          sizeof(list->array[0]) * (list->start + list->num - index));
  int size = strlen(str);
  TCMALLOC(list->array[index].ptr, tclmax(size + 1, TCXSTRUNIT));
  memcpy(list->array[index].ptr, str, size);
  list->array[index].ptr[size] = '\0';
  list->array[index].size = size;
  list->num++;
}


/* Remove an element at the specified location of a list object. */
void *tclistremove(TCLIST *list, int index, int *sp){
  assert(list && index >= 0 && sp);
  if(index >= list->num) return NULL;
  index += list->start;
  void *rv = list->array[index].ptr;
  *sp = list->array[index].size;
  list->num--;
  memmove(list->array + index, list->array + index + 1,
          sizeof(list->array[0]) * (list->start + list->num - index));
  return rv;
}


/* Remove a string element at the specified location of a list object. */
char *tclistremove2(TCLIST *list, int index){
  assert(list && index >= 0);
  if(index >= list->num) return NULL;
  index += list->start;
  void *rv = list->array[index].ptr;
  list->num--;
  memmove(list->array + index, list->array + index + 1,
          sizeof(list->array[0]) * (list->start + list->num - index));
  return rv;
}


/* Overwrite an element at the specified location of a list object. */
void tclistover(TCLIST *list, int index, const void *ptr, int size){
  assert(list && index >= 0 && ptr && size >= 0);
  if(index >= list->num) return;
  index += list->start;
  if(size > list->array[index].size)
    TCREALLOC(list->array[index].ptr, list->array[index].ptr, size + 1);
  memcpy(list->array[index].ptr, ptr, size);
  list->array[index].size = size;
  list->array[index].ptr[size] = '\0';
}


/* Overwrite a string element at the specified location of a list object. */
void tclistover2(TCLIST *list, int index, const char *str){
  assert(list && index >= 0 && str);
  if(index >= list->num) return;
  index += list->start;
  int size = strlen(str);
  if(size > list->array[index].size)
    TCREALLOC(list->array[index].ptr, list->array[index].ptr, size + 1);
  memcpy(list->array[index].ptr, str, size + 1);
  list->array[index].size = size;
}


/* Sort elements of a list object in lexical order. */
void tclistsort(TCLIST *list){
  assert(list);
  qsort(list->array + list->start, list->num, sizeof(list->array[0]), tclistelemcmp);
}


/* Search a list object for an element using liner search. */
int tclistlsearch(const TCLIST *list, const void *ptr, int size){
  assert(list && ptr && size >= 0);
  int end = list->start + list->num;
  for(int i = list->start; i < end; i++){
    if(list->array[i].size == size && !memcmp(list->array[i].ptr, ptr, size))
      return i - list->start;
  }
  return -1;
}


/* Search a list object for an element using binary search. */
int tclistbsearch(const TCLIST *list, const void *ptr, int size){
  assert(list && ptr && size >= 0);
  TCLISTDATUM key;
  key.ptr = (char *)ptr;
  key.size = size;
  TCLISTDATUM *res = bsearch(&key, list->array + list->start,
                             list->num, sizeof(list->array[0]), tclistelemcmp);
  return res ? res - list->array - list->start : -1;
}


/* Clear a list object. */
void tclistclear(TCLIST *list){
  assert(list);
  TCLISTDATUM *array = list->array;
  int end = list->start + list->num;
  for(int i = list->start; i < end; i++){
    TCFREE(array[i].ptr);
  }
  list->start = 0;
  list->num = 0;
}


/* Serialize a list object into a byte array. */
void *tclistdump(const TCLIST *list, int *sp){
  assert(list && sp);
  const TCLISTDATUM *array = list->array;
  int end = list->start + list->num;
  int tsiz = 0;
  for(int i = list->start; i < end; i++){
    tsiz += array[i].size + sizeof(int);
  }
  char *buf;
  TCMALLOC(buf, tsiz + 1);
  char *wp = buf;
  for(int i = list->start; i < end; i++){
    int step;
    TCSETVNUMBUF(step, wp, array[i].size);
    wp += step;
    memcpy(wp, array[i].ptr, array[i].size);
    wp += array[i].size;
  }
  *sp = wp - buf;
  return buf;
}


/* Create a list object from a serialized byte array. */
TCLIST *tclistload(const void *ptr, int size){
  assert(ptr && size >= 0);
  TCLIST *list;
  TCMALLOC(list, sizeof(*list));
  int anum = size / sizeof(int) + 1;
  TCLISTDATUM *array;
  TCMALLOC(array, sizeof(array[0]) * anum);
  int num = 0;
  const char *rp = ptr;
  const char *ep = (char *)ptr + size;
  while(rp < ep){
    int step, vsiz;
    TCREADVNUMBUF(rp, vsiz, step);
    rp += step;
    if(num >= anum){
      anum *= 2;
      TCREALLOC(array, array, anum * sizeof(array[0]));
    }
    TCMALLOC(array[num].ptr, tclmax(vsiz + 1, TCXSTRUNIT));
    memcpy(array[num].ptr, rp, vsiz);
    array[num].ptr[vsiz] = '\0';
    array[num].size = vsiz;
    num++;
    rp += vsiz;
  }
  list->anum = anum;
  list->array = array;
  list->start = 0;
  list->num = num;
  return list;
}


/* Compare two list elements in lexical order.
   `a' specifies the pointer to one element.
   `b' specifies the pointer to the other element.
   The return value is positive if the former is big, negative if the latter is big, 0 if both
   are equivalent. */
static int tclistelemcmp(const void *a, const void *b){
  assert(a && b);
  unsigned char *ao = (unsigned char *)((TCLISTDATUM *)a)->ptr;
  unsigned char *bo = (unsigned char *)((TCLISTDATUM *)b)->ptr;
  int size = (((TCLISTDATUM *)a)->size < ((TCLISTDATUM *)b)->size) ?
    ((TCLISTDATUM *)a)->size : ((TCLISTDATUM *)b)->size;
  for(int i = 0; i < size; i++){
    if(ao[i] > bo[i]) return 1;
    if(ao[i] < bo[i]) return -1;
  }
  return ((TCLISTDATUM *)a)->size - ((TCLISTDATUM *)b)->size;
}


/* Compare two list elements in case-insensitive lexical order..
   `a' specifies the pointer to one element.
   `b' specifies the pointer to the other element.
   The return value is positive if the former is big, negative if the latter is big, 0 if both
   are equivalent. */
static int tclistelemcmpci(const void *a, const void *b){
  assert(a && b);
  TCLISTDATUM *ap = (TCLISTDATUM *)a;
  TCLISTDATUM *bp = (TCLISTDATUM *)b;
  unsigned char *ao = (unsigned char *)ap->ptr;
  unsigned char *bo = (unsigned char *)bp->ptr;
  int size = (ap->size < bp->size) ? ap->size : bp->size;
  for(int i = 0; i < size; i++){
    int ac = ao[i];
    bool ab = false;
    if(ac >= 'A' && ac <= 'Z'){
      ac += 'a' - 'A';
      ab = true;
    }
    int bc = bo[i];
    bool bb = false;
    if(bc >= 'A' && bc <= 'Z'){
      bc += 'a' - 'A';
      bb = true;
    }
    if(ac > bc) return 1;
    if(ac < bc) return -1;
    if(!ab && bb) return 1;
    if(ab && !bb) return -1;
  }
  return ap->size - bp->size;
}



/*************************************************************************************************
 * array list (for experts)
 *************************************************************************************************/


/* Add an allocated element at the end of a list object. */
void tclistpushmalloc(TCLIST *list, void *ptr, int size){
  assert(list && ptr && size >= 0);
  int index = list->start + list->num;
  if(index >= list->anum){
    list->anum += list->num + 1;
    TCREALLOC(list->array, list->array, list->anum * sizeof(list->array[0]));
  }
  TCLISTDATUM *array = list->array;
  TCREALLOC(array[index].ptr, ptr, size + 1);
  array[index].ptr[size] = '\0';
  array[index].size = size;
  list->num++;
}


/* Sort elements of a list object in case-insensitive lexical order. */
void tclistsortci(TCLIST *list){
  assert(list);
  qsort(list->array + list->start, list->num, sizeof(list->array[0]), tclistelemcmpci);
}


/* Sort elements of a list object by an arbitrary comparison function. */
void tclistsortex(TCLIST *list, int (*cmp)(const TCLISTDATUM *, const TCLISTDATUM *)){
  assert(list && cmp);
  qsort(list->array + list->start, list->num, sizeof(list->array[0]),
        (int (*)(const void *, const void *))cmp);
}


/* Invert elements of a list object. */
void tclistinvert(TCLIST *list){
  assert(list);
  TCLISTDATUM *top = list->array + list->start;
  TCLISTDATUM *bot = top + list->num - 1;
  while(top < bot){
    TCLISTDATUM swap = *top;
    *top = *bot;
    *bot = swap;
    top++;
    bot--;
  }
}


/* Perform formatted output into a list object. */
void tclistprintf(TCLIST *list, const char *format, ...){
  assert(list && format);
  TCXSTR *xstr = tcxstrnew();
  va_list ap;
  va_start(ap, format);
  tcvxstrprintf(xstr, format, ap);
  va_end(ap);
  int size = TCXSTRSIZE(xstr);
  char *ptr = tcxstrtomalloc(xstr);
  tclistpushmalloc(list, ptr, size);
}



/*************************************************************************************************
 * hash map
 *************************************************************************************************/


#define TCMAPKMAXSIZ   0xfffff           // maximum size of each key
#define TCMAPDEFBNUM   4093              // default bucket number
#define TCMAPZMMINSIZ  131072            // minimum memory size to use nullified region
#define TCMAPCSUNIT    52                // small allocation unit size of map concatenation
#define TCMAPCBUNIT    252               // big allocation unit size of map concatenation
#define TCMAPTINYBNUM  31                // bucket number of a tiny map

/* get the first hash value */
#define TCMAPHASH1(TC_res, TC_kbuf, TC_ksiz)                            \
  do {                                                                  \
    const unsigned char *_TC_p = (const unsigned char *)(TC_kbuf);      \
    int _TC_ksiz = TC_ksiz;                                             \
    for((TC_res) = 19780211; _TC_ksiz--;){                              \
      (TC_res) = (TC_res) * 37 + *(_TC_p)++;                            \
    }                                                                   \
  } while(false)

/* get the second hash value */
#define TCMAPHASH2(TC_res, TC_kbuf, TC_ksiz)                            \
  do {                                                                  \
    const unsigned char *_TC_p = (const unsigned char *)(TC_kbuf) + TC_ksiz - 1; \
    int _TC_ksiz = TC_ksiz;                                             \
    for((TC_res) = 0x13579bdf; _TC_ksiz--;){                            \
      (TC_res) = (TC_res) * 31 + *(_TC_p)--;                            \
    }                                                                   \
  } while(false)

/* compare two keys */
#define TCKEYCMP(TC_abuf, TC_asiz, TC_bbuf, TC_bsiz)                    \
  ((TC_asiz > TC_bsiz) ? 1 : (TC_asiz < TC_bsiz) ? -1 : memcmp(TC_abuf, TC_bbuf, TC_asiz))


/* Create a map object. */
TCMAP *tcmapnew(void){
  return tcmapnew2(TCMAPDEFBNUM);
}


/* Create a map object with specifying the number of the buckets. */
TCMAP *tcmapnew2(uint32_t bnum){
  if(bnum < 1) bnum = 1;
  TCMAP *map;
  TCMALLOC(map, sizeof(*map));
  TCMAPREC **buckets;
  if(bnum >= TCMAPZMMINSIZ / sizeof(*buckets)){
    buckets = tczeromap(bnum * sizeof(*buckets));
  } else {
    TCCALLOC(buckets, bnum, sizeof(*buckets));
  }
  map->buckets = buckets;
  map->first = NULL;
  map->last = NULL;
  map->cur = NULL;
  map->bnum = bnum;
  map->rnum = 0;
  map->msiz = 0;
  return map;
}


/* Create a map object with initial string elements. */
TCMAP *tcmapnew3(const char *str, ...){
  TCMAP *map = tcmapnew2(TCMAPTINYBNUM);
  if(str){
    va_list ap;
    va_start(ap, str);
    const char *key = str;
    const char *elem;
    while((elem = va_arg(ap, char *)) != NULL){
      if(key){
        tcmapput2(map, key, elem);
        key = NULL;
      } else {
        key = elem;
      }
    }
    va_end(ap);
  }
  return map;
}


/* Copy a map object. */
TCMAP *tcmapdup(const TCMAP *map){
  assert(map);
  TCMAP *nmap = tcmapnew2(tclmax(tclmax(map->bnum, map->rnum), TCMAPDEFBNUM));
  TCMAPREC *rec = map->first;
  while(rec){
    char *dbuf = (char *)rec + sizeof(*rec);
    uint32_t rksiz = rec->ksiz & TCMAPKMAXSIZ;
    tcmapput(nmap, dbuf, rksiz, dbuf + rksiz + TCALIGNPAD(rksiz), rec->vsiz);
    rec = rec->next;
  }
  return nmap;
}


/* Close a map object. */
void tcmapdel(TCMAP *map){
  assert(map);
  TCMAPREC *rec = map->first;
  while(rec){
    TCMAPREC *next = rec->next;
    TCFREE(rec);
    rec = next;
  }
  if(map->bnum >= TCMAPZMMINSIZ / sizeof(map->buckets[0])){
    tczerounmap(map->buckets);
  } else {
    TCFREE(map->buckets);
  }
  TCFREE(map);
}


/* Store a record into a map object. */
void tcmapput(TCMAP *map, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(map && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  if(ksiz > TCMAPKMAXSIZ) ksiz = TCMAPKMAXSIZ;
  uint32_t hash;
  TCMAPHASH1(hash, kbuf, ksiz);
  int bidx = hash % map->bnum;
  TCMAPREC *rec = map->buckets[bidx];
  TCMAPREC **entp = map->buckets + bidx;
  TCMAPHASH2(hash, kbuf, ksiz);
  hash &= ~TCMAPKMAXSIZ;
  while(rec){
    uint32_t rhash = rec->ksiz & ~TCMAPKMAXSIZ;
    uint32_t rksiz = rec->ksiz & TCMAPKMAXSIZ;
    if(hash > rhash){
      entp = &(rec->left);
      rec = rec->left;
    } else if(hash < rhash){
      entp = &(rec->right);
      rec = rec->right;
    } else {
      char *dbuf = (char *)rec + sizeof(*rec);
      int kcmp = TCKEYCMP(kbuf, ksiz, dbuf, rksiz);
      if(kcmp < 0){
        entp = &(rec->left);
        rec = rec->left;
      } else if(kcmp > 0){
        entp = &(rec->right);
        rec = rec->right;
      } else {
        map->msiz += vsiz - rec->vsiz;
        int psiz = TCALIGNPAD(ksiz);
        if(vsiz > rec->vsiz){
          TCMAPREC *old = rec;
          TCREALLOC(rec, rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
          if(rec != old){
            if(map->first == old) map->first = rec;
            if(map->last == old) map->last = rec;
            if(map->cur == old) map->cur = rec;
            *entp = rec;
            if(rec->prev) rec->prev->next = rec;
            if(rec->next) rec->next->prev = rec;
            dbuf = (char *)rec + sizeof(*rec);
          }
        }
        memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
        dbuf[ksiz+psiz+vsiz] = '\0';
        rec->vsiz = vsiz;
        return;
      }
    }
  }
  int psiz = TCALIGNPAD(ksiz);
  map->msiz += ksiz + vsiz;
  TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
  char *dbuf = (char *)rec + sizeof(*rec);
  memcpy(dbuf, kbuf, ksiz);
  dbuf[ksiz] = '\0';
  rec->ksiz = ksiz | hash;
  memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
  dbuf[ksiz+psiz+vsiz] = '\0';
  rec->vsiz = vsiz;
  rec->left = NULL;
  rec->right = NULL;
  rec->prev = map->last;
  rec->next = NULL;
  *entp = rec;
  if(!map->first) map->first = rec;
  if(map->last) map->last->next = rec;
  map->last = rec;
  map->rnum++;
}


/* Store a string record into a map object. */
void tcmapput2(TCMAP *map, const char *kstr, const char *vstr){
  assert(map && kstr && vstr);
  tcmapput(map, kstr, strlen(kstr), vstr, strlen(vstr));
}


/* Store a new record into a map object. */
bool tcmapputkeep(TCMAP *map, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(map && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  if(ksiz > TCMAPKMAXSIZ) ksiz = TCMAPKMAXSIZ;
  uint32_t hash;
  TCMAPHASH1(hash, kbuf, ksiz);
  int bidx = hash % map->bnum;
  TCMAPREC *rec = map->buckets[bidx];
  TCMAPREC **entp = map->buckets + bidx;
  TCMAPHASH2(hash, kbuf, ksiz);
  hash &= ~TCMAPKMAXSIZ;
  while(rec){
    uint32_t rhash = rec->ksiz & ~TCMAPKMAXSIZ;
    uint32_t rksiz = rec->ksiz & TCMAPKMAXSIZ;
    if(hash > rhash){
      entp = &(rec->left);
      rec = rec->left;
    } else if(hash < rhash){
      entp = &(rec->right);
      rec = rec->right;
    } else {
      char *dbuf = (char *)rec + sizeof(*rec);
      int kcmp = TCKEYCMP(kbuf, ksiz, dbuf, rksiz);
      if(kcmp < 0){
        entp = &(rec->left);
        rec = rec->left;
      } else if(kcmp > 0){
        entp = &(rec->right);
        rec = rec->right;
      } else {
        return false;
      }
    }
  }
  int psiz = TCALIGNPAD(ksiz);
  map->msiz += ksiz + vsiz;
  TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
  char *dbuf = (char *)rec + sizeof(*rec);
  memcpy(dbuf, kbuf, ksiz);
  dbuf[ksiz] = '\0';
  rec->ksiz = ksiz | hash;
  memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
  dbuf[ksiz+psiz+vsiz] = '\0';
  rec->vsiz = vsiz;
  rec->left = NULL;
  rec->right = NULL;
  rec->prev = map->last;
  rec->next = NULL;
  *entp = rec;
  if(!map->first) map->first = rec;
  if(map->last) map->last->next = rec;
  map->last = rec;
  map->rnum++;
  return true;
}


/* Store a new string record into a map object. */
bool tcmapputkeep2(TCMAP *map, const char *kstr, const char *vstr){
  assert(map && kstr && vstr);
  return tcmapputkeep(map, kstr, strlen(kstr), vstr, strlen(vstr));
}


/* Concatenate a value at the end of the value of the existing record in a map object. */
void tcmapputcat(TCMAP *map, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(map && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  if(ksiz > TCMAPKMAXSIZ) ksiz = TCMAPKMAXSIZ;
  uint32_t hash;
  TCMAPHASH1(hash, kbuf, ksiz);
  int bidx = hash % map->bnum;
  TCMAPREC *rec = map->buckets[bidx];
  TCMAPREC **entp = map->buckets + bidx;
  TCMAPHASH2(hash, kbuf, ksiz);
  hash &= ~TCMAPKMAXSIZ;
  while(rec){
    uint32_t rhash = rec->ksiz & ~TCMAPKMAXSIZ;
    uint32_t rksiz = rec->ksiz & TCMAPKMAXSIZ;
    if(hash > rhash){
      entp = &(rec->left);
      rec = rec->left;
    } else if(hash < rhash){
      entp = &(rec->right);
      rec = rec->right;
    } else {
      char *dbuf = (char *)rec + sizeof(*rec);
      int kcmp = TCKEYCMP(kbuf, ksiz, dbuf, rksiz);
      if(kcmp < 0){
        entp = &(rec->left);
        rec = rec->left;
      } else if(kcmp > 0){
        entp = &(rec->right);
        rec = rec->right;
      } else {
        map->msiz += vsiz;
        int psiz = TCALIGNPAD(ksiz);
        int asiz = sizeof(*rec) + ksiz + psiz + rec->vsiz + vsiz + 1;
        int unit = (asiz <= TCMAPCSUNIT) ? TCMAPCSUNIT : TCMAPCBUNIT;
        asiz = (asiz - 1) + unit - (asiz - 1) % unit;
        TCMAPREC *old = rec;
        TCREALLOC(rec, rec, asiz);
        if(rec != old){
          if(map->first == old) map->first = rec;
          if(map->last == old) map->last = rec;
          if(map->cur == old) map->cur = rec;
          *entp = rec;
          if(rec->prev) rec->prev->next = rec;
          if(rec->next) rec->next->prev = rec;
          dbuf = (char *)rec + sizeof(*rec);
        }
        memcpy(dbuf + ksiz + psiz + rec->vsiz, vbuf, vsiz);
        rec->vsiz += vsiz;
        dbuf[ksiz+psiz+rec->vsiz] = '\0';
        return;
      }
    }
  }
  int psiz = TCALIGNPAD(ksiz);
  int asiz = sizeof(*rec) + ksiz + psiz + vsiz + 1;
  int unit = (asiz <= TCMAPCSUNIT) ? TCMAPCSUNIT : TCMAPCBUNIT;
  asiz = (asiz - 1) + unit - (asiz - 1) % unit;
  map->msiz += ksiz + vsiz;
  TCMALLOC(rec, asiz);
  char *dbuf = (char *)rec + sizeof(*rec);
  memcpy(dbuf, kbuf, ksiz);
  dbuf[ksiz] = '\0';
  rec->ksiz = ksiz | hash;
  memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
  dbuf[ksiz+psiz+vsiz] = '\0';
  rec->vsiz = vsiz;
  rec->left = NULL;
  rec->right = NULL;
  rec->prev = map->last;
  rec->next = NULL;
  *entp = rec;
  if(!map->first) map->first = rec;
  if(map->last) map->last->next = rec;
  map->last = rec;
  map->rnum++;
}


/* Concatenate a string value at the end of the value of the existing record in a map object. */
void tcmapputcat2(TCMAP *map, const char *kstr, const char *vstr){
  assert(map && kstr && vstr);
  tcmapputcat(map, kstr, strlen(kstr), vstr, strlen(vstr));
}


/* Remove a record of a map object. */
bool tcmapout(TCMAP *map, const void *kbuf, int ksiz){
  assert(map && kbuf && ksiz >= 0);
  if(ksiz > TCMAPKMAXSIZ) ksiz = TCMAPKMAXSIZ;
  uint32_t hash;
  TCMAPHASH1(hash, kbuf, ksiz);
  int bidx = hash % map->bnum;
  TCMAPREC *rec = map->buckets[bidx];
  TCMAPREC **entp = map->buckets + bidx;
  TCMAPHASH2(hash, kbuf, ksiz);
  hash &= ~TCMAPKMAXSIZ;
  while(rec){
    uint32_t rhash = rec->ksiz & ~TCMAPKMAXSIZ;
    uint32_t rksiz = rec->ksiz & TCMAPKMAXSIZ;
    if(hash > rhash){
      entp = &(rec->left);
      rec = rec->left;
    } else if(hash < rhash){
      entp = &(rec->right);
      rec = rec->right;
    } else {
      char *dbuf = (char *)rec + sizeof(*rec);
      int kcmp = TCKEYCMP(kbuf, ksiz, dbuf, rksiz);
      if(kcmp < 0){
        entp = &(rec->left);
        rec = rec->left;
      } else if(kcmp > 0){
        entp = &(rec->right);
        rec = rec->right;
      } else {
        map->rnum--;
        map->msiz -= rksiz + rec->vsiz;
        if(rec->prev) rec->prev->next = rec->next;
        if(rec->next) rec->next->prev = rec->prev;
        if(rec == map->first) map->first = rec->next;
        if(rec == map->last) map->last = rec->prev;
        if(rec == map->cur) map->cur = rec->next;
        if(rec->left && !rec->right){
          *entp = rec->left;
        } else if(!rec->left && rec->right){
          *entp = rec->right;
        } else if(!rec->left){
          *entp = NULL;
        } else {
          *entp = rec->left;
          TCMAPREC *tmp = *entp;
          while(tmp->right){
            tmp = tmp->right;
          }
          tmp->right = rec->right;
        }
        TCFREE(rec);
        return true;
      }
    }
  }
  return false;
}


/* Remove a string record of a map object. */
bool tcmapout2(TCMAP *map, const char *kstr){
  assert(map && kstr);
  return tcmapout(map, kstr, strlen(kstr));
}


/* Retrieve a record in a map object. */
const void *tcmapget(const TCMAP *map, const void *kbuf, int ksiz, int *sp){
  assert(map && kbuf && ksiz >= 0 && sp);
  if(ksiz > TCMAPKMAXSIZ) ksiz = TCMAPKMAXSIZ;
  uint32_t hash;
  TCMAPHASH1(hash, kbuf, ksiz);
  TCMAPREC *rec = map->buckets[hash%map->bnum];
  TCMAPHASH2(hash, kbuf, ksiz);
  hash &= ~TCMAPKMAXSIZ;
  while(rec){
    uint32_t rhash = rec->ksiz & ~TCMAPKMAXSIZ;
    uint32_t rksiz = rec->ksiz & TCMAPKMAXSIZ;
    if(hash > rhash){
      rec = rec->left;
    } else if(hash < rhash){
      rec = rec->right;
    } else {
      char *dbuf = (char *)rec + sizeof(*rec);
      int kcmp = TCKEYCMP(kbuf, ksiz, dbuf, rksiz);
      if(kcmp < 0){
        rec = rec->left;
      } else if(kcmp > 0){
        rec = rec->right;
      } else {
        *sp = rec->vsiz;
        return dbuf + rksiz + TCALIGNPAD(rksiz);
      }
    }
  }
  return NULL;
}


/* Retrieve a string record in a map object. */
const char *tcmapget2(const TCMAP *map, const char *kstr){
  assert(map && kstr);
  int ksiz = strlen(kstr);
  if(ksiz > TCMAPKMAXSIZ) ksiz = TCMAPKMAXSIZ;
  uint32_t hash;
  TCMAPHASH1(hash, kstr, ksiz);
  TCMAPREC *rec = map->buckets[hash%map->bnum];
  TCMAPHASH2(hash, kstr, ksiz);
  hash &= ~TCMAPKMAXSIZ;
  while(rec){
    uint32_t rhash = rec->ksiz & ~TCMAPKMAXSIZ;
    uint32_t rksiz = rec->ksiz & TCMAPKMAXSIZ;
    if(hash > rhash){
      rec = rec->left;
    } else if(hash < rhash){
      rec = rec->right;
    } else {
      char *dbuf = (char *)rec + sizeof(*rec);
      int kcmp = TCKEYCMP(kstr, ksiz, dbuf, rksiz);
      if(kcmp < 0){
        rec = rec->left;
      } else if(kcmp > 0){
        rec = rec->right;
      } else {
        return dbuf + rksiz + TCALIGNPAD(rksiz);
      }
    }
  }
  return NULL;
}


/* Move a record to the edge of a map object. */
bool tcmapmove(TCMAP *map, const void *kbuf, int ksiz, bool head){
  assert(map && kbuf && ksiz >= 0);
  if(ksiz > TCMAPKMAXSIZ) ksiz = TCMAPKMAXSIZ;
  uint32_t hash;
  TCMAPHASH1(hash, kbuf, ksiz);
  TCMAPREC *rec = map->buckets[hash%map->bnum];
  TCMAPHASH2(hash, kbuf, ksiz);
  hash &= ~TCMAPKMAXSIZ;
  while(rec){
    uint32_t rhash = rec->ksiz & ~TCMAPKMAXSIZ;
    uint32_t rksiz = rec->ksiz & TCMAPKMAXSIZ;
    if(hash > rhash){
      rec = rec->left;
    } else if(hash < rhash){
      rec = rec->right;
    } else {
      char *dbuf = (char *)rec + sizeof(*rec);
      int kcmp = TCKEYCMP(kbuf, ksiz, dbuf, rksiz);
      if(kcmp < 0){
        rec = rec->left;
      } else if(kcmp > 0){
        rec = rec->right;
      } else {
        if(head){
          if(map->first == rec) return true;
          if(map->last == rec) map->last = rec->prev;
          if(rec->prev) rec->prev->next = rec->next;
          if(rec->next) rec->next->prev = rec->prev;
          rec->prev = NULL;
          rec->next = map->first;
          map->first->prev = rec;
          map->first = rec;
        } else {
          if(map->last == rec) return true;
          if(map->first == rec) map->first = rec->next;
          if(rec->prev) rec->prev->next = rec->next;
          if(rec->next) rec->next->prev = rec->prev;
          rec->prev = map->last;
          rec->next = NULL;
          map->last->next = rec;
          map->last = rec;
        }
        return true;
      }
    }
  }
  return false;
}


/* Move a string record to the edge of a map object. */
bool tcmapmove2(TCMAP *map, const char *kstr, bool head){
  assert(map && kstr);
  return tcmapmove(map, kstr, strlen(kstr), head);
}


/* Initialize the iterator of a map object. */
void tcmapiterinit(TCMAP *map){
  assert(map);
  map->cur = map->first;
}


/* Get the next key of the iterator of a map object. */
const void *tcmapiternext(TCMAP *map, int *sp){
  assert(map && sp);
  TCMAPREC *rec;
  if(!map->cur) return NULL;
  rec = map->cur;
  map->cur = rec->next;
  *sp = rec->ksiz & TCMAPKMAXSIZ;
  return (char *)rec + sizeof(*rec);
}


/* Get the next key string of the iterator of a map object. */
const char *tcmapiternext2(TCMAP *map){
  assert(map);
  TCMAPREC *rec;
  if(!map->cur) return NULL;
  rec = map->cur;
  map->cur = rec->next;
  return (char *)rec + sizeof(*rec);
}


/* Get the number of records stored in a map object. */
uint64_t tcmaprnum(const TCMAP *map){
  assert(map);
  return map->rnum;
}


/* Get the total size of memory used in a map object. */
uint64_t tcmapmsiz(const TCMAP *map){
  assert(map);
  return map->msiz + map->rnum * (sizeof(*map->first) + sizeof(tcgeneric_t)) +
    map->bnum * sizeof(void *);
}


/* Create a list object containing all keys in a map object. */
TCLIST *tcmapkeys(const TCMAP *map){
  assert(map);
  TCLIST *list = tclistnew2(map->rnum);
  TCMAPREC *rec = map->first;
  while(rec){
    char *dbuf = (char *)rec + sizeof(*rec);
    TCLISTPUSH(list, dbuf, rec->ksiz & TCMAPKMAXSIZ);
    rec = rec->next;
  }
  return list;
}


/* Create a list object containing all values in a map object. */
TCLIST *tcmapvals(const TCMAP *map){
  assert(map);
  TCLIST *list = tclistnew2(map->rnum);
  TCMAPREC *rec = map->first;
  while(rec){
    char *dbuf = (char *)rec + sizeof(*rec);
    uint32_t rksiz = rec->ksiz & TCMAPKMAXSIZ;
    TCLISTPUSH(list, dbuf + rksiz + TCALIGNPAD(rksiz), rec->vsiz);
    rec = rec->next;
  }
  return list;
}


/* Add an integer to a record in a map object. */
int tcmapaddint(TCMAP *map, const void *kbuf, int ksiz, int num){
  assert(map && kbuf && ksiz >= 0);
  if(ksiz > TCMAPKMAXSIZ) ksiz = TCMAPKMAXSIZ;
  uint32_t hash;
  TCMAPHASH1(hash, kbuf, ksiz);
  int bidx = hash % map->bnum;
  TCMAPREC *rec = map->buckets[bidx];
  TCMAPREC **entp = map->buckets + bidx;
  TCMAPHASH2(hash, kbuf, ksiz);
  hash &= ~TCMAPKMAXSIZ;
  while(rec){
    uint32_t rhash = rec->ksiz & ~TCMAPKMAXSIZ;
    uint32_t rksiz = rec->ksiz & TCMAPKMAXSIZ;
    if(hash > rhash){
      entp = &(rec->left);
      rec = rec->left;
    } else if(hash < rhash){
      entp = &(rec->right);
      rec = rec->right;
    } else {
      char *dbuf = (char *)rec + sizeof(*rec);
      int kcmp = TCKEYCMP(kbuf, ksiz, dbuf, rksiz);
      if(kcmp < 0){
        entp = &(rec->left);
        rec = rec->left;
      } else if(kcmp > 0){
        entp = &(rec->right);
        rec = rec->right;
      } else {
        if(rec->vsiz != sizeof(num)) return INT_MIN;
        int *resp = (int *)(dbuf + ksiz + TCALIGNPAD(ksiz));
        return *resp += num;
      }
    }
  }
  int psiz = TCALIGNPAD(ksiz);
  TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + sizeof(num) + 1);
  char *dbuf = (char *)rec + sizeof(*rec);
  memcpy(dbuf, kbuf, ksiz);
  dbuf[ksiz] = '\0';
  rec->ksiz = ksiz | hash;
  memcpy(dbuf + ksiz + psiz, &num, sizeof(num));
  dbuf[ksiz+psiz+sizeof(num)] = '\0';
  rec->vsiz = sizeof(num);
  rec->left = NULL;
  rec->right = NULL;
  rec->prev = map->last;
  rec->next = NULL;
  *entp = rec;
  if(!map->first) map->first = rec;
  if(map->last) map->last->next = rec;
  map->last = rec;
  map->rnum++;
  return num;
}


/* Add a real number to a record in a map object. */
double tcmapadddouble(TCMAP *map, const void *kbuf, int ksiz, double num){
  assert(map && kbuf && ksiz >= 0);
  if(ksiz > TCMAPKMAXSIZ) ksiz = TCMAPKMAXSIZ;
  uint32_t hash;
  TCMAPHASH1(hash, kbuf, ksiz);
  int bidx = hash % map->bnum;
  TCMAPREC *rec = map->buckets[bidx];
  TCMAPREC **entp = map->buckets + bidx;
  TCMAPHASH2(hash, kbuf, ksiz);
  hash &= ~TCMAPKMAXSIZ;
  while(rec){
    uint32_t rhash = rec->ksiz & ~TCMAPKMAXSIZ;
    uint32_t rksiz = rec->ksiz & TCMAPKMAXSIZ;
    if(hash > rhash){
      entp = &(rec->left);
      rec = rec->left;
    } else if(hash < rhash){
      entp = &(rec->right);
      rec = rec->right;
    } else {
      char *dbuf = (char *)rec + sizeof(*rec);
      int kcmp = TCKEYCMP(kbuf, ksiz, dbuf, rksiz);
      if(kcmp < 0){
        entp = &(rec->left);
        rec = rec->left;
      } else if(kcmp > 0){
        entp = &(rec->right);
        rec = rec->right;
      } else {
        if(rec->vsiz != sizeof(num)) return nan("");
        double *resp = (double *)(dbuf + ksiz + TCALIGNPAD(ksiz));
        return *resp += num;
      }
    }
  }
  int psiz = TCALIGNPAD(ksiz);
  TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + sizeof(num) + 1);
  char *dbuf = (char *)rec + sizeof(*rec);
  memcpy(dbuf, kbuf, ksiz);
  dbuf[ksiz] = '\0';
  rec->ksiz = ksiz | hash;
  memcpy(dbuf + ksiz + psiz, &num, sizeof(num));
  dbuf[ksiz+psiz+sizeof(num)] = '\0';
  rec->vsiz = sizeof(num);
  rec->left = NULL;
  rec->right = NULL;
  rec->prev = map->last;
  rec->next = NULL;
  *entp = rec;
  if(!map->first) map->first = rec;
  if(map->last) map->last->next = rec;
  map->last = rec;
  map->rnum++;
  return num;
}


/* Clear a map object. */
void tcmapclear(TCMAP *map){
  assert(map);
  TCMAPREC *rec = map->first;
  while(rec){
    TCMAPREC *next = rec->next;
    TCFREE(rec);
    rec = next;
  }
  TCMAPREC **buckets = map->buckets;
  int bnum = map->bnum;
  for(int i = 0; i < bnum; i++){
    buckets[i] = NULL;
  }
  map->first = NULL;
  map->last = NULL;
  map->cur = NULL;
  map->rnum = 0;
  map->msiz = 0;
}


/* Remove front records of a map object. */
void tcmapcutfront(TCMAP *map, int num){
  assert(map && num >= 0);
  tcmapiterinit(map);
  while(num-- > 0){
    int ksiz;
    const char *kbuf = tcmapiternext(map, &ksiz);
    if(!kbuf) break;
    tcmapout(map, kbuf, ksiz);
  }
}


/* Serialize a map object into a byte array. */
void *tcmapdump(const TCMAP *map, int *sp){
  assert(map && sp);
  int tsiz = 0;
  TCMAPREC *rec = map->first;
  while(rec){
    tsiz += (rec->ksiz & TCMAPKMAXSIZ) + rec->vsiz + sizeof(int) * 2;
    rec = rec->next;
  }
  char *buf;
  TCMALLOC(buf, tsiz + 1);
  char *wp = buf;
  rec = map->first;
  while(rec){
    const char *kbuf = (char *)rec + sizeof(*rec);
    int ksiz = rec->ksiz & TCMAPKMAXSIZ;
    const char *vbuf = kbuf + ksiz + TCALIGNPAD(ksiz);
    int vsiz = rec->vsiz;
    int step;
    TCSETVNUMBUF(step, wp, ksiz);
    wp += step;
    memcpy(wp, kbuf, ksiz);
    wp += ksiz;
    TCSETVNUMBUF(step, wp, vsiz);
    wp += step;
    memcpy(wp, vbuf, vsiz);
    wp += vsiz;
    rec = rec->next;
  }
  *sp = wp - buf;
  return buf;
}


/* Create a map object from a serialized byte array. */
TCMAP *tcmapload(const void *ptr, int size){
  assert(ptr && size >= 0);
  TCMAP *map = tcmapnew2(tclmin(size / 6 + 1, TCMAPDEFBNUM));
  const char *rp = ptr;
  const char *ep = (char *)ptr + size;
  while(rp < ep){
    int step, ksiz, vsiz;
    TCREADVNUMBUF(rp, ksiz, step);
    rp += step;
    const char *kbuf = rp;
    rp += ksiz;
    TCREADVNUMBUF(rp, vsiz, step);
    rp += step;
    tcmapputkeep(map, kbuf, ksiz, rp, vsiz);
    rp += vsiz;
  }
  return map;
}



/*************************************************************************************************
 * hash map (for experts)
 *************************************************************************************************/


/* Store a record and make it semivolatile in a map object. */
void tcmapput3(TCMAP *map, const void *kbuf, int ksiz, const char *vbuf, int vsiz){
  assert(map && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  if(ksiz > TCMAPKMAXSIZ) ksiz = TCMAPKMAXSIZ;
  uint32_t hash;
  TCMAPHASH1(hash, kbuf, ksiz);
  int bidx = hash % map->bnum;
  TCMAPREC *rec = map->buckets[bidx];
  TCMAPREC **entp = map->buckets + bidx;
  TCMAPHASH2(hash, kbuf, ksiz);
  hash &= ~TCMAPKMAXSIZ;
  while(rec){
    uint32_t rhash = rec->ksiz & ~TCMAPKMAXSIZ;
    uint32_t rksiz = rec->ksiz & TCMAPKMAXSIZ;
    if(hash > rhash){
      entp = &(rec->left);
      rec = rec->left;
    } else if(hash < rhash){
      entp = &(rec->right);
      rec = rec->right;
    } else {
      char *dbuf = (char *)rec + sizeof(*rec);
      int kcmp = TCKEYCMP(kbuf, ksiz, dbuf, rksiz);
      if(kcmp < 0){
        entp = &(rec->left);
        rec = rec->left;
      } else if(kcmp > 0){
        entp = &(rec->right);
        rec = rec->right;
      } else {
        map->msiz += vsiz - rec->vsiz;
        int psiz = TCALIGNPAD(ksiz);
        if(vsiz > rec->vsiz){
          TCMAPREC *old = rec;
          TCREALLOC(rec, rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
          if(rec != old){
            if(map->first == old) map->first = rec;
            if(map->last == old) map->last = rec;
            if(map->cur == old) map->cur = rec;
            *entp = rec;
            if(rec->prev) rec->prev->next = rec;
            if(rec->next) rec->next->prev = rec;
            dbuf = (char *)rec + sizeof(*rec);
          }
        }
        memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
        dbuf[ksiz+psiz+vsiz] = '\0';
        rec->vsiz = vsiz;
        if(map->last != rec){
          if(map->first == rec) map->first = rec->next;
          if(rec->prev) rec->prev->next = rec->next;
          if(rec->next) rec->next->prev = rec->prev;
          rec->prev = map->last;
          rec->next = NULL;
          map->last->next = rec;
          map->last = rec;
        }
        return;
      }
    }
  }
  int psiz = TCALIGNPAD(ksiz);
  map->msiz += ksiz + vsiz;
  TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
  char *dbuf = (char *)rec + sizeof(*rec);
  memcpy(dbuf, kbuf, ksiz);
  dbuf[ksiz] = '\0';
  rec->ksiz = ksiz | hash;
  memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
  dbuf[ksiz+psiz+vsiz] = '\0';
  rec->vsiz = vsiz;
  rec->left = NULL;
  rec->right = NULL;
  rec->prev = map->last;
  rec->next = NULL;
  *entp = rec;
  if(!map->first) map->first = rec;
  if(map->last) map->last->next = rec;
  map->last = rec;
  map->rnum++;
}


/* Store a record of the value of two regions into a map object. */
void tcmapput4(TCMAP *map, const void *kbuf, int ksiz,
               const void *fvbuf, int fvsiz, const void *lvbuf, int lvsiz){
  assert(map && kbuf && ksiz >= 0 && fvbuf && fvsiz >= 0 && lvbuf && lvsiz >= 0);
  if(ksiz > TCMAPKMAXSIZ) ksiz = TCMAPKMAXSIZ;
  uint32_t hash;
  TCMAPHASH1(hash, kbuf, ksiz);
  int bidx = hash % map->bnum;
  TCMAPREC *rec = map->buckets[bidx];
  TCMAPREC **entp = map->buckets + bidx;
  TCMAPHASH2(hash, kbuf, ksiz);
  hash &= ~TCMAPKMAXSIZ;
  while(rec){
    uint32_t rhash = rec->ksiz & ~TCMAPKMAXSIZ;
    uint32_t rksiz = rec->ksiz & TCMAPKMAXSIZ;
    if(hash > rhash){
      entp = &(rec->left);
      rec = rec->left;
    } else if(hash < rhash){
      entp = &(rec->right);
      rec = rec->right;
    } else {
      char *dbuf = (char *)rec + sizeof(*rec);
      int kcmp = TCKEYCMP(kbuf, ksiz, dbuf, rksiz);
      if(kcmp < 0){
        entp = &(rec->left);
        rec = rec->left;
      } else if(kcmp > 0){
        entp = &(rec->right);
        rec = rec->right;
      } else {
        int vsiz = fvsiz + lvsiz;
        map->msiz += vsiz - rec->vsiz;
        int psiz = TCALIGNPAD(ksiz);
        ksiz += psiz;
        if(vsiz > rec->vsiz){
          TCMAPREC *old = rec;
          TCREALLOC(rec, rec, sizeof(*rec) + ksiz + vsiz + 1);
          if(rec != old){
            if(map->first == old) map->first = rec;
            if(map->last == old) map->last = rec;
            if(map->cur == old) map->cur = rec;
            *entp = rec;
            if(rec->prev) rec->prev->next = rec;
            if(rec->next) rec->next->prev = rec;
            dbuf = (char *)rec + sizeof(*rec);
          }
        }
        memcpy(dbuf + ksiz, fvbuf, fvsiz);
        memcpy(dbuf + ksiz + fvsiz, lvbuf, lvsiz);
        dbuf[ksiz+vsiz] = '\0';
        rec->vsiz = vsiz;
        return;
      }
    }
  }
  int vsiz = fvsiz + lvsiz;
  int psiz = TCALIGNPAD(ksiz);
  map->msiz += ksiz + vsiz;
  TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
  char *dbuf = (char *)rec + sizeof(*rec);
  memcpy(dbuf, kbuf, ksiz);
  dbuf[ksiz] = '\0';
  rec->ksiz = ksiz | hash;
  ksiz += psiz;
  memcpy(dbuf + ksiz, fvbuf, fvsiz);
  memcpy(dbuf + ksiz + fvsiz, lvbuf, lvsiz);
  dbuf[ksiz+vsiz] = '\0';
  rec->vsiz = vsiz;
  rec->left = NULL;
  rec->right = NULL;
  rec->prev = map->last;
  rec->next = NULL;
  *entp = rec;
  if(!map->first) map->first = rec;
  if(map->last) map->last->next = rec;
  map->last = rec;
  map->rnum++;
}


/* Concatenate a value at the existing record and make it semivolatile in a map object. */
void tcmapputcat3(TCMAP *map, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(map && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  if(ksiz > TCMAPKMAXSIZ) ksiz = TCMAPKMAXSIZ;
  uint32_t hash;
  TCMAPHASH1(hash, kbuf, ksiz);
  int bidx = hash % map->bnum;
  TCMAPREC *rec = map->buckets[bidx];
  TCMAPREC **entp = map->buckets + bidx;
  TCMAPHASH2(hash, kbuf, ksiz);
  hash &= ~TCMAPKMAXSIZ;
  while(rec){
    uint32_t rhash = rec->ksiz & ~TCMAPKMAXSIZ;
    uint32_t rksiz = rec->ksiz & TCMAPKMAXSIZ;
    if(hash > rhash){
      entp = &(rec->left);
      rec = rec->left;
    } else if(hash < rhash){
      entp = &(rec->right);
      rec = rec->right;
    } else {
      char *dbuf = (char *)rec + sizeof(*rec);
      int kcmp = TCKEYCMP(kbuf, ksiz, dbuf, rksiz);
      if(kcmp < 0){
        entp = &(rec->left);
        rec = rec->left;
      } else if(kcmp > 0){
        entp = &(rec->right);
        rec = rec->right;
      } else {
        map->msiz += vsiz;
        int psiz = TCALIGNPAD(ksiz);
        int asiz = sizeof(*rec) + ksiz + psiz + rec->vsiz + vsiz + 1;
        int unit = (asiz <= TCMAPCSUNIT) ? TCMAPCSUNIT : TCMAPCBUNIT;
        asiz = (asiz - 1) + unit - (asiz - 1) % unit;
        TCMAPREC *old = rec;
        TCREALLOC(rec, rec, asiz);
        if(rec != old){
          if(map->first == old) map->first = rec;
          if(map->last == old) map->last = rec;
          if(map->cur == old) map->cur = rec;
          *entp = rec;
          if(rec->prev) rec->prev->next = rec;
          if(rec->next) rec->next->prev = rec;
          dbuf = (char *)rec + sizeof(*rec);
        }
        memcpy(dbuf + ksiz + psiz + rec->vsiz, vbuf, vsiz);
        rec->vsiz += vsiz;
        dbuf[ksiz+psiz+rec->vsiz] = '\0';
        if(map->last != rec){
          if(map->first == rec) map->first = rec->next;
          if(rec->prev) rec->prev->next = rec->next;
          if(rec->next) rec->next->prev = rec->prev;
          rec->prev = map->last;
          rec->next = NULL;
          map->last->next = rec;
          map->last = rec;
        }
        return;
      }
    }
  }
  int psiz = TCALIGNPAD(ksiz);
  int asiz = sizeof(*rec) + ksiz + psiz + vsiz + 1;
  int unit = (asiz <= TCMAPCSUNIT) ? TCMAPCSUNIT : TCMAPCBUNIT;
  asiz = (asiz - 1) + unit - (asiz - 1) % unit;
  map->msiz += ksiz + vsiz;
  TCMALLOC(rec, asiz);
  char *dbuf = (char *)rec + sizeof(*rec);
  memcpy(dbuf, kbuf, ksiz);
  dbuf[ksiz] = '\0';
  rec->ksiz = ksiz | hash;
  memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
  dbuf[ksiz+psiz+vsiz] = '\0';
  rec->vsiz = vsiz;
  rec->left = NULL;
  rec->right = NULL;
  rec->prev = map->last;
  rec->next = NULL;
  *entp = rec;
  if(!map->first) map->first = rec;
  if(map->last) map->last->next = rec;
  map->last = rec;
  map->rnum++;
}


/* Store a record into a map object with a duplication handler. */
bool tcmapputproc(TCMAP *map, const void *kbuf, int ksiz, const void *vbuf, int vsiz,
                  TCPDPROC proc, void *op){
  assert(map && kbuf && ksiz >= 0 && proc);
  if(ksiz > TCMAPKMAXSIZ) ksiz = TCMAPKMAXSIZ;
  uint32_t hash;
  TCMAPHASH1(hash, kbuf, ksiz);
  int bidx = hash % map->bnum;
  TCMAPREC *rec = map->buckets[bidx];
  TCMAPREC **entp = map->buckets + bidx;
  TCMAPHASH2(hash, kbuf, ksiz);
  hash &= ~TCMAPKMAXSIZ;
  while(rec){
    uint32_t rhash = rec->ksiz & ~TCMAPKMAXSIZ;
    uint32_t rksiz = rec->ksiz & TCMAPKMAXSIZ;
    if(hash > rhash){
      entp = &(rec->left);
      rec = rec->left;
    } else if(hash < rhash){
      entp = &(rec->right);
      rec = rec->right;
    } else {
      char *dbuf = (char *)rec + sizeof(*rec);
      int kcmp = TCKEYCMP(kbuf, ksiz, dbuf, rksiz);
      if(kcmp < 0){
        entp = &(rec->left);
        rec = rec->left;
      } else if(kcmp > 0){
        entp = &(rec->right);
        rec = rec->right;
      } else {
        int psiz = TCALIGNPAD(ksiz);
        int nvsiz;
        char *nvbuf = proc(dbuf + ksiz + psiz, rec->vsiz, &nvsiz, op);
        if(nvbuf == (void *)-1){
          map->rnum--;
          map->msiz -= rksiz + rec->vsiz;
          if(rec->prev) rec->prev->next = rec->next;
          if(rec->next) rec->next->prev = rec->prev;
          if(rec == map->first) map->first = rec->next;
          if(rec == map->last) map->last = rec->prev;
          if(rec == map->cur) map->cur = rec->next;
          if(rec->left && !rec->right){
            *entp = rec->left;
          } else if(!rec->left && rec->right){
            *entp = rec->right;
          } else if(!rec->left && !rec->left){
            *entp = NULL;
          } else {
            *entp = rec->left;
            TCMAPREC *tmp = *entp;
            while(tmp->right){
              tmp = tmp->right;
            }
            tmp->right = rec->right;
          }
          TCFREE(rec);
          return true;
        }
        if(!nvbuf) return false;
        map->msiz += nvsiz - rec->vsiz;
        if(nvsiz > rec->vsiz){
          TCMAPREC *old = rec;
          TCREALLOC(rec, rec, sizeof(*rec) + ksiz + psiz + nvsiz + 1);
          if(rec != old){
            if(map->first == old) map->first = rec;
            if(map->last == old) map->last = rec;
            if(map->cur == old) map->cur = rec;
            *entp = rec;
            if(rec->prev) rec->prev->next = rec;
            if(rec->next) rec->next->prev = rec;
            dbuf = (char *)rec + sizeof(*rec);
          }
        }
        memcpy(dbuf + ksiz + psiz, nvbuf, nvsiz);
        dbuf[ksiz+psiz+nvsiz] = '\0';
        rec->vsiz = nvsiz;
        TCFREE(nvbuf);
        return true;
      }
    }
  }
  if(!vbuf) return false;
  int psiz = TCALIGNPAD(ksiz);
  int asiz = sizeof(*rec) + ksiz + psiz + vsiz + 1;
  int unit = (asiz <= TCMAPCSUNIT) ? TCMAPCSUNIT : TCMAPCBUNIT;
  asiz = (asiz - 1) + unit - (asiz - 1) % unit;
  map->msiz += ksiz + vsiz;
  TCMALLOC(rec, asiz);
  char *dbuf = (char *)rec + sizeof(*rec);
  memcpy(dbuf, kbuf, ksiz);
  dbuf[ksiz] = '\0';
  rec->ksiz = ksiz | hash;
  memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
  dbuf[ksiz+psiz+vsiz] = '\0';
  rec->vsiz = vsiz;
  rec->left = NULL;
  rec->right = NULL;
  rec->prev = map->last;
  rec->next = NULL;
  *entp = rec;
  if(!map->first) map->first = rec;
  if(map->last) map->last->next = rec;
  map->last = rec;
  map->rnum++;
  return true;
}


/* Retrieve a semivolatile record in a map object. */
const void *tcmapget3(TCMAP *map, const void *kbuf, int ksiz, int *sp){
  assert(map && kbuf && ksiz >= 0 && sp);
  if(ksiz > TCMAPKMAXSIZ) ksiz = TCMAPKMAXSIZ;
  uint32_t hash;
  TCMAPHASH1(hash, kbuf, ksiz);
  TCMAPREC *rec = map->buckets[hash%map->bnum];
  TCMAPHASH2(hash, kbuf, ksiz);
  hash &= ~TCMAPKMAXSIZ;
  while(rec){
    uint32_t rhash = rec->ksiz & ~TCMAPKMAXSIZ;
    uint32_t rksiz = rec->ksiz & TCMAPKMAXSIZ;
    if(hash > rhash){
      rec = rec->left;
    } else if(hash < rhash){
      rec = rec->right;
    } else {
      char *dbuf = (char *)rec + sizeof(*rec);
      int kcmp = TCKEYCMP(kbuf, ksiz, dbuf, rksiz);
      if(kcmp < 0){
        rec = rec->left;
      } else if(kcmp > 0){
        rec = rec->right;
      } else {
        if(map->last != rec){
          if(map->first == rec) map->first = rec->next;
          if(rec->prev) rec->prev->next = rec->next;
          if(rec->next) rec->next->prev = rec->prev;
          rec->prev = map->last;
          rec->next = NULL;
          map->last->next = rec;
          map->last = rec;
        }
        *sp = rec->vsiz;
        return dbuf + rksiz + TCALIGNPAD(rksiz);
      }
    }
  }
  return NULL;
}


/* Retrieve a string record in a map object with specifying the default value string. */
const char *tcmapget4(TCMAP *map, const char *kstr, const char *dstr){
  assert(map && kstr && dstr);
  int vsiz;
  const char *vbuf = tcmapget(map, kstr, strlen(kstr), &vsiz);
  return vbuf ? vbuf : dstr;
}


/* Initialize the iterator of a map object at the record corresponding a key. */
void tcmapiterinit2(TCMAP *map, const void *kbuf, int ksiz){
  assert(map && kbuf && ksiz >= 0);
  if(ksiz > TCMAPKMAXSIZ) ksiz = TCMAPKMAXSIZ;
  uint32_t hash;
  TCMAPHASH1(hash, kbuf, ksiz);
  TCMAPREC *rec = map->buckets[hash%map->bnum];
  TCMAPHASH2(hash, kbuf, ksiz);
  hash &= ~TCMAPKMAXSIZ;
  while(rec){
    uint32_t rhash = rec->ksiz & ~TCMAPKMAXSIZ;
    uint32_t rksiz = rec->ksiz & TCMAPKMAXSIZ;
    if(hash > rhash){
      rec = rec->left;
    } else if(hash < rhash){
      rec = rec->right;
    } else {
      char *dbuf = (char *)rec + sizeof(*rec);
      int kcmp = TCKEYCMP(kbuf, ksiz, dbuf, rksiz);
      if(kcmp < 0){
        rec = rec->left;
      } else if(kcmp > 0){
        rec = rec->right;
      } else {
        map->cur = rec;
        return;
      }
    }
  }
}


/* Initialize the iterator of a map object at the record corresponding a key string. */
void tcmapiterinit3(TCMAP *map, const char *kstr){
  assert(map && kstr);
  tcmapiterinit2(map, kstr, strlen(kstr));
}


/* Get the value bound to the key fetched from the iterator of a map object. */
const void *tcmapiterval(const void *kbuf, int *sp){
  assert(kbuf && sp);
  TCMAPREC *rec = (TCMAPREC *)((char *)kbuf - sizeof(*rec));
  uint32_t rksiz = rec->ksiz & TCMAPKMAXSIZ;
  *sp = rec->vsiz;
  return (char *)kbuf + rksiz + TCALIGNPAD(rksiz);
}


/* Get the value string bound to the key fetched from the iterator of a map object. */
const char *tcmapiterval2(const char *kstr){
  assert(kstr);
  TCMAPREC *rec = (TCMAPREC *)(kstr - sizeof(*rec));
  uint32_t rksiz = rec->ksiz & TCMAPKMAXSIZ;
  return kstr + rksiz + TCALIGNPAD(rksiz);
}


/* Create an array of strings of all keys in a map object. */
const char **tcmapkeys2(const TCMAP *map, int *np){
  assert(map && np);
  const char **ary;
  TCMALLOC(ary, sizeof(*ary) * map->rnum + 1);
  int anum = 0;
  TCMAPREC *rec = map->first;
  while(rec){
    ary[(anum++)] = (char *)rec + sizeof(*rec);
    rec = rec->next;
  }
  *np = anum;
  return ary;
}


/* Create an array of strings of all values in a map object. */
const char **tcmapvals2(const TCMAP *map, int *np){
  assert(map && np);
  const char **ary;
  TCMALLOC(ary, sizeof(*ary) * map->rnum + 1);
  int anum = 0;
  TCMAPREC *rec = map->first;
  while(rec){
    uint32_t rksiz = rec->ksiz & TCMAPKMAXSIZ;
    ary[(anum++)] = (char *)rec + sizeof(*rec) + rksiz + TCALIGNPAD(rksiz);
    rec = rec->next;
  }
  *np = anum;
  return ary;
}


/* Extract a map record from a serialized byte array. */
void *tcmaploadone(const void *ptr, int size, const void *kbuf, int ksiz, int *sp){
  assert(ptr && size >= 0 && kbuf && ksiz >= 0 && sp);
  const char *rp = ptr;
  const char *ep = (char *)ptr + size;
  while(rp < ep){
    int step, rsiz;
    TCREADVNUMBUF(rp, rsiz, step);
    rp += step;
    if(rsiz == ksiz && !memcmp(kbuf, rp, rsiz)){
      rp += rsiz;
      TCREADVNUMBUF(rp, rsiz, step);
      rp += step;
      *sp = rsiz;
      char *rv;
      TCMEMDUP(rv, rp, rsiz);
      return rv;
    }
    rp += rsiz;
    TCREADVNUMBUF(rp, rsiz, step);
    rp += step;
    rp += rsiz;
  }
  return NULL;
}


/* Perform formatted output into a map object. */
void tcmapprintf(TCMAP *map, const char *kstr, const char *format, ...){
  assert(map && kstr && format);
  TCXSTR *xstr = tcxstrnew();
  va_list ap;
  va_start(ap, format);
  tcvxstrprintf(xstr, format, ap);
  va_end(ap);
  tcmapput(map, kstr, strlen(kstr), TCXSTRPTR(xstr), TCXSTRSIZE(xstr));
  tcxstrdel(xstr);
}



/*************************************************************************************************
 * ordered tree
 *************************************************************************************************/


#define TREESTACKNUM   2048              // capacity of the stack of ordered tree
#define TCTREECSUNIT   52                // small allocation unit size of map concatenation
#define TCTREECBUNIT   252               // big allocation unit size of map concatenation


/* private function prototypes */
static TCTREEREC* tctreesplay(TCTREE *tree, const void *kbuf, int ksiz);


/* Create a tree object. */
TCTREE *tctreenew(void){
  return tctreenew2(tccmplexical, NULL);
}


/* Create a tree object with specifying the custom comparison function. */
TCTREE *tctreenew2(TCCMP cmp, void *cmpop){
  assert(cmp);
  TCTREE *tree;
  TCMALLOC(tree, sizeof(*tree));
  tree->root = NULL;
  tree->cur = NULL;
  tree->rnum = 0;
  tree->msiz = 0;
  tree->cmp = cmp;
  tree->cmpop = cmpop;
  return tree;
}


/* Copy a tree object. */
TCTREE *tctreedup(const TCTREE *tree){
  assert(tree);
  TCTREE *ntree = tctreenew2(tree->cmp, tree->cmpop);
  if(tree->root){
    TCTREEREC *histbuf[TREESTACKNUM];
    TCTREEREC **history = histbuf;
    int hnum = 0;
    history[hnum++] = tree->root;
    while(hnum > 0){
      TCTREEREC *rec = history[--hnum];
      if(hnum >= TREESTACKNUM - 2 && history == histbuf){
        TCMALLOC(history, sizeof(*history) * tree->rnum);
        memcpy(history, histbuf, sizeof(*history) * hnum);
      }
      if(rec->left) history[hnum++] = rec->left;
      if(rec->right) history[hnum++] = rec->right;
      char *dbuf = (char *)rec + sizeof(*rec);
      tctreeput(ntree, dbuf, rec->ksiz, dbuf + rec->ksiz + TCALIGNPAD(rec->ksiz), rec->vsiz);
    }
    if(history != histbuf) TCFREE(history);
  }
  return ntree;
}


/* Delete a tree object. */
void tctreedel(TCTREE *tree){
  assert(tree);
  if(tree->root){
    TCTREEREC *histbuf[TREESTACKNUM];
    TCTREEREC **history = histbuf;
    int hnum = 0;
    history[hnum++] = tree->root;
    while(hnum > 0){
      TCTREEREC *rec = history[--hnum];
      if(hnum >= TREESTACKNUM - 2 && history == histbuf){
        TCMALLOC(history, sizeof(*history) * tree->rnum);
        memcpy(history, histbuf, sizeof(*history) * hnum);
      }
      if(rec->left) history[hnum++] = rec->left;
      if(rec->right) history[hnum++] = rec->right;
      TCFREE(rec);
    }
    if(history != histbuf) TCFREE(history);
  }
  TCFREE(tree);
}


/* Store a record into a tree object. */
void tctreeput(TCTREE *tree, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(tree && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  TCTREEREC *top = tctreesplay(tree, kbuf, ksiz);
  if(!top){
    int psiz = TCALIGNPAD(ksiz);
    TCTREEREC *rec;
    TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
    char *dbuf = (char *)rec + sizeof(*rec);
    memcpy(dbuf, kbuf, ksiz);
    dbuf[ksiz] = '\0';
    rec->ksiz = ksiz;
    memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
    dbuf[ksiz+psiz+vsiz] = '\0';
    rec->vsiz = vsiz;
    rec->left = NULL;
    rec->right = NULL;
    tree->root = rec;
    tree->rnum = 1;
    tree->msiz = ksiz + vsiz;
    return;
  }
  char *dbuf = (char *)top + sizeof(*top);
  int cv = tree->cmp(kbuf, ksiz, dbuf, top->ksiz, tree->cmpop);
  if(cv < 0){
    int psiz = TCALIGNPAD(ksiz);
    TCTREEREC *rec;
    TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
    dbuf = (char *)rec + sizeof(*rec);
    memcpy(dbuf, kbuf, ksiz);
    dbuf[ksiz] = '\0';
    rec->ksiz = ksiz;
    memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
    dbuf[ksiz+psiz+vsiz] = '\0';
    rec->vsiz = vsiz;
    rec->left = top->left;
    rec->right = top;
    top->left = NULL;
    tree->rnum++;
    tree->msiz += ksiz + vsiz;
    tree->root = rec;
  } else if(cv > 0){
    int psiz = TCALIGNPAD(ksiz);
    TCTREEREC *rec;
    TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
    dbuf = (char *)rec + sizeof(*rec);
    memcpy(dbuf, kbuf, ksiz);
    dbuf[ksiz] = '\0';
    rec->ksiz = ksiz;
    memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
    dbuf[ksiz+psiz+vsiz] = '\0';
    rec->vsiz = vsiz;
    rec->left = top;
    rec->right = top->right;
    top->right = NULL;
    tree->rnum++;
    tree->msiz += ksiz + vsiz;
    tree->root = rec;
  } else {
    tree->msiz += vsiz - top->vsiz;
    int psiz = TCALIGNPAD(ksiz);
    if(vsiz > top->vsiz){
      TCTREEREC *old = top;
      TCREALLOC(top, top, sizeof(*top) + ksiz + psiz + vsiz + 1);
      if(top != old){
        if(tree->cur == old) tree->cur = top;
        dbuf = (char *)top + sizeof(*top);
      }
    }
    memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
    dbuf[ksiz+psiz+vsiz] = '\0';
    top->vsiz = vsiz;
    tree->root = top;
  }
}


/* Store a string record into a tree object. */
void tctreeput2(TCTREE *tree, const char *kstr, const char *vstr){
  assert(tree && kstr && vstr);
  tctreeput(tree, kstr, strlen(kstr), vstr, strlen(vstr));
}


/* Store a new record into a tree object. */
bool tctreeputkeep(TCTREE *tree, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(tree && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  TCTREEREC *top = tctreesplay(tree, kbuf, ksiz);
  if(!top){
    int psiz = TCALIGNPAD(ksiz);
    TCTREEREC *rec;
    TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
    char *dbuf = (char *)rec + sizeof(*rec);
    memcpy(dbuf, kbuf, ksiz);
    dbuf[ksiz] = '\0';
    rec->ksiz = ksiz;
    memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
    dbuf[ksiz+psiz+vsiz] = '\0';
    rec->vsiz = vsiz;
    rec->left = NULL;
    rec->right = NULL;
    tree->root = rec;
    tree->rnum = 1;
    tree->msiz = ksiz + vsiz;
    return true;
  }
  char *dbuf = (char *)top + sizeof(*top);
  int cv = tree->cmp(kbuf, ksiz, dbuf, top->ksiz, tree->cmpop);
  if(cv < 0){
    int psiz = TCALIGNPAD(ksiz);
    TCTREEREC *rec;
    TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
    dbuf = (char *)rec + sizeof(*rec);
    memcpy(dbuf, kbuf, ksiz);
    dbuf[ksiz] = '\0';
    rec->ksiz = ksiz;
    memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
    dbuf[ksiz+psiz+vsiz] = '\0';
    rec->vsiz = vsiz;
    rec->left = top->left;
    rec->right = top;
    top->left = NULL;
    tree->rnum++;
    tree->msiz += ksiz + vsiz;
    tree->root = rec;
  } else if(cv > 0){
    int psiz = TCALIGNPAD(ksiz);
    TCTREEREC *rec;
    TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
    dbuf = (char *)rec + sizeof(*rec);
    memcpy(dbuf, kbuf, ksiz);
    dbuf[ksiz] = '\0';
    rec->ksiz = ksiz;
    memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
    dbuf[ksiz+psiz+vsiz] = '\0';
    rec->vsiz = vsiz;
    rec->left = top;
    rec->right = top->right;
    top->right = NULL;
    tree->rnum++;
    tree->msiz += ksiz + vsiz;
    tree->root = rec;
  } else {
    tree->root = top;
    return false;
  }
  return true;
}


/* Store a new string record into a tree object. */
bool tctreeputkeep2(TCTREE *tree, const char *kstr, const char *vstr){
  assert(tree && kstr && vstr);
  return tctreeputkeep(tree, kstr, strlen(kstr), vstr, strlen(vstr));
}


/* Concatenate a value at the end of the value of the existing record in a tree object. */
void tctreeputcat(TCTREE *tree, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(tree && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  TCTREEREC *top = tctreesplay(tree, kbuf, ksiz);
  if(!top){
    int psiz = TCALIGNPAD(ksiz);
    TCTREEREC *rec;
    TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
    char *dbuf = (char *)rec + sizeof(*rec);
    memcpy(dbuf, kbuf, ksiz);
    dbuf[ksiz] = '\0';
    rec->ksiz = ksiz;
    memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
    dbuf[ksiz+psiz+vsiz] = '\0';
    rec->vsiz = vsiz;
    rec->left = NULL;
    rec->right = NULL;
    tree->root = rec;
    tree->rnum = 1;
    tree->msiz = ksiz + vsiz;
    return;
  }
  char *dbuf = (char *)top + sizeof(*top);
  int cv = tree->cmp(kbuf, ksiz, dbuf, top->ksiz, tree->cmpop);
  if(cv < 0){
    int psiz = TCALIGNPAD(ksiz);
    TCTREEREC *rec;
    TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
    dbuf = (char *)rec + sizeof(*rec);
    memcpy(dbuf, kbuf, ksiz);
    dbuf[ksiz] = '\0';
    rec->ksiz = ksiz;
    memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
    dbuf[ksiz+psiz+vsiz] = '\0';
    rec->vsiz = vsiz;
    rec->left = top->left;
    rec->right = top;
    top->left = NULL;
    tree->rnum++;
    tree->msiz += ksiz + vsiz;
    tree->root = rec;
  } else if(cv > 0){
    int psiz = TCALIGNPAD(ksiz);
    TCTREEREC *rec;
    TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
    dbuf = (char *)rec + sizeof(*rec);
    memcpy(dbuf, kbuf, ksiz);
    dbuf[ksiz] = '\0';
    rec->ksiz = ksiz;
    memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
    dbuf[ksiz+psiz+vsiz] = '\0';
    rec->vsiz = vsiz;
    rec->left = top;
    rec->right = top->right;
    top->right = NULL;
    tree->rnum++;
    tree->msiz += ksiz + vsiz;
    tree->root = rec;
  } else {
    tree->msiz += vsiz;
    int psiz = TCALIGNPAD(ksiz);
    int asiz = sizeof(*top) + ksiz + psiz + top->vsiz + vsiz + 1;
    int unit = (asiz <= TCTREECSUNIT) ? TCTREECSUNIT : TCTREECBUNIT;
    asiz = (asiz - 1) + unit - (asiz - 1) % unit;
    TCTREEREC *old = top;
    TCREALLOC(top, top, asiz);
    if(top != old){
      if(tree->cur == old) tree->cur = top;
      dbuf = (char *)top + sizeof(*top);
    }
    memcpy(dbuf + ksiz + psiz + top->vsiz, vbuf, vsiz);
    top->vsiz += vsiz;
    dbuf[ksiz+psiz+top->vsiz] = '\0';
    tree->root = top;
  }
}


/* Concatenate a string value at the end of the value of the existing record in a tree object. */
void tctreeputcat2(TCTREE *tree, const char *kstr, const char *vstr){
  assert(tree && kstr && vstr);
  tctreeputcat(tree, kstr, strlen(kstr), vstr, strlen(vstr));
}


/* Store a record into a tree object with a duplication handler. */
bool tctreeputproc(TCTREE *tree, const void *kbuf, int ksiz, const void *vbuf, int vsiz,
                   TCPDPROC proc, void *op){
  assert(tree && kbuf && ksiz >= 0 && proc);
  TCTREEREC *top = tctreesplay(tree, kbuf, ksiz);
  if(!top){
    if(!vbuf) return false;
    int psiz = TCALIGNPAD(ksiz);
    TCTREEREC *rec;
    TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
    char *dbuf = (char *)rec + sizeof(*rec);
    memcpy(dbuf, kbuf, ksiz);
    dbuf[ksiz] = '\0';
    rec->ksiz = ksiz;
    memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
    dbuf[ksiz+psiz+vsiz] = '\0';
    rec->vsiz = vsiz;
    rec->left = NULL;
    rec->right = NULL;
    tree->root = rec;
    tree->rnum = 1;
    tree->msiz = ksiz + vsiz;
    return true;
  }
  char *dbuf = (char *)top + sizeof(*top);
  int cv = tree->cmp(kbuf, ksiz, dbuf, top->ksiz, tree->cmpop);
  if(cv < 0){
    if(!vbuf){
      tree->root = top;
      return false;
    }
    int psiz = TCALIGNPAD(ksiz);
    TCTREEREC *rec;
    TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
    dbuf = (char *)rec + sizeof(*rec);
    memcpy(dbuf, kbuf, ksiz);
    dbuf[ksiz] = '\0';
    rec->ksiz = ksiz;
    memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
    dbuf[ksiz+psiz+vsiz] = '\0';
    rec->vsiz = vsiz;
    rec->left = top->left;
    rec->right = top;
    top->left = NULL;
    tree->rnum++;
    tree->msiz += ksiz + vsiz;
    tree->root = rec;
  } else if(cv > 0){
    if(!vbuf){
      tree->root = top;
      return false;
    }
    int psiz = TCALIGNPAD(ksiz);
    TCTREEREC *rec;
    TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
    dbuf = (char *)rec + sizeof(*rec);
    memcpy(dbuf, kbuf, ksiz);
    dbuf[ksiz] = '\0';
    rec->ksiz = ksiz;
    memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
    dbuf[ksiz+psiz+vsiz] = '\0';
    rec->vsiz = vsiz;
    rec->left = top;
    rec->right = top->right;
    top->right = NULL;
    tree->rnum++;
    tree->msiz += ksiz + vsiz;
    tree->root = rec;
  } else {
    int psiz = TCALIGNPAD(ksiz);
    int nvsiz;
    char *nvbuf = proc(dbuf + ksiz + psiz, top->vsiz, &nvsiz, op);
    if(nvbuf == (void *)-1){
      tree->rnum--;
      tree->msiz -= top->ksiz + top->vsiz;
      if(tree->cur == top){
        TCTREEREC *rec = top->right;
        if(rec){
          while(rec->left){
            rec = rec->left;
          }
        }
        tree->cur = rec;
      }
      if(!top->left){
        tree->root = top->right;
      } else if(!top->right){
        tree->root = top->left;
      } else {
        tree->root = top->left;
        TCTREEREC *rec = tctreesplay(tree, kbuf, ksiz);
        rec->right = top->right;
        tree->root = rec;
      }
      TCFREE(top);
      return true;
    }
    if(!nvbuf){
      tree->root = top;
      return false;
    }
    tree->msiz += nvsiz - top->vsiz;
    if(nvsiz > top->vsiz){
      TCTREEREC *old = top;
      TCREALLOC(top, top, sizeof(*top) + ksiz + psiz + nvsiz + 1);
      if(top != old){
        if(tree->cur == old) tree->cur = top;
        dbuf = (char *)top + sizeof(*top);
      }
    }
    memcpy(dbuf + ksiz + psiz, nvbuf, nvsiz);
    dbuf[ksiz+psiz+nvsiz] = '\0';
    top->vsiz = nvsiz;
    TCFREE(nvbuf);
    tree->root = top;
  }
  return true;
}


/* Remove a record of a tree object. */
bool tctreeout(TCTREE *tree, const void *kbuf, int ksiz){
  assert(tree && kbuf && ksiz >= 0);
  TCTREEREC *top = tctreesplay(tree, kbuf, ksiz);
  if(!top) return false;
  char *dbuf = (char *)top + sizeof(*top);
  int cv = tree->cmp(kbuf, ksiz, dbuf, top->ksiz, tree->cmpop);
  if(cv != 0){
    tree->root = top;
    return false;
  }
  tree->rnum--;
  tree->msiz -= top->ksiz + top->vsiz;
  if(tree->cur == top){
    TCTREEREC *rec = top->right;
    if(rec){
      while(rec->left){
        rec = rec->left;
      }
    }
    tree->cur = rec;
  }
  if(!top->left){
    tree->root = top->right;
  } else if(!top->right){
    tree->root = top->left;
  } else {
    tree->root = top->left;
    TCTREEREC *rec = tctreesplay(tree, kbuf, ksiz);
    rec->right = top->right;
    tree->root = rec;
  }
  TCFREE(top);
  return true;
}


/* Remove a string record of a tree object. */
bool tctreeout2(TCTREE *tree, const char *kstr){
  assert(tree && kstr);
  return tctreeout(tree, kstr, strlen(kstr));
}


/* Retrieve a record in a tree object. */
const void *tctreeget(TCTREE *tree, const void *kbuf, int ksiz, int *sp){
  assert(tree && kbuf && ksiz >= 0 && sp);
  TCTREEREC *top = tctreesplay(tree, kbuf, ksiz);
  if(!top) return NULL;
  char *dbuf = (char *)top + sizeof(*top);
  int cv = tree->cmp(kbuf, ksiz, dbuf, top->ksiz, tree->cmpop);
  if(cv != 0){
    tree->root = top;
    return NULL;
  }
  tree->root = top;
  *sp = top->vsiz;
  return dbuf + top->ksiz + TCALIGNPAD(top->ksiz);
}


/* Retrieve a string record in a tree object. */
const char *tctreeget2(TCTREE *tree, const char *kstr){
  assert(tree && kstr);
  int vsiz;
  return tctreeget(tree, kstr, strlen(kstr), &vsiz);
}


/* Initialize the iterator of a tree object. */
void tctreeiterinit(TCTREE *tree){
  assert(tree);
  TCTREEREC *rec = tree->root;
  if(!rec) return;
  while(rec->left){
    rec = rec->left;
  }
  tree->cur = rec;
}


/* Get the next key of the iterator of a tree object. */
const void *tctreeiternext(TCTREE *tree, int *sp){
  assert(tree && sp);
  if(!tree->cur) return NULL;
  TCTREEREC *rec = tree->cur;
  const char *kbuf = (char *)rec + sizeof(*rec);
  int ksiz = rec->ksiz;
  rec = tctreesplay(tree, kbuf, ksiz);
  if(!rec) return NULL;
  tree->root = rec;
  rec = rec->right;
  if(rec){
    while(rec->left){
      rec = rec->left;
    }
  }
  tree->cur = rec;
  *sp = ksiz;
  return kbuf;
}


/* Get the next key string of the iterator of a tree object. */
const char *tctreeiternext2(TCTREE *tree){
  assert(tree);
  int ksiz;
  return tctreeiternext(tree, &ksiz);
}


/* Get the number of records stored in a tree object. */
uint64_t tctreernum(const TCTREE *tree){
  assert(tree);
  return tree->rnum;
}


/* Get the total size of memory used in a tree object. */
uint64_t tctreemsiz(const TCTREE *tree){
  assert(tree);
  return tree->msiz + tree->rnum * (sizeof(*tree->root) + sizeof(tcgeneric_t));
}


/* Create a list object containing all keys in a tree object. */
TCLIST *tctreekeys(const TCTREE *tree){
  assert(tree);
  TCLIST *list = tclistnew2(tree->rnum);
  if(tree->root){
    TCTREEREC **history;
    TCMALLOC(history, sizeof(*history) * tree->rnum);
    TCTREEREC **result;
    TCMALLOC(result, sizeof(*history) * tree->rnum);
    int hnum = 0;
    history[hnum++] = tree->root;
    while(hnum > 0){
      TCTREEREC *rec = history[--hnum];
      if(!rec){
        rec = result[hnum];
        char *dbuf = (char *)rec + sizeof(*rec);
        TCLISTPUSH(list, dbuf, rec->ksiz);
        continue;
      }
      if(rec->right) history[hnum++] = rec->right;
      history[hnum] = NULL;
      result[hnum] = rec;
      hnum++;
      if(rec->left) history[hnum++] = rec->left;
    }
    TCFREE(result);
    TCFREE(history);
  }
  return list;
}


/* Create a list object containing all values in a tree object. */
TCLIST *tctreevals(const TCTREE *tree){
  assert(tree);
  TCLIST *list = tclistnew2(tree->rnum);
  if(tree->root){
    TCTREEREC **history;
    TCMALLOC(history, sizeof(*history) * tree->rnum);
    TCTREEREC **result;
    TCMALLOC(result, sizeof(*history) * tree->rnum);
    int hnum = 0;
    history[hnum++] = tree->root;
    while(hnum > 0){
      TCTREEREC *rec = history[--hnum];
      if(!rec){
        rec = result[hnum];
        char *dbuf = (char *)rec + sizeof(*rec);
        TCLISTPUSH(list, dbuf + rec->ksiz + TCALIGNPAD(rec->ksiz), rec->vsiz);
        continue;
      }
      if(rec->right) history[hnum++] = rec->right;
      history[hnum] = NULL;
      result[hnum] = rec;
      hnum++;
      if(rec->left) history[hnum++] = rec->left;
    }
    TCFREE(result);
    TCFREE(history);
  }
  return list;
}


/* Add an integer to a record in a tree object. */
int tctreeaddint(TCTREE *tree, const void *kbuf, int ksiz, int num){
  assert(tree && kbuf && ksiz >= 0);
  TCTREEREC *top = tctreesplay(tree, kbuf, ksiz);
  if(!top){
    int psiz = TCALIGNPAD(ksiz);
    TCTREEREC *rec;
    TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + sizeof(num) + 1);
    char *dbuf = (char *)rec + sizeof(*rec);
    memcpy(dbuf, kbuf, ksiz);
    dbuf[ksiz] = '\0';
    rec->ksiz = ksiz;
    memcpy(dbuf + ksiz + psiz, &num, sizeof(num));
    dbuf[ksiz+psiz+sizeof(num)] = '\0';
    rec->vsiz = sizeof(num);
    rec->left = NULL;
    rec->right = NULL;
    tree->root = rec;
    tree->rnum = 1;
    tree->msiz = ksiz + sizeof(num);
    return num;
  }
  char *dbuf = (char *)top + sizeof(*top);
  int cv = tree->cmp(kbuf, ksiz, dbuf, top->ksiz, tree->cmpop);
  if(cv < 0){
    int psiz = TCALIGNPAD(ksiz);
    TCTREEREC *rec;
    TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + sizeof(num) + 1);
    dbuf = (char *)rec + sizeof(*rec);
    memcpy(dbuf, kbuf, ksiz);
    dbuf[ksiz] = '\0';
    rec->ksiz = ksiz;
    memcpy(dbuf + ksiz + psiz, &num, sizeof(num));
    dbuf[ksiz+psiz+sizeof(num)] = '\0';
    rec->vsiz = sizeof(num);
    rec->left = top->left;
    rec->right = top;
    top->left = NULL;
    tree->rnum++;
    tree->msiz += ksiz + sizeof(num);
    tree->root = rec;
  } else if(cv > 0){
    int psiz = TCALIGNPAD(ksiz);
    TCTREEREC *rec;
    TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + sizeof(num) + 1);
    dbuf = (char *)rec + sizeof(*rec);
    memcpy(dbuf, kbuf, ksiz);
    dbuf[ksiz] = '\0';
    rec->ksiz = ksiz;
    memcpy(dbuf + ksiz + psiz, &num, sizeof(num));
    dbuf[ksiz+psiz+sizeof(num)] = '\0';
    rec->vsiz = sizeof(num);
    rec->left = top;
    rec->right = top->right;
    top->right = NULL;
    tree->rnum++;
    tree->msiz += ksiz + sizeof(num);
    tree->root = rec;
  } else {
    tree->root = top;
    if(top->vsiz != sizeof(num)) return INT_MIN;
    int *resp = (int *)(dbuf + ksiz + TCALIGNPAD(ksiz));
    return *resp += num;
  }
  return num;
}


/* Add a real number to a record in a tree object. */
double tctreeadddouble(TCTREE *tree, const void *kbuf, int ksiz, double num){
  assert(tree && kbuf && ksiz >= 0);
  TCTREEREC *top = tctreesplay(tree, kbuf, ksiz);
  if(!top){
    int psiz = TCALIGNPAD(ksiz);
    TCTREEREC *rec;
    TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + sizeof(num) + 1);
    char *dbuf = (char *)rec + sizeof(*rec);
    memcpy(dbuf, kbuf, ksiz);
    dbuf[ksiz] = '\0';
    rec->ksiz = ksiz;
    memcpy(dbuf + ksiz + psiz, &num, sizeof(num));
    dbuf[ksiz+psiz+sizeof(num)] = '\0';
    rec->vsiz = sizeof(num);
    rec->left = NULL;
    rec->right = NULL;
    tree->root = rec;
    tree->rnum = 1;
    tree->msiz = ksiz + sizeof(num);
    return num;
  }
  char *dbuf = (char *)top + sizeof(*top);
  int cv = tree->cmp(kbuf, ksiz, dbuf, top->ksiz, tree->cmpop);
  if(cv < 0){
    int psiz = TCALIGNPAD(ksiz);
    TCTREEREC *rec;
    TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + sizeof(num) + 1);
    dbuf = (char *)rec + sizeof(*rec);
    memcpy(dbuf, kbuf, ksiz);
    dbuf[ksiz] = '\0';
    rec->ksiz = ksiz;
    memcpy(dbuf + ksiz + psiz, &num, sizeof(num));
    dbuf[ksiz+psiz+sizeof(num)] = '\0';
    rec->vsiz = sizeof(num);
    rec->left = top->left;
    rec->right = top;
    top->left = NULL;
    tree->rnum++;
    tree->msiz += ksiz + sizeof(num);
    tree->root = rec;
  } else if(cv > 0){
    int psiz = TCALIGNPAD(ksiz);
    TCTREEREC *rec;
    TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + sizeof(num) + 1);
    dbuf = (char *)rec + sizeof(*rec);
    memcpy(dbuf, kbuf, ksiz);
    dbuf[ksiz] = '\0';
    rec->ksiz = ksiz;
    memcpy(dbuf + ksiz + psiz, &num, sizeof(num));
    dbuf[ksiz+psiz+sizeof(num)] = '\0';
    rec->vsiz = sizeof(num);
    rec->left = top;
    rec->right = top->right;
    top->right = NULL;
    tree->rnum++;
    tree->msiz += ksiz + sizeof(num);
    tree->root = rec;
  } else {
    tree->root = top;
    if(top->vsiz != sizeof(num)) return nan("");
    double *resp = (double *)(dbuf + ksiz + TCALIGNPAD(ksiz));
    return *resp += num;
  }
  return num;
}


/* Clear a tree object. */
void tctreeclear(TCTREE *tree){
  assert(tree);
  if(tree->root){
    TCTREEREC *histbuf[TREESTACKNUM];
    TCTREEREC **history = histbuf;
    int hnum = 0;
    history[hnum++] = tree->root;
    while(hnum > 0){
      TCTREEREC *rec = history[--hnum];
      if(hnum >= TREESTACKNUM - 2 && history == histbuf){
        TCMALLOC(history, sizeof(*history) * tree->rnum);
        memcpy(history, histbuf, sizeof(*history) * hnum);
      }
      if(rec->left) history[hnum++] = rec->left;
      if(rec->right) history[hnum++] = rec->right;
      TCFREE(rec);
    }
    if(history != histbuf) TCFREE(history);
  }
  tree->root = NULL;
  tree->cur = NULL;
  tree->rnum = 0;
  tree->msiz = 0;
}


/* Remove fringe records of a tree object. */
void tctreecutfringe(TCTREE *tree, int num){
  assert(tree && num >= 0);
  if(!tree->root || num < 1) return;
  TCTREEREC **history;
  TCMALLOC(history, sizeof(*history) * tree->rnum);
  int hnum = 0;
  history[hnum++] = tree->root;
  for(int i = 0; i < hnum; i++){
    TCTREEREC *rec = history[i];
    if(rec->left) history[hnum++] = rec->left;
    if(rec->right) history[hnum++] = rec->right;
  }
  TCTREEREC *cur = NULL;
  for(int i = hnum - 1; i >= 0; i--){
    TCTREEREC *rec = history[i];
    if(rec->left){
      TCTREEREC *child = rec->left;
      tree->rnum--;
      tree->msiz -= child->ksiz + child->vsiz;
      rec->left = NULL;
      if(tree->cur == child){
        tree->cur = NULL;
        cur = child;
      } else {
        TCFREE(child);
      }
      if(--num < 1) break;
    }
    if(rec->right){
      TCTREEREC *child = rec->right;
      tree->rnum--;
      tree->msiz -= child->ksiz + child->vsiz;
      rec->right = NULL;
      if(tree->cur == child){
        tree->cur = NULL;
        cur = child;
      } else {
        TCFREE(child);
      }
      if(--num < 1) break;
    }
  }
  if(num > 0){
    TCFREE(tree->root);
    tree->root = NULL;
    tree->cur = NULL;
    tree->rnum = 0;
    tree->msiz = 0;
  }
  if(cur){
    char *dbuf = (char *)cur + sizeof(*cur);
    tctreeiterinit2(tree, dbuf, cur->ksiz);
    TCFREE(cur);
  }
  TCFREE(history);
}


/* Serialize a tree object into a byte array. */
void *tctreedump(const TCTREE *tree, int *sp){
  assert(tree && sp);
  int tsiz = 0;
  if(tree->root){
    TCTREEREC *histbuf[TREESTACKNUM];
    TCTREEREC **history = histbuf;
    int hnum = 0;
    history[hnum++] = tree->root;
    while(hnum > 0){
      TCTREEREC *rec = history[--hnum];
      if(hnum >= TREESTACKNUM - 2 && history == histbuf){
        TCMALLOC(history, sizeof(*history) * tree->rnum);
        memcpy(history, histbuf, sizeof(*history) * hnum);
      }
      if(rec->left) history[hnum++] = rec->left;
      if(rec->right) history[hnum++] = rec->right;
      tsiz += rec->ksiz + rec->vsiz + sizeof(int) * 2;
    }
    if(history != histbuf) TCFREE(history);
  }
  char *buf;
  TCMALLOC(buf, tsiz + 1);
  char *wp = buf;
  if(tree->root){
    TCTREEREC *histbuf[TREESTACKNUM];
    TCTREEREC **history = histbuf;
    int hnum = 0;
    history[hnum++] = tree->root;
    while(hnum > 0){
      TCTREEREC *rec = history[--hnum];
      if(hnum >= TREESTACKNUM - 2 && history == histbuf){
        TCMALLOC(history, sizeof(*history) * tree->rnum);
        memcpy(history, histbuf, sizeof(*history) * hnum);
      }
      if(rec->left) history[hnum++] = rec->left;
      if(rec->right) history[hnum++] = rec->right;
      const char *kbuf = (char *)rec + sizeof(*rec);
      int ksiz = rec->ksiz;
      const char *vbuf = kbuf + ksiz + TCALIGNPAD(ksiz);
      int vsiz = rec->vsiz;
      int step;
      TCSETVNUMBUF(step, wp, ksiz);
      wp += step;
      memcpy(wp, kbuf, ksiz);
      wp += ksiz;
      TCSETVNUMBUF(step, wp, vsiz);
      wp += step;
      memcpy(wp, vbuf, vsiz);
      wp += vsiz;
    }
    if(history != histbuf) TCFREE(history);
  }
  *sp = wp - buf;
  return buf;
}


/* Create a tree object from a serialized byte array. */
TCTREE *tctreeload(const void *ptr, int size, TCCMP cmp, void *cmpop){
  assert(ptr && size >= 0 && cmp);
  TCTREE *tree = tctreenew2(cmp, cmpop);
  const char *rp = ptr;
  const char *ep = (char *)ptr + size;
  while(rp < ep){
    int step, ksiz, vsiz;
    TCREADVNUMBUF(rp, ksiz, step);
    rp += step;
    const char *kbuf = rp;
    rp += ksiz;
    TCREADVNUMBUF(rp, vsiz, step);
    rp += step;
    tctreeputkeep(tree, kbuf, ksiz, rp, vsiz);
    rp += vsiz;
  }
  return tree;
}


/* Perform the splay operation of a tree object.
   `tree' specifies the tree object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   The return value is the pointer to the record corresponding the key. */
static TCTREEREC* tctreesplay(TCTREE *tree, const void *kbuf, int ksiz){
  assert(tree && kbuf && ksiz >= 0);
  TCTREEREC *top = tree->root;
  if(!top) return NULL;
  TCCMP cmp = tree->cmp;
  void *cmpop = tree->cmpop;
  TCTREEREC ent;
  ent.left = NULL;
  ent.right = NULL;
  TCTREEREC *lrec = &ent;
  TCTREEREC *rrec = &ent;
  while(true){
    char *dbuf = (char *)top + sizeof(*top);
    int cv = cmp(kbuf, ksiz, dbuf, top->ksiz, cmpop);
    if(cv < 0){
      if(!top->left) break;
      dbuf = (char *)top->left + sizeof(*top);
      cv = cmp(kbuf, ksiz, dbuf, top->left->ksiz, cmpop);
      if(cv < 0){
        TCTREEREC *swap = top->left;
        top->left = swap->right;
        swap->right = top;
        top = swap;
        if(!top->left) break;
      }
      rrec->left = top;
      rrec = top;
      top = top->left;
    } else if(cv > 0){
      if(!top->right) break;
      dbuf = (char *)top->right + sizeof(*top);
      cv = cmp(kbuf, ksiz, dbuf, top->right->ksiz, cmpop);
      if(cv > 0){
        TCTREEREC *swap = top->right;
        top->right = swap->left;
        swap->left = top;
        top = swap;
        if(!top->right) break;
      }
      lrec->right = top;
      lrec = top;
      top = top->right;
    } else {
      break;
    }
  }
  lrec->right = top->left;
  rrec->left = top->right;
  top->left = ent.right;
  top->right = ent.left;
  return top;
}



/*************************************************************************************************
 * ordered tree (for experts)
 *************************************************************************************************/


/* Store a record into a tree object without balancing nodes. */
void tctreeput3(TCTREE *tree, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(tree && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  TCTREEREC *rec = tree->root;
  TCTREEREC **entp = NULL;
  while(rec){
    char *dbuf = (char *)rec + sizeof(*rec);
    int cv = tree->cmp(kbuf, ksiz, dbuf, rec->ksiz, tree->cmpop);
    if(cv < 0){
      entp = &(rec->left);
      rec = rec->left;
    } else if(cv > 0){
      entp = &(rec->right);
      rec = rec->right;
    } else {
      tree->msiz += vsiz - rec->vsiz;
      int psiz = TCALIGNPAD(ksiz);
      if(vsiz > rec->vsiz){
        TCTREEREC *old = rec;
        TCREALLOC(rec, rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
        if(rec != old){
          if(tree->root == old) tree->root = rec;
          if(tree->cur == old) tree->cur = rec;
          if(entp) *entp = rec;
          dbuf = (char *)rec + sizeof(*rec);
        }
      }
      memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
      dbuf[ksiz+psiz+vsiz] = '\0';
      rec->vsiz = vsiz;
      return;
    }
  }
  int psiz = TCALIGNPAD(ksiz);
  TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
  char *dbuf = (char *)rec + sizeof(*rec);
  memcpy(dbuf, kbuf, ksiz);
  dbuf[ksiz] = '\0';
  rec->ksiz = ksiz;
  memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
  dbuf[ksiz+psiz+vsiz] = '\0';
  rec->vsiz = vsiz;
  rec->left = NULL;
  rec->right = NULL;
  if(entp){
    *entp = rec;
  } else {
    tree->root = rec;
  }
  tree->rnum++;
  tree->msiz += ksiz + vsiz;
}


/* Store a new record into a map object without balancing nodes. */
bool tctreeputkeep3(TCTREE *tree, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(tree && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  TCTREEREC *rec = tree->root;
  TCTREEREC **entp = NULL;
  while(rec){
    char *dbuf = (char *)rec + sizeof(*rec);
    int cv = tree->cmp(kbuf, ksiz, dbuf, rec->ksiz, tree->cmpop);
    if(cv < 0){
      entp = &(rec->left);
      rec = rec->left;
    } else if(cv > 0){
      entp = &(rec->right);
      rec = rec->right;
    } else {
      return false;
    }
  }
  int psiz = TCALIGNPAD(ksiz);
  TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
  char *dbuf = (char *)rec + sizeof(*rec);
  memcpy(dbuf, kbuf, ksiz);
  dbuf[ksiz] = '\0';
  rec->ksiz = ksiz;
  memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
  dbuf[ksiz+psiz+vsiz] = '\0';
  rec->vsiz = vsiz;
  rec->left = NULL;
  rec->right = NULL;
  if(entp){
    *entp = rec;
  } else {
    tree->root = rec;
  }
  tree->rnum++;
  tree->msiz += ksiz + vsiz;
  return true;
}


/* Concatenate a value at the existing record in a tree object without balancing nodes. */
void tctreeputcat3(TCTREE *tree, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(tree && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  TCTREEREC *rec = tree->root;
  TCTREEREC **entp = NULL;
  while(rec){
    char *dbuf = (char *)rec + sizeof(*rec);
    int cv = tree->cmp(kbuf, ksiz, dbuf, rec->ksiz, tree->cmpop);
    if(cv < 0){
      entp = &(rec->left);
      rec = rec->left;
    } else if(cv > 0){
      entp = &(rec->right);
      rec = rec->right;
    } else {
      tree->msiz += vsiz;
      int psiz = TCALIGNPAD(ksiz);
      int asiz = sizeof(*rec) + ksiz + psiz + rec->vsiz + vsiz + 1;
      int unit = (asiz <= TCTREECSUNIT) ? TCTREECSUNIT : TCTREECBUNIT;
      asiz = (asiz - 1) + unit - (asiz - 1) % unit;
      TCTREEREC *old = rec;
      TCREALLOC(rec, rec, asiz);
      if(rec != old){
        if(tree->root == old) tree->root = rec;
        if(tree->cur == old) tree->cur = rec;
        if(entp) *entp = rec;
        dbuf = (char *)rec + sizeof(*rec);
      }
      memcpy(dbuf + ksiz + psiz + rec->vsiz, vbuf, vsiz);
      rec->vsiz += vsiz;
      dbuf[ksiz+psiz+rec->vsiz] = '\0';
      return;
    }
  }
  int psiz = TCALIGNPAD(ksiz);
  TCMALLOC(rec, sizeof(*rec) + ksiz + psiz + vsiz + 1);
  char *dbuf = (char *)rec + sizeof(*rec);
  memcpy(dbuf, kbuf, ksiz);
  dbuf[ksiz] = '\0';
  rec->ksiz = ksiz;
  memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
  dbuf[ksiz+psiz+vsiz] = '\0';
  rec->vsiz = vsiz;
  rec->left = NULL;
  rec->right = NULL;
  if(entp){
    *entp = rec;
  } else {
    tree->root = rec;
  }
  tree->rnum++;
  tree->msiz += ksiz + vsiz;
}


/* Retrieve a record in a tree object without balancing nodes. */
const void *tctreeget3(const TCTREE *tree, const void *kbuf, int ksiz, int *sp){
  assert(tree && kbuf && ksiz >= 0 && sp);
  TCTREEREC *rec = tree->root;
  while(rec){
    char *dbuf = (char *)rec + sizeof(*rec);
    int cv = tree->cmp(kbuf, ksiz, dbuf, rec->ksiz, tree->cmpop);
    if(cv < 0){
      rec = rec->left;
    } else if(cv > 0){
      rec = rec->right;
    } else {
      *sp = rec->vsiz;
      return dbuf + rec->ksiz + TCALIGNPAD(rec->ksiz);
    }
  }
  return NULL;
}


/* Retrieve a string record in a tree object with specifying the default value string. */
const char *tctreeget4(TCTREE *tree, const char *kstr, const char *dstr){
  assert(tree && kstr && dstr);
  int vsiz;
  const char *vbuf = tctreeget(tree, kstr, strlen(kstr), &vsiz);
  return vbuf ? vbuf : dstr;
}


/* Initialize the iterator of a tree object in front of records corresponding a key. */
void tctreeiterinit2(TCTREE *tree, const void *kbuf, int ksiz){
  assert(tree && kbuf && ksiz >= 0);
  TCTREEREC *rec = tree->root;
  while(rec){
    char *dbuf = (char *)rec + sizeof(*rec);
    int cv = tree->cmp(kbuf, ksiz, dbuf, rec->ksiz, tree->cmpop);
    if(cv < 0){
      tree->cur = rec;
      rec = rec->left;
    } else if(cv > 0){
      rec = rec->right;
    } else {
      tree->cur = rec;
      return;
    }
  }
}


/* Initialize the iterator of a tree object in front of records corresponding a key string. */
void tctreeiterinit3(TCTREE *tree, const char *kstr){
  assert(tree);
  tctreeiterinit2(tree, kstr, strlen(kstr));
}


/* Get the value bound to the key fetched from the iterator of a tree object. */
const void *tctreeiterval(const void *kbuf, int *sp){
  assert(kbuf && sp);
  TCTREEREC *rec = (TCTREEREC *)((char *)kbuf - sizeof(*rec));
  *sp = rec->vsiz;
  return (char *)kbuf + rec->ksiz + TCALIGNPAD(rec->ksiz);
}


/* Get the value string bound to the key fetched from the iterator of a tree object. */
const char *tctreeiterval2(const char *kstr){
  assert(kstr);
  TCTREEREC *rec = (TCTREEREC *)(kstr - sizeof(*rec));
  return kstr + rec->ksiz + TCALIGNPAD(rec->ksiz);
}


/* Create an array of strings of all keys in a tree object. */
const char **tctreekeys2(const TCTREE *tree, int *np){
  assert(tree && np);
  const char **ary;
  TCMALLOC(ary, sizeof(*ary) * tree->rnum + 1);
  int anum = 0;
  if(tree->root){
    TCTREEREC **history;
    TCMALLOC(history, sizeof(*history) * tree->rnum);
    TCTREEREC **result;
    TCMALLOC(result, sizeof(*history) * tree->rnum);
    int hnum = 0;
    history[hnum++] = tree->root;
    while(hnum > 0){
      TCTREEREC *rec = history[--hnum];
      if(!rec){
        rec = result[hnum];
        ary[(anum++)] = (char *)rec + sizeof(*rec);
        continue;
      }
      if(rec->right) history[hnum++] = rec->right;
      history[hnum] = NULL;
      result[hnum] = rec;
      hnum++;
      if(rec->left) history[hnum++] = rec->left;
    }
    TCFREE(result);
    TCFREE(history);
  }
  *np = anum;
  return ary;
}


/* Create an array of strings of all values in a tree object. */
const char **tctreevals2(const TCTREE *tree, int *np){
  assert(tree && np);
  const char **ary;
  TCMALLOC(ary, sizeof(*ary) * tree->rnum + 1);
  int anum = 0;
  if(tree->root){
    TCTREEREC **history;
    TCMALLOC(history, sizeof(*history) * tree->rnum);
    TCTREEREC **result;
    TCMALLOC(result, sizeof(*history) * tree->rnum);
    int hnum = 0;
    history[hnum++] = tree->root;
    while(hnum > 0){
      TCTREEREC *rec = history[--hnum];
      if(!rec){
        rec = result[hnum];
        ary[(anum++)] = (char *)rec + sizeof(*rec);
        continue;
      }
      if(rec->right) history[hnum++] = rec->right;
      history[hnum] = NULL;
      result[hnum] = rec;
      hnum++;
      if(rec->left) history[hnum++] = rec->left;
    }
    TCFREE(result);
    TCFREE(history);
  }
  *np = anum;
  return ary;
}


/* Extract a tree record from a serialized byte array. */
void *tctreeloadone(const void *ptr, int size, const void *kbuf, int ksiz, int *sp){
  assert(ptr && size >= 0 && kbuf && ksiz >= 0 && sp);
  const char *rp = ptr;
  const char *ep = (char *)ptr + size;
  while(rp < ep){
    int step, rsiz;
    TCREADVNUMBUF(rp, rsiz, step);
    rp += step;
    if(rsiz == ksiz && !memcmp(kbuf, rp, rsiz)){
      rp += rsiz;
      TCREADVNUMBUF(rp, rsiz, step);
      rp += step;
      *sp = rsiz;
      char *rv;
      TCMEMDUP(rv, rp, rsiz);
      return rv;
    }
    rp += rsiz;
    TCREADVNUMBUF(rp, rsiz, step);
    rp += step;
    rp += rsiz;
  }
  return NULL;
}


/* Perform formatted output into a tree object. */
void tctreeprintf(TCTREE *tree, const char *kstr, const char *format, ...){
  assert(tree && kstr && format);
  TCXSTR *xstr = tcxstrnew();
  va_list ap;
  va_start(ap, format);
  tcvxstrprintf(xstr, format, ap);
  va_end(ap);
  tctreeput(tree, kstr, strlen(kstr), TCXSTRPTR(xstr), TCXSTRSIZE(xstr));
  tcxstrdel(xstr);
}



/*************************************************************************************************
 * on-memory hash database
 *************************************************************************************************/


#define TCMDBMNUM      8                 // number of internal maps
#define TCMDBDEFBNUM   65536             // default bucket number

/* get the first hash value */
#define TCMDBHASH(TC_res, TC_kbuf, TC_ksiz)                             \
  do {                                                                  \
    const unsigned char *_TC_p = (const unsigned char *)(TC_kbuf) + TC_ksiz - 1; \
    int _TC_ksiz = TC_ksiz;                                             \
    for((TC_res) = 0x20071123; _TC_ksiz--;){                            \
      (TC_res) = (TC_res) * 33 + *(_TC_p)--;                            \
    }                                                                   \
    (TC_res) &= TCMDBMNUM - 1;                                          \
  } while(false)


/* Create an on-memory hash database object. */
TCMDB *tcmdbnew(void){
  return tcmdbnew2(TCMDBDEFBNUM);
}


/* Create an on-memory hash database with specifying the number of the buckets. */
TCMDB *tcmdbnew2(uint32_t bnum){
  TCMDB *mdb;
  if(bnum < 1) bnum = TCMDBDEFBNUM;
  bnum = bnum / TCMDBMNUM + 17;
  TCMALLOC(mdb, sizeof(*mdb));
  TCMALLOC(mdb->mmtxs, sizeof(pthread_rwlock_t) * TCMDBMNUM);
  TCMALLOC(mdb->imtx, sizeof(pthread_mutex_t));
  TCMALLOC(mdb->maps, sizeof(TCMAP *) * TCMDBMNUM);
  if(pthread_mutex_init(mdb->imtx, NULL) != 0) tcmyfatal("mutex error");
  for(int i = 0; i < TCMDBMNUM; i++){
    if(pthread_rwlock_init((pthread_rwlock_t *)mdb->mmtxs + i, NULL) != 0)
      tcmyfatal("rwlock error");
    mdb->maps[i] = tcmapnew2(bnum);
  }
  mdb->iter = -1;
  return mdb;
}


/* Delete an on-memory hash database object. */
void tcmdbdel(TCMDB *mdb){
  assert(mdb);
  for(int i = TCMDBMNUM - 1; i >= 0; i--){
    tcmapdel(mdb->maps[i]);
    pthread_rwlock_destroy((pthread_rwlock_t *)mdb->mmtxs + i);
  }
  pthread_mutex_destroy(mdb->imtx);
  TCFREE(mdb->maps);
  TCFREE(mdb->imtx);
  TCFREE(mdb->mmtxs);
  TCFREE(mdb);
}


/* Store a record into an on-memory hash database. */
void tcmdbput(TCMDB *mdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(mdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  unsigned int mi;
  TCMDBHASH(mi, kbuf, ksiz);
  if(pthread_rwlock_wrlock((pthread_rwlock_t *)mdb->mmtxs + mi) != 0) return;
  tcmapput(mdb->maps[mi], kbuf, ksiz, vbuf, vsiz);
  pthread_rwlock_unlock((pthread_rwlock_t *)mdb->mmtxs + mi);
}


/* Store a string record into an on-memory hash database. */
void tcmdbput2(TCMDB *mdb, const char *kstr, const char *vstr){
  assert(mdb && kstr && vstr);
  tcmdbput(mdb, kstr, strlen(kstr), vstr, strlen(vstr));
}


/* Store a new record into an on-memory hash database. */
bool tcmdbputkeep(TCMDB *mdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(mdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  unsigned int mi;
  TCMDBHASH(mi, kbuf, ksiz);
  if(pthread_rwlock_wrlock((pthread_rwlock_t *)mdb->mmtxs + mi) != 0) return false;
  bool rv = tcmapputkeep(mdb->maps[mi], kbuf, ksiz, vbuf, vsiz);
  pthread_rwlock_unlock((pthread_rwlock_t *)mdb->mmtxs + mi);
  return rv;
}


/* Store a new string record into an on-memory hash database. */
bool tcmdbputkeep2(TCMDB *mdb, const char *kstr, const char *vstr){
  assert(mdb && kstr && vstr);
  return tcmdbputkeep(mdb, kstr, strlen(kstr), vstr, strlen(vstr));
}


/* Concatenate a value at the end of the existing record in an on-memory hash database. */
void tcmdbputcat(TCMDB *mdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(mdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  unsigned int mi;
  TCMDBHASH(mi, kbuf, ksiz);
  if(pthread_rwlock_wrlock((pthread_rwlock_t *)mdb->mmtxs + mi) != 0) return;
  tcmapputcat(mdb->maps[mi], kbuf, ksiz, vbuf, vsiz);
  pthread_rwlock_unlock((pthread_rwlock_t *)mdb->mmtxs + mi);
}


/* Concatenate a string at the end of the existing record in an on-memory hash database. */
void tcmdbputcat2(TCMDB *mdb, const char *kstr, const char *vstr){
  assert(mdb && kstr && vstr);
  tcmdbputcat(mdb, kstr, strlen(kstr), vstr, strlen(vstr));
}


/* Remove a record of an on-memory hash database. */
bool tcmdbout(TCMDB *mdb, const void *kbuf, int ksiz){
  assert(mdb && kbuf && ksiz >= 0);
  unsigned int mi;
  TCMDBHASH(mi, kbuf, ksiz);
  if(pthread_rwlock_wrlock((pthread_rwlock_t *)mdb->mmtxs + mi) != 0) return false;
  bool rv = tcmapout(mdb->maps[mi], kbuf, ksiz);
  pthread_rwlock_unlock((pthread_rwlock_t *)mdb->mmtxs + mi);
  return rv;
}


/* Remove a string record of an on-memory hash database. */
bool tcmdbout2(TCMDB *mdb, const char *kstr){
  assert(mdb && kstr);
  return tcmdbout(mdb, kstr, strlen(kstr));
}


/* Retrieve a record in an on-memory hash database. */
void *tcmdbget(TCMDB *mdb, const void *kbuf, int ksiz, int *sp){
  assert(mdb && kbuf && ksiz >= 0 && sp);
  unsigned int mi;
  TCMDBHASH(mi, kbuf, ksiz);
  if(pthread_rwlock_rdlock((pthread_rwlock_t *)mdb->mmtxs + mi) != 0) return NULL;
  int vsiz;
  const char *vbuf = tcmapget(mdb->maps[mi], kbuf, ksiz, &vsiz);
  char *rv;
  if(vbuf){
    TCMEMDUP(rv, vbuf, vsiz);
    *sp = vsiz;
  } else {
    rv = NULL;
  }
  pthread_rwlock_unlock((pthread_rwlock_t *)mdb->mmtxs + mi);
  return rv;
}


/* Retrieve a string record in an on-memory hash database. */
char *tcmdbget2(TCMDB *mdb, const char *kstr){
  assert(mdb && kstr);
  int vsiz;
  return tcmdbget(mdb, kstr, strlen(kstr), &vsiz);
}


/* Get the size of the value of a record in an on-memory hash database object. */
int tcmdbvsiz(TCMDB *mdb, const void *kbuf, int ksiz){
  assert(mdb && kbuf && ksiz >= 0);
  unsigned int mi;
  TCMDBHASH(mi, kbuf, ksiz);
  if(pthread_rwlock_rdlock((pthread_rwlock_t *)mdb->mmtxs + mi) != 0) return -1;
  int vsiz;
  const char *vbuf = tcmapget(mdb->maps[mi], kbuf, ksiz, &vsiz);
  if(!vbuf) vsiz = -1;
  pthread_rwlock_unlock((pthread_rwlock_t *)mdb->mmtxs + mi);
  return vsiz;
}


/* Get the size of the value of a string record in an on-memory hash database object. */
int tcmdbvsiz2(TCMDB *mdb, const char *kstr){
  assert(mdb && kstr);
  return tcmdbvsiz(mdb, kstr, strlen(kstr));
}


/* Initialize the iterator of an on-memory hash database. */
void tcmdbiterinit(TCMDB *mdb){
  assert(mdb);
  if(pthread_mutex_lock(mdb->imtx) != 0) return;
  for(int i = 0; i < TCMDBMNUM; i++){
    tcmapiterinit(mdb->maps[i]);
  }
  mdb->iter = 0;
  pthread_mutex_unlock(mdb->imtx);
}


/* Get the next key of the iterator of an on-memory hash database. */
void *tcmdbiternext(TCMDB *mdb, int *sp){
  assert(mdb && sp);
  if(pthread_mutex_lock(mdb->imtx) != 0) return NULL;
  if(mdb->iter < 0 || mdb->iter >= TCMDBMNUM){
    pthread_mutex_unlock(mdb->imtx);
    return NULL;
  }
  int mi = mdb->iter;
  if(pthread_rwlock_wrlock((pthread_rwlock_t *)mdb->mmtxs + mi) != 0){
    pthread_mutex_unlock(mdb->imtx);
    return NULL;
  }
  int ksiz;
  const char *kbuf;
  while(!(kbuf = tcmapiternext(mdb->maps[mi], &ksiz)) && mi < TCMDBMNUM - 1){
    pthread_rwlock_unlock((pthread_rwlock_t *)mdb->mmtxs + mi);
    mi = ++mdb->iter;
    if(pthread_rwlock_wrlock((pthread_rwlock_t *)mdb->mmtxs + mi) != 0){
      pthread_mutex_unlock(mdb->imtx);
      return NULL;
    }
  }
  char *rv;
  if(kbuf){
    TCMEMDUP(rv, kbuf, ksiz);
    *sp = ksiz;
  } else {
    rv = NULL;
  }
  pthread_rwlock_unlock((pthread_rwlock_t *)mdb->mmtxs + mi);
  pthread_mutex_unlock(mdb->imtx);
  return rv;
}


/* Get the next key string of the iterator of an on-memory hash database. */
char *tcmdbiternext2(TCMDB *mdb){
  assert(mdb);
  int ksiz;
  return tcmdbiternext(mdb, &ksiz);
}


/* Get forward matching keys in an on-memory hash database object. */
TCLIST *tcmdbfwmkeys(TCMDB *mdb, const void *pbuf, int psiz, int max){
  assert(mdb && pbuf && psiz >= 0);
  TCLIST* keys = tclistnew();
  if(pthread_mutex_lock(mdb->imtx) != 0) return keys;
  if(max < 0) max = INT_MAX;
  for(int i = 0; i < TCMDBMNUM && TCLISTNUM(keys) < max; i++){
    if(pthread_rwlock_wrlock((pthread_rwlock_t *)mdb->mmtxs + i) == 0){
      TCMAP *map = mdb->maps[i];
      TCMAPREC *cur = map->cur;
      tcmapiterinit(map);
      const char *kbuf;
      int ksiz;
      while(TCLISTNUM(keys) < max && (kbuf = tcmapiternext(map, &ksiz)) != NULL){
        if(ksiz >= psiz && !memcmp(kbuf, pbuf, psiz)) TCLISTPUSH(keys, kbuf, ksiz);
      }
      map->cur = cur;
      pthread_rwlock_unlock((pthread_rwlock_t *)mdb->mmtxs + i);
    }
  }
  pthread_mutex_unlock(mdb->imtx);
  return keys;
}


/* Get forward matching string keys in an on-memory hash database object. */
TCLIST *tcmdbfwmkeys2(TCMDB *mdb, const char *pstr, int max){
  assert(mdb && pstr);
  return tcmdbfwmkeys(mdb, pstr, strlen(pstr), max);
}


/* Get the number of records stored in an on-memory hash database. */
uint64_t tcmdbrnum(TCMDB *mdb){
  assert(mdb);
  uint64_t rnum = 0;
  for(int i = 0; i < TCMDBMNUM; i++){
    rnum += tcmaprnum(mdb->maps[i]);
  }
  return rnum;
}


/* Get the total size of memory used in an on-memory hash database object. */
uint64_t tcmdbmsiz(TCMDB *mdb){
  assert(mdb);
  uint64_t msiz = 0;
  for(int i = 0; i < TCMDBMNUM; i++){
    msiz += tcmapmsiz(mdb->maps[i]);
  }
  return msiz;
}


/* Add an integer to a record in an on-memory hash database object. */
int tcmdbaddint(TCMDB *mdb, const void *kbuf, int ksiz, int num){
  assert(mdb && kbuf && ksiz >= 0);
  unsigned int mi;
  TCMDBHASH(mi, kbuf, ksiz);
  if(pthread_rwlock_wrlock((pthread_rwlock_t *)mdb->mmtxs + mi) != 0) return INT_MIN;
  int rv = tcmapaddint(mdb->maps[mi], kbuf, ksiz, num);
  pthread_rwlock_unlock((pthread_rwlock_t *)mdb->mmtxs + mi);
  return rv;
}


/* Add a real number to a record in an on-memory hash database object. */
double tcmdbadddouble(TCMDB *mdb, const void *kbuf, int ksiz, double num){
  assert(mdb && kbuf && ksiz >= 0);
  unsigned int mi;
  TCMDBHASH(mi, kbuf, ksiz);
  if(pthread_rwlock_wrlock((pthread_rwlock_t *)mdb->mmtxs + mi) != 0) return nan("");
  double rv = tcmapadddouble(mdb->maps[mi], kbuf, ksiz, num);
  pthread_rwlock_unlock((pthread_rwlock_t *)mdb->mmtxs + mi);
  return rv;
}


/* Clear an on-memory hash database object. */
void tcmdbvanish(TCMDB *mdb){
  assert(mdb);
  for(int i = 0; i < TCMDBMNUM; i++){
    if(pthread_rwlock_wrlock((pthread_rwlock_t *)mdb->mmtxs + i) == 0){
      tcmapclear(mdb->maps[i]);
      pthread_rwlock_unlock((pthread_rwlock_t *)mdb->mmtxs + i);
    }
  }
}


/* Remove front records of a map object. */
void tcmdbcutfront(TCMDB *mdb, int num){
  assert(mdb && num >= 0);
  num = num / TCMDBMNUM + 1;
  for(int i = 0; i < TCMDBMNUM; i++){
    if(pthread_rwlock_wrlock((pthread_rwlock_t *)mdb->mmtxs + i) == 0){
      tcmapcutfront(mdb->maps[i], num);
      pthread_rwlock_unlock((pthread_rwlock_t *)mdb->mmtxs + i);
    }
  }
}



/*************************************************************************************************
 * on-memory hash database (for experts)
 *************************************************************************************************/


/* Store a record and make it semivolatile in an on-memory hash database object. */
void tcmdbput3(TCMDB *mdb, const void *kbuf, int ksiz, const char *vbuf, int vsiz){
  assert(mdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  unsigned int mi;
  TCMDBHASH(mi, kbuf, ksiz);
  if(pthread_rwlock_wrlock((pthread_rwlock_t *)mdb->mmtxs + mi) != 0) return;
  tcmapput3(mdb->maps[mi], kbuf, ksiz, vbuf, vsiz);
  pthread_rwlock_unlock((pthread_rwlock_t *)mdb->mmtxs + mi);
}


/* Store a record of the value of two regions into an on-memory hash database object. */
void tcmdbput4(TCMDB *mdb, const void *kbuf, int ksiz,
               const void *fvbuf, int fvsiz, const void *lvbuf, int lvsiz){
  assert(mdb && kbuf && ksiz >= 0 && fvbuf && fvsiz >= 0 && lvbuf && lvsiz >= 0);
  unsigned int mi;
  TCMDBHASH(mi, kbuf, ksiz);
  if(pthread_rwlock_wrlock((pthread_rwlock_t *)mdb->mmtxs + mi) != 0) return;
  tcmapput4(mdb->maps[mi], kbuf, ksiz, fvbuf, fvsiz, lvbuf, lvsiz);
  pthread_rwlock_unlock((pthread_rwlock_t *)mdb->mmtxs + mi);
}


/* Concatenate a value and make it semivolatile in on-memory hash database object. */
void tcmdbputcat3(TCMDB *mdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(mdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  unsigned int mi;
  TCMDBHASH(mi, kbuf, ksiz);
  if(pthread_rwlock_wrlock((pthread_rwlock_t *)mdb->mmtxs + mi) != 0) return;
  tcmapputcat3(mdb->maps[mi], kbuf, ksiz, vbuf, vsiz);
  pthread_rwlock_unlock((pthread_rwlock_t *)mdb->mmtxs + mi);
}


/* Store a record into a on-memory hash database object with a duplication handler. */
bool tcmdbputproc(TCMDB *mdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz,
                  TCPDPROC proc, void *op){
  assert(mdb && kbuf && ksiz >= 0 && proc);
  unsigned int mi;
  TCMDBHASH(mi, kbuf, ksiz);
  if(pthread_rwlock_wrlock((pthread_rwlock_t *)mdb->mmtxs + mi) != 0) return false;
  bool rv = tcmapputproc(mdb->maps[mi], kbuf, ksiz, vbuf, vsiz, proc, op);
  pthread_rwlock_unlock((pthread_rwlock_t *)mdb->mmtxs + mi);
  return rv;
}


/* Retrieve a record and move it astern in an on-memory hash database. */
void *tcmdbget3(TCMDB *mdb, const void *kbuf, int ksiz, int *sp){
  assert(mdb && kbuf && ksiz >= 0 && sp);
  unsigned int mi;
  TCMDBHASH(mi, kbuf, ksiz);
  if(pthread_rwlock_wrlock((pthread_rwlock_t *)mdb->mmtxs + mi) != 0) return NULL;
  int vsiz;
  const char *vbuf = tcmapget3(mdb->maps[mi], kbuf, ksiz, &vsiz);
  char *rv;
  if(vbuf){
    TCMEMDUP(rv, vbuf, vsiz);
    *sp = vsiz;
  } else {
    rv = NULL;
  }
  pthread_rwlock_unlock((pthread_rwlock_t *)mdb->mmtxs + mi);
  return rv;
}


/* Initialize the iterator of an on-memory map database object in front of a key. */
void tcmdbiterinit2(TCMDB *mdb, const void *kbuf, int ksiz){
  if(pthread_mutex_lock(mdb->imtx) != 0) return;
  unsigned int mi;
  TCMDBHASH(mi, kbuf, ksiz);
  if(pthread_rwlock_rdlock((pthread_rwlock_t *)mdb->mmtxs + mi) != 0){
    pthread_mutex_unlock(mdb->imtx);
    return;
  }
  int vsiz;
  if(tcmapget(mdb->maps[mi], kbuf, ksiz, &vsiz)){
    for(int i = 0; i < TCMDBMNUM; i++){
      tcmapiterinit(mdb->maps[i]);
    }
    tcmapiterinit2(mdb->maps[mi], kbuf, ksiz);
    mdb->iter = mi;
  }
  pthread_rwlock_unlock((pthread_rwlock_t *)mdb->mmtxs + mi);
  pthread_mutex_unlock(mdb->imtx);
}


/* Initialize the iterator of an on-memory map database object in front of a key string. */
void tcmdbiterinit3(TCMDB *mdb, const char *kstr){
  assert(mdb && kstr);
  tcmdbiterinit2(mdb, kstr, strlen(kstr));
}


/* Process each record atomically of an on-memory hash database object. */
void tcmdbforeach(TCMDB *mdb, TCITER iter, void *op){
  assert(mdb && iter);
  for(int i = 0; i < TCMDBMNUM; i++){
    if(pthread_rwlock_wrlock((pthread_rwlock_t *)mdb->mmtxs + i) != 0){
      while(i >= 0){
        pthread_rwlock_unlock((pthread_rwlock_t *)mdb->mmtxs + i);
        i--;
      }
      return;
    }
  }
  bool cont = true;
  for(int i = 0; cont && i < TCMDBMNUM; i++){
    TCMAP *map = mdb->maps[i];
    TCMAPREC *cur = map->cur;
    tcmapiterinit(map);
    int ksiz;
    const char *kbuf;
    while(cont && (kbuf = tcmapiternext(map, &ksiz)) != NULL){
      int vsiz;
      const char *vbuf = tcmapiterval(kbuf, &vsiz);
      if(!iter(kbuf, ksiz, vbuf, vsiz, op)) cont = false;
    }
    map->cur = cur;
  }
  for(int i = TCMDBMNUM - 1; i >= 0; i--){
    pthread_rwlock_unlock((pthread_rwlock_t *)mdb->mmtxs + i);
  }
}



/*************************************************************************************************
 * on-memory tree database
 *************************************************************************************************/


/* Create an on-memory tree database object. */
TCNDB *tcndbnew(void){
  return tcndbnew2(tccmplexical, NULL);
}


/* Create an on-memory tree database object with specifying the custom comparison function. */
TCNDB *tcndbnew2(TCCMP cmp, void *cmpop){
  assert(cmp);
  TCNDB *ndb;
  TCMALLOC(ndb, sizeof(*ndb));
  TCMALLOC(ndb->mmtx, sizeof(pthread_mutex_t));
  if(pthread_mutex_init(ndb->mmtx, NULL) != 0) tcmyfatal("mutex error");
  ndb->tree = tctreenew2(cmp, cmpop);
  return ndb;
}


/* Delete an on-memory tree database object. */
void tcndbdel(TCNDB *ndb){
  assert(ndb);
  tctreedel(ndb->tree);
  pthread_mutex_destroy(ndb->mmtx);
  TCFREE(ndb->mmtx);
  TCFREE(ndb);
}


/* Store a record into an on-memory tree database object. */
void tcndbput(TCNDB *ndb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(ndb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  if(pthread_mutex_lock((pthread_mutex_t *)ndb->mmtx) != 0) return;
  tctreeput(ndb->tree, kbuf, ksiz, vbuf, vsiz);
  pthread_mutex_unlock((pthread_mutex_t *)ndb->mmtx);
}


/* Store a string record into an on-memory tree database object. */
void tcndbput2(TCNDB *ndb, const char *kstr, const char *vstr){
  assert(ndb && kstr && vstr);
  tcndbput(ndb, kstr, strlen(kstr), vstr, strlen(vstr));
}


/* Store a new record into an on-memory tree database object. */
bool tcndbputkeep(TCNDB *ndb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(ndb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  if(pthread_mutex_lock((pthread_mutex_t *)ndb->mmtx) != 0) return false;
  bool rv = tctreeputkeep(ndb->tree, kbuf, ksiz, vbuf, vsiz);
  pthread_mutex_unlock((pthread_mutex_t *)ndb->mmtx);
  return rv;
}


/* Store a new string record into an on-memory tree database object. */
bool tcndbputkeep2(TCNDB *ndb, const char *kstr, const char *vstr){
  assert(ndb && kstr && vstr);
  return tcndbputkeep(ndb, kstr, strlen(kstr), vstr, strlen(vstr));
}


/* Concatenate a value at the end of the existing record in an on-memory tree database. */
void tcndbputcat(TCNDB *ndb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(ndb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  if(pthread_mutex_lock((pthread_mutex_t *)ndb->mmtx) != 0) return;
  tctreeputcat(ndb->tree, kbuf, ksiz, vbuf, vsiz);
  pthread_mutex_unlock((pthread_mutex_t *)ndb->mmtx);
}


/* Concatenate a string at the end of the existing record in an on-memory tree database. */
void tcndbputcat2(TCNDB *ndb, const char *kstr, const char *vstr){
  assert(ndb && kstr && vstr);
  tcndbputcat(ndb, kstr, strlen(kstr), vstr, strlen(vstr));
}


/* Remove a record of an on-memory tree database object. */
bool tcndbout(TCNDB *ndb, const void *kbuf, int ksiz){
  assert(ndb && kbuf && ksiz >= 0);
  if(pthread_mutex_lock((pthread_mutex_t *)ndb->mmtx) != 0) return false;
  bool rv = tctreeout(ndb->tree, kbuf, ksiz);
  pthread_mutex_unlock((pthread_mutex_t *)ndb->mmtx);
  return rv;
}


/* Remove a string record of an on-memory tree database object. */
bool tcndbout2(TCNDB *ndb, const char *kstr){
  assert(ndb && kstr);
  return tcndbout(ndb, kstr, strlen(kstr));
}


/* Retrieve a record in an on-memory tree database object. */
void *tcndbget(TCNDB *ndb, const void *kbuf, int ksiz, int *sp){
  assert(ndb && kbuf && ksiz >= 0 && sp);
  if(pthread_mutex_lock((pthread_mutex_t *)ndb->mmtx) != 0) return NULL;
  int vsiz;
  const char *vbuf = tctreeget(ndb->tree, kbuf, ksiz, &vsiz);
  char *rv;
  if(vbuf){
    TCMEMDUP(rv, vbuf, vsiz);
    *sp = vsiz;
  } else {
    rv = NULL;
  }
  pthread_mutex_unlock((pthread_mutex_t *)ndb->mmtx);
  return rv;
}


/* Retrieve a string record in an on-memory tree database object. */
char *tcndbget2(TCNDB *ndb, const char *kstr){
  assert(ndb && kstr);
  int vsiz;
  return tcndbget(ndb, kstr, strlen(kstr), &vsiz);
}


/* Get the size of the value of a record in an on-memory tree database object. */
int tcndbvsiz(TCNDB *ndb, const void *kbuf, int ksiz){
  assert(ndb && kbuf && ksiz >= 0);
  if(pthread_mutex_lock((pthread_mutex_t *)ndb->mmtx) != 0) return -1;
  int vsiz;
  const char *vbuf = tctreeget(ndb->tree, kbuf, ksiz, &vsiz);
  if(!vbuf) vsiz = -1;
  pthread_mutex_unlock((pthread_mutex_t *)ndb->mmtx);
  return vsiz;
}


/* Get the size of the value of a string record in an on-memory tree database object. */
int tcndbvsiz2(TCNDB *ndb, const char *kstr){
  assert(ndb && kstr);
  return tcndbvsiz(ndb, kstr, strlen(kstr));
}


/* Initialize the iterator of an on-memory tree database object. */
void tcndbiterinit(TCNDB *ndb){
  assert(ndb);
  if(pthread_mutex_lock((pthread_mutex_t *)ndb->mmtx) != 0) return;
  tctreeiterinit(ndb->tree);
  pthread_mutex_unlock((pthread_mutex_t *)ndb->mmtx);
}


/* Get the next key of the iterator of an on-memory tree database object. */
void *tcndbiternext(TCNDB *ndb, int *sp){
  assert(ndb && sp);
  if(pthread_mutex_lock((pthread_mutex_t *)ndb->mmtx) != 0) return NULL;
  int ksiz;
  const char *kbuf = tctreeiternext(ndb->tree, &ksiz);
  char *rv;
  if(kbuf){
    TCMEMDUP(rv, kbuf, ksiz);
    *sp = ksiz;
  } else {
    rv = NULL;
  }
  pthread_mutex_unlock((pthread_mutex_t *)ndb->mmtx);
  return rv;
}


/* Get the next key string of the iterator of an on-memory tree database object. */
char *tcndbiternext2(TCNDB *ndb){
  assert(ndb);
  int ksiz;
  return tcndbiternext(ndb, &ksiz);
}


/* Get forward matching keys in an on-memory tree database object. */
TCLIST *tcndbfwmkeys(TCNDB *ndb, const void *pbuf, int psiz, int max){
  assert(ndb && pbuf && psiz >= 0);
  TCLIST* keys = tclistnew();
  if(pthread_mutex_lock(ndb->mmtx) != 0) return keys;
  if(max < 0) max = INT_MAX;
  TCTREE *tree = ndb->tree;
  TCTREEREC *cur = tree->cur;
  tctreeiterinit2(tree, pbuf, psiz);
  const char *lbuf = NULL;
  int lsiz = 0;
  const char *kbuf;
  int ksiz;
  while(TCLISTNUM(keys) < max && (kbuf = tctreeiternext(tree, &ksiz)) != NULL){
    if(ksiz < psiz || memcmp(kbuf, pbuf, psiz)) break;
    if(!lbuf || lsiz != ksiz || memcmp(kbuf, lbuf, ksiz)){
      TCLISTPUSH(keys, kbuf, ksiz);
      if(TCLISTNUM(keys) >= max) break;
      lbuf = kbuf;
      lsiz = ksiz;
    }
  }
  tree->cur = cur;
  pthread_mutex_unlock(ndb->mmtx);
  return keys;
}


/* Get forward matching string keys in an on-memory tree database object. */
TCLIST *tcndbfwmkeys2(TCNDB *ndb, const char *pstr, int max){
  assert(ndb && pstr);
  return tcndbfwmkeys(ndb, pstr, strlen(pstr), max);
}


/* Get the number of records stored in an on-memory tree database object. */
uint64_t tcndbrnum(TCNDB *ndb){
  assert(ndb);
  return tctreernum(ndb->tree);
}


/* Get the total size of memory used in an on-memory tree database object. */
uint64_t tcndbmsiz(TCNDB *ndb){
  assert(ndb);
  return tctreemsiz(ndb->tree);
}


/* Add an integer to a record in an on-memory tree database object. */
int tcndbaddint(TCNDB *ndb, const void *kbuf, int ksiz, int num){
  assert(ndb && kbuf && ksiz >= 0);
  if(pthread_mutex_lock((pthread_mutex_t *)ndb->mmtx) != 0) return INT_MIN;
  int rv = tctreeaddint(ndb->tree, kbuf, ksiz, num);
  pthread_mutex_unlock((pthread_mutex_t *)ndb->mmtx);
  return rv;
}


/* Add a real number to a record in an on-memory tree database object. */
double tcndbadddouble(TCNDB *ndb, const void *kbuf, int ksiz, double num){
  assert(ndb && kbuf && ksiz >= 0);
  if(pthread_mutex_lock((pthread_mutex_t *)ndb->mmtx) != 0) return nan("");
  double rv = tctreeadddouble(ndb->tree, kbuf, ksiz, num);
  pthread_mutex_unlock((pthread_mutex_t *)ndb->mmtx);
  return rv;
}


/* Clear an on-memory tree database object. */
void tcndbvanish(TCNDB *ndb){
  assert(ndb);
  if(pthread_mutex_lock((pthread_mutex_t *)ndb->mmtx) != 0);
  tctreeclear(ndb->tree);
  pthread_mutex_unlock((pthread_mutex_t *)ndb->mmtx);
}


/* Remove fringe records of an on-memory tree database object. */
void tcndbcutfringe(TCNDB *ndb, int num){
  assert(ndb && num >= 0);
  if(pthread_mutex_lock((pthread_mutex_t *)ndb->mmtx) != 0);
  tctreecutfringe(ndb->tree, num);
  pthread_mutex_unlock((pthread_mutex_t *)ndb->mmtx);
}



/*************************************************************************************************
 * ordered tree (for experts)
 *************************************************************************************************/


/* Store a record into a on-memory tree database without balancing nodes. */
void tcndbput3(TCNDB *ndb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(ndb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  if(pthread_mutex_lock((pthread_mutex_t *)ndb->mmtx) != 0) return;
  tctreeput3(ndb->tree, kbuf, ksiz, vbuf, vsiz);
  pthread_mutex_unlock((pthread_mutex_t *)ndb->mmtx);
}


/* Store a new record into a on-memory tree database object without balancing nodes. */
bool tcndbputkeep3(TCNDB *ndb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(ndb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  if(pthread_mutex_lock((pthread_mutex_t *)ndb->mmtx) != 0) return false;
  bool rv = tctreeputkeep3(ndb->tree, kbuf, ksiz, vbuf, vsiz);
  pthread_mutex_unlock((pthread_mutex_t *)ndb->mmtx);
  return rv;
}


/* Concatenate a value in a on-memory tree database without balancing nodes. */
void tcndbputcat3(TCNDB *ndb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(ndb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  if(pthread_mutex_lock((pthread_mutex_t *)ndb->mmtx) != 0) return;
  tctreeputcat3(ndb->tree, kbuf, ksiz, vbuf, vsiz);
  pthread_mutex_unlock((pthread_mutex_t *)ndb->mmtx);
}


/* Store a record into a on-memory tree database object with a duplication handler. */
bool tcndbputproc(TCNDB *ndb, const void *kbuf, int ksiz, const void *vbuf, int vsiz,
                  TCPDPROC proc, void *op){
  assert(ndb && kbuf && ksiz >= 0 && proc);
  if(pthread_mutex_lock((pthread_mutex_t *)ndb->mmtx) != 0) return false;
  bool rv = tctreeputproc(ndb->tree, kbuf, ksiz, vbuf, vsiz, proc, op);
  pthread_mutex_unlock((pthread_mutex_t *)ndb->mmtx);
  return rv;
}


/* Retrieve a record in an on-memory tree database object without balancing nodes. */
void *tcndbget3(TCNDB *ndb, const void *kbuf, int ksiz, int *sp){
  assert(ndb && kbuf && ksiz >= 0 && sp);
  if(pthread_mutex_lock((pthread_mutex_t *)ndb->mmtx) != 0) return NULL;
  int vsiz;
  const char *vbuf = tctreeget3(ndb->tree, kbuf, ksiz, &vsiz);
  char *rv;
  if(vbuf){
    TCMEMDUP(rv, vbuf, vsiz);
    *sp = vsiz;
  } else {
    rv = NULL;
  }
  pthread_mutex_unlock((pthread_mutex_t *)ndb->mmtx);
  return rv;
}


/* Initialize the iterator of an on-memory tree database object in front of a key. */
void tcndbiterinit2(TCNDB *ndb, const void *kbuf, int ksiz){
  assert(ndb && kbuf && ksiz >= 0);
  if(pthread_mutex_lock((pthread_mutex_t *)ndb->mmtx) != 0) return;
  tctreeiterinit2(ndb->tree, kbuf, ksiz);
  pthread_mutex_unlock((pthread_mutex_t *)ndb->mmtx);
}


/* Initialize the iterator of an on-memory tree database object in front of a key string. */
void tcndbiterinit3(TCNDB *ndb, const char *kstr){
  assert(ndb && kstr);
  tcndbiterinit2(ndb, kstr, strlen(kstr));
}


/* Process each record atomically of an on-memory tree database object. */
void tcndbforeach(TCNDB *ndb, TCITER iter, void *op){
  assert(ndb && iter);
  if(pthread_mutex_lock((pthread_mutex_t *)ndb->mmtx) != 0) return;
  TCTREE *tree = ndb->tree;
  TCTREEREC *cur = tree->cur;
  tctreeiterinit(tree);
  int ksiz;
  const char *kbuf;
  while((kbuf = tctreeiternext(tree, &ksiz)) != NULL){
    int vsiz;
    const char *vbuf = tctreeiterval(kbuf, &vsiz);
    if(!iter(kbuf, ksiz, vbuf, vsiz, op)) break;
  }
  tree->cur = cur;
  pthread_mutex_unlock((pthread_mutex_t *)ndb->mmtx);
}



/*************************************************************************************************
 * memory pool
 *************************************************************************************************/


#define TCMPOOLUNIT    128               // allocation unit size of memory pool elements


/* Global memory pool object. */
TCMPOOL *tcglobalmemorypool = NULL;


/* private function prototypes */
static void tcmpooldelglobal(void);


/* Create a memory pool object. */
TCMPOOL *tcmpoolnew(void){
  TCMPOOL *mpool;
  TCMALLOC(mpool, sizeof(*mpool));
  TCMALLOC(mpool->mutex, sizeof(pthread_mutex_t));
  if(pthread_mutex_init(mpool->mutex, NULL) != 0) tcmyfatal("locking failed");
  mpool->anum = TCMPOOLUNIT;
  TCMALLOC(mpool->elems, sizeof(mpool->elems[0]) * mpool->anum);
  mpool->num = 0;
  return mpool;
}


/* Delete a memory pool object. */
void tcmpooldel(TCMPOOL *mpool){
  assert(mpool);
  TCMPELEM *elems = mpool->elems;
  for(int i = mpool->num - 1; i >= 0; i--){
    elems[i].del(elems[i].ptr);
  }
  TCFREE(elems);
  pthread_mutex_destroy(mpool->mutex);
  TCFREE(mpool->mutex);
  TCFREE(mpool);
}


/* Relegate an arbitrary object to a memory pool object. */
void *tcmpoolpush(TCMPOOL *mpool, void *ptr, void (*del)(void *)){
  assert(mpool && del);
  if(!ptr) return NULL;
  if(pthread_mutex_lock(mpool->mutex) != 0) tcmyfatal("locking failed");
  int num = mpool->num;
  if(num >= mpool->anum){
    mpool->anum *= 2;
    TCREALLOC(mpool->elems, mpool->elems, mpool->anum * sizeof(mpool->elems[0]));
  }
  mpool->elems[num].ptr = ptr;
  mpool->elems[num].del = del;
  mpool->num++;
  pthread_mutex_unlock(mpool->mutex);
  return ptr;
}


/* Relegate an allocated region to a memory pool object. */
void *tcmpoolpushptr(TCMPOOL *mpool, void *ptr){
  assert(mpool);
  return tcmpoolpush(mpool, ptr, (void (*)(void *))free);
}


/* Relegate an extensible string object to a memory pool object. */
TCXSTR *tcmpoolpushxstr(TCMPOOL *mpool, TCXSTR *xstr){
  assert(mpool);
  return tcmpoolpush(mpool, xstr, (void (*)(void *))tcxstrdel);
}


/* Relegate a list object to a memory pool object. */
TCLIST *tcmpoolpushlist(TCMPOOL *mpool, TCLIST *list){
  assert(mpool);
  return tcmpoolpush(mpool, list, (void (*)(void *))tclistdel);
}


/* Relegate a map object to a memory pool object. */
TCMAP *tcmpoolpushmap(TCMPOOL *mpool, TCMAP *map){
  assert(mpool);
  return tcmpoolpush(mpool, map, (void (*)(void *))tcmapdel);
}


/* Relegate a tree object to a memory pool object. */
TCTREE *tcmpoolpushtree(TCMPOOL *mpool, TCTREE *tree){
  assert(mpool);
  return tcmpoolpush(mpool, tree, (void (*)(void *))tctreedel);
}


/* Allocate a region relegated to a memory pool object. */
void *tcmpoolmalloc(TCMPOOL *mpool, size_t size){
  assert(mpool && size > 0);
  void *ptr;
  TCMALLOC(ptr, size);
  tcmpoolpush(mpool, ptr, (void (*)(void *))free);
  return ptr;
}


/* Create an extensible string object relegated to a memory pool object. */
TCXSTR *tcmpoolxstrnew(TCMPOOL *mpool){
  assert(mpool);
  TCXSTR *xstr = tcxstrnew();
  tcmpoolpush(mpool, xstr, (void (*)(void *))tcxstrdel);
  return xstr;
}


/* Create a list object relegated to a memory pool object. */
TCLIST *tcmpoollistnew(TCMPOOL *mpool){
  assert(mpool);
  TCLIST *list = tclistnew();
  tcmpoolpush(mpool, list, (void (*)(void *))tclistdel);
  return list;
}


/* Create a map object relegated to a memory pool object. */
TCMAP *tcmpoolmapnew(TCMPOOL *mpool){
  assert(mpool);
  TCMAP *map = tcmapnew();
  tcmpoolpush(mpool, map, (void (*)(void *))tcmapdel);
  return map;
}


/* Create a tree object relegated to a memory pool object. */
TCTREE *tcmpooltreenew(TCMPOOL *mpool){
  assert(mpool);
  TCTREE *tree = tctreenew();
  tcmpoolpush(mpool, tree, (void (*)(void *))tctreedel);
  return tree;
}


/* Remove the most recently installed cleanup handler of a memory pool object. */
void tcmpoolpop(TCMPOOL *mpool, bool exe){
  assert(mpool);
  if(pthread_mutex_lock(mpool->mutex) != 0) tcmyfatal("locking failed");
  if(mpool->num > 0){
    mpool->num--;
    if(exe) mpool->elems[mpool->num].del(mpool->elems[mpool->num].ptr);
  }
  pthread_mutex_unlock(mpool->mutex);
}


/* Remove all cleanup handler of a memory pool object.
   `mpool' specifies the memory pool object. */
void tcmpoolclear(TCMPOOL *mpool, bool exe){
  assert(mpool);
  if(pthread_mutex_lock(mpool->mutex) != 0) tcmyfatal("locking failed");
  if(exe){
    for(int i = mpool->num - 1; i >= 0; i--){
      mpool->elems[i].del(mpool->elems[i].ptr);
    }
  }
  mpool->num = 0;
  pthread_mutex_unlock(mpool->mutex);
}


/* Get the global memory pool object. */
TCMPOOL *tcmpoolglobal(void){
  if(tcglobalmemorypool) return tcglobalmemorypool;
  tcglobalmemorypool = tcmpoolnew();
  atexit(tcmpooldelglobal);
  return tcglobalmemorypool;
}


/* Detete the global memory pool object. */
static void tcmpooldelglobal(void){
  if(tcglobalmemorypool) tcmpooldel(tcglobalmemorypool);
}



/*************************************************************************************************
 * miscellaneous utilities
 *************************************************************************************************/


#define TCRANDDEV      "/dev/urandom"    // path of the random device file
#define TCDISTMAXLEN   4096              // maximum size of a string for distance checking
#define TCDISTBUFSIZ   16384             // size of a distance buffer
#define TCLDBLCOLMAX   16                // maximum number of columns of the long double


/* File descriptor of random number generator. */
int tcrandomdevfd = -1;


/* private function prototypes */
static void tcrandomfdclose(void);
static time_t tcmkgmtime(struct tm *tm);


/* Get the larger value of two integers. */
long tclmax(long a, long b){
  return (a > b) ? a : b;
}


/* Get the lesser value of two integers. */
long tclmin(long a, long b){
  return (a < b) ? a : b;
}


/* Get a random number as long integer based on uniform distribution. */
unsigned long tclrand(void){
  static uint32_t cnt = 0;
  static uint64_t seed = 0;
  static uint64_t mask = 0;
  static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  if((cnt & 0xff) == 0 && pthread_mutex_lock(&mutex) == 0){
    if(cnt == 0) seed += time(NULL);
    if(tcrandomdevfd == -1 && (tcrandomdevfd = open(TCRANDDEV, O_RDONLY, 00644)) != -1)
      atexit(tcrandomfdclose);
    if(tcrandomdevfd == -1 || read(tcrandomdevfd, &mask, sizeof(mask)) != sizeof(mask)){
      double t = tctime();
      uint64_t tmask;
      memcpy(&tmask, &t, tclmin(sizeof(t), sizeof(tmask)));
      mask = (mask << 8) ^ tmask;
    }
    pthread_mutex_unlock(&mutex);
  }
  seed = seed * 123456789012301LL + 211;
  uint64_t num = (mask ^ cnt++) ^ seed;
  return TCSWAB64(num);
}


/* Get a random number as double decimal based on uniform distribution. */
double tcdrand(void){
  double val = tclrand() / (double)ULONG_MAX;
  return val < 1.0 ? val : 0.0;
}


/* Get a random number as double decimal based on normal distribution. */
double tcdrandnd(double avg, double sd){
  assert(sd >= 0.0);
  return sqrt(-2.0 * log(tcdrand())) * cos(2 * 3.141592653589793 * tcdrand()) * sd + avg;
}


/* Compare two strings with case insensitive evaluation. */
int tcstricmp(const char *astr, const char *bstr){
  assert(astr && bstr);
  while(*astr != '\0'){
    if(*bstr == '\0') return 1;
    int ac = (*astr >= 'A' && *astr <= 'Z') ? *astr + ('a' - 'A') : *(unsigned char *)astr;
    int bc = (*bstr >= 'A' && *bstr <= 'Z') ? *bstr + ('a' - 'A') : *(unsigned char *)bstr;
    if(ac != bc) return ac - bc;
    astr++;
    bstr++;
  }
  return (*bstr == '\0') ? 0 : -1;
}


/* Check whether a string begins with a key. */
bool tcstrfwm(const char *str, const char *key){
  assert(str && key);
  while(*key != '\0'){
    if(*str != *key || *str == '\0') return false;
    key++;
    str++;
  }
  return true;
}


/* Check whether a string begins with a key with case insensitive evaluation. */
bool tcstrifwm(const char *str, const char *key){
  assert(str && key);
  while(*key != '\0'){
    if(*str == '\0') return false;
    int sc = *str;
    if(sc >= 'A' && sc <= 'Z') sc += 'a' - 'A';
    int kc = *key;
    if(kc >= 'A' && kc <= 'Z') kc += 'a' - 'A';
    if(sc != kc) return false;
    key++;
    str++;
  }
  return true;
}


/* Check whether a string ends with a key. */
bool tcstrbwm(const char *str, const char *key){
  assert(str && key);
  int slen = strlen(str);
  int klen = strlen(key);
  for(int i = 1; i <= klen; i++){
    if(i > slen || str[slen-i] != key[klen-i]) return false;
  }
  return true;
}


/* Check whether a string ends with a key with case insensitive evaluation. */
bool tcstribwm(const char *str, const char *key){
  assert(str && key);
  int slen = strlen(str);
  int klen = strlen(key);
  for(int i = 1; i <= klen; i++){
    if(i > slen) return false;
    int sc = str[slen-i];
    if(sc >= 'A' && sc <= 'Z') sc += 'a' - 'A';
    int kc = key[klen-i];
    if(kc >= 'A' && kc <= 'Z') kc += 'a' - 'A';
    if(sc != kc) return false;
  }
  return true;
}


/* Calculate the edit distance of two strings. */
int tcstrdist(const char *astr, const char *bstr){
  assert(astr && bstr);
  int alen = tclmin(strlen(astr), TCDISTMAXLEN);
  int blen = tclmin(strlen(bstr), TCDISTMAXLEN);
  int dsiz = blen + 1;
  int tbuf[TCDISTBUFSIZ];
  int *tbl;
  if((alen + 1) * dsiz < TCDISTBUFSIZ){
    tbl = tbuf;
  } else {
    TCMALLOC(tbl, (alen + 1) * dsiz * sizeof(*tbl));
  }
  for(int i = 0; i <= alen; i++){
    tbl[i*dsiz] = i;
  }
  for(int i = 1; i <= blen; i++){
    tbl[i] = i;
  }
  astr--;
  bstr--;
  for(int i = 1; i <= alen; i++){
    for(int j = 1; j <= blen; j++){
      int ac = tbl[(i-1)*dsiz+j] + 1;
      int bc = tbl[i*dsiz+j-1] + 1;
      int cc = tbl[(i-1)*dsiz+j-1] + (astr[i] != bstr[j]);
      ac = ac < bc ? ac : bc;
      tbl[i*dsiz+j] = ac < cc ? ac : cc;
    }
  }
  int rv = tbl[alen*dsiz+blen];
  if(tbl != tbuf) TCFREE(tbl);
  return rv;
}


/* Calculate the edit distance of two UTF-8 strings. */
int tcstrdistutf(const char *astr, const char *bstr){
  assert(astr && bstr);
  int alen = strlen(astr);
  uint16_t abuf[TCDISTBUFSIZ];
  uint16_t *aary;
  if(alen < TCDISTBUFSIZ){
    aary = abuf;
  } else {
    TCMALLOC(aary, alen * sizeof(*aary));
  }
  tcstrutftoucs(astr, aary, &alen);
  int blen = strlen(bstr);
  uint16_t bbuf[TCDISTBUFSIZ];
  uint16_t *bary;
  if(blen < TCDISTBUFSIZ){
    bary = bbuf;
  } else {
    TCMALLOC(bary, blen * sizeof(*bary));
  }
  tcstrutftoucs(bstr, bary, &blen);
  if(alen > TCDISTMAXLEN) alen = TCDISTMAXLEN;
  if(blen > TCDISTMAXLEN) blen = TCDISTMAXLEN;
  int dsiz = blen + 1;
  int tbuf[TCDISTBUFSIZ];
  int *tbl;
  if((alen + 1) * dsiz < TCDISTBUFSIZ){
    tbl = tbuf;
  } else {
    TCMALLOC(tbl, (alen + 1) * dsiz * sizeof(*tbl));
  }
  for(int i = 0; i <= alen; i++){
    tbl[i*dsiz] = i;
  }
  for(int i = 1; i <= blen; i++){
    tbl[i] = i;
  }
  aary--;
  bary--;
  for(int i = 1; i <= alen; i++){
    for(int j = 1; j <= blen; j++){
      int ac = tbl[(i-1)*dsiz+j] + 1;
      int bc = tbl[i*dsiz+j-1] + 1;
      int cc = tbl[(i-1)*dsiz+j-1] + (aary[i] != bary[j]);
      ac = ac < bc ? ac : bc;
      tbl[i*dsiz+j] = ac < cc ? ac : cc;
    }
  }
  aary++;
  bary++;
  int rv = tbl[alen*dsiz+blen];
  if(tbl != tbuf) TCFREE(tbl);
  if(bary != bbuf) TCFREE(bary);
  if(aary != abuf) TCFREE(aary);
  return rv;
}


/* Convert the letters of a string into upper case. */
char *tcstrtoupper(char *str){
  assert(str);
  char *wp = str;
  while(*wp != '\0'){
    if(*wp >= 'a' && *wp <= 'z') *wp -= 'a' - 'A';
    wp++;
  }
  return str;
}


/* Convert the letters of a string into lower case. */
char *tcstrtolower(char *str){
  assert(str);
  char *wp = str;
  while(*wp != '\0'){
    if(*wp >= 'A' && *wp <= 'Z') *wp += 'a' - 'A';
    wp++;
  }
  return str;
}


/* Cut space characters at head or tail of a string. */
char *tcstrtrim(char *str){
  assert(str);
  const char *rp = str;
  char *wp = str;
  bool head = true;
  while(*rp != '\0'){
    if(*rp > '\0' && *rp <= ' '){
      if(!head) *(wp++) = *rp;
    } else {
      *(wp++) = *rp;
      head = false;
    }
    rp++;
  }
  *wp = '\0';
  while(wp > str && wp[-1] > '\0' && wp[-1] <= ' '){
    *(--wp) = '\0';
  }
  return str;
}


/* Squeeze space characters in a string and trim it. */
char *tcstrsqzspc(char *str){
  assert(str);
  char *rp = str;
  char *wp = str;
  bool spc = true;
  while(*rp != '\0'){
    if(*rp > 0 && *rp <= ' '){
      if(!spc) *(wp++) = *rp;
      spc = true;
    } else {
      *(wp++) = *rp;
      spc = false;
    }
    rp++;
  }
  *wp = '\0';
  for(wp--; wp >= str; wp--){
    if(*wp > 0 && *wp <= ' '){
      *wp = '\0';
    } else {
      break;
    }
  }
  return str;
}


/* Substitute characters in a string. */
char *tcstrsubchr(char *str, const char *rstr, const char *sstr){
  assert(str && rstr && sstr);
  int slen = strlen(sstr);
  char *wp = str;
  for(int i = 0; str[i] != '\0'; i++){
    const char *p = strchr(rstr, str[i]);
    if(p){
      int idx = p - rstr;
      if(idx < slen) *(wp++) = sstr[idx];
    } else {
      *(wp++) = str[i];
    }
  }
  *wp = '\0';
  return str;
}


/* Count the number of characters in a string of UTF-8. */
int tcstrcntutf(const char *str){
  assert(str);
  const unsigned char *rp = (unsigned char *)str;
  int cnt = 0;
  while(*rp != '\0'){
    if((*rp & 0x80) == 0x00 || (*rp & 0xe0) == 0xc0 ||
       (*rp & 0xf0) == 0xe0 || (*rp & 0xf8) == 0xf0) cnt++;
    rp++;
  }
  return cnt;
}


/* Cut a string of UTF-8 at the specified number of characters. */
char *tcstrcututf(char *str, int num){
  assert(str && num >= 0);
  unsigned char *wp = (unsigned char *)str;
  int cnt = 0;
  while(*wp != '\0'){
    if((*wp & 0x80) == 0x00 || (*wp & 0xe0) == 0xc0 ||
       (*wp & 0xf0) == 0xe0 || (*wp & 0xf8) == 0xf0){
      cnt++;
      if(cnt > num){
        *wp = '\0';
        break;
      }
    }
    wp++;
  }
  return str;
}


/* Convert a UTF-8 string into a UCS-2 array. */
void tcstrutftoucs(const char *str, uint16_t *ary, int *np){
  assert(str && ary && np);
  const unsigned char *rp = (unsigned char *)str;
  unsigned int wi = 0;
  while(*rp != '\0'){
    int c = *(unsigned char *)rp;
    if(c < 0x80){
      ary[wi++] = c;
    } else if(c < 0xe0){
      if(rp[1] >= 0x80){
        ary[wi++] = ((rp[0] & 0x1f) << 6) | (rp[1] & 0x3f);
        rp++;
      }
    } else if(c < 0xf0){
      if(rp[1] >= 0x80 && rp[2] >= 0x80){
        ary[wi++] = ((rp[0] & 0xf) << 12) | ((rp[1] & 0x3f) << 6) | (rp[2] & 0x3f);
        rp += 2;
      }
    }
    rp++;
  }
  *np = wi;
}


/* Convert a UCS-2 array into a UTF-8 string. */
int tcstrucstoutf(const uint16_t *ary, int num, char *str){
  assert(ary && num >= 0 && str);
  unsigned char *wp = (unsigned char *)str;
  for(int i = 0; i < num; i++){
    unsigned int c = ary[i];
    if(c < 0x80){
      *(wp++) = c;
    } else if(c < 0x800){
      *(wp++) = 0xc0 | (c >> 6);
      *(wp++) = 0x80 | (c & 0x3f);
    } else {
      *(wp++) = 0xe0 | (c >> 12);
      *(wp++) = 0x80 | ((c & 0xfff) >> 6);
      *(wp++) = 0x80 | (c & 0x3f);
    }
  }
  *wp = '\0';
  return (char *)wp - str;
}


/* Create a list object by splitting a string. */
TCLIST *tcstrsplit(const char *str, const char *delims){
  assert(str && delims);
  TCLIST *list = tclistnew();
  while(true){
    const char *sp = str;
    while(*str != '\0' && !strchr(delims, *str)){
      str++;
    }
    TCLISTPUSH(list, sp, str - sp);
    if(*str == '\0') break;
    str++;
  }
  return list;
}


/* Create a string by joining all elements of a list object. */
char *tcstrjoin(const TCLIST *list, char delim){
  assert(list);
  int num = TCLISTNUM(list);
  int size = num + 1;
  for(int i = 0; i < num; i++){
    size += TCLISTVALSIZ(list, i);
  }
  char *buf;
  TCMALLOC(buf, size);
  char *wp = buf;
  for(int i = 0; i < num; i++){
    if(i > 0) *(wp++) = delim;
    int vsiz;
    const char *vbuf = tclistval(list, i, &vsiz);
    memcpy(wp, vbuf, vsiz);
    wp += vsiz;
  }
  *wp = '\0';
  return buf;
}


/* Convert a string to an integer. */
int64_t tcatoi(const char *str){
  assert(str);
  while(*str > '\0' && *str <= ' '){
    str++;
  }
  int sign = 1;
  int64_t num = 0;
  if(*str == '-'){
    str++;
    sign = -1;
  } else if(*str == '+'){
    str++;
  }
  while(*str != '\0'){
    if(*str < '0' || *str > '9') break;
    num = num * 10 + *str - '0';
    str++;
  }
  return num * sign;
}


/* Convert a string with a metric prefix to an integer. */
int64_t tcatoix(const char *str){
  assert(str);
  while(*str > '\0' && *str <= ' '){
    str++;
  }
  int sign = 1;
  if(*str == '-'){
    str++;
    sign = -1;
  } else if(*str == '+'){
    str++;
  }
  long double num = 0;
  while(*str != '\0'){
    if(*str < '0' || *str > '9') break;
    num = num * 10 + *str - '0';
    str++;
  }
  if(*str == '.'){
    str++;
    long double base = 10;
    while(*str != '\0'){
      if(*str < '0' || *str > '9') break;
      num += (*str - '0') / base;
      str++;
      base *= 10;
    }
  }
  num *= sign;
  while(*str > '\0' && *str <= ' '){
    str++;
  }
  if(*str == 'k' || *str == 'K'){
    num *= 1LL << 10;
  } else if(*str == 'm' || *str == 'M'){
    num *= 1LL << 20;
  } else if(*str == 'g' || *str == 'G'){
    num *= 1LL << 30;
  } else if(*str == 't' || *str == 'T'){
    num *= 1LL << 40;
  } else if(*str == 'p' || *str == 'P'){
    num *= 1LL << 50;
  } else if(*str == 'e' || *str == 'E'){
    num *= 1LL << 60;
  }
  if(num > INT64_MAX) return INT64_MAX;
  if(num < INT64_MIN) return INT64_MIN;
  return num;
}


/* Convert a string to a real number. */
double tcatof(const char *str){
  assert(str);
  while(*str > '\0' && *str <= ' '){
    str++;
  }
  int sign = 1;
  if(*str == '-'){
    str++;
    sign = -1;
  } else if(*str == '+'){
    str++;
  }
  if(tcstrifwm(str, "inf")) return HUGE_VAL * sign;
  if(tcstrifwm(str, "nan")) return nan("");
  long double num = 0;
  int col = 0;
  while(*str != '\0'){
    if(*str < '0' || *str > '9') break;
    num = num * 10 + *str - '0';
    str++;
    if(num > 0) col++;
  }
  if(*str == '.'){
    str++;
    long double fract = 0.0;
    long double base = 10;
    while(col < TCLDBLCOLMAX && *str != '\0'){
      if(*str < '0' || *str > '9') break;
      fract += (*str - '0') / base;
      str++;
      col++;
      base *= 10;
    }
    num += fract;
  }
  if(*str == 'e' || *str == 'E'){
    str++;
    num *= pow(10, tcatoi(str));
  }
  return num * sign;
}


/* Check whether a string matches a regular expression. */
bool tcregexmatch(const char *str, const char *regex){
  assert(str && regex);
  int options = REG_EXTENDED | REG_NOSUB;
  if(*regex == '*'){
    options |= REG_ICASE;
    regex++;
  }
  regex_t rbuf;
  if(regcomp(&rbuf, regex, options) != 0) return false;
  bool rv = regexec(&rbuf, str, 0, NULL, 0) == 0;
  regfree(&rbuf);
  return rv;
}


/* Replace each substring matching a regular expression string. */
char *tcregexreplace(const char *str, const char *regex, const char *alt){
  assert(str && regex && alt);
  int options = REG_EXTENDED;
  if(*regex == '*'){
    options |= REG_ICASE;
    regex++;
  }
  regex_t rbuf;
  if(regex[0] == '\0' || regcomp(&rbuf, regex, options) != 0) return tcstrdup(str);
  regmatch_t subs[256];
  if(regexec(&rbuf, str, 32, subs, 0) != 0){
    regfree(&rbuf);
    return tcstrdup(str);
  }
  const char *sp = str;
  TCXSTR *xstr = tcxstrnew();
  bool first = true;
  while(sp[0] != '\0' && regexec(&rbuf, sp, 10, subs, first ? 0 : REG_NOTBOL) == 0){
    first = false;
    if(subs[0].rm_so == -1) break;
    tcxstrcat(xstr, sp, subs[0].rm_so);
    for(const char *rp = alt; *rp != '\0'; rp++){
      if(*rp == '\\'){
        if(rp[1] >= '0' && rp[1] <= '9'){
          int num = rp[1] - '0';
          if(subs[num].rm_so != -1 && subs[num].rm_eo != -1)
            tcxstrcat(xstr, sp + subs[num].rm_so, subs[num].rm_eo - subs[num].rm_so);
          ++rp;
        } else if(rp[1] != '\0'){
          tcxstrcat(xstr, ++rp, 1);
        }
      } else if(*rp == '&'){
        tcxstrcat(xstr, sp + subs[0].rm_so, subs[0].rm_eo - subs[0].rm_so);
      } else {
        tcxstrcat(xstr, rp, 1);
      }
    }
    sp += subs[0].rm_eo;
    if(subs[0].rm_eo < 1) break;
  }
  tcxstrcat2(xstr, sp);
  regfree(&rbuf);
  return tcxstrtomalloc(xstr);
}


/* Get the MD5 hash value of a serial object. */
void tcmd5hash(const void *ptr, int size, char *buf){
  assert(ptr && size >= 0 && buf);
  int i;
  md5_state_t ms;
  md5_init(&ms);
  md5_append(&ms, (md5_byte_t *)ptr, size);
  unsigned char digest[16];
  md5_finish(&ms, (md5_byte_t *)digest);
  char *wp = buf;
  for(i = 0; i < 16; i++){
    wp += sprintf(wp, "%02x", digest[i]);
  }
  *wp = '\0';
}


/* Cipher or decipher a serial object with the Arcfour stream cipher. */
void tcarccipher(const void *ptr, int size, const void *kbuf, int ksiz, void *obuf){
  assert(ptr && size >= 0 && kbuf && ksiz >= 0 && obuf);
  if(ksiz < 1){
    kbuf = "";
    ksiz = 1;
  }
  uint32_t sbox[0x100], kbox[0x100];
  for(int i = 0; i < 0x100; i++){
    sbox[i] = i;
    kbox[i] = ((uint8_t *)kbuf)[i%ksiz];
  }
  int sidx = 0;
  for(int i = 0; i < 0x100; i++){
    sidx = (sidx + sbox[i] + kbox[i]) & 0xff;
    uint32_t swap = sbox[i];
    sbox[i] = sbox[sidx];
    sbox[sidx] = swap;
  }
  int x = 0;
  int y = 0;
  for(int i = 0; i < size; i++){
    x = (x + 1) & 0xff;
    y = (y + sbox[x]) & 0xff;
    int32_t swap = sbox[x];
    sbox[x] = sbox[y];
    sbox[y] = swap;
    ((uint8_t *)obuf)[i] = ((uint8_t *)ptr)[i] ^ sbox[(sbox[x]+sbox[y])&0xff];
  }
}


/* Get the time of day in seconds. */
double tctime(void){
  struct timeval tv;
  if(gettimeofday(&tv, NULL) == -1) return 0.0;
  return (double)tv.tv_sec + (double)tv.tv_usec / 1000000.0;
}


/* Get the Gregorian calendar of a time. */
void tccalendar(int64_t t, int jl, int *yearp, int *monp, int *dayp,
                int *hourp, int *minp, int *secp){
  if(t == INT64_MAX) t = time(NULL);
  if(jl == INT_MAX) jl = tcjetlag();
  time_t tt = (time_t)t + jl;
  struct tm ts;
  if(!gmtime_r(&tt, &ts)){
    if(yearp) *yearp = 0;
    if(monp) *monp = 0;
    if(dayp) *dayp = 0;
    if(hourp) *hourp = 0;
    if(minp) *minp = 0;
    if(secp) *secp = 0;
  }
  if(yearp) *yearp = ts.tm_year + 1900;
  if(monp) *monp = ts.tm_mon + 1;
  if(dayp) *dayp = ts.tm_mday;
  if(hourp) *hourp = ts.tm_hour;
  if(minp) *minp = ts.tm_min;
  if(secp) *secp = ts.tm_sec;
}


/* Format a date as a string in W3CDTF. */
void tcdatestrwww(int64_t t, int jl, char *buf){
  assert(buf);
  if(t == INT64_MAX) t = time(NULL);
  if(jl == INT_MAX) jl = tcjetlag();
  time_t tt = (time_t)t + jl;
  struct tm ts;
  if(!gmtime_r(&tt, &ts)) memset(&ts, 0, sizeof(ts));
  ts.tm_year += 1900;
  ts.tm_mon += 1;
  jl /= 60;
  char tzone[16];
  if(jl == 0){
    sprintf(tzone, "Z");
  } else if(jl < 0){
    jl *= -1;
    sprintf(tzone, "-%02d:%02d", jl / 60, jl % 60);
  } else {
    sprintf(tzone, "+%02d:%02d", jl / 60, jl % 60);
  }
  sprintf(buf, "%04d-%02d-%02dT%02d:%02d:%02d%s",
          ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour, ts.tm_min, ts.tm_sec, tzone);
}


/* Format a date as a string in RFC 1123 format. */
void tcdatestrhttp(int64_t t, int jl, char *buf){
  assert(buf);
  if(t == INT64_MAX) t = time(NULL);
  if(jl == INT_MAX) jl = tcjetlag();
  time_t tt = (time_t)t + jl;
  struct tm ts;
  if(!gmtime_r(&tt, &ts)) memset(&ts, 0, sizeof(ts));
  ts.tm_year += 1900;
  ts.tm_mon += 1;
  jl /= 60;
  char *wp = buf;
  switch(tcdayofweek(ts.tm_year, ts.tm_mon, ts.tm_mday)){
    case 0: wp += sprintf(wp, "Sun, "); break;
    case 1: wp += sprintf(wp, "Mon, "); break;
    case 2: wp += sprintf(wp, "Tue, "); break;
    case 3: wp += sprintf(wp, "Wed, "); break;
    case 4: wp += sprintf(wp, "Thu, "); break;
    case 5: wp += sprintf(wp, "Fri, "); break;
    case 6: wp += sprintf(wp, "Sat, "); break;
  }
  wp += sprintf(wp, "%02d ", ts.tm_mday);
  switch(ts.tm_mon){
    case 1: wp += sprintf(wp, "Jan "); break;
    case 2: wp += sprintf(wp, "Feb "); break;
    case 3: wp += sprintf(wp, "Mar "); break;
    case 4: wp += sprintf(wp, "Apr "); break;
    case 5: wp += sprintf(wp, "May "); break;
    case 6: wp += sprintf(wp, "Jun "); break;
    case 7: wp += sprintf(wp, "Jul "); break;
    case 8: wp += sprintf(wp, "Aug "); break;
    case 9: wp += sprintf(wp, "Sep "); break;
    case 10: wp += sprintf(wp, "Oct "); break;
    case 11: wp += sprintf(wp, "Nov "); break;
    case 12: wp += sprintf(wp, "Dec "); break;
  }
  wp += sprintf(wp, "%04d %02d:%02d:%02d ", ts.tm_year, ts.tm_hour, ts.tm_min, ts.tm_sec);
  if(jl == 0){
    sprintf(wp, "GMT");
  } else if(jl < 0){
    jl *= -1;
    sprintf(wp, "-%02d%02d", jl / 60, jl % 60);
  } else {
    sprintf(wp, "+%02d%02d", jl / 60, jl % 60);
  }
}


/* Get the time value of a date string. */
int64_t tcstrmktime(const char *str){
  assert(str);
  while(*str > '\0' && *str <= ' '){
    str++;
  }
  if(*str == '\0') return INT64_MIN;
  if(str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) return tcatoih(str + 2);
  struct tm ts;
  memset(&ts, 0, sizeof(ts));
  ts.tm_year = 70;
  ts.tm_mon = 0;
  ts.tm_mday = 1;
  ts.tm_hour = 0;
  ts.tm_min = 0;
  ts.tm_sec = 0;
  ts.tm_isdst = 0;
  int len = strlen(str);
  time_t t = (time_t)tcatoi(str);
  const char *pv = str;
  while(*pv >= '0' && *pv <= '9'){
    pv++;
  }
  while(*pv > '\0' && *pv <= ' '){
    pv++;
  }
  if(*pv == '\0') return (int64_t)t;
  if((pv[0] == 's' || pv[0] == 'S') && pv[1] >= '\0' && pv[1] <= ' ')
    return (int64_t)t;
  if((pv[0] == 'm' || pv[0] == 'M') && pv[1] >= '\0' && pv[1] <= ' ')
    return (int64_t)t * 60;
  if((pv[0] == 'h' || pv[0] == 'H') && pv[1] >= '\0' && pv[1] <= ' ')
    return (int64_t)t * 60 * 60;
  if((pv[0] == 'd' || pv[0] == 'D') && pv[1] >= '\0' && pv[1] <= ' ')
    return (int64_t)t * 60 * 60 * 24;
  if(len > 4 && str[4] == '-'){
    ts.tm_year = tcatoi(str) - 1900;
    if((pv = strchr(str, '-')) != NULL && pv - str == 4){
      const char *rp = pv + 1;
      ts.tm_mon = tcatoi(rp) - 1;
      if((pv = strchr(rp, '-')) != NULL && pv - str == 7){
        rp = pv + 1;
        ts.tm_mday = tcatoi(rp);
        if((pv = strchr(rp, 'T')) != NULL && pv - str == 10){
          rp = pv + 1;
          ts.tm_hour = tcatoi(rp);
          if((pv = strchr(rp, ':')) != NULL && pv - str == 13){
            rp = pv + 1;
            ts.tm_min = tcatoi(rp);
          }
          if((pv = strchr(rp, ':')) != NULL && pv - str == 16){
            rp = pv + 1;
            ts.tm_sec = tcatoi(rp);
          }
          if((pv = strchr(rp, '.')) != NULL && pv - str >= 19) rp = pv + 1;
          pv = rp;
          while(*pv >= '0' && *pv <= '9'){
            pv++;
          }
          if((*pv == '+' || *pv == '-') && strlen(pv) >= 6 && pv[3] == ':')
            ts.tm_sec -= (tcatoi(pv + 1) * 3600 + tcatoi(pv + 4) * 60) * (pv[0] == '+' ? 1 : -1);
        }
      }
    }
    return (int64_t)tcmkgmtime(&ts);
  }
  if(len > 4 && str[4] == '/'){
    ts.tm_year = tcatoi(str) - 1900;
    if((pv = strchr(str, '/')) != NULL && pv - str == 4){
      const char *rp = pv + 1;
      ts.tm_mon = tcatoi(rp) - 1;
      if((pv = strchr(rp, '/')) != NULL && pv - str == 7){
        rp = pv + 1;
        ts.tm_mday = tcatoi(rp);
        if((pv = strchr(rp, ' ')) != NULL && pv - str == 10){
          rp = pv + 1;
          ts.tm_hour = tcatoi(rp);
          if((pv = strchr(rp, ':')) != NULL && pv - str == 13){
            rp = pv + 1;
            ts.tm_min = tcatoi(rp);
          }
          if((pv = strchr(rp, ':')) != NULL && pv - str == 16){
            rp = pv + 1;
            ts.tm_sec = tcatoi(rp);
          }
          if((pv = strchr(rp, '.')) != NULL && pv - str >= 19) rp = pv + 1;
          pv = rp;
          while(*pv >= '0' && *pv <= '9'){
            pv++;
          }
          if((*pv == '+' || *pv == '-') && strlen(pv) >= 6 && pv[3] == ':')
            ts.tm_sec -= (tcatoi(pv + 1) * 3600 + tcatoi(pv + 4) * 60) * (pv[0] == '+' ? 1 : -1);
        }
      }
    }
    return (int64_t)tcmkgmtime(&ts);
  }
  const char *crp = str;
  if(len >= 4 && str[3] == ',') crp = str + 4;
  while(*crp == ' '){
    crp++;
  }
  ts.tm_mday = tcatoi(crp);
  while((*crp >= '0' && *crp <= '9') || *crp == ' '){
    crp++;
  }
  if(tcstrifwm(crp, "Jan")){
    ts.tm_mon = 0;
  } else if(tcstrifwm(crp, "Feb")){
    ts.tm_mon = 1;
  } else if(tcstrifwm(crp, "Mar")){
    ts.tm_mon = 2;
  } else if(tcstrifwm(crp, "Apr")){
    ts.tm_mon = 3;
  } else if(tcstrifwm(crp, "May")){
    ts.tm_mon = 4;
  } else if(tcstrifwm(crp, "Jun")){
    ts.tm_mon = 5;
  } else if(tcstrifwm(crp, "Jul")){
    ts.tm_mon = 6;
  } else if(tcstrifwm(crp, "Aug")){
    ts.tm_mon = 7;
  } else if(tcstrifwm(crp, "Sep")){
    ts.tm_mon = 8;
  } else if(tcstrifwm(crp, "Oct")){
    ts.tm_mon = 9;
  } else if(tcstrifwm(crp, "Nov")){
    ts.tm_mon = 10;
  } else if(tcstrifwm(crp, "Dec")){
    ts.tm_mon = 11;
  } else {
    ts.tm_mon = -1;
  }
  if(ts.tm_mon >= 0) crp += 3;
  while(*crp == ' '){
    crp++;
  }
  ts.tm_year = tcatoi(crp);
  if(ts.tm_year >= 1969) ts.tm_year -= 1900;
  while(*crp >= '0' && *crp <= '9'){
    crp++;
  }
  while(*crp == ' '){
    crp++;
  }
  if(ts.tm_mday > 0 && ts.tm_mon >= 0 && ts.tm_year >= 0){
    int clen = strlen(crp);
    if(clen >= 8 && crp[2] == ':' && crp[5] == ':'){
      ts.tm_hour = tcatoi(crp + 0);
      ts.tm_min = tcatoi(crp + 3);
      ts.tm_sec = tcatoi(crp + 6);
      if(clen >= 14 && crp[8] == ' ' && (crp[9] == '+' || crp[9] == '-')){
        ts.tm_sec -= ((crp[10] - '0') * 36000 + (crp[11] - '0') * 3600 +
                      (crp[12] - '0') * 600 + (crp[13] - '0') * 60) * (crp[9] == '+' ? 1 : -1);
      } else if(clen > 9){
        if(!strcmp(crp + 9, "JST")){
          ts.tm_sec -= 9 * 3600;
        } else if(!strcmp(crp + 9, "CCT")){
          ts.tm_sec -= 8 * 3600;
        } else if(!strcmp(crp + 9, "KST")){
          ts.tm_sec -= 9 * 3600;
        } else if(!strcmp(crp + 9, "EDT")){
          ts.tm_sec -= -4 * 3600;
        } else if(!strcmp(crp + 9, "EST")){
          ts.tm_sec -= -5 * 3600;
        } else if(!strcmp(crp + 9, "CDT")){
          ts.tm_sec -= -5 * 3600;
        } else if(!strcmp(crp + 9, "CST")){
          ts.tm_sec -= -6 * 3600;
        } else if(!strcmp(crp + 9, "MDT")){
          ts.tm_sec -= -6 * 3600;
        } else if(!strcmp(crp + 9, "MST")){
          ts.tm_sec -= -7 * 3600;
        } else if(!strcmp(crp + 9, "PDT")){
          ts.tm_sec -= -7 * 3600;
        } else if(!strcmp(crp + 9, "PST")){
          ts.tm_sec -= -8 * 3600;
        } else if(!strcmp(crp + 9, "HDT")){
          ts.tm_sec -= -9 * 3600;
        } else if(!strcmp(crp + 9, "HST")){
          ts.tm_sec -= -10 * 3600;
        }
      }
    }
    return (int64_t)tcmkgmtime(&ts);
  }
  return INT64_MIN;
}


/* Get the jet lag of the local time. */
int tcjetlag(void){
#if defined(_SYS_LINUX_)
  tzset();
  return -timezone;
#else
  time_t t = 86400;
  struct tm gts;
  if(!gmtime_r(&t, &gts)) return 0;
  struct tm lts;
  t = 86400;
  if(!localtime_r(&t, &lts)) return 0;
  return mktime(&lts) - mktime(&gts);
#endif
}


/* Get the day of week of a date. */
int tcdayofweek(int year, int mon, int day){
  if(mon < 3){
    year--;
    mon += 12;
  }
  return (day + ((8 + (13 * mon)) / 5) + (year + (year / 4) - (year / 100) + (year / 400))) % 7;
}


/* Close the random number generator. */
static void tcrandomfdclose(void){
  close(tcrandomdevfd);
}


/* Make the GMT from a time structure.
   `tm' specifies the pointer to the time structure.
   The return value is the GMT. */
static time_t tcmkgmtime(struct tm *tm){
#if defined(_SYS_LINUX_)
  assert(tm);
  return timegm(tm);
#else
  assert(tm);
  return mktime(tm) + tcjetlag();
#endif
}



/*************************************************************************************************
 * miscellaneous utilities (for experts)
 *************************************************************************************************/


#define TCCHIDXVNNUM   128               // number of virtual node of consistent hashing


/* private function prototypes */
static int tcstrutfkwicputtext(const uint16_t *oary, const uint16_t *nary, int si, int ti,
                               int end, char *buf, const TCLIST *uwords, int opts);
static int tcchidxcmp(const void *a, const void *b);


/* Check whether a string is numeric completely or not. */
bool tcstrisnum(const char *str){
  assert(str);
  bool isnum = false;
  while(*str > '\0' && *str <= ' '){
    str++;
  }
  if(*str == '-') str++;
  while(*str >= '0' && *str <= '9'){
    isnum = true;
    str++;
  }
  if(*str == '.') str++;
  while(*str >= '0' && *str <= '9'){
    isnum = true;
    str++;
  }
  while(*str > '\0' && *str <= ' '){
    str++;
  }
  return isnum && *str == '\0';
}


/* Convert a hexadecimal string to an integer. */
int64_t tcatoih(const char *str){
  assert(str);
  while(*str > '\0' && *str <= ' '){
    str++;
  }
  if(str[0] == '0' && (str[1] == 'x' || str[1] == 'X')){
    str += 2;
  }
  int64_t num = 0;
  while(true){
    if(*str >= '0' && *str <= '9'){
      num = num * 0x10 + *str - '0';
    } else if(*str >= 'a' && *str <= 'f'){
      num = num * 0x10 + *str - 'a' + 10;
    } else if(*str >= 'A' && *str <= 'F'){
      num = num * 0x10 + *str - 'A' + 10;
    } else {
      break;
    }
    str++;
  }
  return num;
}


/* Skip space characters at head of a string. */
const char *tcstrskipspc(const char *str){
  assert(str);
  while(*str > '\0' && *str <= ' '){
    str++;
  }
  return str;
}


/* Normalize a UTF-8 string. */
char *tcstrutfnorm(char *str, int opts){
  assert(str);
  uint16_t buf[TCDISTBUFSIZ];
  uint16_t *ary;
  int len = strlen(str);
  if(len < TCDISTBUFSIZ){
    ary = buf;
  } else {
    TCMALLOC(ary, len * sizeof(*ary));
  }
  int num;
  tcstrutftoucs(str, ary, &num);
  num = tcstrucsnorm(ary, num, opts);
  tcstrucstoutf(ary, num, str);
  if(ary != buf) TCFREE(ary);
  return str;
}


/* Normalize a UCS-2 array. */
int tcstrucsnorm(uint16_t *ary, int num, int opts){
  assert(ary && num >= 0);
  bool spcmode = opts & TCUNSPACE;
  bool lowmode = opts & TCUNLOWER;
  bool nacmode = opts & TCUNNOACC;
  bool widmode = opts & TCUNWIDTH;
  int wi = 0;
  for(int i = 0; i < num; i++){
    int c = ary[i];
    int high = c >> 8;
    if(high == 0x00){
      if(c <= 0x0020 || c == 0x007f){
        // control characters
        if(spcmode){
          ary[wi++] = 0x0020;
          if(wi < 2 || ary[wi-2] == 0x0020) wi--;
        } else if(c == 0x0009 || c == 0x000a || c == 0x000d){
          ary[wi++] = c;
        } else {
          ary[wi++] = 0x0020;
        }
      } else if(c == 0x00a0){
        // no-break space
        if(spcmode){
          ary[wi++] = 0x0020;
          if(wi < 2 || ary[wi-2] == 0x0020) wi--;
        } else {
          ary[wi++] = c;
        }
      } else {
        // otherwise
        if(lowmode){
          if(c < 0x007f){
            if(c >= 0x0041 && c <= 0x005a) c += 0x20;
          } else if(c >= 0x00c0 && c <= 0x00de && c != 0x00d7){
            c += 0x20;
          }
        }
        if(nacmode){
          if(c >= 0x00c0 && c <= 0x00c5){
            c = 'A';
          } else if(c == 0x00c7){
            c = 'C';
          } if(c >= 0x00c7 && c <= 0x00cb){
            c = 'E';
          } if(c >= 0x00cc && c <= 0x00cf){
            c = 'I';
          } else if(c == 0x00d0){
            c = 'D';
          } else if(c == 0x00d1){
            c = 'N';
          } if((c >= 0x00d2 && c <= 0x00d6) || c == 0x00d8){
            c = 'O';
          } if(c >= 0x00d9 && c <= 0x00dc){
            c = 'U';
          } if(c == 0x00dd || c == 0x00de){
            c = 'Y';
          } else if(c == 0x00df){
            c = 's';
          } else if(c >= 0x00e0 && c <= 0x00e5){
            c = 'a';
          } else if(c == 0x00e7){
            c = 'c';
          } if(c >= 0x00e7 && c <= 0x00eb){
            c = 'e';
          } if(c >= 0x00ec && c <= 0x00ef){
            c = 'i';
          } else if(c == 0x00f0){
            c = 'd';
          } else if(c == 0x00f1){
            c = 'n';
          } if((c >= 0x00f2 && c <= 0x00f6) || c == 0x00f8){
            c = 'o';
          } if(c >= 0x00f9 && c <= 0x00fc){
            c = 'u';
          } if(c >= 0x00fd && c <= 0x00ff){
            c = 'y';
          }
        }
        ary[wi++] = c;
      }
    } else if(high == 0x01){
      // latin-1 extended
      if(lowmode){
        if(c <= 0x0137){
          if((c & 1) == 0) c++;
        } else if(c == 0x0138){
          c += 0;
        } else if(c <= 0x0148){
          if((c & 1) == 1) c++;
        } else if(c == 0x0149){
          c += 0;
        } else if(c <= 0x0177){
          if((c & 1) == 0) c++;
        } else if(c == 0x0178){
          c = 0x00ff;
        } else if(c <= 0x017e){
          if((c & 1) == 1) c++;
        } else if(c == 0x017f){
          c += 0;
        }
      }
      if(nacmode){
        if(c == 0x00ff){
          c = 'y';
        } else if(c <= 0x0105){
          c = ((c & 1) == 0) ? 'A' : 'a';
        } else if(c <= 0x010d){
          c = ((c & 1) == 0) ? 'C' : 'c';
        } else if(c <= 0x0111){
          c = ((c & 1) == 0) ? 'D' : 'd';
        } else if(c <= 0x011b){
          c = ((c & 1) == 0) ? 'E' : 'e';
        } else if(c <= 0x0123){
          c = ((c & 1) == 0) ? 'G' : 'g';
        } else if(c <= 0x0127){
          c = ((c & 1) == 0) ? 'H' : 'h';
        } else if(c <= 0x0131){
          c = ((c & 1) == 0) ? 'I' : 'i';
        } else if(c == 0x0134){
          c = 'J';
        } else if(c == 0x0135){
          c = 'j';
        } else if(c == 0x0136){
          c = 'K';
        } else if(c == 0x0137){
          c = 'k';
        } else if(c == 0x0138){
          c = 'k';
        } else if(c >= 0x0139 && c <= 0x0142){
          c = ((c & 1) == 1) ? 'L' : 'l';
        } else if(c >= 0x0143 && c <= 0x0148){
          c = ((c & 1) == 1) ? 'N' : 'n';
        } else if(c >= 0x0149 && c <= 0x014b){
          c = ((c & 1) == 0) ? 'N' : 'n';
        } else if(c >= 0x014c && c <= 0x0151){
          c = ((c & 1) == 0) ? 'O' : 'o';
        } else if(c >= 0x0154 && c <= 0x0159){
          c = ((c & 1) == 0) ? 'R' : 'r';
        } else if(c >= 0x015a && c <= 0x0161){
          c = ((c & 1) == 0) ? 'S' : 's';
        } else if(c >= 0x0162 && c <= 0x0167){
          c = ((c & 1) == 0) ? 'T' : 't';
        } else if(c >= 0x0168 && c <= 0x0173){
          c = ((c & 1) == 0) ? 'U' : 'u';
        } else if(c == 0x0174){
          c = 'W';
        } else if(c == 0x0175){
          c = 'w';
        } else if(c == 0x0176){
          c = 'Y';
        } else if(c == 0x0177){
          c = 'y';
        } else if(c == 0x0178){
          c = 'Y';
        } else if(c >= 0x0179 && c <= 0x017e){
          c = ((c & 1) == 1) ? 'Z' : 'z';
        } else if(c == 0x017f){
          c = 's';
        }
      }
      ary[wi++] = c;
    } else if(high == 0x03){
      // greek
      if(lowmode){
        if(c >= 0x0391 && c <= 0x03a9){
          c += 0x20;
        } else if(c >= 0x03d8 && c <= 0x03ef){
          if((c & 1) == 0) c++;
        } else if(c == 0x0374 || c == 0x03f7 || c == 0x03fa){
          c++;
        }
      }
      ary[wi++] = c;
    } else if(high == 0x04){
      // cyrillic
      if(lowmode){
        if(c <= 0x040f){
          c += 0x50;
        } else if(c <= 0x042f){
          c += 0x20;
        } else if(c >= 0x0460 && c <= 0x0481){
          if((c & 1) == 0) c++;
        } else if(c >= 0x048a && c <= 0x04bf){
          if((c & 1) == 0) c++;
        } else if(c == 0x04c0){
          c = 0x04cf;
        } else if(c >= 0x04c1 && c <= 0x04ce){
          if((c & 1) == 1) c++;
        } else if(c >= 0x04d0){
          if((c & 1) == 0) c++;
        }
      }
      ary[wi++] = c;
    } else if(high == 0x20){
      if(c == 0x2002 || c == 0x2003 || c == 0x2009){
        // en space, em space, thin space
        if(spcmode){
          ary[wi++] = 0x0020;
          if(wi < 2 || ary[wi-2] == 0x0020) wi--;
        } else {
          ary[wi++] = c;
        }
      } else if(c == 0x2010){
        // hyphen
        ary[wi++] = widmode ? 0x002d : c;
      } else if(c == 0x2015){
        // fullwidth horizontal line
        ary[wi++] = widmode ? 0x002d : c;
      } else if(c == 0x2019){
        // apostrophe
        ary[wi++] = widmode ? 0x0027 : c;
      } else if(c == 0x2033){
        // double quotes
        ary[wi++] = widmode ? 0x0022 : c;
      } else {
        // (otherwise)
        ary[wi++] = c;
      }
    } else if(high == 0x22){
      if(c == 0x2212){
        // minus sign
        ary[wi++] = widmode ? 0x002d : c;
      } else {
        // (otherwise)
        ary[wi++] = c;
      }
    } else if(high == 0x30){
      if(c == 0x3000){
        // fullwidth space
        if(spcmode){
          ary[wi++] = 0x0020;
          if(wi < 2 || ary[wi-2] == 0x0020) wi--;
        } else if(widmode){
          ary[wi++] = 0x0020;
        } else {
          ary[wi++] = c;
        }
      } else {
        // (otherwise)
        ary[wi++] = c;
      }
    } else if(high == 0xff){
      if(c == 0xff01){
        // fullwidth exclamation
        ary[wi++] = widmode ? 0x0021 : c;
      } else if(c == 0xff03){
        // fullwidth igeta
        ary[wi++] = widmode ? 0x0023 : c;
      } else if(c == 0xff04){
        // fullwidth dollar
        ary[wi++] = widmode ? 0x0024 : c;
      } else if(c == 0xff05){
        // fullwidth parcent
        ary[wi++] = widmode ? 0x0025 : c;
      } else if(c == 0xff06){
        // fullwidth ampersand
        ary[wi++] = widmode ? 0x0026 : c;
      } else if(c == 0xff0a){
        // fullwidth asterisk
        ary[wi++] = widmode ? 0x002a : c;
      } else if(c == 0xff0b){
        // fullwidth plus
        ary[wi++] = widmode ? 0x002b : c;
      } else if(c == 0xff0c){
        // fullwidth comma
        ary[wi++] = widmode ? 0x002c : c;
      } else if(c == 0xff0e){
        // fullwidth period
        ary[wi++] = widmode ? 0x002e : c;
      } else if(c == 0xff0f){
        // fullwidth slash
        ary[wi++] = widmode ? 0x002f : c;
      } else if(c == 0xff1a){
        // fullwidth colon
        ary[wi++] = widmode ? 0x003a : c;
      } else if(c == 0xff1b){
        // fullwidth semicolon
        ary[wi++] = widmode ? 0x003b : c;
      } else if(c == 0xff1d){
        // fullwidth equal
        ary[wi++] = widmode ? 0x003d : c;
      } else if(c == 0xff1f){
        // fullwidth question
        ary[wi++] = widmode ? 0x003f : c;
      } else if(c == 0xff20){
        // fullwidth atmark
        ary[wi++] = widmode ? 0x0040 : c;
      } else if(c == 0xff3c){
        // fullwidth backslash
        ary[wi++] = widmode ? 0x005c : c;
      } else if(c == 0xff3e){
        // fullwidth circumflex
        ary[wi++] = widmode ? 0x005e : c;
      } else if(c == 0xff3f){
        // fullwidth underscore
        ary[wi++] = widmode ? 0x005f : c;
      } else if(c == 0xff5c){
        // fullwidth vertical line
        ary[wi++] = widmode ? 0x007c : c;
      } else if(c >= 0xff21 && c <= 0xff3a){
        // fullwidth alphabets
        if(widmode){
          if(lowmode){
            ary[wi++] = c - 0xfee0 + 0x20;
          } else {
            ary[wi++] = c - 0xfee0;
          }
        } else if(lowmode){
          ary[wi++] = c + 0x20;
        } else {
          ary[wi++] = c;
        }
      } else if(c >= 0xff41 && c <= 0xff5a){
        // fullwidth small alphabets
        ary[wi++] = widmode ? c - 0xfee0 : c;
      } else if(c >= 0xff10 && c <= 0xff19){
        // fullwidth numbers
        ary[wi++] = widmode ? c - 0xfee0 : c;
      } else if(c == 0xff61){
        // halfwidth full stop
        ary[wi++] = widmode ? 0x3002 : c;
      } else if(c == 0xff62){
        // halfwidth left corner
        ary[wi++] = widmode ? 0x300c : c;
      } else if(c == 0xff63){
        // halfwidth right corner
        ary[wi++] = widmode ? 0x300d : c;
      } else if(c == 0xff64){
        // halfwidth comma
        ary[wi++] = widmode ? 0x3001 : c;
      } else if(c == 0xff65){
        // halfwidth middle dot
        ary[wi++] = widmode ? 0x30fb : c;
      } else if(c == 0xff66){
        // halfwidth wo
        ary[wi++] = widmode ? 0x30f2 : c;
      } else if(c >= 0xff67 && c <= 0xff6b){
        // halfwidth small a-o
        ary[wi++] = widmode ? (c - 0xff67) * 2 + 0x30a1 : c;
      } else if(c >= 0xff6c && c <= 0xff6e){
        // halfwidth small ya-yo
        ary[wi++] = widmode ? (c - 0xff6c) * 2 + 0x30e3 : c;
      } else if(c == 0xff6f){
        // halfwidth small tu
        ary[wi++] = widmode ? 0x30c3 : c;
      } else if(c == 0xff70){
        // halfwidth prolonged mark
        ary[wi++] = widmode ? 0x30fc : c;
      } else if(c >= 0xff71 && c <= 0xff75){
        // halfwidth a-o
        if(widmode){
          ary[wi] = (c - 0xff71) * 2 + 0x30a2;
          if(c == 0xff73 && i < num - 1 && ary[i+1] == 0xff9e){
            ary[wi] = 0x30f4;
            i++;
          }
          wi++;
        } else {
          ary[wi++] = c;
        }
      } else if(c >= 0xff76 && c <= 0xff7a){
        // halfwidth ka-ko
        if(widmode){
          ary[wi] = (c - 0xff76) * 2 + 0x30ab;
          if(i < num - 1 && ary[i+1] == 0xff9e){
            ary[wi]++;
            i++;
          }
          wi++;
        } else {
          ary[wi++] = c;
        }
      } else if(c >= 0xff7b && c <= 0xff7f){
        // halfwidth sa-so
        if(widmode){
          ary[wi] = (c - 0xff7b) * 2 + 0x30b5;
          if(i < num - 1 && ary[i+1] == 0xff9e){
            ary[wi]++;
            i++;
          }
          wi++;
        } else {
          ary[wi++] = c;
        }
      } else if(c >= 0xff80 && c <= 0xff84){
        // halfwidth ta-to
        if(widmode){
          ary[wi] = (c - 0xff80) * 2 + 0x30bf + (c >= 0xff82 ? 1 : 0);
          if(i < num - 1 && ary[i+1] == 0xff9e){
            ary[wi]++;
            i++;
          }
          wi++;
        } else {
          ary[wi++] = c;
        }
      } else if(c >= 0xff85 && c <= 0xff89){
        // halfwidth na-no
        ary[wi++] = widmode ? c - 0xcebb : c;
      } else if(c >= 0xff8a && c <= 0xff8e){
        // halfwidth ha-ho
        if(widmode){
          ary[wi] = (c - 0xff8a) * 3 + 0x30cf;
          if(i < num - 1 && ary[i+1] == 0xff9e){
            ary[wi]++;
            i++;
          } else if(i < num - 1 && ary[i+1] == 0xff9f){
            ary[wi] += 2;
            i++;
          }
          wi++;
        } else {
          ary[wi++] = c;
        }
      } else if(c >= 0xff8f && c <= 0xff93){
        // halfwidth ma-mo
        ary[wi++] = widmode ? c - 0xceb1 : c;
      } else if(c >= 0xff94 && c <= 0xff96){
        // halfwidth ya-yo
        ary[wi++] = widmode ? (c - 0xff94) * 2 + 0x30e4 : c;
      } else if(c >= 0xff97 && c <= 0xff9b){
        // halfwidth ra-ro
        ary[wi++] = widmode ? c - 0xceae : c;
      } else if(c == 0xff9c){
        // halfwidth wa
        ary[wi++] = widmode ? 0x30ef : c;
      } else if(c == 0xff9d){
        // halfwidth nn
        ary[wi++] = widmode ? 0x30f3 : c;
      } else {
        // otherwise
        ary[wi++] = c;
      }
    } else {
      // otherwise
      ary[wi++] = c;
    }
  }
  if(spcmode){
    while(wi > 0 && ary[wi-1] == 0x0020){
      wi--;
    }
  }
  return wi;
}


/* Generate a keyword-in-context string from a text and keywords. */
TCLIST *tcstrkwic(const char *str, const TCLIST *words, int width, int opts){
  assert(str && words && width >= 0);
  TCLIST *texts = tclistnew();
  int len = strlen(str);
  uint16_t *oary, *nary;
  TCMALLOC(oary, sizeof(*oary) * len + 1);
  TCMALLOC(nary, sizeof(*nary) * len + 1);
  int oanum, nanum;
  tcstrutftoucs(str, oary, &oanum);
  tcstrutftoucs(str, nary, &nanum);
  nanum = tcstrucsnorm(nary, nanum, TCUNLOWER | TCUNNOACC | TCUNWIDTH);
  if(nanum != oanum){
    memcpy(nary, oary, sizeof(*oary) * oanum);
    for(int i = 0; i < oanum; i++){
      if(nary[i] >= 'A' && nary[i] <= 'Z') nary[i] += 'a' - 'A';
    }
    nanum = oanum;
  }
  int wnum = TCLISTNUM(words);
  TCLIST *uwords = tclistnew2(wnum);
  for(int i = 0; i < wnum; i++){
    const char *word;
    int wsiz;
    TCLISTVAL(word, words, i, wsiz);
    uint16_t *uwary;
    TCMALLOC(uwary, sizeof(*uwary) * wsiz + 1);
    int uwnum;
    tcstrutftoucs(word, uwary, &uwnum);
    uwnum = tcstrucsnorm(uwary, uwnum, TCUNSPACE | TCUNLOWER | TCUNNOACC | TCUNWIDTH);
    if(uwnum > 0){
      tclistpushmalloc(uwords, uwary, sizeof(*uwary) * uwnum);
    } else {
      TCFREE(uwary);
    }
  }
  wnum = TCLISTNUM(uwords);
  int ri = 0;
  int pi = 0;
  while(ri < nanum){
    int step = 0;
    for(int i = 0; i < wnum; i++){
      const char *val;
      int uwnum;
      TCLISTVAL(val, uwords, i, uwnum);
      uint16_t *uwary = (uint16_t *)val;
      uwnum /= sizeof(*uwary);
      if(ri + uwnum <= nanum){
        int ci = 0;
        while(ci < uwnum && nary[ri+ci] == uwary[ci]){
          ci++;
        }
        if(ci == uwnum){
          int si = tclmax(ri - width, 0);
          if(opts & TCKWNOOVER) si = tclmax(si, pi);
          int ti = tclmin(ri + uwnum + width, nanum);
          char *tbuf;
          TCMALLOC(tbuf, (ti - si) * 5 + 1);
          int wi = 0;
          if(ri > si) wi += tcstrutfkwicputtext(oary, nary, si, ri, ri,
                                                tbuf + wi, uwords, opts);
          if(opts & TCKWMUTAB){
            tbuf[wi++] = '\t';
          } else if(opts & TCKWMUCTRL){
            tbuf[wi++] = 0x02;
          } else if(opts & TCKWMUBRCT){
            tbuf[wi++] = '[';
          }
          wi += tcstrucstoutf(oary + ri, ci, tbuf + wi);
          if(opts & TCKWMUTAB){
            tbuf[wi++] = '\t';
          } else if(opts & TCKWMUCTRL){
            tbuf[wi++] = 0x03;
          } else if(opts & TCKWMUBRCT){
            tbuf[wi++] = ']';
          }
          if(ti > ri + ci) wi += tcstrutfkwicputtext(oary, nary, ri + ci, ti,
                                                     nanum, tbuf + wi, uwords, opts);
          if(wi > 0){
            tclistpushmalloc(texts, tbuf, wi);
          } else {
            TCFREE(tbuf);
          }
          if(ti > step) step = ti;
          if(step > pi) pi = step;
          if(opts & TCKWNOOVER) break;
        }
      }
    }
    if(ri == 0 && step < 1 && (opts & TCKWPULEAD)){
      int ti = tclmin(ri + width * 2, nanum);
      if(ti > 0){
        char *tbuf;
        TCMALLOC(tbuf, ti * 5 + 1);
        int wi = 0;
        wi += tcstrutfkwicputtext(oary, nary, 0, ti, nanum, tbuf + wi, uwords, opts);
        if(!(opts & TCKWNOOVER) && opts & TCKWMUTAB){
          tbuf[wi++] = '\t';
          tbuf[wi++] = '\t';
        }
        tclistpushmalloc(texts, tbuf, wi);
      }
      step = ti;
    }
    if(opts & TCKWNOOVER){
      ri = (step > 0) ? step : ri + 1;
    } else {
      ri++;
    }
  }
  tclistdel(uwords);
  TCFREE(nary);
  TCFREE(oary);
  return texts;
}


/* Tokenize a text separating by white space characters. */
TCLIST *tcstrtokenize(const char *str){
  assert(str);
  TCLIST *tokens = tclistnew();
  const unsigned char *rp = (unsigned char *)str;
  while(*rp != '\0'){
    while(*rp <= ' '){
      rp++;
    }
    if(*rp == '"'){
      rp++;
      TCXSTR *buf = tcxstrnew();
      while(*rp != '\0'){
        if(*rp == '\\'){
          rp++;
          if(*rp != '\0') TCXSTRCAT(buf, rp, 1);
          rp++;
        } else if(*rp == '"'){
          rp++;
          break;
        } else {
          TCXSTRCAT(buf, rp, 1);
          rp++;
        }
      }
      int size = TCXSTRSIZE(buf);
      tclistpushmalloc(tokens, tcxstrtomalloc(buf), size);
    } else {
      const unsigned char *ep = rp;
      while(*ep > ' '){
        ep++;
      }
      if(ep > rp) TCLISTPUSH(tokens, rp, ep - rp);
      if(*ep != '\0'){
        rp = ep + 1;
      } else {
        break;
      }
    }
  }
  return tokens;
}


/* Create a list object by splitting a region by zero code. */
TCLIST *tcstrsplit2(const void *ptr, int size){
  assert(ptr && size >= 0);
  TCLIST *list = tclistnew();
  while(size >= 0){
    const char *rp = ptr;
    const char *ep = (const char *)ptr + size;
    while(rp < ep){
      if(*rp == '\0') break;
      rp++;
    }
    TCLISTPUSH(list, ptr, rp - (const char *)ptr);
    rp++;
    size -= rp - (const char *)ptr;
    ptr = rp;
  }
  return list;
}


/* Create a map object by splitting a string. */
TCMAP *tcstrsplit3(const char *str, const char *delims){
  assert(str && delims);
  TCMAP *map = tcmapnew2(TCMAPTINYBNUM);
  const char *kbuf = NULL;
  int ksiz = 0;
  while(true){
    const char *sp = str;
    while(*str != '\0' && !strchr(delims, *str)){
      str++;
    }
    if(kbuf){
      tcmapput(map, kbuf, ksiz, sp, str - sp);
      kbuf = NULL;
    } else {
      kbuf = sp;
      ksiz = str - sp;
    }
    if(*str == '\0') break;
    str++;
  }
  return map;
}


/* Create a map object by splitting a region by zero code. */
TCMAP *tcstrsplit4(const void *ptr, int size){
  assert(ptr && size >= 0);
  TCMAP *map = tcmapnew2(tclmin(size / 6 + 1, TCMAPDEFBNUM));
  const char *kbuf = NULL;
  int ksiz = 0;
  while(size >= 0){
    const char *rp = ptr;
    const char *ep = (const char *)ptr + size;
    while(rp < ep){
      if(*rp == '\0') break;
      rp++;
    }
    if(kbuf){
      tcmapput(map, kbuf, ksiz, ptr, rp - (const char *)ptr);
      kbuf = NULL;
    } else {
      kbuf = ptr;
      ksiz = rp - (const char *)ptr;
    }
    rp++;
    size -= rp - (const char *)ptr;
    ptr = rp;
  }
  return map;
}


/* Create a region separated by zero code by joining all elements of a list object. */
void *tcstrjoin2(const TCLIST *list, int *sp){
  assert(list && sp);
  int num = TCLISTNUM(list);
  int size = num + 1;
  for(int i = 0; i < num; i++){
    size += TCLISTVALSIZ(list, i);
  }
  char *buf;
  TCMALLOC(buf, size);
  char *wp = buf;
  for(int i = 0; i < num; i++){
    if(i > 0) *(wp++) = '\0';
    int vsiz;
    const char *vbuf = tclistval(list, i, &vsiz);
    memcpy(wp, vbuf, vsiz);
    wp += vsiz;
  }
  *wp = '\0';
  *sp = wp - buf;
  return buf;
}


/* Create a string by joining all records of a map object. */
char *tcstrjoin3(const TCMAP *map, char delim){
  assert(map);
  int num = (int)TCMAPRNUM(map);
  int size = num * 2 + 1;
  TCMAPREC *cur = map->cur;
  tcmapiterinit((TCMAP *)map);
  const char *kbuf;
  int ksiz;
  while((kbuf = tcmapiternext((TCMAP *)map, &ksiz)) != NULL){
    int vsiz;
    tcmapiterval(kbuf, &vsiz);
    size += ksiz + vsiz;
  }
  char *buf;
  TCMALLOC(buf, size);
  char *wp = buf;
  tcmapiterinit((TCMAP *)map);
  bool first = true;
  while((kbuf = tcmapiternext((TCMAP *)map, &ksiz)) != NULL){
    if(first){
      first = false;
    } else {
      *(wp++) = delim;
    }
    memcpy(wp, kbuf, ksiz);
    wp += ksiz;
    int vsiz;
    const char *vbuf = tcmapiterval(kbuf, &vsiz);
    *(wp++) = delim;
    memcpy(wp, vbuf, vsiz);
    wp += vsiz;
  }
  *wp = '\0';
  ((TCMAP *)map)->cur = cur;
  return buf;
}


/* Create a region separated by zero code by joining all records of a map object. */
void *tcstrjoin4(const TCMAP *map, int *sp){
  assert(map && sp);
  int num = (int)TCMAPRNUM(map);
  int size = num * 2 + 1;
  TCMAPREC *cur = map->cur;
  tcmapiterinit((TCMAP *)map);
  const char *kbuf;
  int ksiz;
  while((kbuf = tcmapiternext((TCMAP *)map, &ksiz)) != NULL){
    int vsiz;
    tcmapiterval(kbuf, &vsiz);
    size += ksiz + vsiz;
  }
  char *buf;
  TCMALLOC(buf, size);
  char *wp = buf;
  tcmapiterinit((TCMAP *)map);
  bool first = true;
  while((kbuf = tcmapiternext((TCMAP *)map, &ksiz)) != NULL){
    if(first){
      first = false;
    } else {
      *(wp++) = '\0';
    }
    memcpy(wp, kbuf, ksiz);
    wp += ksiz;
    int vsiz;
    const char *vbuf = tcmapiterval(kbuf, &vsiz);
    *(wp++) = '\0';
    memcpy(wp, vbuf, vsiz);
    wp += vsiz;
  }
  *wp = '\0';
  *sp = wp - buf;
  ((TCMAP *)map)->cur = cur;
  return buf;
}


/* Sort top records of an array. */
void tctopsort(void *base, size_t nmemb, size_t size, size_t top,
               int(*compar)(const void *, const void *)){
  assert(base && size > 0 && compar);
  if(nmemb < 1) return;
  if(top > nmemb) top = nmemb;
  char *bp = base;
  char *ep = bp + nmemb * size;
  char *rp = bp + size;
  int num = 1;
  char swap[size];
  while(rp < ep){
    if(num < top){
      int cidx = num;
      while(cidx > 0){
        int pidx = (cidx - 1) / 2;
        if(compar(bp + cidx * size, bp + pidx * size) <= 0) break;
        memcpy(swap, bp + cidx * size, size);
        memcpy(bp + cidx * size, bp + pidx * size, size);
        memcpy(bp + pidx * size, swap, size);
        cidx = pidx;
      }
      num++;
    } else if(compar(rp, bp) < 0){
      memcpy(swap, bp, size);
      memcpy(bp, rp, size);
      memcpy(rp, swap, size);
      int pidx = 0;
      int bot = num / 2;
      while(pidx < bot){
        int cidx = pidx * 2 + 1;
        if(cidx < num - 1 && compar(bp + cidx * size, bp + (cidx + 1) * size) < 0) cidx++;
        if(compar(bp + pidx * size, bp + cidx * size) > 0) break;
        memcpy(swap, bp + pidx * size, size);
        memcpy(bp + pidx * size, bp + cidx * size, size);
        memcpy(bp + cidx * size, swap, size);
        pidx = cidx;
      }
    }
    rp += size;
  }
  num = top - 1;
  while(num > 0){
    memcpy(swap, bp, size);
    memcpy(bp, bp + num * size, size);
    memcpy(bp + num * size, swap, size);
    int pidx = 0;
    int bot = num / 2;
    while(pidx < bot){
      int cidx = pidx * 2 + 1;
      if(cidx < num - 1 && compar(bp + cidx * size, bp + (cidx + 1) * size) < 0) cidx++;
      if(compar(bp + pidx * size, bp + cidx * size) > 0) break;
      memcpy(swap, bp + pidx * size, size);
      memcpy(bp + pidx * size, bp + cidx * size, size);
      memcpy(bp + cidx * size, swap, size);
      pidx = cidx;
    }
    num--;
  }
}


/* Suspend execution of the current thread. */
bool tcsleep(double sec){
  if(!isnormal(sec) || sec <= 0.0) return false;
  if(sec <= 1.0 / sysconf(_SC_CLK_TCK)) return sched_yield() == 0;
  double integ, fract;
  fract = modf(sec, &integ);
  struct timespec req, rem;
  req.tv_sec = integ;
  req.tv_nsec = tclmin(fract * 1000.0 * 1000.0 * 1000.0, 999999999);
  while(nanosleep(&req, &rem) != 0){
    if(errno != EINTR) return false;
    req = rem;
  }
  return true;
}


/* Get the current system information. */
TCMAP *tcsysinfo(void){
#if defined(_SYS_LINUX_)
  TCMAP *info = tcmapnew2(TCMAPTINYBNUM);
  struct rusage rbuf;
  memset(&rbuf, 0, sizeof(rbuf));
  if(getrusage(RUSAGE_SELF, &rbuf) == 0){
    tcmapprintf(info, "utime", "%0.6f",
                rbuf.ru_utime.tv_sec + rbuf.ru_utime.tv_usec / 1000000.0);
    tcmapprintf(info, "stime", "%0.6f",
                rbuf.ru_stime.tv_sec + rbuf.ru_stime.tv_usec / 1000000.0);
  }
  TCLIST *lines = tcreadfilelines("/proc/self/status");
  if(lines){
    int ln = tclistnum(lines);
    for(int i = 0; i < ln; i++){
      const char *line = TCLISTVALPTR(lines, i);
      const char *rp = strchr(line, ':');
      if(!rp) continue;
      rp++;
      while(*rp > '\0' && *rp <= ' '){
        rp++;
      }
      if(tcstrifwm(line, "VmSize:")){
        int64_t size = tcatoix(rp);
        if(size > 0) tcmapprintf(info, "size", "%lld", (long long)size);
      } else if(tcstrifwm(line, "VmRSS:")){
        int64_t size = tcatoix(rp);
        if(size > 0) tcmapprintf(info, "rss", "%lld", (long long)size);
      }
    }
    tclistdel(lines);
  }
  lines = tcreadfilelines("/proc/meminfo");
  if(lines){
    int ln = tclistnum(lines);
    for(int i = 0; i < ln; i++){
      const char *line = TCLISTVALPTR(lines, i);
      const char *rp = strchr(line, ':');
      if(!rp) continue;
      rp++;
      while(*rp > '\0' && *rp <= ' '){
        rp++;
      }
      if(tcstrifwm(line, "MemTotal:")){
        int64_t size = tcatoix(rp);
        if(size > 0) tcmapprintf(info, "total", "%lld", (long long)size);
      } else if(tcstrifwm(line, "MemFree:")){
        int64_t size = tcatoix(rp);
        if(size > 0) tcmapprintf(info, "free", "%lld", (long long)size);
      } else if(tcstrifwm(line, "Cached:")){
        int64_t size = tcatoix(rp);
        if(size > 0) tcmapprintf(info, "cached", "%lld", (long long)size);
      }
    }
    tclistdel(lines);
  }
  lines = tcreadfilelines("/proc/cpuinfo");
  if(lines){
    int cnum = 0;
    int ln = tclistnum(lines);
    for(int i = 0; i < ln; i++){
      const char *line = TCLISTVALPTR(lines, i);
      if(tcstrifwm(line, "processor")) cnum++;
    }
    if(cnum > 0) tcmapprintf(info, "corenum", "%lld", (long long)cnum);
    tclistdel(lines);
  }
  return info;
#elif defined(_SYS_FREEBSD_) || defined(_SYS_MACOSX_)
  TCMAP *info = tcmapnew2(TCMAPTINYBNUM);
  struct rusage rbuf;
  memset(&rbuf, 0, sizeof(rbuf));
  if(getrusage(RUSAGE_SELF, &rbuf) == 0){
    tcmapprintf(info, "utime", "%0.6f",
                rbuf.ru_utime.tv_sec + rbuf.ru_utime.tv_usec / 1000000.0);
    tcmapprintf(info, "stime", "%0.6f",
                rbuf.ru_stime.tv_sec + rbuf.ru_stime.tv_usec / 1000000.0);
    long tck = sysconf(_SC_CLK_TCK);
    int64_t size = (((double)rbuf.ru_ixrss + rbuf.ru_idrss + rbuf.ru_isrss) / tck) * 1024.0;
    if(size > 0) tcmapprintf(info, "rss", "%lld", (long long)size);
  }
  return info;
#else
  TCMAP *info = tcmapnew2(TCMAPTINYBNUM);
  struct rusage rbuf;
  memset(&rbuf, 0, sizeof(rbuf));
  if(getrusage(RUSAGE_SELF, &rbuf) == 0){
    tcmapprintf(info, "utime", "%0.6f",
                rbuf.ru_utime.tv_sec + rbuf.ru_utime.tv_usec / 1000000.0);
    tcmapprintf(info, "stime", "%0.6f",
                rbuf.ru_stime.tv_sec + rbuf.ru_stime.tv_usec / 1000000.0);
  }
  return info;
#endif
}


/* Create a consistent hashing object. */
TCCHIDX *tcchidxnew(int range){
  assert(range > 0);
  TCCHIDX *chidx;
  TCMALLOC(chidx, sizeof(*chidx));
  int nnum = range * TCCHIDXVNNUM;
  TCCHIDXNODE *nodes;
  TCMALLOC(nodes, nnum * sizeof(*nodes));
  unsigned int seed = 725;
  for(int i = 0; i < range; i++){
    int end = (i + 1) * TCCHIDXVNNUM;
    for(int j = i * TCCHIDXVNNUM; j < end; j++){
      nodes[j].seq = i;
      nodes[j].hash = (seed = seed * 123456761 + 211);
    }
  }
  qsort(nodes, nnum, sizeof(*nodes), tcchidxcmp);
  chidx->nodes = nodes;
  chidx->nnum = nnum;
  return chidx;
}


/* Delete a consistent hashing object. */
void tcchidxdel(TCCHIDX *chidx){
  assert(chidx);
  TCFREE(chidx->nodes);
  TCFREE(chidx);
}


/* Get the consistent hashing value of a record. */
int tcchidxhash(TCCHIDX *chidx, const void *ptr, int size){
  assert(chidx && ptr && size >= 0);
  uint32_t hash = 19771007;
  const char *rp = (char *)ptr + size;
  while(size--){
    hash = (hash * 31) ^ *(uint8_t *)--rp;
    hash ^= hash << 7;
  }
  TCCHIDXNODE *nodes = chidx->nodes;
  int low = 0;
  int high = chidx->nnum;
  while(low < high){
    int mid = (low + high) >> 1;
    uint32_t nhash = nodes[mid].hash;
    if(hash < nhash){
      high = mid;
    } else if(hash > nhash){
      low = mid + 1;
    } else {
      low = mid;
      break;
    }
  }
  if(low >= chidx->nnum) low = 0;
  return nodes[low].seq & INT_MAX;
}


/* Put a text into a KWIC buffer.
   `oary' specifies the original code array.
   `nary' specifies the normalized code array.
   `si' specifies the start index of the text.
   `ti' specifies the terminal index of the text.
   `end' specifies the end index of the code array.
   `buf' specifies the pointer to the output buffer.
   `uwords' specifies the list object of the words to be marked up.
   `opts' specifies the options.
   The return value is the length of the output. */
static int tcstrutfkwicputtext(const uint16_t *oary, const uint16_t *nary, int si, int ti,
                               int end, char *buf, const TCLIST *uwords, int opts){
  assert(oary && nary && si >= 0 && ti >= 0 && end >= 0 && buf && uwords);
  if(!(opts & TCKWNOOVER)) return tcstrucstoutf(oary + si, ti - si, buf);
  if(!(opts & TCKWMUTAB) && !(opts & TCKWMUCTRL) && !(opts & TCKWMUBRCT))
    return tcstrucstoutf(oary + si, ti - si, buf);
  int wnum = TCLISTNUM(uwords);
  int ri = si;
  int wi = 0;
  while(ri < ti){
    int step = 0;
    for(int i = 0; i < wnum; i++){
      const char *val;
      int uwnum;
      TCLISTVAL(val, uwords, i, uwnum);
      uint16_t *uwary = (uint16_t *)val;
      uwnum /= sizeof(*uwary);
      if(ri + uwnum <= end){
        int ci = 0;
        while(ci < uwnum && nary[ri+ci] == uwary[ci]){
          ci++;
        }
        if(ci == uwnum){
          if(opts & TCKWMUTAB){
            buf[wi++] = '\t';
          } else if(opts & TCKWMUCTRL){
            buf[wi++] = 0x02;
          } else if(opts & TCKWMUBRCT){
            buf[wi++] = '[';
          }
          wi += tcstrucstoutf(oary + ri, ci, buf + wi);
          if(opts & TCKWMUTAB){
            buf[wi++] = '\t';
          } else if(opts & TCKWMUCTRL){
            buf[wi++] = 0x03;
          } else if(opts & TCKWMUBRCT){
            buf[wi++] = ']';
          }
          step = ri + ci;
          break;
        }
      }
    }
    if(step > 0){
      ri = step;
    } else {
      wi += tcstrucstoutf(oary + ri, 1, buf + wi);
      ri++;
    }
  }
  return wi;
}


/* Compare two consistent hashing nodes.
   `a' specifies the pointer to one node object.
   `b' specifies the pointer to the other node object.
   The return value is positive if the former is big, negative if the latter is big, 0 if both
   are equivalent. */
static int tcchidxcmp(const void *a, const void *b){
  if(((TCCHIDXNODE *)a)->hash == ((TCCHIDXNODE *)b)->hash) return 0;
  return ((TCCHIDXNODE *)a)->hash > ((TCCHIDXNODE *)b)->hash;
}



/*************************************************************************************************
 * filesystem utilities
 *************************************************************************************************/


#define TCFILEMODE     00644             // permission of a creating file
#define TCIOBUFSIZ     16384             // size of an I/O buffer


/* Get the canonicalized absolute path of a file. */
char *tcrealpath(const char *path){
  assert(path);
  char buf[PATH_MAX+1];
  if(realpath(path, buf)) return tcstrdup(buf);
  if(errno == ENOENT){
    const char *pv = strrchr(path, MYPATHCHR);
    if(pv){
      if(pv == path) return tcstrdup(path);
      char *prefix = tcmemdup(path, pv - path);
      if(!realpath(prefix, buf)){
        TCFREE(prefix);
        return NULL;
      }
      TCFREE(prefix);
      pv++;
    } else {
      if(!realpath(MYCDIRSTR, buf)) return NULL;
      pv = path;
    }
    if(buf[0] == MYPATHCHR && buf[1] == '\0') buf[0] = '\0';
    char *str;
    TCMALLOC(str, strlen(buf) + strlen(pv) + 2);
    sprintf(str, "%s%c%s", buf, MYPATHCHR, pv);
    return str;
  }
  return NULL;
}


/* Get the status information of a file. */
bool tcstatfile(const char *path, bool *isdirp, int64_t *sizep, int64_t *mtimep){
  assert(path);
  struct stat sbuf;
  if(stat(path, &sbuf) != 0) return false;
  if(isdirp) *isdirp = S_ISDIR(sbuf.st_mode);
  if(sizep) *sizep = sbuf.st_size;
  if(mtimep) *mtimep = sbuf.st_mtime;
  return true;
}


/* Read whole data of a file. */
void *tcreadfile(const char *path, int limit, int *sp){
  int fd = path ? open(path, O_RDONLY, TCFILEMODE) : 0;
  if(fd == -1) return NULL;
  if(fd == 0){
    TCXSTR *xstr = tcxstrnew();
    char buf[TCIOBUFSIZ];
    limit = limit > 0 ? limit : INT_MAX;
    int rsiz;
    while((rsiz = read(fd, buf, tclmin(TCIOBUFSIZ, limit))) > 0){
      TCXSTRCAT(xstr, buf, rsiz);
      limit -= rsiz;
    }
    if(sp) *sp = TCXSTRSIZE(xstr);
    return tcxstrtomalloc(xstr);
  }
  struct stat sbuf;
  if(fstat(fd, &sbuf) == -1 || !S_ISREG(sbuf.st_mode)){
    close(fd);
    return NULL;
  }
  limit = limit > 0 ? tclmin((int)sbuf.st_size, limit) : sbuf.st_size;
  char *buf;
  TCMALLOC(buf, sbuf.st_size + 1);
  char *wp = buf;
  int rsiz;
  while((rsiz = read(fd, wp, limit - (wp - buf))) > 0){
    wp += rsiz;
  }
  *wp = '\0';
  close(fd);
  if(sp) *sp = wp - buf;
  return buf;
}


/* Read every line of a file. */
TCLIST *tcreadfilelines(const char *path){
  int fd = path ? open(path, O_RDONLY, TCFILEMODE) : 0;
  if(fd == -1) return NULL;
  TCLIST *list = tclistnew();
  TCXSTR *xstr = tcxstrnew();
  char buf[TCIOBUFSIZ];
  int rsiz;
  while((rsiz = read(fd, buf, TCIOBUFSIZ)) > 0){
    for(int i = 0; i < rsiz; i++){
      switch(buf[i]){
        case '\r':
          break;
        case '\n':
          TCLISTPUSH(list, TCXSTRPTR(xstr), TCXSTRSIZE(xstr));
          tcxstrclear(xstr);
          break;
        default:
          TCXSTRCAT(xstr, buf + i, 1);
          break;
      }
    }
  }
  TCLISTPUSH(list, TCXSTRPTR(xstr), TCXSTRSIZE(xstr));
  tcxstrdel(xstr);
  if(path) close(fd);
  return list;
}


/* Write data into a file. */
bool tcwritefile(const char *path, const void *ptr, int size){
  assert(ptr && size >= 0);
  int fd = 1;
  if(path && (fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, TCFILEMODE)) == -1) return false;
  bool err = false;
  if(!tcwrite(fd, ptr, size)) err = true;
  if(close(fd) == -1) err = true;
  return !err;
}


/* Copy a file. */
bool tccopyfile(const char *src, const char *dest){
  int ifd = open(src, O_RDONLY, TCFILEMODE);
  if(ifd == -1) return false;
  int ofd = open(dest, O_WRONLY | O_CREAT | O_TRUNC, TCFILEMODE);
  if(ofd == -1){
    close(ifd);
    return false;
  }
  bool err = false;
  while(true){
    char buf[TCIOBUFSIZ];
    int size = read(ifd, buf, TCIOBUFSIZ);
    if(size > 0){
      if(!tcwrite(ofd, buf, size)){
        err = true;
        break;
      }
    } else if(size == -1){
      if(errno != EINTR){
        err = true;
        break;
      }
    } else {
      break;
    }
  }
  if(close(ofd) == -1) err = true;
  if(close(ifd) == -1) err = true;
  return !err;
}


/* Read names of files in a directory. */
TCLIST *tcreaddir(const char *path){
  assert(path);
  DIR *DD;
  struct dirent *dp;
  if(!(DD = opendir(path))) return NULL;
  TCLIST *list = tclistnew();
  while((dp = readdir(DD)) != NULL){
    if(!strcmp(dp->d_name, MYCDIRSTR) || !strcmp(dp->d_name, MYPDIRSTR)) continue;
    TCLISTPUSH(list, dp->d_name, strlen(dp->d_name));
  }
  closedir(DD);
  return list;
}


/* Expand a pattern into a list of matched paths. */
TCLIST *tcglobpat(const char *pattern){
  assert(pattern);
  TCLIST *list = tclistnew();
  glob_t gbuf;
  memset(&gbuf, 0, sizeof(gbuf));
  if(glob(pattern, GLOB_ERR | GLOB_NOSORT, NULL, &gbuf) == 0){
    for(int i = 0; i < gbuf.gl_pathc; i++){
      tclistpush2(list, gbuf.gl_pathv[i]);
    }
    globfree(&gbuf);
  }
  return list;
}


/* Remove a file or a directory and its sub ones recursively. */
bool tcremovelink(const char *path){
  assert(path);
  struct stat sbuf;
  if(lstat(path, &sbuf) == -1) return false;
  if(unlink(path) == 0) return true;
  TCLIST *list;
  if(!S_ISDIR(sbuf.st_mode) || !(list = tcreaddir(path))) return false;
  bool tail = path[0] != '\0' && path[strlen(path)-1] == MYPATHCHR;
  for(int i = 0; i < TCLISTNUM(list); i++){
    const char *elem = TCLISTVALPTR(list, i);
    if(!strcmp(MYCDIRSTR, elem) || !strcmp(MYPDIRSTR, elem)) continue;
    char *cpath;
    if(tail){
      cpath = tcsprintf("%s%s", path, elem);
    } else {
      cpath = tcsprintf("%s%c%s", path, MYPATHCHR, elem);
    }
    tcremovelink(cpath);
    TCFREE(cpath);
  }
  tclistdel(list);
  return rmdir(path) == 0 ? true : false;
}


/* Write data into a file. */
bool tcwrite(int fd, const void *buf, size_t size){
  assert(fd >= 0 && buf && size >= 0);
  const char *rp = buf;
  do {
    int wb = write(fd, rp, size);
    switch(wb){
      case -1: if(errno != EINTR) return false;
      case 0: break;
      default:
        rp += wb;
        size -= wb;
        break;
    }
  } while(size > 0);
  return true;
}


/* Read data from a file. */
bool tcread(int fd, void *buf, size_t size){
  assert(fd >= 0 && buf && size >= 0);
  char *wp = buf;
  do {
    int rb = read(fd, wp, size);
    switch(rb){
      case -1: if(errno != EINTR) return false;
      case 0: return size < 1;
      default:
        wp += rb;
        size -= rb;
    }
  } while(size > 0);
  return true;
}


/* Lock a file. */
bool tclock(int fd, bool ex, bool nb){
  assert(fd >= 0);
  struct flock lock;
  memset(&lock, 0, sizeof(struct flock));
  lock.l_type = ex ? F_WRLCK : F_RDLCK;
  lock.l_whence = SEEK_SET;
  lock.l_start = 0;
  lock.l_len = 0;
  lock.l_pid = 0;
  while(fcntl(fd, nb ? F_SETLK : F_SETLKW, &lock) == -1){
    if(errno != EINTR) return false;
  }
  return true;
}


/* Unlock a file. */
bool tcunlock(int fd){
  assert(fd >= 0);
  struct flock lock;
  memset(&lock, 0, sizeof(struct flock));
  lock.l_type = F_UNLCK;
  lock.l_whence = SEEK_SET;
  lock.l_start = 0;
  lock.l_len = 0;
  lock.l_pid = 0;
  while(fcntl(fd, F_SETLKW, &lock) == -1){
    if(errno != EINTR) return false;
  }
  return true;
}


/* Execute a shell command. */
int tcsystem(const char **args, int anum){
  assert(args && anum >= 0);
  if(anum < 1) return -1;
  TCXSTR *phrase = tcxstrnew3(anum * TCNUMBUFSIZ + 1);
  for(int i = 0; i < anum; i++){
    const char *rp = args[i];
    int len = strlen(rp);
    char *token;
    TCMALLOC(token, len * 2 + 1);
    char *wp = token;
    while(*rp != '\0'){
      switch(*rp){
        case '"': case '\\': case '$': case '`':
          *(wp++) = '\\';
          *(wp++) = *rp;
          break;
        default:
          *(wp++) = *rp;
          break;
      }
      rp++;
    }
    *wp = '\0';
    if(tcxstrsize(phrase)) tcxstrcat(phrase, " ", 1);
    tcxstrprintf(phrase, "\"%s\"", token);
    TCFREE(token);
  }
  int rv = system(tcxstrptr(phrase));
  if(WIFEXITED(rv)){
    rv = WEXITSTATUS(rv);
  } else {
    rv = INT_MAX;
  }
  tcxstrdel(phrase);
  return rv;
}



/*************************************************************************************************
 * encoding utilities
 *************************************************************************************************/


#define TCENCBUFSIZ    32                // size of a buffer for encoding name
#define TCXMLATBNUM    31                // bucket number of XML attributes


/* Encode a serial object with URL encoding. */
char *tcurlencode(const char *ptr, int size){
  assert(ptr && size >= 0);
  char *buf;
  TCMALLOC(buf, size * 3 + 1);
  char *wp = buf;
  for(int i = 0; i < size; i++){
    int c = ((unsigned char *)ptr)[i];
    if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
       (c >= '0' && c <= '9') || (c != '\0' && strchr("_-.!~*'()", c))){
      *(wp++) = c;
    } else {
      wp += sprintf(wp, "%%%02X", c);
    }
  }
  *wp = '\0';
  return buf;
}


/* Decode a string encoded with URL encoding. */
char *tcurldecode(const char *str, int *sp){
  assert(str && sp);
  char *buf = tcstrdup(str);
  char *wp = buf;
  while(*str != '\0'){
    if(*str == '%'){
      str++;
      if(((str[0] >= '0' && str[0] <= '9') || (str[0] >= 'A' && str[0] <= 'F') ||
          (str[0] >= 'a' && str[0] <= 'f')) &&
         ((str[1] >= '0' && str[1] <= '9') || (str[1] >= 'A' && str[1] <= 'F') ||
          (str[1] >= 'a' && str[1] <= 'f'))){
        unsigned char c = *str;
        if(c >= 'A' && c <= 'Z') c += 'a' - 'A';
        if(c >= 'a' && c <= 'z'){
          *wp = c - 'a' + 10;
        } else {
          *wp = c - '0';
        }
        *wp *= 0x10;
        str++;
        c = *str;
        if(c >= 'A' && c <= 'Z') c += 'a' - 'A';
        if(c >= 'a' && c <= 'z'){
          *wp += c - 'a' + 10;
        } else {
          *wp += c - '0';
        }
        str++;
        wp++;
      } else {
        break;
      }
    } else if(*str == '+'){
      *wp = ' ';
      str++;
      wp++;
    } else {
      *wp = *str;
      str++;
      wp++;
    }
  }
  *wp = '\0';
  *sp = wp - buf;
  return buf;
}


/* Break up a URL into elements. */
TCMAP *tcurlbreak(const char *str){
  assert(str);
  TCMAP *map = tcmapnew2(TCMAPTINYBNUM);
  char *trim = tcstrdup(str);
  tcstrtrim(trim);
  const char *rp = trim;
  char *norm;
  TCMALLOC(norm, strlen(trim) * 3 + 1);
  char *wp = norm;
  while(*rp != '\0'){
    if(*rp > 0x20 && *rp < 0x7f){
      *(wp++) = *rp;
    } else {
      wp += sprintf(wp, "%%%02X", *(unsigned char *)rp);
    }
    rp++;
  }
  *wp = '\0';
  rp = norm;
  tcmapput2(map, "self", rp);
  bool serv = false;
  if(tcstrifwm(rp, "http://")){
    tcmapput2(map, "scheme", "http");
    rp += 7;
    serv = true;
  } else if(tcstrifwm(rp, "https://")){
    tcmapput2(map, "scheme", "https");
    rp += 8;
    serv = true;
  } else if(tcstrifwm(rp, "ftp://")){
    tcmapput2(map, "scheme", "ftp");
    rp += 6;
    serv = true;
  } else if(tcstrifwm(rp, "sftp://")){
    tcmapput2(map, "scheme", "sftp");
    rp += 7;
    serv = true;
  } else if(tcstrifwm(rp, "ftps://")){
    tcmapput2(map, "scheme", "ftps");
    rp += 7;
    serv = true;
  } else if(tcstrifwm(rp, "tftp://")){
    tcmapput2(map, "scheme", "tftp");
    rp += 7;
    serv = true;
  } else if(tcstrifwm(rp, "ldap://")){
    tcmapput2(map, "scheme", "ldap");
    rp += 7;
    serv = true;
  } else if(tcstrifwm(rp, "ldaps://")){
    tcmapput2(map, "scheme", "ldaps");
    rp += 8;
    serv = true;
  } else if(tcstrifwm(rp, "file://")){
    tcmapput2(map, "scheme", "file");
    rp += 7;
    serv = true;
  }
  char *ep;
  if((ep = strchr(rp, '#')) != NULL){
    tcmapput2(map, "fragment", ep + 1);
    *ep = '\0';
  }
  if((ep = strchr(rp, '?')) != NULL){
    tcmapput2(map, "query", ep + 1);
    *ep = '\0';
  }
  if(serv){
    if((ep = strchr(rp, '/')) != NULL){
      tcmapput2(map, "path", ep);
      *ep = '\0';
    } else {
      tcmapput2(map, "path", "/");
    }
    if((ep = strchr(rp, '@')) != NULL){
      *ep = '\0';
      if(rp[0] != '\0') tcmapput2(map, "authority", rp);
      rp = ep + 1;
    }
    if((ep = strchr(rp, ':')) != NULL){
      if(ep[1] != '\0') tcmapput2(map, "port", ep + 1);
      *ep = '\0';
    }
    if(rp[0] != '\0') tcmapput2(map, "host", rp);
  } else {
    tcmapput2(map, "path", rp);
  }
  TCFREE(norm);
  TCFREE(trim);
  if((rp = tcmapget2(map, "path")) != NULL){
    if((ep = strrchr(rp, '/')) != NULL){
      if(ep[1] != '\0') tcmapput2(map, "file", ep + 1);
    } else {
      tcmapput2(map, "file", rp);
    }
  }
  if((rp = tcmapget2(map, "file")) != NULL && (!strcmp(rp, ".") || !strcmp(rp, "..")))
    tcmapout2(map, "file");
  return map;
}


/* Resolve a relative URL with an absolute URL. */
char *tcurlresolve(const char *base, const char *target){
  assert(base && target);
  const char *vbuf, *path;
  char *tmp, *wp, *enc;
  while(*base > '\0' && *base <= ' '){
    base++;
  }
  while(*target > '\0' && *target <= ' '){
    target++;
  }
  if(*target == '\0') target = base;
  TCXSTR *rbuf = tcxstrnew();
  TCMAP *telems = tcurlbreak(target);
  int port = 80;
  TCMAP *belems = tcurlbreak(tcmapget2(telems, "scheme") ? target : base);
  if((vbuf = tcmapget2(belems, "scheme")) != NULL){
    tcxstrcat2(rbuf, vbuf);
    TCXSTRCAT(rbuf, "://", 3);
    if(!tcstricmp(vbuf, "https")){
      port = 443;
    } else if(!tcstricmp(vbuf, "ftp")){
      port = 21;
    } else if(!tcstricmp(vbuf, "sftp")){
      port = 115;
    } else if(!tcstricmp(vbuf, "ftps")){
      port = 22;
    } else if(!tcstricmp(vbuf, "tftp")){
      port = 69;
    } else if(!tcstricmp(vbuf, "ldap")){
      port = 389;
    } else if(!tcstricmp(vbuf, "ldaps")){
      port = 636;
    }
  } else {
    tcxstrcat2(rbuf, "http://");
  }
  int vsiz;
  if((vbuf = tcmapget2(belems, "authority")) != NULL){
    if((wp = strchr(vbuf, ':')) != NULL){
      *wp = '\0';
      tmp = tcurldecode(vbuf, &vsiz);
      enc = tcurlencode(tmp, vsiz);
      tcxstrcat2(rbuf, enc);
      TCFREE(enc);
      TCFREE(tmp);
      TCXSTRCAT(rbuf, ":", 1);
      wp++;
      tmp = tcurldecode(wp, &vsiz);
      enc = tcurlencode(tmp, vsiz);
      tcxstrcat2(rbuf, enc);
      TCFREE(enc);
      TCFREE(tmp);
    } else {
      tmp = tcurldecode(vbuf, &vsiz);
      enc = tcurlencode(tmp, vsiz);
      tcxstrcat2(rbuf, enc);
      TCFREE(enc);
      TCFREE(tmp);
    }
    TCXSTRCAT(rbuf, "@", 1);
  }
  if((vbuf = tcmapget2(belems, "host")) != NULL){
    tmp = tcurldecode(vbuf, &vsiz);
    tcstrtolower(tmp);
    enc = tcurlencode(tmp, vsiz);
    tcxstrcat2(rbuf, enc);
    TCFREE(enc);
    TCFREE(tmp);
  } else {
    TCXSTRCAT(rbuf, "localhost", 9);
  }
  int num;
  char numbuf[TCNUMBUFSIZ];
  if((vbuf = tcmapget2(belems, "port")) != NULL && (num = tcatoi(vbuf)) != port && num > 0){
    sprintf(numbuf, ":%d", num);
    tcxstrcat2(rbuf, numbuf);
  }
  if(!(path = tcmapget2(telems, "path"))) path = "/";
  if(path[0] == '\0' && (vbuf = tcmapget2(belems, "path")) != NULL) path = vbuf;
  if(path[0] == '\0') path = "/";
  TCLIST *bpaths = tclistnew();
  TCLIST *opaths;
  if(path[0] != '/' && (vbuf = tcmapget2(belems, "path")) != NULL){
    opaths = tcstrsplit(vbuf, "/");
  } else {
    opaths = tcstrsplit("/", "/");
  }
  TCFREE(tclistpop2(opaths));
  for(int i = 0; i < TCLISTNUM(opaths); i++){
    vbuf = tclistval(opaths, i, &vsiz);
    if(vsiz < 1 || !strcmp(vbuf, ".")) continue;
    if(!strcmp(vbuf, "..")){
      TCFREE(tclistpop2(bpaths));
    } else {
      TCLISTPUSH(bpaths, vbuf, vsiz);
    }
  }
  tclistdel(opaths);
  opaths = tcstrsplit(path, "/");
  for(int i = 0; i < TCLISTNUM(opaths); i++){
    vbuf = tclistval(opaths, i, &vsiz);
    if(vsiz < 1 || !strcmp(vbuf, ".")) continue;
    if(!strcmp(vbuf, "..")){
      TCFREE(tclistpop2(bpaths));
    } else {
      TCLISTPUSH(bpaths, vbuf, vsiz);
    }
  }
  tclistdel(opaths);
  for(int i = 0; i < TCLISTNUM(bpaths); i++){
    vbuf = TCLISTVALPTR(bpaths, i);
    if(strchr(vbuf, '%')){
      tmp = tcurldecode(vbuf, &vsiz);
    } else {
      tmp = tcstrdup(vbuf);
    }
    enc = tcurlencode(tmp, strlen(tmp));
    TCXSTRCAT(rbuf, "/", 1);
    tcxstrcat2(rbuf, enc);
    TCFREE(enc);
    TCFREE(tmp);
  }
  if(tcstrbwm(path, "/")) TCXSTRCAT(rbuf, "/", 1);
  tclistdel(bpaths);
  if((vbuf = tcmapget2(telems, "query")) != NULL ||
     (*target == '#' && (vbuf = tcmapget2(belems, "query")) != NULL)){
    TCXSTRCAT(rbuf, "?", 1);
    TCLIST *qelems = tcstrsplit(vbuf, "&;");
    for(int i = 0; i < TCLISTNUM(qelems); i++){
      vbuf = TCLISTVALPTR(qelems, i);
      if(i > 0) TCXSTRCAT(rbuf, "&", 1);
      if((wp = strchr(vbuf, '=')) != NULL){
        *wp = '\0';
        tmp = tcurldecode(vbuf, &vsiz);
        enc = tcurlencode(tmp, vsiz);
        tcxstrcat2(rbuf, enc);
        TCFREE(enc);
        TCFREE(tmp);
        TCXSTRCAT(rbuf, "=", 1);
        wp++;
        tmp = tcurldecode(wp, &vsiz);
        enc = tcurlencode(tmp, strlen(tmp));
        tcxstrcat2(rbuf, enc);
        TCFREE(enc);
        TCFREE(tmp);
      } else {
        tmp = tcurldecode(vbuf, &vsiz);
        enc = tcurlencode(tmp, vsiz);
        tcxstrcat2(rbuf, enc);
        TCFREE(enc);
        TCFREE(tmp);
      }
    }
    tclistdel(qelems);
  }
  if((vbuf = tcmapget2(telems, "fragment")) != NULL){
    tmp = tcurldecode(vbuf, &vsiz);
    enc = tcurlencode(tmp, vsiz);
    TCXSTRCAT(rbuf, "#", 1);
    tcxstrcat2(rbuf, enc);
    TCFREE(enc);
    TCFREE(tmp);
  }
  tcmapdel(belems);
  tcmapdel(telems);
  return tcxstrtomalloc(rbuf);
}


/* Encode a serial object with Base64 encoding. */
char *tcbaseencode(const char *ptr, int size){
  assert(ptr && size >= 0);
  char *tbl = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  const unsigned char *obj = (const unsigned char *)ptr;
  char *buf;
  TCMALLOC(buf, 4 * (size + 2) / 3 + 1);
  char *wp = buf;
  for(int i = 0; i < size; i += 3){
    switch(size - i){
      case 1:
        *wp++ = tbl[obj[0] >> 2];
        *wp++ = tbl[(obj[0] & 3) << 4];
        *wp++ = '=';
        *wp++ = '=';
        break;
      case 2:
        *wp++ = tbl[obj[0] >> 2];
        *wp++ = tbl[((obj[0] & 3) << 4) + (obj[1] >> 4)];
        *wp++ = tbl[(obj[1] & 0xf) << 2];
        *wp++ = '=';
        break;
      default:
        *wp++ = tbl[obj[0] >> 2];
        *wp++ = tbl[((obj[0] & 3) << 4) + (obj[1] >> 4)];
        *wp++ = tbl[((obj[1] & 0xf) << 2) + (obj[2] >> 6)];
        *wp++ = tbl[obj[2] & 0x3f];
        break;
    }
    obj += 3;
  }
  *wp = '\0';
  return buf;
}


/* Decode a string encoded with Base64 encoding. */
char *tcbasedecode(const char *str, int *sp){
  assert(str && sp);
  int cnt = 0;
  int bpos = 0;
  int eqcnt = 0;
  int len = strlen(str);
  unsigned char *obj;
  TCMALLOC(obj, len + 4);
  unsigned char *wp = obj;
  while(bpos < len && eqcnt == 0){
    int bits = 0;
    int i;
    for(i = 0; bpos < len && i < 4; bpos++){
      if(str[bpos] >= 'A' && str[bpos] <= 'Z'){
        bits = (bits << 6) | (str[bpos] - 'A');
        i++;
      } else if(str[bpos] >= 'a' && str[bpos] <= 'z'){
        bits = (bits << 6) | (str[bpos] - 'a' + 26);
        i++;
      } else if(str[bpos] >= '0' && str[bpos] <= '9'){
        bits = (bits << 6) | (str[bpos] - '0' + 52);
        i++;
      } else if(str[bpos] == '+'){
        bits = (bits << 6) | 62;
        i++;
      } else if(str[bpos] == '/'){
        bits = (bits << 6) | 63;
        i++;
      } else if(str[bpos] == '='){
        bits <<= 6;
        i++;
        eqcnt++;
      }
    }
    if(i == 0 && bpos >= len) continue;
    switch(eqcnt){
      case 0:
        *wp++ = (bits >> 16) & 0xff;
        *wp++ = (bits >> 8) & 0xff;
        *wp++ = bits & 0xff;
        cnt += 3;
        break;
      case 1:
        *wp++ = (bits >> 16) & 0xff;
        *wp++ = (bits >> 8) & 0xff;
        cnt += 2;
        break;
      case 2:
        *wp++ = (bits >> 16) & 0xff;
        cnt += 1;
        break;
    }
  }
  obj[cnt] = '\0';
  *sp = cnt;
  return (char *)obj;
}


/* Encode a serial object with Quoted-printable encoding. */
char *tcquoteencode(const char *ptr, int size){
  assert(ptr && size >= 0);
  const unsigned char *rp = (const unsigned char *)ptr;
  char *buf;
  TCMALLOC(buf, size * 3 + 1);
  char *wp = buf;
  int cols = 0;
  for(int i = 0; i < size; i++){
    if(rp[i] == '=' || (rp[i] < 0x20 && rp[i] != '\r' && rp[i] != '\n' && rp[i] != '\t') ||
       rp[i] > 0x7e){
      wp += sprintf(wp, "=%02X", rp[i]);
      cols += 3;
    } else {
      *(wp++) = rp[i];
      cols++;
    }
  }
  *wp = '\0';
  return buf;
}


/* Decode a string encoded with Quoted-printable encoding. */
char *tcquotedecode(const char *str, int *sp){
  assert(str && sp);
  char *buf;
  TCMALLOC(buf, strlen(str) + 1);
  char *wp = buf;
  for(; *str != '\0'; str++){
    if(*str == '='){
      str++;
      if(*str == '\0'){
        break;
      } else if(str[0] == '\r' && str[1] == '\n'){
        str++;
      } else if(str[0] != '\n' && str[0] != '\r'){
        if(*str >= 'A' && *str <= 'Z'){
          *wp = (*str - 'A' + 10) * 16;
        } else if(*str >= 'a' && *str <= 'z'){
          *wp = (*str - 'a' + 10) * 16;
        } else {
          *wp = (*str - '0') * 16;
        }
        str++;
        if(*str == '\0') break;
        if(*str >= 'A' && *str <= 'Z'){
          *wp += *str - 'A' + 10;
        } else if(*str >= 'a' && *str <= 'z'){
          *wp += *str - 'a' + 10;
        } else {
          *wp += *str - '0';
        }
        wp++;
      }
    } else {
      *wp = *str;
      wp++;
    }
  }
  *wp = '\0';
  *sp = wp - buf;
  return buf;
}


/* Encode a string with MIME encoding. */
char *tcmimeencode(const char *str, const char *encname, bool base){
  assert(str && encname);
  int len = strlen(str);
  char *buf;
  TCMALLOC(buf, len * 3 + strlen(encname) + 16);
  char *wp = buf;
  wp += sprintf(wp, "=?%s?%c?", encname, base ? 'B' : 'Q');
  char *enc = base ? tcbaseencode(str, len) : tcquoteencode(str, len);
  wp += sprintf(wp, "%s?=", enc);
  TCFREE(enc);
  return buf;
}


/* Decode a string encoded with MIME encoding. */
char *tcmimedecode(const char *str, char *enp){
  assert(str);
  if(enp) sprintf(enp, "US-ASCII");
  char *buf;
  TCMALLOC(buf, strlen(str) + 1);
  char *wp = buf;
  while(*str != '\0'){
    if(tcstrfwm(str, "=?")){
      str += 2;
      const char *pv = str;
      const char *ep = strchr(str, '?');
      if(!ep) continue;
      if(enp && ep - pv < TCENCBUFSIZ){
        memcpy(enp, pv, ep - pv);
        enp[ep-pv] = '\0';
      }
      pv = ep + 1;
      bool quoted = (*pv == 'Q' || *pv == 'q');
      if(*pv != '\0') pv++;
      if(*pv != '\0') pv++;
      if(!(ep = strchr(pv, '?'))) continue;
      char *tmp;
      TCMEMDUP(tmp, pv, ep - pv);
      int len;
      char *dec = quoted ? tcquotedecode(tmp, &len) : tcbasedecode(tmp, &len);
      wp += sprintf(wp, "%s", dec);
      TCFREE(dec);
      TCFREE(tmp);
      str = ep + 1;
      if(*str != '\0') str++;
    } else {
      *(wp++) = *str;
      str++;
    }
  }
  *wp = '\0';
  return buf;
}


/* Split a string of MIME into headers and the body. */
char *tcmimebreak(const char *ptr, int size, TCMAP *headers, int *sp){
  assert(ptr && size >= 0 && sp);
  const char *head = NULL;
  int hlen = 0;
  for(int i = 0; i < size; i++){
    if(i < size - 4 && ptr[i] == '\r' && ptr[i+1] == '\n' &&
       ptr[i+2] == '\r' && ptr[i+3] == '\n'){
      head = ptr;
      hlen = i;
      ptr += i + 4;
      size -= i + 4;
      break;
    } else if(i < size - 2 && ptr[i] == '\n' && ptr[i+1] == '\n'){
      head = ptr;
      hlen = i;
      ptr += i + 2;
      size -= i + 2;
      break;
    }
  }
  if(head && headers){
    char *hbuf;
    TCMALLOC(hbuf, hlen + 1);
    int wi = 0;
    for(int i = 0; i < hlen; i++){
      if(head[i] == '\r') continue;
      if(i < hlen - 1 && head[i] == '\n' && (head[i+1] == ' ' || head[i+1] == '\t')){
        hbuf[wi++] = ' ';
        i++;
      } else {
        hbuf[wi++] = head[i];
      }
    }
    hbuf[wi] = '\0';
    TCLIST *list = tcstrsplit(hbuf, "\n");
    int ln = TCLISTNUM(list);
    for(int i = 0; i < ln; i++){
      const char *line = TCLISTVALPTR(list, i);
      const char *pv = strchr(line, ':');
      if(pv){
        char *name;
        TCMEMDUP(name, line, pv - line);
        for(int j = 0; name[j] != '\0'; j++){
          if(name[j] >= 'A' && name[j] <= 'Z') name[j] -= 'A' - 'a';
        }
        pv++;
        while(*pv == ' ' || *pv == '\t'){
          pv++;
        }
        tcmapput2(headers, name, pv);
        TCFREE(name);
      }
    }
    tclistdel(list);
    TCFREE(hbuf);
    const char *pv = tcmapget2(headers, "content-type");
    if(pv){
      const char *ep = strchr(pv, ';');
      if(ep){
        tcmapput(headers, "TYPE", 4, pv, ep - pv);
        do {
          ep++;
          while(ep[0] == ' '){
            ep++;
          }
          if(tcstrifwm(ep, "charset=")){
            ep += 8;
            while(*ep > '\0' && *ep <= ' '){
              ep++;
            }
            if(ep[0] == '"') ep++;
            pv = ep;
            while(ep[0] != '\0' && ep[0] != ' ' && ep[0] != '"' && ep[0] != ';'){
              ep++;
            }
            tcmapput(headers, "CHARSET", 7, pv, ep - pv);
          } else if(tcstrifwm(ep, "boundary=")){
            ep += 9;
            while(*ep > '\0' && *ep <= ' '){
              ep++;
            }
            if(ep[0] == '"'){
              ep++;
              pv = ep;
              while(ep[0] != '\0' && ep[0] != '"'){
                ep++;
              }
            } else {
              pv = ep;
              while(ep[0] != '\0' && ep[0] != ' ' && ep[0] != '"' && ep[0] != ';'){
                ep++;
              }
            }
            tcmapput(headers, "BOUNDARY", 8, pv, ep - pv);
          }
        } while((ep = strchr(ep, ';')) != NULL);
      } else {
        tcmapput(headers, "TYPE", 4, pv, strlen(pv));
      }
    }
    if((pv = tcmapget2(headers, "content-disposition")) != NULL){
      char *ep = strchr(pv, ';');
      if(ep){
        tcmapput(headers, "DISPOSITION", 11, pv, ep - pv);
        do {
          ep++;
          while(ep[0] == ' '){
            ep++;
          }
          if(tcstrifwm(ep, "filename=")){
            ep += 9;
            if(ep[0] == '"') ep++;
            pv = ep;
            while(ep[0] != '\0' && ep[0] != '"'){
              ep++;
            }
            tcmapput(headers, "FILENAME", 8, pv, ep - pv);
          } else if(tcstrifwm(ep, "name=")){
            ep += 5;
            if(ep[0] == '"') ep++;
            pv = ep;
            while(ep[0] != '\0' && ep[0] != '"'){
              ep++;
            }
            tcmapput(headers, "NAME", 4, pv, ep - pv);
          }
        } while((ep = strchr(ep, ';')) != NULL);
      } else {
        tcmapput(headers, "DISPOSITION", 11, pv, strlen(pv));
      }
    }
  }
  *sp = size;
  char *rv;
  TCMEMDUP(rv, ptr, size);
  return rv;
}


/* Split multipart data of MIME into its parts. */
TCLIST *tcmimeparts(const char *ptr, int size, const char *boundary){
  assert(ptr && size >= 0 && boundary);
  TCLIST *list = tclistnew();
  int blen = strlen(boundary);
  if(blen < 1) return list;
  const char *pv = NULL;
  for(int i = 0; i < size; i++){
    if(ptr[i] == '-' && ptr[i+1] == '-' && i + 2 + blen < size &&
       tcstrfwm(ptr + i + 2, boundary) && strchr("\t\n\v\f\r ", ptr[i+2+blen])){
      pv = ptr + i + 2 + blen;
      if(*pv == '\r') pv++;
      if(*pv == '\n') pv++;
      size -= pv - ptr;
      ptr = pv;
      break;
    }
  }
  if(!pv) return list;
  for(int i = 0; i < size; i++){
    if(ptr[i] == '-' && ptr[i+1] == '-' && i + 2 + blen < size &&
       tcstrfwm(ptr + i + 2, boundary) && strchr("\t\n\v\f\r -", ptr[i+2+blen])){
      const char *ep = ptr + i;
      if(ep > ptr && ep[-1] == '\n') ep--;
      if(ep > ptr && ep[-1] == '\r') ep--;
      if(ep > pv) TCLISTPUSH(list, pv, ep - pv);
      pv = ptr + i + 2 + blen;
      if(*pv == '\r') pv++;
      if(*pv == '\n') pv++;
    }
  }
  return list;
}


/* Encode a serial object with hexadecimal encoding. */
char *tchexencode(const char *ptr, int size){
  assert(ptr && size >= 0);
  const unsigned char *rp = (const unsigned char *)ptr;
  char *buf;
  TCMALLOC(buf, size * 2 + 1);
  char *wp = buf;
  for(int i = 0; i < size; i++){
    wp += sprintf(wp, "%02x", rp[i]);
  }
  *wp = '\0';
  return buf;
}


/* Decode a string encoded with hexadecimal encoding. */
char *tchexdecode(const char *str, int *sp){
  assert(str && sp);
  int len = strlen(str);
  char *buf;
  TCMALLOC(buf, len + 1);
  char *wp = buf;
  for(int i = 0; i < len; i += 2){
    while(str[i] >= '\0' && str[i] <= ' '){
      i++;
    }
    int num = 0;
    int c = str[i];
    if(c == '\0') break;
    if(c >= '0' && c <= '9'){
      num = c - '0';
    } else if(c >= 'a' && c <= 'f'){
      num = c - 'a' + 10;
    } else if(c >= 'A' && c <= 'F'){
      num = c - 'A' + 10;
    } else if(c == '\0'){
      break;
    }
    c = str[i+1];
    if(c >= '0' && c <= '9'){
      num = num * 0x10 + c - '0';
    } else if(c >= 'a' && c <= 'f'){
      num = num * 0x10 + c - 'a' + 10;
    } else if(c >= 'A' && c <= 'F'){
      num = num * 0x10 + c - 'A' + 10;
    } else if(c == '\0'){
      break;
    }
    *(wp++) = num;
  }
  *wp = '\0';
  *sp = wp - buf;
  return buf;
}


/* Compress a serial object with Packbits encoding. */
char *tcpackencode(const char *ptr, int size, int *sp){
  assert(ptr && size >= 0 && sp);
  char *buf;
  TCMALLOC(buf, size * 2 + 1);
  char *wp = buf;
  const char *end = ptr + size;
  while(ptr < end){
    char *sp = wp;
    const char *rp = ptr + 1;
    int step = 1;
    while(rp < end && step < 0x7f && *rp == *ptr){
      step++;
      rp++;
    }
    if(step <= 1 && rp < end){
      wp = sp + 1;
      *(wp++) = *ptr;
      while(rp < end && step < 0x7f && *rp != *(rp - 1)){
        *(wp++) = *rp;
        step++;
        rp++;
      }
      if(rp < end && *(rp - 1) == *rp){
        wp--;
        rp--;
        step--;
      }
      *sp = step == 1 ? 1 : -step;
    } else {
      *(wp++) = step;
      *(wp++) = *ptr;
    }
    ptr += step;
  }
  *sp = wp - buf;
  return buf;
}


/* Decompress a serial object compressed with Packbits encoding. */
char *tcpackdecode(const char *ptr, int size, int *sp){
  assert(ptr && size >= 0 && sp);
  int asiz = size * 3;
  char *buf;
  TCMALLOC(buf, asiz + 1);
  int wi = 0;
  const char *end = ptr + size;
  while(ptr < end){
    int step = abs(*ptr);
    if(wi + step >= asiz){
      asiz = asiz * 2 + step;
      TCREALLOC(buf, buf, asiz + 1);
    }
    if(*(ptr++) >= 0){
      memset(buf + wi, *ptr, step);
      ptr++;
    } else {
      step = tclmin(step, end - ptr);
      memcpy(buf + wi, ptr, step);
      ptr += step;
    }
    wi += step;
  }
  buf[wi] = '\0';
  *sp = wi;
  return buf;
}


/* Compress a serial object with Deflate encoding. */
char *tcdeflate(const char *ptr, int size, int *sp){
  assert(ptr && size >= 0 && sp);
  if(!_tc_deflate) return NULL;
  return _tc_deflate(ptr, size, sp, _TCZMZLIB);
}


/* Decompress a serial object compressed with Deflate encoding. */
char *tcinflate(const char *ptr, int size, int *sp){
  assert(ptr && size >= 0 && sp);
  if(!_tc_inflate) return NULL;
  return _tc_inflate(ptr, size, sp, _TCZMZLIB);
}


/* Compress a serial object with GZIP encoding. */
char *tcgzipencode(const char *ptr, int size, int *sp){
  assert(ptr && size >= 0 && sp);
  if(!_tc_deflate) return NULL;
  return _tc_deflate(ptr, size, sp, _TCZMGZIP);
}


/* Decompress a serial object compressed with GZIP encoding. */
char *tcgzipdecode(const char *ptr, int size, int *sp){
  assert(ptr && size >= 0 && sp);
  if(!_tc_inflate) return NULL;
  return _tc_inflate(ptr, size, sp, _TCZMGZIP);
}


/* Get the CRC32 checksum of a serial object. */
unsigned int tcgetcrc(const char *ptr, int size){
  assert(ptr && size >= 0);
  if(!_tc_getcrc) return 0;
  return _tc_getcrc(ptr, size);
}


/* Compress a serial object with BZIP2 encoding. */
char *tcbzipencode(const char *ptr, int size, int *sp){
  assert(ptr && size >= 0 && sp);
  if(!_tc_bzcompress) return NULL;
  return _tc_bzcompress(ptr, size, sp);
}


/* Decompress a serial object compressed with BZIP2 encoding. */
char *tcbzipdecode(const char *ptr, int size, int *sp){
  assert(ptr && size >= 0 && sp);
  if(!_tc_bzdecompress) return NULL;
  return _tc_bzdecompress(ptr, size, sp);
}


/* Encode an array of nonnegative integers with BER encoding. */
char *tcberencode(const unsigned int *ary, int anum, int *sp){
  assert(ary && anum >= 0 && sp);
  char *buf;
  TCMALLOC(buf, anum * (sizeof(int) + 1) + 1);
  char *wp = buf;
  for(int i = 0; i < anum; i++){
    unsigned int num = ary[i];
    if(num < (1 << 7)){
      *(wp++) = num;
    } else if(num < (1 << 14)){
      *(wp++) = (num >> 7) | 0x80;
      *(wp++) = num & 0x7f;
    } else if(num < (1 << 21)){
      *(wp++) = (num >> 14) | 0x80;
      *(wp++) = ((num >> 7) & 0x7f) | 0x80;
      *(wp++) = num & 0x7f;
    } else if(num < (1 << 28)){
      *(wp++) = (num >> 21) | 0x80;
      *(wp++) = ((num >> 14) & 0x7f) | 0x80;
      *(wp++) = ((num >> 7) & 0x7f) | 0x80;
      *(wp++) = num & 0x7f;
    } else {
      *(wp++) = (num >> 28) | 0x80;
      *(wp++) = ((num >> 21) & 0x7f) | 0x80;
      *(wp++) = ((num >> 14) & 0x7f) | 0x80;
      *(wp++) = ((num >> 7) & 0x7f) | 0x80;
      *(wp++) = num & 0x7f;
    }
  }
  *sp = wp - buf;
  return buf;
}


/* Decode a serial object encoded with BER encoding. */
unsigned int *tcberdecode(const char *ptr, int size, int *np){
  assert(ptr && size >= 0 && np);
  unsigned int *buf;
  TCMALLOC(buf, size * sizeof(*buf) + 1);
  unsigned int *wp = buf;
  while(size > 0){
    unsigned int num = 0;
    int c;
    do {
      c = *(unsigned char *)ptr;
      num = num * 0x80 + (c & 0x7f);
      ptr++;
      size--;
    } while(c >= 0x80 && size > 0);
    *(wp++) = num;
  }
  *np = wp - buf;
  return buf;
}


/* Escape meta characters in a string with the entity references of XML. */
char *tcxmlescape(const char *str){
  assert(str);
  const char *rp = str;
  int bsiz = 0;
  while(*rp != '\0'){
    switch(*rp){
      case '&':
        bsiz += 5;
        break;
      case '<':
        bsiz += 4;
        break;
      case '>':
        bsiz += 4;
        break;
      case '"':
        bsiz += 6;
        break;
      default:
        bsiz++;
        break;
    }
    rp++;
  }
  char *buf;
  TCMALLOC(buf, bsiz + 1);
  char *wp = buf;
  while(*str != '\0'){
    switch(*str){
      case '&':
        memcpy(wp, "&amp;", 5);
        wp += 5;
        break;
      case '<':
        memcpy(wp, "&lt;", 4);
        wp += 4;
        break;
      case '>':
        memcpy(wp, "&gt;", 4);
        wp += 4;
        break;
      case '"':
        memcpy(wp, "&quot;", 6);
        wp += 6;
        break;
      default:
        *(wp++) = *str;
        break;
    }
    str++;
  }
  *wp = '\0';
  return buf;
}


/* Unescape entity references in a string of XML. */
char *tcxmlunescape(const char *str){
  assert(str);
  char *buf;
  TCMALLOC(buf, strlen(str) + 1);
  char *wp = buf;
  while(*str != '\0'){
    if(*str == '&'){
      if(tcstrfwm(str, "&amp;")){
        *(wp++) = '&';
        str += 5;
      } else if(tcstrfwm(str, "&lt;")){
        *(wp++) = '<';
        str += 4;
      } else if(tcstrfwm(str, "&gt;")){
        *(wp++) = '>';
        str += 4;
      } else if(tcstrfwm(str, "&quot;")){
        *(wp++) = '"';
        str += 6;
      } else {
        *(wp++) = *(str++);
      }
    } else {
      *(wp++) = *(str++);
    }
  }
  *wp = '\0';
  return buf;
}



/*************************************************************************************************
 * encoding utilities (for experts)
 *************************************************************************************************/


/* Encode a map object into a string in the x-www-form-urlencoded format. */
char *tcwwwformencode(const TCMAP *params){
  assert(params);
  TCXSTR *xstr = tcxstrnew3(tcmaprnum(params) * TCXSTRUNIT * 3 + 1);
  TCMAPREC *cur = params->cur;
  tcmapiterinit((TCMAP *)params);
  const char *kbuf;
  int ksiz;
  while((kbuf = tcmapiternext((TCMAP *)params, &ksiz)) != NULL){
    int vsiz;
    const char *vbuf = tcmapiterval(kbuf, &vsiz);
    char *kenc = tcurlencode(kbuf, ksiz);
    char *venc = tcurlencode(vbuf, vsiz);
    if(TCXSTRSIZE(xstr) > 0) TCXSTRCAT(xstr, "&", 1);
    tcxstrcat2(xstr, kenc);
    TCXSTRCAT(xstr, "=", 1);
    tcxstrcat2(xstr, venc);
    TCFREE(venc);
    TCFREE(kenc);
  }
  ((TCMAP *)params)->cur = cur;
  return tcxstrtomalloc(xstr);
}


/* Decode a query string in the x-www-form-urlencoded format. */
void tcwwwformdecode(const char *str, TCMAP *params){
  assert(str && params);
  tcwwwformdecode2(str, strlen(str), NULL, params);
}


/* Decode a data region in the x-www-form-urlencoded or multipart-form-data format. */
void tcwwwformdecode2(const void *ptr, int size, const char *type, TCMAP *params){
  assert(ptr && size >= 0 && params);
  if(type && tcstrfwm(tcstrskipspc(type), "multipart/")){
    const char *brd = strstr(type, "boundary=");
    if(brd){
      brd += 9;
      if(*brd == '"') brd++;
      char *bstr = tcstrdup(brd);
      char *wp = strchr(bstr, ';');
      if(wp) *wp = '\0';
      wp = strchr(bstr, '"');
      if(wp) *wp = '\0';
      TCLIST *parts = tcmimeparts(ptr, size, bstr);
      int pnum = tclistnum(parts);
      for(int i = 0; i < pnum; i++){
        int psiz;
        const char *part = tclistval(parts, i, &psiz);
        TCMAP *hmap = tcmapnew2(TCMAPTINYBNUM);
        int bsiz;
        char *body = tcmimebreak(part, psiz, hmap, &bsiz);
        int nsiz;
        const char *name = tcmapget(hmap, "NAME", 4, &nsiz);
        char numbuf[TCNUMBUFSIZ];
        if(!name){
          nsiz = sprintf(numbuf, "part:%d", i + 1);
          name = numbuf;
        }
        const char *tenc = tcmapget2(hmap, "content-transfer-encoding");
        if(tenc){
          if(tcstrifwm(tenc, "base64")){
            char *ebuf = tcbasedecode(body, &bsiz);
            TCFREE(body);
            body = ebuf;
          } else if(tcstrifwm(tenc, "quoted-printable")){
            char *ebuf = tcquotedecode(body, &bsiz);
            TCFREE(body);
            body = ebuf;
          }
        }
        tcmapputkeep(params, name, nsiz, body, bsiz);
        const char *fname = tcmapget2(hmap, "FILENAME");
        if(fname){
          if(*fname == '/'){
            fname = strrchr(fname, '/') + 1;
          } else if(((*fname >= 'a' && *fname <= 'z') || (*fname >= 'A' && *fname <= 'Z')) &&
                    fname[1] == ':' && fname[2] == '\\'){
            fname = strrchr(fname, '\\') + 1;
          }
          if(*fname != '\0'){
            char key[nsiz+10];
            sprintf(key, "%s_filename", name);
            tcmapput2(params, key, fname);
          }
        }
        tcfree(body);
        tcmapdel(hmap);
      }
      tclistdel(parts);
      tcfree(bstr);
    }
  } else {
    const char *rp = ptr;
    const char *pv = rp;
    const char *ep = rp + size;
    char stack[TCIOBUFSIZ];
    while(rp < ep){
      if(*rp == '&' || *rp == ';'){
        while(pv < rp && *pv > '\0' && *pv <= ' '){
          pv++;
        }
        if(rp > pv){
          int len = rp - pv;
          char *rbuf;
          if(len < sizeof(stack)){
            rbuf = stack;
          } else {
            TCMALLOC(rbuf, len + 1);
          }
          memcpy(rbuf, pv, len);
          rbuf[len] = '\0';
          char *sep = strchr(rbuf, '=');
          if(sep){
            *(sep++) = '\0';
          } else {
            sep = "";
          }
          int ksiz;
          char *kbuf = tcurldecode(rbuf, &ksiz);
          int vsiz;
          char *vbuf = tcurldecode(sep, &vsiz);
          if(!tcmapputkeep(params, kbuf, ksiz, vbuf, vsiz)){
            tcmapputcat(params, kbuf, ksiz, "", 1);
            tcmapputcat(params, kbuf, ksiz, vbuf, vsiz);
          }
          TCFREE(vbuf);
          TCFREE(kbuf);
          if(rbuf != stack) TCFREE(rbuf);
        }
        pv = rp + 1;
      }
      rp++;
    }
    while(pv < rp && *pv > '\0' && *pv <= ' '){
      pv++;
    }
    if(rp > pv){
      int len = rp - pv;
      char *rbuf;
      if(len < sizeof(stack)){
        rbuf = stack;
      } else {
        TCMALLOC(rbuf, len + 1);
      }
      memcpy(rbuf, pv, len);
      rbuf[len] = '\0';
      char *sep = strchr(rbuf, '=');
      if(sep){
        *(sep++) = '\0';
      } else {
        sep = "";
      }
      int ksiz;
      char *kbuf = tcurldecode(rbuf, &ksiz);
      int vsiz;
      char *vbuf = tcurldecode(sep, &vsiz);
      if(!tcmapputkeep(params, kbuf, ksiz, vbuf, vsiz)){
        tcmapputcat(params, kbuf, ksiz, "", 1);
        tcmapputcat(params, kbuf, ksiz, vbuf, vsiz);
      }
      TCFREE(vbuf);
      TCFREE(kbuf);
      if(rbuf != stack) TCFREE(rbuf);
    }
  }
}


/* Split an XML string into tags and text sections. */
TCLIST *tcxmlbreak(const char *str){
  assert(str);
  TCLIST *list = tclistnew();
  int i = 0;
  int pv = 0;
  bool tag = false;
  char *ep;
  while(true){
    if(str[i] == '\0'){
      if(i > pv) TCLISTPUSH(list, str + pv, i - pv);
      break;
    } else if(!tag && str[i] == '<'){
      if(str[i+1] == '!' && str[i+2] == '-' && str[i+3] == '-'){
        if(i > pv) TCLISTPUSH(list, str + pv, i - pv);
        if((ep = strstr(str + i, "-->")) != NULL){
          TCLISTPUSH(list, str + i, ep - str - i + 3);
          i = ep - str + 2;
          pv = i + 1;
        }
      } else if(str[i+1] == '!' && str[i+2] == '[' && tcstrifwm(str + i, "<![CDATA[")){
        if(i > pv) TCLISTPUSH(list, str + pv, i - pv);
        if((ep = strstr(str + i, "]]>")) != NULL){
          i += 9;
          TCXSTR *xstr = tcxstrnew();
          while(str + i < ep){
            if(str[i] == '&'){
              TCXSTRCAT(xstr, "&amp;", 5);
            } else if(str[i] == '<'){
              TCXSTRCAT(xstr, "&lt;", 4);
            } else if(str[i] == '>'){
              TCXSTRCAT(xstr, "&gt;", 4);
            } else {
              TCXSTRCAT(xstr, str + i, 1);
            }
            i++;
          }
          if(TCXSTRSIZE(xstr) > 0) TCLISTPUSH(list, TCXSTRPTR(xstr), TCXSTRSIZE(xstr));
          tcxstrdel(xstr);
          i = ep - str + 2;
          pv = i + 1;
        }
      } else {
        if(i > pv) TCLISTPUSH(list, str + pv, i - pv);
        tag = true;
        pv = i;
      }
    } else if(tag && str[i] == '>'){
      if(i > pv) TCLISTPUSH(list, str + pv, i - pv + 1);
      tag = false;
      pv = i + 1;
    }
    i++;
  }
  return list;
}


/* Get the map of attributes of an XML tag. */
TCMAP *tcxmlattrs(const char *str){
  assert(str);
  TCMAP *map = tcmapnew2(TCXMLATBNUM);
  const unsigned char *rp = (unsigned char *)str;
  while(*rp == '<' || *rp == '/' || *rp == '?' || *rp == '!' || *rp == ' '){
    rp++;
  }
  const unsigned char *key = rp;
  while(*rp > 0x20 && *rp != '/' && *rp != '>'){
    rp++;
  }
  tcmapputkeep(map, "", 0, (char *)key, rp - key);
  while(*rp != '\0'){
    while(*rp != '\0' && (*rp <= 0x20 || *rp == '/' || *rp == '?' || *rp == '>')){
      rp++;
    }
    key = rp;
    while(*rp > 0x20 && *rp != '/' && *rp != '>' && *rp != '='){
      rp++;
    }
    int ksiz = rp - key;
    while(*rp != '\0' && (*rp == '=' || *rp <= 0x20)){
      rp++;
    }
    const unsigned char *val;
    int vsiz;
    if(*rp == '"'){
      rp++;
      val = rp;
      while(*rp != '\0' && *rp != '"'){
        rp++;
      }
      vsiz = rp - val;
    } else if(*rp == '\''){
      rp++;
      val = rp;
      while(*rp != '\0' && *rp != '\''){
        rp++;
      }
      vsiz = rp - val;
    } else {
      val = rp;
      while(*rp > 0x20 && *rp != '"' && *rp != '\'' && *rp != '>'){
        rp++;
      }
      vsiz = rp - val;
    }
    if(*rp != '\0') rp++;
    if(ksiz > 0){
      char *copy;
      TCMEMDUP(copy, val, vsiz);
      char *raw = tcxmlunescape(copy);
      tcmapputkeep(map, (char *)key, ksiz, raw, strlen(raw));
      TCFREE(raw);
      TCFREE(copy);
    }
  }
  return map;
}


/* Escape meta characters in a string with backslash escaping of the C language. */
char *tccstrescape(const char *str){
  assert(str);
  int asiz = TCXSTRUNIT * 2;
  char *buf;
  TCMALLOC(buf, asiz + 4);
  int wi = 0;
  bool hex = false;
  int c;
  while((c = *(unsigned char *)str) != '\0'){
    if(wi >= asiz){
      asiz *= 2;
      TCREALLOC(buf, buf, asiz + 4);
    }
    if(c < ' ' || c == 0x7f || c == '"' || c == '\'' || c == '\\'){
      switch(c){
        case '\t': wi += sprintf(buf + wi, "\\t"); break;
        case '\n': wi += sprintf(buf + wi, "\\n"); break;
        case '\r': wi += sprintf(buf + wi, "\\r"); break;
        case '\\': wi += sprintf(buf + wi, "\\\\"); break;
        default:
          wi += sprintf(buf + wi, "\\x%02X", c);
          hex = true;
          break;
      }
    } else {
      if(hex && ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))){
        wi += sprintf(buf + wi, "\\x%02X", c);
        hex = true;
      } else {
        buf[wi++] = c;
        hex = false;
      }
    }
    str++;
  }
  buf[wi] = '\0';
  return buf;
}


/* Unescape a string escaped by backslash escaping of the C language. */
char *tccstrunescape(const char *str){
  assert(str);
  int asiz = TCXSTRUNIT * 2;
  char *buf;
  TCMALLOC(buf, asiz + 4);
  int wi = 0;
  int c;
  while((c = *(unsigned char *)str) != '\0'){
    if(wi >= asiz){
      asiz *= 2;
      TCREALLOC(buf, buf, asiz + 4);
    }
    if(c == '\\' && str[1] != '\0'){
      str++;
      int si = wi;
      switch(*str){
        case 'a': buf[wi++] = '\a'; break;
        case 'b': buf[wi++] = '\b'; break;
        case 't': buf[wi++] = '\t'; break;
        case 'n': buf[wi++] = '\n'; break;
        case 'v': buf[wi++] = '\v'; break;
        case 'f': buf[wi++] = '\f'; break;
        case 'r': buf[wi++] = '\r'; break;
      }
      if(si == wi){
        c = *str;
        if(c == 'x'){
          str++;
          int code = 0;
          for(int i = 0; i < 2; i++){
            c = *str;
            if(c >= '0' && c <= '9'){
              code = code * 0x10 + c - '0';
            } else if(c >= 'A' && c <= 'F'){
              code = code * 0x10 + c - 'A' + 10;
            } else if(c >= 'a' && c <= 'f'){
              code = code * 0x10 + c - 'a' + 10;
            } else {
              break;
            }
            str++;
          }
          buf[wi++] = code;
        } else if(c == 'u' || c == 'U'){
          int len = (c == 'U') ? 8 : 4;
          str++;
          int code = 0;
          for(int i = 0; i < len; i++){
            c = *str;
            if(c >= '0' && c <= '9'){
              code = code * 0x10 + c - '0';
            } else if(c >= 'A' && c <= 'F'){
              code = code * 0x10 + c - 'A' + 10;
            } else if(c >= 'a' && c <= 'f'){
              code = code * 0x10 + c - 'a' + 10;
            } else {
              break;
            }
            str++;
          }
          uint16_t ary[1];
          ary[0] = code;
          wi += tcstrucstoutf(ary, 1, buf + wi);
        } else if(c >= '0' && c <= '8'){
          int code = 0;
          for(int i = 0; i < 3; i++){
            c = *str;
            if(c >= '0' && c <= '7'){
              code = code * 8 + c - '0';
            } else {
              break;
            }
            str++;
          }
          buf[wi++] = code;
        } else if(c != '\0'){
          buf[wi++] = c;
          str++;
        }
      } else {
        str++;
      }
    } else {
      buf[wi++] = c;
      str++;
    }
  }
  buf[wi] = '\0';
  return buf;
}


/* Escape meta characters in a string with backslash escaping of JSON. */
char *tcjsonescape(const char *str){
  assert(str);
  int asiz = TCXSTRUNIT * 2;
  char *buf;
  TCMALLOC(buf, asiz + 6);
  int wi = 0;
  int c;
  while((c = *(unsigned char *)str) != '\0'){
    if(wi >= asiz){
      asiz *= 2;
      TCREALLOC(buf, buf, asiz + 6);
    }
    if(c < ' ' || c == 0x7f || c == '"' || c == '\'' || c == '\\'){
      switch(c){
        case '\t': wi += sprintf(buf + wi, "\\t"); break;
        case '\n': wi += sprintf(buf + wi, "\\n"); break;
        case '\r': wi += sprintf(buf + wi, "\\r"); break;
        case '\\': wi += sprintf(buf + wi, "\\\\"); break;
        default: wi += sprintf(buf + wi, "\\u%04X", c); break;
      }
    } else {
      buf[wi++] = c;
    }
    str++;
  }
  buf[wi] = '\0';
  return buf;
}


/* Unescape a string escaped by backslash escaping of JSON. */
char *tcjsonunescape(const char *str){
  assert(str);
  return tccstrunescape(str);
}



/*************************************************************************************************
 * template serializer
 *************************************************************************************************/


#define TCTMPLUNIT     65536             // allocation unit size of a template string
#define TCTMPLMAXDEP   256               // maximum depth of template blocks
#define TCTMPLBEGSEP   "[%"              // default beginning separator
#define TCTMPLENDSEP   "%]"              // default beginning separator
#define TCTYPRFXLIST   "[list]\0:"       // type prefix for a list object
#define TCTYPRFXMAP    "[map]\0:"        // type prefix for a list object


/* private function prototypes */
static TCLIST *tctmpltokenize(const char *expr);
static int tctmpldumpeval(TCXSTR *xstr, const char *expr, const TCLIST *elems, int cur, int num,
                          const TCMAP **stack, int depth);
static const char *tctmpldumpevalvar(const TCMAP **stack, int depth, const char *name,
                                     int *sp, int *np);


/* Create a template object. */
TCTMPL *tctmplnew(void){
  TCTMPL *tmpl;
  TCMALLOC(tmpl, sizeof(*tmpl));
  tmpl->elems = NULL;
  tmpl->begsep = NULL;
  tmpl->endsep = NULL;
  tmpl->conf = tcmapnew2(TCMAPTINYBNUM);
  return tmpl;
}


/* Delete a template object. */
void tctmpldel(TCTMPL *tmpl){
  assert(tmpl);
  tcmapdel(tmpl->conf);
  if(tmpl->endsep) TCFREE(tmpl->endsep);
  if(tmpl->begsep) TCFREE(tmpl->begsep);
  if(tmpl->elems) tclistdel(tmpl->elems);
  TCFREE(tmpl);
}


/* Set the separator strings of a template object. */
void tctmplsetsep(TCTMPL *tmpl, const char *begsep, const char *endsep){
  assert(tmpl && begsep && endsep);
  if(tmpl->endsep) TCFREE(tmpl->endsep);
  if(tmpl->begsep) TCFREE(tmpl->begsep);
  tmpl->begsep = tcstrdup(begsep);
  tmpl->endsep = tcstrdup(endsep);
}


/* Load a template string into a template object. */
void tctmplload(TCTMPL *tmpl, const char *str){
  assert(tmpl && str);
  const char *begsep = tmpl->begsep;
  if(!begsep) begsep = TCTMPLBEGSEP;
  const char *endsep = tmpl->endsep;
  if(!endsep) endsep = TCTMPLENDSEP;
  int beglen = strlen(begsep);
  int endlen = strlen(endsep);
  if(beglen < 1 || endlen < 1) return;
  int begchr = *begsep;
  int endchr = *endsep;
  if(tmpl->elems) tclistdel(tmpl->elems);
  tcmapclear(tmpl->conf);
  TCLIST *elems = tclistnew();
  const char *rp = str;
  const char *pv = rp;
  while(*rp != '\0'){
    if(*rp == begchr && tcstrfwm(rp, begsep)){
      if(rp > pv) TCLISTPUSH(elems, pv, rp - pv);
      rp += beglen;
      pv = rp;
      while(*rp != '\0'){
        if(*rp == endchr && tcstrfwm(rp, endsep)){
          bool chop = false;
          while(pv < rp && *pv > '\0' && *pv <= ' '){
            pv++;
          }
          if(*pv == '"'){
            pv++;
            const char *sp = pv;
            while(pv < rp && *pv != '"'){
              pv++;
            }
            if(pv > sp) TCLISTPUSH(elems, sp, pv - sp);
          } else if(*pv == '\''){
            pv++;
            const char *sp = pv;
            while(pv < rp && *pv != '\''){
              pv++;
            }
            if(pv > sp) TCLISTPUSH(elems, sp, pv - sp);
          } else {
            const char *ep = rp;
            if(ep > pv && ep[-1] == '\\'){
              ep--;
              chop = true;
            }
            while(ep > pv && ((unsigned char *)ep)[-1] <= ' '){
              ep--;
            }
            int len = ep - pv;
            char *buf;
            TCMALLOC(buf, len + 1);
            *buf = '\0';
            memcpy(buf + 1, pv, len);
            tclistpushmalloc(elems, buf, len + 1);
            if(tcstrfwm(pv, "CONF")){
              const char *expr = (char *)TCLISTVALPTR(elems, TCLISTNUM(elems) - 1) + 1;
              TCLIST *tokens = tctmpltokenize(expr);
              int tnum = TCLISTNUM(tokens);
              if(tnum > 1 && !strcmp(TCLISTVALPTR(tokens, 0), "CONF")){
                const char *name = TCLISTVALPTR(tokens, 1);
                const char *value = (tnum > 2) ? TCLISTVALPTR(tokens, 2) : "";
                tcmapput2(tmpl->conf, name, value);
              }
              tclistdel(tokens);
            }
          }
          rp += endlen;
          if(chop){
            if(*rp == '\r') rp++;
            if(*rp == '\n') rp++;
          }
          break;
        }
        rp++;
      }
      pv = rp;
    } else {
      rp++;
    }
  }
  if(rp > pv) TCLISTPUSH(elems, pv, rp - pv);
  tmpl->elems = elems;
}


/* Load a template string from a file into a template object. */
bool tctmplload2(TCTMPL *tmpl, const char *path){
  assert(tmpl && path);
  char *str = tcreadfile(path, -1, NULL);
  if(!str) return false;
  tctmplload(tmpl, str);
  TCFREE(str);
  return true;
}


/* Serialize the template string of a template object. */
char *tctmpldump(TCTMPL *tmpl, const TCMAP *vars){
  assert(tmpl && vars);
  TCXSTR *xstr = tcxstrnew3(TCTMPLUNIT);
  TCLIST *elems = tmpl->elems;
  if(elems){
    TCMAP *svars = tcmapnew2(TCMAPTINYBNUM);
    int cur = 0;
    int num = TCLISTNUM(elems);
    const TCMAP *stack[TCTMPLMAXDEP];
    int depth = 0;
    stack[depth++] = tmpl->conf;
    stack[depth++] = svars;
    stack[depth++] = vars;
    while(cur < num){
      const char *elem;
      int esiz;
      TCLISTVAL(elem, elems, cur, esiz);
      if(*elem == '\0' && esiz > 0){
        cur = tctmpldumpeval(xstr, elem + 1, elems, cur, num, stack, depth);
      } else {
        TCXSTRCAT(xstr, elem, esiz);
        cur++;
      }
    }
    tcmapdel(svars);
  }
  return tcxstrtomalloc(xstr);
}


/* Get the value of a configuration variable of a template object. */
const char *tctmplconf(TCTMPL *tmpl, const char *name){
  assert(tmpl && name);
  return tcmapget2(tmpl->conf, name);
}


/* Store a list object into a list object with the type information. */
void tclistpushlist(TCLIST *list, const TCLIST *obj){
  assert(list && obj);
  char vbuf[sizeof(TCTYPRFXLIST) - 1 + sizeof(obj)];
  memcpy(vbuf, TCTYPRFXLIST, sizeof(TCTYPRFXLIST) - 1);
  memcpy(vbuf + sizeof(TCTYPRFXLIST) - 1, &obj, sizeof(obj));
  tclistpush(list, vbuf, sizeof(vbuf));
}


/* Store a map object into a list object with the type information. */
void tclistpushmap(TCLIST *list, const TCMAP *obj){
  assert(list && obj);
  char vbuf[sizeof(TCTYPRFXMAP) - 1 + sizeof(obj)];
  memcpy(vbuf, TCTYPRFXMAP, sizeof(TCTYPRFXMAP) - 1);
  memcpy(vbuf + sizeof(TCTYPRFXMAP) - 1, &obj, sizeof(obj));
  tclistpush(list, vbuf, sizeof(vbuf));
}


/* Store a list object into a map object with the type information. */
void tcmapputlist(TCMAP *map, const char *kstr, const TCLIST *obj){
  assert(map && kstr && obj);
  char vbuf[sizeof(TCTYPRFXLIST) - 1 + sizeof(obj)];
  memcpy(vbuf, TCTYPRFXLIST, sizeof(TCTYPRFXLIST) - 1);
  memcpy(vbuf + sizeof(TCTYPRFXLIST) - 1, &obj, sizeof(obj));
  tcmapput(map, kstr, strlen(kstr), vbuf, sizeof(vbuf));
}


/* Store a map object into a map object with the type information. */
void tcmapputmap(TCMAP *map, const char *kstr, const TCMAP *obj){
  assert(map && kstr && obj);
  char vbuf[sizeof(TCTYPRFXMAP) - 1 + sizeof(obj)];
  memcpy(vbuf, TCTYPRFXMAP, sizeof(TCTYPRFXMAP) - 1);
  memcpy(vbuf + sizeof(TCTYPRFXMAP) - 1, &obj, sizeof(obj));
  tcmapput(map, kstr, strlen(kstr), vbuf, sizeof(vbuf));
}


/* Tokenize an template expression.
   `expr' specifies the expression.
   The return value is a list object of tokens. */
static TCLIST *tctmpltokenize(const char *expr){
  TCLIST *tokens = tclistnew();
  const unsigned char *rp = (unsigned char *)expr;
  while(*rp != '\0'){
    while(*rp > '\0' && *rp <= ' '){
      rp++;
    }
    const unsigned char *pv = rp;
    if(*rp == '"'){
      pv++;
      rp++;
      while(*rp != '\0' && *rp != '"'){
        rp++;
      }
      TCLISTPUSH(tokens, pv, rp - pv);
      if(*rp == '"') rp++;
    } else if(*rp == '\''){
      pv++;
      rp++;
      while(*rp != '\0' && *rp != '\''){
        rp++;
      }
      TCLISTPUSH(tokens, pv, rp - pv);
      if(*rp == '\'') rp++;
    } else {
      while(*rp > ' '){
        rp++;
      }
      if(rp > pv) TCLISTPUSH(tokens, pv, rp - pv);
    }
  }
  return tokens;
}


/* Evaluate an template expression.
   `xstr' specifies the output buffer.
   `expr' specifies the expression.
   `elems' specifies the list of elements.
   `cur' specifies the current offset of the elements.
   `num' specifies the number of the elements.
   `stack' specifies the variable scope stack.
   `depth' specifies the current depth of the stack.
   The return value is the next offset of the elements. */
static int tctmpldumpeval(TCXSTR *xstr, const char *expr, const TCLIST *elems, int cur, int num,
                          const TCMAP **stack, int depth){
  assert(expr && elems && cur >= 0 && num >= 0 && stack && depth >= 0);
  cur++;
  TCLIST *tokens = tctmpltokenize(expr);
  int tnum = TCLISTNUM(tokens);
  if(tnum > 0){
    const char *cmd = TCLISTVALPTR(tokens, 0);
    if(!strcmp(cmd, "IF")){
      bool sign = true;
      const char *eq = NULL;
      const char *inc = NULL;
      bool prt = false;
      const char *rx = NULL;
      for(int i = 1; i < tnum; i++){
        const char *token = TCLISTVALPTR(tokens, i);
        if(!strcmp(token, "NOT")){
          sign = !sign;
        } else if(!strcmp(token, "EQ")){
          if(++i < tnum) eq = TCLISTVALPTR(tokens, i);
        } else if(!strcmp(token, "INC")){
          if(++i < tnum) inc = TCLISTVALPTR(tokens, i);
        } else if(!strcmp(token, "PRT")){
          prt = true;
        } else if(!strcmp(token, "RX")){
          if(++i < tnum) rx = TCLISTVALPTR(tokens, i);
        }
      }
      TCXSTR *altxstr = NULL;
      if(xstr){
        const char *name = (tnum > 1) ? TCLISTVALPTR(tokens, 1) : "__";
        int vsiz, vnum;
        const char *vbuf = tctmpldumpevalvar(stack, depth, name, &vsiz, &vnum);
        char numbuf[TCNUMBUFSIZ];
        if(vbuf && vnum >= 0){
          vsiz = sprintf(numbuf, "%d", vnum);
          vbuf = numbuf;
        }
        bool bval = false;
        if(vbuf){
          if(eq){
            if(!strcmp(vbuf, eq)) bval = true;
          } else if(inc){
            if(strstr(vbuf, inc)) bval = true;
          } else if(prt){
            if(*tcstrskipspc(vbuf) != '\0') bval = true;
          } else if(rx){
            if(tcregexmatch(vbuf, rx)) bval = true;
          } else {
            bval = true;
          }
        }
        if(bval != sign){
          altxstr = xstr;
          xstr = NULL;
        }
      }
      while(cur < num){
        const char *elem;
        int esiz;
        TCLISTVAL(elem, elems, cur, esiz);
        if(*elem == '\0' && esiz > 0){
          cur = tctmpldumpeval(xstr, elem + 1, elems, cur, num, stack, depth);
          if(!strcmp(elem + 1, "ELSE")){
            xstr = altxstr;
          } else if(!strcmp(elem + 1, "END")){
            break;
          }
        } else {
          if(xstr) TCXSTRCAT(xstr, elem, esiz);
          cur++;
        }
      }
    } else if(!strcmp(cmd, "FOREACH")){
      const TCLIST *list = NULL;
      if(xstr){
        const char *name = (tnum > 1) ? TCLISTVALPTR(tokens, 1) : "";
        int vsiz, vnum;
        const char *vbuf = tctmpldumpevalvar(stack, depth, name, &vsiz, &vnum);
        if(vbuf && vsiz == sizeof(TCTYPRFXLIST) - 1 + sizeof(list) &&
           !memcmp(vbuf, TCTYPRFXLIST, sizeof(TCTYPRFXLIST) - 1)){
          memcpy(&list, vbuf + sizeof(TCTYPRFXLIST) - 1, sizeof(list));
        }
      }
      if(list && TCLISTNUM(list) > 0){
        const char *name = (tnum > 2) ? TCLISTVALPTR(tokens, 2) : "";
        TCMAP *vars = tcmapnew2(1);
        if(depth < TCTMPLMAXDEP){
          stack[depth] = vars;
          depth++;
        }
        int lnum = TCLISTNUM(list);
        int beg = cur;
        for(int i = 0; i < lnum; i++){
          const char *vbuf;
          int vsiz;
          TCLISTVAL(vbuf, list, i, vsiz);
          if(vsiz == sizeof(TCTYPRFXLIST) - 1 + sizeof(TCLIST *) &&
             !memcmp(vbuf, TCTYPRFXLIST, sizeof(TCTYPRFXLIST) - 1)){
            TCLIST *obj;
            memcpy(&obj, vbuf + sizeof(TCTYPRFXLIST) - 1, sizeof(obj));
            tcmapputlist(vars, name, obj);
          } else if(vsiz == sizeof(TCTYPRFXMAP) - 1 + sizeof(TCMAP *) &&
                    !memcmp(vbuf, TCTYPRFXMAP, sizeof(TCTYPRFXMAP) - 1)){
            TCMAP *obj;
            memcpy(&obj, vbuf + sizeof(TCTYPRFXMAP) - 1, sizeof(obj));
            tcmapputmap(vars, name, obj);
          } else {
            tcmapput2(vars, name, vbuf);
          }
          cur = beg;
          while(cur < num){
            const char *elem;
            int esiz;
            TCLISTVAL(elem, elems, cur, esiz);
            if(*elem == '\0' && esiz > 0){
              cur = tctmpldumpeval(xstr, elem + 1, elems, cur, num, stack, depth);
              if(!strcmp(elem + 1, "END")) break;
            } else {
              if(xstr) TCXSTRCAT(xstr, elem, esiz);
              cur++;
            }
          }
        }
        tcmapdel(vars);
      } else {
        while(cur < num){
          const char *elem;
          int esiz;
          TCLISTVAL(elem, elems, cur, esiz);
          if(*elem == '\0' && esiz > 0){
            cur = tctmpldumpeval(NULL, elem + 1, elems, cur, num, stack, depth);
            if(!strcmp(elem + 1, "END")) break;
          } else {
            cur++;
          }
        }
      }
    } else if(!strcmp(cmd, "SET")){
      if(xstr){
        const char *name = (tnum > 1) ? TCLISTVALPTR(tokens, 1) : "";
        const char *value = (tnum > 2) ? TCLISTVALPTR(tokens, 2) : "";
        tcmapput2((TCMAP *)stack[1], name, value);
      }
    } else if(xstr){
      int vsiz, vnum;
      const char *vbuf = tctmpldumpevalvar(stack, depth, cmd, &vsiz, &vnum);
      char numbuf[TCNUMBUFSIZ];
      if(vbuf && vnum >= 0){
        vsiz = sprintf(numbuf, "%d", vnum);
        vbuf = numbuf;
      }
      const char *enc = "";
      const char *def = NULL;
      for(int i = 1; i < tnum; i++){
        const char *token = TCLISTVALPTR(tokens, i);
        if(!strcmp(token, "ENC")){
          if(++i < tnum) enc = TCLISTVALPTR(tokens, i);
        } else if(!strcmp(token, "DEF")){
          if(++i < tnum) def = TCLISTVALPTR(tokens, i);
        }
      }
      if(!vbuf && def){
        vbuf = def;
        vsiz = strlen(def);
      }
      if(vbuf){
        if(!strcmp(enc, "URL")){
          char *ebuf = tcurlencode(vbuf, vsiz);
          tcxstrcat2(xstr, ebuf);
          TCFREE(ebuf);
        } else if(!strcmp(enc, "BASE")){
          char *ebuf = tcbaseencode(vbuf, vsiz);
          tcxstrcat2(xstr, ebuf);
          TCFREE(ebuf);
        } else if(!strcmp(enc, "QUOTE")){
          char *ebuf = tcquoteencode(vbuf, vsiz);
          tcxstrcat2(xstr, ebuf);
          TCFREE(ebuf);
        } else if(!strcmp(enc, "HEX")){
          char *ebuf = tchexencode(vbuf, vsiz);
          tcxstrcat2(xstr, ebuf);
          TCFREE(ebuf);
        } else if(!strcmp(enc, "XML")){
          char *ebuf = tcxmlescape(vbuf);
          tcxstrcat2(xstr, ebuf);
          TCFREE(ebuf);
        } else if(!strcmp(enc, "CSTR")){
          char *ebuf = tccstrescape(vbuf);
          tcxstrcat2(xstr, ebuf);
          TCFREE(ebuf);
        } else if(!strcmp(enc, "JSON")){
          char *ebuf = tcjsonescape(vbuf);
          tcxstrcat2(xstr, ebuf);
          TCFREE(ebuf);
        } else if(!strcmp(enc, "MD5")){
          char ebuf[48];
          tcmd5hash(vbuf, vsiz, ebuf);
          tcxstrcat2(xstr, ebuf);
        } else {
          tcxstrcat2(xstr, vbuf);
        }
      }
    }
  }
  tclistdel(tokens);
  return cur;
}


/* Evaluate a variable of a template expression.
   `stack' specifies the variable scope stack.
   `depth' specifies the current depth of the stack.
   `name' specifies the variable name.
   `sp' specifies the length of the region of the return value.
   `np' specifies the number information of the return value.
   The return value is the pointer to the region of the evaluated value. */
static const char *tctmpldumpevalvar(const TCMAP **stack, int depth, const char *name,
                                     int *sp, int *np){
  assert(stack && depth >= 0 && name && sp && np);
  const char *result = NULL;
  TCLIST *tokens = tcstrsplit(name, ".");
  int tnum = TCLISTNUM(tokens);
  if(tnum > 0){
    const char *token;
    int tsiz;
    TCLISTVAL(token, tokens, 0, tsiz);
    for(int i = depth - 1; i >= 0; i--){
      const TCMAP *vars = stack[i];
      int vsiz;
      const char *vbuf = tcmapget(vars, token, tsiz, &vsiz);
      int tidx = 1;
      if(vbuf){
        while(vbuf){
          if(vsiz == sizeof(TCTYPRFXLIST) - 1 + sizeof(TCLIST *) &&
             !memcmp(vbuf, TCTYPRFXLIST, sizeof(TCTYPRFXLIST) - 1)){
            result = vbuf;
            *sp = vsiz;
            TCLIST *list;
            memcpy(&list, vbuf + sizeof(TCTYPRFXLIST) - 1, sizeof(list));
            *np = tclistnum(list);
            break;
          } else if(vsiz == sizeof(TCTYPRFXMAP) - 1 + sizeof(TCMAP *) &&
                    !memcmp(vbuf, TCTYPRFXMAP, sizeof(TCTYPRFXMAP) - 1)){
            if(tidx < tnum){
              memcpy(&vars, vbuf + sizeof(TCTYPRFXMAP) - 1, sizeof(TCMAP *));
              TCLISTVAL(token, tokens, tidx, tsiz);
              vbuf = tcmapget(vars, token, tsiz, &vsiz);
              tidx++;
            } else {
              result = vbuf;
              *sp = vsiz;
              TCMAP *map;
              memcpy(&map, vbuf + sizeof(TCTYPRFXMAP) - 1, sizeof(map));
              *np = tcmaprnum(map);
              break;
            }
          } else {
            result = vbuf;
            *sp = vsiz;
            *np = -1;
            break;
          }
        }
        break;
      }
    }
  }
  tclistdel(tokens);
  return result;
}



/*************************************************************************************************
 * pointer list
 *************************************************************************************************/


/* Create a pointer list object. */
TCPTRLIST *tcptrlistnew(void){
  TCPTRLIST *ptrlist;
  TCMALLOC(ptrlist, sizeof(*ptrlist));
  ptrlist->anum = TCLISTUNIT;
  TCMALLOC(ptrlist->array, sizeof(ptrlist->array[0]) * ptrlist->anum);
  ptrlist->start = 0;
  ptrlist->num = 0;
  return ptrlist;
}


/* Create a pointer list object with expecting the number of elements. */
TCPTRLIST *tcptrlistnew2(int anum){
  TCPTRLIST *ptrlist;
  TCMALLOC(ptrlist, sizeof(*ptrlist));
  if(anum < 1) anum = 1;
  ptrlist->anum = anum;
  TCMALLOC(ptrlist->array, sizeof(ptrlist->array[0]) * ptrlist->anum);
  ptrlist->start = 0;
  ptrlist->num = 0;
  return ptrlist;
}


/* Copy a pointer list object. */
TCPTRLIST *tcptrlistdup(const TCPTRLIST *ptrlist){
  assert(ptrlist);
  int num = ptrlist->num;
  if(num < 1) return tcptrlistnew();
  void **array = ptrlist->array + ptrlist->start;
  TCPTRLIST *nptrlist;
  TCMALLOC(nptrlist, sizeof(*nptrlist));
  void **narray;
  TCMALLOC(narray, sizeof(*narray) * num);
  memcpy(narray, array, sizeof(*narray) * num);
  nptrlist->anum = num;
  nptrlist->array = narray;
  nptrlist->start = 0;
  nptrlist->num = num;
  return nptrlist;
}


/* Delete a pointer list object. */
void tcptrlistdel(TCPTRLIST *ptrlist){
  assert(ptrlist);
  TCFREE(ptrlist->array);
  TCFREE(ptrlist);
}


/* Get the number of elements of a pointer list object. */
int tcptrlistnum(const TCPTRLIST *ptrlist){
  assert(ptrlist);
  return ptrlist->num;
}


/* Get the pointer to the region of an element of a pointer list object. */
void *tcptrlistval(const TCPTRLIST *ptrlist, int index){
  assert(ptrlist && index >= 0);
  if(index >= ptrlist->num) return NULL;
  return ptrlist->array[ptrlist->start+index];
}


/* Add an element at the end of a pointer list object. */
void tcptrlistpush(TCPTRLIST *ptrlist, void *ptr){
  assert(ptrlist && ptr);
  int index = ptrlist->start + ptrlist->num;
  if(index >= ptrlist->anum){
    ptrlist->anum += ptrlist->num + 1;
    TCREALLOC(ptrlist->array, ptrlist->array, ptrlist->anum * sizeof(ptrlist->array[0]));
  }
  ptrlist->array[index] = ptr;
  ptrlist->num++;
}


/* Remove an element of the end of a pointer list object. */
void *tcptrlistpop(TCPTRLIST *ptrlist){
  assert(ptrlist);
  if(ptrlist->num < 1) return NULL;
  int index = ptrlist->start + ptrlist->num - 1;
  ptrlist->num--;
  return ptrlist->array[index];
}


/* Add an element at the top of a pointer list object. */
void tcptrlistunshift(TCPTRLIST *ptrlist, void *ptr){
  assert(ptrlist && ptr);
  if(ptrlist->start < 1){
    if(ptrlist->start + ptrlist->num >= ptrlist->anum){
      ptrlist->anum += ptrlist->num + 1;
      TCREALLOC(ptrlist->array, ptrlist->array, ptrlist->anum * sizeof(ptrlist->array[0]));
    }
    ptrlist->start = ptrlist->anum - ptrlist->num;
    memmove(ptrlist->array + ptrlist->start, ptrlist->array,
            ptrlist->num * sizeof(ptrlist->array[0]));
  }
  ptrlist->start--;
  ptrlist->array[ptrlist->start] = ptr;
  ptrlist->num++;
}


/* Remove an element of the top of a pointer list object. */
void *tcptrlistshift(TCPTRLIST *ptrlist){
  assert(ptrlist);
  if(ptrlist->num < 1) return NULL;
  int index = ptrlist->start;
  ptrlist->start++;
  ptrlist->num--;
  void *rv = ptrlist->array[index];
  if((ptrlist->start & 0xff) == 0 && ptrlist->start > (ptrlist->num >> 1)){
    memmove(ptrlist->array, ptrlist->array + ptrlist->start,
            ptrlist->num * sizeof(ptrlist->array[0]));
    ptrlist->start = 0;
  }
  return rv;
}


/* Add an element at the specified location of a pointer list object. */
void tcptrlistinsert(TCPTRLIST *ptrlist, int index, void *ptr){
  assert(ptrlist && index >= 0 && ptr);
  if(index > ptrlist->num) return;
  index += ptrlist->start;
  if(ptrlist->start + ptrlist->num >= ptrlist->anum){
    ptrlist->anum += ptrlist->num + 1;
    TCREALLOC(ptrlist->array, ptrlist->array, ptrlist->anum * sizeof(ptrlist->array[0]));
  }
  memmove(ptrlist->array + index + 1, ptrlist->array + index,
          sizeof(ptrlist->array[0]) * (ptrlist->start + ptrlist->num - index));
  ptrlist->array[index] = ptr;
  ptrlist->num++;
}


/* Remove an element at the specified location of a pointer list object. */
void *tcptrlistremove(TCPTRLIST *ptrlist, int index){
  assert(ptrlist && index >= 0);
  if(index >= ptrlist->num) return NULL;
  index += ptrlist->start;
  void *rv = ptrlist->array[index];
  ptrlist->num--;
  memmove(ptrlist->array + index, ptrlist->array + index + 1,
          sizeof(ptrlist->array[0]) * (ptrlist->start + ptrlist->num - index));
  return rv;
}


/* Overwrite an element at the specified location of a pointer list object. */
void tcptrlistover(TCPTRLIST *ptrlist, int index, void *ptr){
  assert(ptrlist && index >= 0 && ptr);
  if(index >= ptrlist->num) return;
  index += ptrlist->start;
  ptrlist->array[index] = ptr;
}


/* Clear a pointer list object. */
void tcptrlistclear(TCPTRLIST *ptrlist){
  assert(ptrlist);
  ptrlist->start = 0;
  ptrlist->num = 0;
}



/*************************************************************************************************
 * features for experts
 *************************************************************************************************/


#define TCBSENCUNIT    8192             // unit size of TCBS encoding
#define TCBWTCNTMIN    64               // minimum element number of counting sort
#define TCBWTCNTLV     4                // maximum recursion level of counting sort
#define TCBWTBUFNUM    16384            // number of elements of BWT buffer

typedef struct {                         // type of structure for a BWT character
  int fchr;                              // character code of the first character
  int tchr;                              // character code of the last character
} TCBWTREC;


/* private function prototypes */
static void tcglobalinit(void);
static void tcglobaldestroy(void);
static void tcbwtsortstrcount(const char **arrays, int anum, int len, int level);
static void tcbwtsortstrinsert(const char **arrays, int anum, int len, int skip);
static void tcbwtsortstrheap(const char **arrays, int anum, int len, int skip);
static void tcbwtsortchrcount(unsigned char *str, int len);
static void tcbwtsortchrinsert(unsigned char *str, int len);
static void tcbwtsortreccount(TCBWTREC *arrays, int anum);
static void tcbwtsortrecinsert(TCBWTREC *array, int anum);
static int tcbwtsearchrec(TCBWTREC *array, int anum, int tchr);
static void tcmtfencode(char *ptr, int size);
static void tcmtfdecode(char *ptr, int size);
static int tcgammaencode(const char *ptr, int size, char *obuf);
static int tcgammadecode(const char *ptr, int size, char *obuf);


/* Get the message string corresponding to an error code. */
const char *tcerrmsg(int ecode){
  switch(ecode){
    case TCESUCCESS: return "success";
    case TCETHREAD: return "threading error";
    case TCEINVALID: return "invalid operation";
    case TCENOFILE: return "file not found";
    case TCENOPERM: return "no permission";
    case TCEMETA: return "invalid meta data";
    case TCERHEAD: return "invalid record header";
    case TCEOPEN: return "open error";
    case TCECLOSE: return "close error";
    case TCETRUNC: return "trunc error";
    case TCESYNC: return "sync error";
    case TCESTAT: return "stat error";
    case TCESEEK: return "seek error";
    case TCEREAD: return "read error";
    case TCEWRITE: return "write error";
    case TCEMMAP: return "mmap error";
    case TCELOCK: return "lock error";
    case TCEUNLINK: return "unlink error";
    case TCERENAME: return "rename error";
    case TCEMKDIR: return "mkdir error";
    case TCERMDIR: return "rmdir error";
    case TCEKEEP: return "existing record";
    case TCENOREC: return "no record found";
    case TCEMISC: return "miscellaneous error";
  }
  return "unknown error";
}


/* Show error message on the standard error output and exit. */
void *tcmyfatal(const char *message){
  assert(message);
  if(tcfatalfunc){
    tcfatalfunc(message);
  } else {
    fprintf(stderr, "fatal error: %s\n", message);
  }
  exit(1);
  return NULL;
}


/* Allocate a large nullified region. */
void *tczeromap(uint64_t size){
#if defined(_SYS_LINUX_)
  assert(size > 0);
  void *ptr = mmap(0, sizeof(size) + size,
                   PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if(ptr == MAP_FAILED) tcmyfatal("out of memory");
  *(uint64_t *)ptr = size;
  return (char *)ptr + sizeof(size);
#else
  assert(size > 0);
  void *ptr;
  TCCALLOC(ptr, 1, size);
  return ptr;
#endif
}


/* Free a large nullfied region. */
void tczerounmap(void *ptr){
#if defined(_SYS_LINUX_)
  assert(ptr);
  uint64_t size = *((uint64_t *)ptr - 1);
  munmap((char *)ptr - sizeof(size), sizeof(size) + size);
#else
  assert(ptr);
  TCFREE(ptr);
#endif
}


/* Global mutex object. */
static pthread_once_t tcglobalonce = PTHREAD_ONCE_INIT;
static pthread_rwlock_t tcglobalmutex;
static pthread_mutex_t tcpathmutex;
static TCMAP *tcpathmap;


/* Lock the global mutex object. */
bool tcglobalmutexlock(void){
  pthread_once(&tcglobalonce, tcglobalinit);
  return pthread_rwlock_wrlock(&tcglobalmutex) == 0;
}


/* Lock the global mutex object by shared locking. */
bool tcglobalmutexlockshared(void){
  pthread_once(&tcglobalonce, tcglobalinit);
  return pthread_rwlock_rdlock(&tcglobalmutex) == 0;
}


/* Unlock the global mutex object. */
bool tcglobalmutexunlock(void){
  return pthread_rwlock_unlock(&tcglobalmutex) == 0;
}


/* Lock the absolute path of a file. */
bool tcpathlock(const char *path){
  assert(path);
  pthread_once(&tcglobalonce, tcglobalinit);
  if(pthread_mutex_lock(&tcpathmutex) != 0) return false;
  bool err = false;
  if(tcpathmap && !tcmapputkeep2(tcpathmap, path, "")) err = true;
  if(pthread_mutex_unlock(&tcpathmutex) != 0) err = true;
  return !err;
}


/* Unock the absolute path of a file. */
bool tcpathunlock(const char *path){
  assert(path);
  pthread_once(&tcglobalonce, tcglobalinit);
  if(pthread_mutex_lock(&tcpathmutex) != 0) return false;
  bool err = false;
  if(tcpathmap && !tcmapout2(tcpathmap, path)) err = true;
  if(pthread_mutex_unlock(&tcpathmutex) != 0) err = true;
  return !err;
}


/* Convert an integer to the string as binary numbers. */
int tcnumtostrbin(uint64_t num, char *buf, int col, int fc){
  assert(buf);
  char *wp = buf;
  int len = sizeof(num) * 8;
  bool zero = true;
  while(len-- > 0){
    if(num & (1ULL << 63)){
      *(wp++) = '1';
      zero = false;
    } else if(!zero){
      *(wp++) = '0';
    }
    num <<= 1;
  }
  if(col > 0){
    if(col > sizeof(num) * 8) col = sizeof(num) * 8;
    len = col - (wp - buf);
    if(len > 0){
      memmove(buf + len, buf, wp - buf);
      for(int i = 0; i < len; i++){
        buf[i] = fc;
      }
      wp += len;
    }
  } else if(zero){
    *(wp++) = '0';
  }
  *wp = '\0';
  return wp - buf;
}


/* Compare keys of two records by lexical order. */
int tccmplexical(const char *aptr, int asiz, const char *bptr, int bsiz, void *op){
  assert(aptr && asiz >= 0 && bptr && bsiz >= 0);
  int rv;
  TCCMPLEXICAL(rv, aptr, asiz, bptr, bsiz);
  return rv;
}


/* Compare two keys as decimal strings of real numbers. */
int tccmpdecimal(const char *aptr, int asiz, const char *bptr, int bsiz, void *op){
  assert(aptr && asiz >= 0 && bptr && bsiz >= 0);
  const unsigned char *arp = (unsigned char *)aptr;
  int alen = asiz;
  while(alen > 0 && (*arp <= ' ' || *arp == 0x7f)){
    arp++;
    alen--;
  }
  int64_t anum = 0;
  int asign = 1;
  if(alen > 0 && *arp == '-'){
    arp++;
    alen--;
    asign = -1;
  }
  while(alen > 0){
    int c = *arp;
    if(c < '0' || c > '9') break;
    anum = anum * 10 + c - '0';
    arp++;
    alen--;
  }
  anum *= asign;
  const unsigned char *brp = (unsigned char *)bptr;
  int blen = bsiz;
  while(blen > 0 && (*brp <= ' ' || *brp == 0x7f)){
    brp++;
    blen--;
  }
  int64_t bnum = 0;
  int bsign = 1;
  if(blen > 0 && *brp == '-'){
    brp++;
    blen--;
    bsign = -1;
  }
  while(blen > 0){
    int c = *brp;
    if(c < '0' || c > '9') break;
    bnum = bnum * 10 + c - '0';
    brp++;
    blen--;
  }
  bnum *= bsign;
  if(anum < bnum) return -1;
  if(anum > bnum) return 1;
  if((alen > 1 && *arp == '.') || (blen > 1 && *brp == '.')){
    long double aflt = 0;
    if(alen > 1 && *arp == '.'){
      arp++;
      alen--;
      if(alen > TCLDBLCOLMAX) alen = TCLDBLCOLMAX;
      long double base = 10;
      while(alen > 0){
        if(*arp < '0' || *arp > '9') break;
        aflt += (*arp - '0') / base;
        arp++;
        alen--;
        base *= 10;
      }
      aflt *= asign;
    }
    long double bflt = 0;
    if(blen > 1 && *brp == '.'){
      brp++;
      blen--;
      if(blen > TCLDBLCOLMAX) blen = TCLDBLCOLMAX;
      long double base = 10;
      while(blen > 0){
        if(*brp < '0' || *brp > '9') break;
        bflt += (*brp - '0') / base;
        brp++;
        blen--;
        base *= 10;
      }
      bflt *= bsign;
    }
    if(aflt < bflt) return -1;
    if(aflt > bflt) return 1;
  }
  int rv;
  TCCMPLEXICAL(rv, aptr, asiz, bptr, bsiz);
  return rv;
}


/* Compare two keys as 32-bit integers in the native byte order. */
int tccmpint32(const char *aptr, int asiz, const char *bptr, int bsiz, void *op){
  assert(aptr && bptr);
  int32_t anum, bnum;
  if(asiz == sizeof(int32_t)){
    memcpy(&anum, aptr, sizeof(int32_t));
  } else if(asiz < sizeof(int32_t)){
    memset(&anum, 0, sizeof(int32_t));
    memcpy(&anum, aptr, asiz);
  } else {
    memcpy(&anum, aptr, sizeof(int32_t));
  }
  if(bsiz == sizeof(int32_t)){
    memcpy(&bnum, bptr, sizeof(int32_t));
  } else if(bsiz < sizeof(int32_t)){
    memset(&bnum, 0, sizeof(int32_t));
    memcpy(&bnum, bptr, bsiz);
  } else {
    memcpy(&bnum, bptr, sizeof(int32_t));
  }
  return (anum < bnum) ? -1 : anum > bnum;
}


/* Compare two keys as 64-bit integers in the native byte order. */
int tccmpint64(const char *aptr, int asiz, const char *bptr, int bsiz, void *op){
  assert(aptr && bptr);
  int64_t anum, bnum;
  if(asiz == sizeof(int64_t)){
    memcpy(&anum, aptr, sizeof(int64_t));
  } else if(asiz < sizeof(int64_t)){
    memset(&anum, 0, sizeof(int64_t));
    memcpy(&anum, aptr, asiz);
  } else {
    memcpy(&anum, aptr, sizeof(int64_t));
  }
  if(bsiz == sizeof(int64_t)){
    memcpy(&bnum, bptr, sizeof(int64_t));
  } else if(bsiz < sizeof(int64_t)){
    memset(&bnum, 0, sizeof(int64_t));
    memcpy(&bnum, bptr, bsiz);
  } else {
    memcpy(&bnum, bptr, sizeof(int64_t));
  }
  return (anum < bnum) ? -1 : anum > bnum;
}


/* Compress a serial object with TCBS encoding. */
char *tcbsencode(const char *ptr, int size, int *sp){
  assert(ptr && size >= 0 && sp);
  char *result;
  TCMALLOC(result, (size * 7) / 3 + (size / TCBSENCUNIT + 1) * sizeof(uint16_t) +
           TCBSENCUNIT * 2 + 0x200);
  char *pv = result + size + 0x100;
  char *wp = pv;
  char *tp = pv + size + 0x100;
  const char *end = ptr + size;
  while(ptr < end){
    int usiz = tclmin(TCBSENCUNIT, end - ptr);
    memcpy(tp, ptr, usiz);
    memcpy(tp + usiz, ptr, usiz);
    char *sp = wp;
    uint16_t idx = 0;
    wp += sizeof(idx);
    const char *arrays[usiz+1];
    for(int i = 0; i < usiz; i++){
      arrays[i] = tp + i;
    }
    const char *fp = arrays[0];
    if(usiz >= TCBWTCNTMIN){
      tcbwtsortstrcount(arrays, usiz, usiz, 0);
    } else if(usiz > 1){
      tcbwtsortstrinsert(arrays, usiz, usiz, 0);
    }
    for(int i = 0; i < usiz; i++){
      int tidx = arrays[i] - fp;
      if(tidx == 0){
        idx = i;
        *(wp++) = ptr[usiz-1];
      } else {
        *(wp++) = ptr[tidx-1];
      }
    }
    idx = TCHTOIS(idx);
    memcpy(sp, &idx, sizeof(idx));
    ptr += TCBSENCUNIT;
  }
  size = wp - pv;
  tcmtfencode(pv, size);
  int nsiz = tcgammaencode(pv, size, result);
  *sp = nsiz;
  return result;
}


/* Decompress a serial object compressed with TCBS encoding. */
char *tcbsdecode(const char *ptr, int size, int *sp){
  char *result;
  TCMALLOC(result, size * 9 + 0x200);
  char *wp = result + size + 0x100;
  int nsiz = tcgammadecode(ptr, size, wp);
  tcmtfdecode(wp, nsiz);
  ptr = wp;
  wp = result;
  const char *end = ptr + nsiz;
  while(ptr < end){
    uint16_t idx;
    memcpy(&idx, ptr, sizeof(idx));
    idx = TCITOHS(idx);
    ptr += sizeof(idx);
    int usiz = tclmin(TCBSENCUNIT, end - ptr);
    if(idx >= usiz) idx = 0;
    char rbuf[usiz+1];
    memcpy(rbuf, ptr, usiz);
    if(usiz >= TCBWTCNTMIN){
      tcbwtsortchrcount((unsigned char *)rbuf, usiz);
    } else if(usiz > 0){
      tcbwtsortchrinsert((unsigned char *)rbuf, usiz);
    }
    int fnums[0x100], tnums[0x100];
    memset(fnums, 0, sizeof(fnums));
    memset(tnums, 0, sizeof(tnums));
    TCBWTREC array[usiz+1];
    TCBWTREC *rp = array;
    for(int i = 0; i < usiz; i++){
      int fc = *(unsigned char *)(rbuf + i);
      rp->fchr = (fc << 23) + fnums[fc]++;
      int tc = *(unsigned char *)(ptr + i);
      rp->tchr = (tc << 23) + tnums[tc]++;
      rp++;
    }
    unsigned int fchr = array[idx].fchr;
    if(usiz >= TCBWTCNTMIN){
      tcbwtsortreccount(array, usiz);
    } else if(usiz > 1){
      tcbwtsortrecinsert(array, usiz);
    }
    for(int i = 0; i < usiz; i++){
      if(array[i].fchr == fchr){
        idx = i;
        break;
      }
    }
    for(int i = 0; i < usiz; i++){
      *(wp++) = array[idx].fchr >> 23;
      idx = tcbwtsearchrec(array, usiz, array[idx].fchr);
    }
    ptr += usiz;
  }
  *wp = '\0';
  *sp = wp - result;
  return result;
}


/* Encode a serial object with BWT encoding. */
char *tcbwtencode(const char *ptr, int size, int *idxp){
  assert(ptr && size >= 0 && idxp);
  if(size < 1){
    *idxp = 0;
    char *rv;
    TCMEMDUP(rv, "", 0);
    return rv;
  }
  char *result;
  TCMALLOC(result, size * 3 + 1);
  char *tp = result + size + 1;
  memcpy(tp, ptr, size);
  memcpy(tp + size, ptr, size);
  const char *abuf[TCBWTBUFNUM];
  const char **arrays = abuf;
  if(size > TCBWTBUFNUM) TCMALLOC(arrays, sizeof(*arrays) * size);
  for(int i = 0; i < size; i++){
    arrays[i] = tp + i;
  }
  const char *fp = arrays[0];
  if(size >= TCBWTCNTMIN){
    tcbwtsortstrcount(arrays, size, size, -1);
  } else if(size > 1){
    tcbwtsortstrinsert(arrays, size, size, 0);
  }
  for(int i = 0; i < size; i++){
    int idx = arrays[i] - fp;
    if(idx == 0){
      *idxp = i;
      result[i] = ptr[size-1];
    } else {
      result[i] = ptr[idx-1];
    }
  }
  if(arrays != abuf) TCFREE(arrays);
  result[size] = '\0';
  return result;
}


/* Decode a serial object encoded with BWT encoding. */
char *tcbwtdecode(const char *ptr, int size, int idx){
  assert(ptr && size >= 0);
  if(size < 1 || idx < 0){
    char *rv;
    TCMEMDUP(rv, "", 0);
    return rv;
  }
  if(idx >= size) idx = 0;
  char *result;
  TCMALLOC(result, size + 1);
  memcpy(result, ptr, size);
  if(size >= TCBWTCNTMIN){
    tcbwtsortchrcount((unsigned char *)result, size);
  } else {
    tcbwtsortchrinsert((unsigned char *)result, size);
  }
  int fnums[0x100], tnums[0x100];
  memset(fnums, 0, sizeof(fnums));
  memset(tnums, 0, sizeof(tnums));
  TCBWTREC abuf[TCBWTBUFNUM];
  TCBWTREC *array = abuf;
  if(size > TCBWTBUFNUM) TCMALLOC(array, sizeof(*array) * size);
  TCBWTREC *rp = array;
  for(int i = 0; i < size; i++){
    int fc = *(unsigned char *)(result + i);
    rp->fchr = (fc << 23) + fnums[fc]++;
    int tc = *(unsigned char *)(ptr + i);
    rp->tchr = (tc << 23) + tnums[tc]++;
    rp++;
  }
  unsigned int fchr = array[idx].fchr;
  if(size >= TCBWTCNTMIN){
    tcbwtsortreccount(array, size);
  } else if(size > 1){
    tcbwtsortrecinsert(array, size);
  }
  for(int i = 0; i < size; i++){
    if(array[i].fchr == fchr){
      idx = i;
      break;
    }
  }
  char *wp = result;
  for(int i = 0; i < size; i++){
    *(wp++) = array[idx].fchr >> 23;
    idx = tcbwtsearchrec(array, size, array[idx].fchr);
  }
  *wp = '\0';
  if(array != abuf) TCFREE(array);
  return result;
}


/* Get the binary logarithm of an integer. */
long tclog2l(long num){
  if(num <= 1) return 0;
  num >>= 1;
  long rv = 0;
  while(num > 0){
    rv++;
    num >>= 1;
  }
  return rv;
}


/* Get the binary logarithm of a real number. */
double tclog2d(double num){
  return log(num) / log(2);
}


/* Get the aligned offset of a file offset. */
uint64_t tcpagealign(uint64_t off){
  int ps = sysconf(_SC_PAGESIZE);
  int diff = off & (ps - 1);
  return (diff > 0) ? off + ps - diff : off;
}


/* Initialize the global mutex object */
static void tcglobalinit(void){
  if(!TCUSEPTHREAD){
    memset(&tcglobalmutex, 0, sizeof(tcglobalmutex));
    memset(&tcpathmutex, 0, sizeof(tcpathmutex));
  }
  if(pthread_rwlock_init(&tcglobalmutex, NULL) != 0) tcmyfatal("rwlock error");
  if(pthread_mutex_init(&tcpathmutex, NULL) != 0) tcmyfatal("mutex error");
  tcpathmap = tcmapnew2(TCMAPTINYBNUM);
  atexit(tcglobaldestroy);
}


/* Destroy the global mutex object */
static void tcglobaldestroy(void){
  tcmapdel(tcpathmap);
  pthread_mutex_destroy(&tcpathmutex);
  pthread_rwlock_destroy(&tcglobalmutex);
}


/* Sort BWT string arrays by dicrionary order by counting sort.
   `array' specifies an array of string arrays.
   `anum' specifies the number of the array.
   `len' specifies the size of each string.
   `level' specifies the level of recursion. */
static void tcbwtsortstrcount(const char **arrays, int anum, int len, int level){
  assert(arrays && anum >= 0 && len >= 0);
  const char *nbuf[TCBWTBUFNUM];
  const char **narrays = nbuf;
  if(anum > TCBWTBUFNUM) TCMALLOC(narrays, sizeof(*narrays) * anum);
  int count[0x100], accum[0x100];
  memset(count, 0, sizeof(count));
  int skip = level < 0 ? 0 : level;
  for(int i = 0; i < anum; i++){
    count[((unsigned char *)arrays[i])[skip]]++;
  }
  memcpy(accum, count, sizeof(count));
  for(int i = 1; i < 0x100; i++){
    accum[i] = accum[i-1] + accum[i];
  }
  for(int i = 0; i < anum; i++){
    narrays[--accum[((unsigned char *)arrays[i])[skip]]] = arrays[i];
  }
  int off = 0;
  if(level >= 0 && level < TCBWTCNTLV){
    for(int i = 0; i < 0x100; i++){
      int c = count[i];
      if(c > 1){
        if(c >= TCBWTCNTMIN){
          tcbwtsortstrcount(narrays + off, c, len, level + 1);
        } else {
          tcbwtsortstrinsert(narrays + off, c, len, skip + 1);
        }
      }
      off += c;
    }
  } else {
    for(int i = 0; i < 0x100; i++){
      int c = count[i];
      if(c > 1){
        if(c >= TCBWTCNTMIN){
          tcbwtsortstrheap(narrays + off, c, len, skip + 1);
        } else {
          tcbwtsortstrinsert(narrays + off, c, len, skip + 1);
        }
      }
      off += c;
    }
  }
  memcpy(arrays, narrays, anum * sizeof(*narrays));
  if(narrays != nbuf) TCFREE(narrays);
}


/* Sort BWT string arrays by dicrionary order by insertion sort.
   `array' specifies an array of string arrays.
   `anum' specifies the number of the array.
   `len' specifies the size of each string.
   `skip' specifies the number of skipped bytes. */
static void tcbwtsortstrinsert(const char **arrays, int anum, int len, int skip){
  assert(arrays && anum >= 0 && len >= 0);
  for(int i = 1; i < anum; i++){
    int cmp = 0;
    const unsigned char *ap = (unsigned char *)arrays[i-1];
    const unsigned char *bp = (unsigned char *)arrays[i];
    for(int j = skip; j < len; j++){
      if(ap[j] != bp[j]){
        cmp = ap[j] - bp[j];
        break;
      }
    }
    if(cmp > 0){
      const char *swap = arrays[i];
      int j;
      for(j = i; j > 0; j--){
        int cmp = 0;
        const unsigned char *ap = (unsigned char *)arrays[j-1];
        const unsigned char *bp = (unsigned char *)swap;
        for(int k = skip; k < len; k++){
          if(ap[k] != bp[k]){
            cmp = ap[k] - bp[k];
            break;
          }
        }
        if(cmp < 0) break;
        arrays[j] = arrays[j-1];
      }
      arrays[j] = swap;
    }
  }
}


/* Sort BWT string arrays by dicrionary order by heap sort.
   `array' specifies an array of string arrays.
   `anum' specifies the number of the array.
   `len' specifies the size of each string.
   `skip' specifies the number of skipped bytes. */
static void tcbwtsortstrheap(const char **arrays, int anum, int len, int skip){
  assert(arrays && anum >= 0 && len >= 0);
  anum--;
  int bottom = (anum >> 1) + 1;
  int top = anum;
  while(bottom > 0){
    bottom--;
    int mybot = bottom;
    int i = mybot * 2;
    while(i <= top){
      if(i < top){
        int cmp = 0;
        const unsigned char *ap = (unsigned char *)arrays[i+1];
        const unsigned char *bp = (unsigned char *)arrays[i];
        for(int j = skip; j < len; j++){
          if(ap[j] != bp[j]){
            cmp = ap[j] - bp[j];
            break;
          }
        }
        if(cmp > 0) i++;
      }
      int cmp = 0;
      const unsigned char *ap = (unsigned char *)arrays[mybot];
      const unsigned char *bp = (unsigned char *)arrays[i];
      for(int j = skip; j < len; j++){
        if(ap[j] != bp[j]){
          cmp = ap[j] - bp[j];
          break;
        }
      }
      if(cmp >= 0) break;
      const char *swap = arrays[mybot];
      arrays[mybot] = arrays[i];
      arrays[i] = swap;
      mybot = i;
      i = mybot * 2;
    }
  }
  while(top > 0){
    const char *swap = arrays[0];
    arrays[0] = arrays[top];
    arrays[top] = swap;
    top--;
    int mybot = bottom;
    int i = mybot * 2;
    while(i <= top){
      if(i < top){
        int cmp = 0;
        const unsigned char *ap = (unsigned char *)arrays[i+1];
        const unsigned char *bp = (unsigned char *)arrays[i];
        for(int j = 0; j < len; j++){
          if(ap[j] != bp[j]){
            cmp = ap[j] - bp[j];
            break;
          }
        }
        if(cmp > 0) i++;
      }
      int cmp = 0;
      const unsigned char *ap = (unsigned char *)arrays[mybot];
      const unsigned char *bp = (unsigned char *)arrays[i];
      for(int j = 0; j < len; j++){
        if(ap[j] != bp[j]){
          cmp = ap[j] - bp[j];
          break;
        }
      }
      if(cmp >= 0) break;
      swap = arrays[mybot];
      arrays[mybot] = arrays[i];
      arrays[i] = swap;
      mybot = i;
      i = mybot * 2;
    }
  }
}


/* Sort BWT characters by code number by counting sort.
   `str' specifies a string.
   `len' specifies the length of the string. */
static void tcbwtsortchrcount(unsigned char *str, int len){
  assert(str && len >= 0);
  int cnt[0x100];
  memset(cnt, 0, sizeof(cnt));
  for(int i = 0; i < len; i++){
    cnt[str[i]]++;
  }
  int pos = 0;
  for(int i = 0; i < 0x100; i++){
    memset(str + pos, i, cnt[i]);
    pos += cnt[i];
  }
}


/* Sort BWT characters by code number by insertion sort.
   `str' specifies a string.
   `len' specifies the length of the string. */
static void tcbwtsortchrinsert(unsigned char *str, int len){
  assert(str && len >= 0);
  for(int i = 1; i < len; i++){
    if(str[i-1] - str[i] > 0){
      unsigned char swap = str[i];
      int j;
      for(j = i; j > 0; j--){
        if(str[j-1] - swap < 0) break;
        str[j] = str[j-1];
      }
      str[j] = swap;
    }
  }
}


/* Sort BWT records by code number by counting sort.
   `array' specifies an array of records.
   `anum' specifies the number of the array. */
static void tcbwtsortreccount(TCBWTREC *array, int anum){
  assert(array && anum >= 0);
  TCBWTREC nbuf[TCBWTBUFNUM];
  TCBWTREC *narray = nbuf;
  if(anum > TCBWTBUFNUM) TCMALLOC(narray, sizeof(*narray) * anum);
  int count[0x100], accum[0x100];
  memset(count, 0, sizeof(count));
  for(int i = 0; i < anum; i++){
    count[array[i].tchr>>23]++;
  }
  memcpy(accum, count, sizeof(count));
  for(int i = 1; i < 0x100; i++){
    accum[i] = accum[i-1] + accum[i];
  }
  for(int i = 0; i < 0x100; i++){
    accum[i] -= count[i];
  }
  for(int i = 0; i < anum; i++){
    narray[accum[array[i].tchr>>23]++] = array[i];
  }
  memcpy(array, narray, anum * sizeof(*narray));
  if(narray != nbuf) TCFREE(narray);
}


/* Sort BWT records by code number by insertion sort.
   `array' specifies an array of records..
   `anum' specifies the number of the array. */
static void tcbwtsortrecinsert(TCBWTREC *array, int anum){
  assert(array && anum >= 0);
  for(int i = 1; i < anum; i++){
    if(array[i-1].tchr - array[i].tchr > 0){
      TCBWTREC swap = array[i];
      int j;
      for(j = i; j > 0; j--){
        if(array[j-1].tchr - swap.tchr < 0) break;
        array[j] = array[j-1];
      }
      array[j] = swap;
    }
  }
}


/* Search the element of BWT records.
   `array' specifies an array of records.
   `anum' specifies the number of the array.
   `tchr' specifies the last code number. */
static int tcbwtsearchrec(TCBWTREC *array, int anum, int tchr){
  assert(array && anum >= 0);
  int bottom = 0;
  int top = anum;
  int mid;
  do {
    mid = (bottom + top) >> 1;
    if(array[mid].tchr == tchr){
      return mid;
    } else if(array[mid].tchr < tchr){
      bottom = mid + 1;
      if(bottom >= anum) break;
    } else {
      top = mid - 1;
    }
  } while(bottom <= top);
  return -1;
}


/* Initialization table for MTF encoder. */
const unsigned char tcmtftable[] = {
  0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,
  0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,
  0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,
  0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,
  0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,
  0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f,
  0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
  0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,
  0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,
  0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f,
  0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf,
  0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbf,
  0xc0,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xcb,0xcc,0xcd,0xce,0xcf,
  0xd0,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xdb,0xdc,0xdd,0xde,0xdf,
  0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef,
  0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff
};


/* Encode a region with MTF encoding.
   `ptr' specifies the pointer to the region.
   `size' specifies the size of the region. */
static void tcmtfencode(char *ptr, int size){
  unsigned char table1[0x100], table2[0x100], *table, *another;
  assert(ptr && size >= 0);
  memcpy(table1, tcmtftable, sizeof(tcmtftable));
  table = table1;
  another = table2;
  const char *end = ptr + size;
  char *wp = ptr;
  while(ptr < end){
    unsigned char c = *ptr;
    unsigned char *tp = table;
    unsigned char *tend = table + 0x100;
    while(tp < tend && *tp != c){
      tp++;
    }
    int idx = tp - table;
    *(wp++) = idx;
    if(idx > 0){
      memcpy(another, &c, 1);
      memcpy(another + 1, table, idx);
      memcpy(another + 1 + idx, table + idx + 1, 255 - idx);
      unsigned char *swap = table;
      table = another;
      another = swap;
    }
    ptr++;
  }
}


/* Decode a region compressed with MTF encoding.
   `ptr' specifies the pointer to the region.
   `size' specifies the size of the region. */
static void tcmtfdecode(char *ptr, int size){
  assert(ptr && size >= 0);
  unsigned char table1[0x100], table2[0x100], *table, *another;
  assert(ptr && size >= 0);
  memcpy(table1, tcmtftable, sizeof(tcmtftable));
  table = table1;
  another = table2;
  const char *end = ptr + size;
  char *wp = ptr;
  while(ptr < end){
    int idx = *(unsigned char *)ptr;
    unsigned char c = table[idx];
    *(wp++) = c;
    if(idx > 0){
      memcpy(another, &c, 1);
      memcpy(another + 1, table, idx);
      memcpy(another + 1 + idx, table + idx + 1, 255 - idx);
      unsigned char *swap = table;
      table = another;
      another = swap;
    }
    ptr++;
  }
}


/* Encode a region with Elias gamma encoding.
   `ptr' specifies the pointer to the region.
   `size' specifies the size of the region.
   `obuf' specifies the pointer to the output buffer.
   The return value is the size of the output buffer. */
static int tcgammaencode(const char *ptr, int size, char *obuf){
  assert(ptr && size >= 0 && obuf);
  TCBITSTRM strm;
  TCBITSTRMINITW(strm, obuf);
  const char *end = ptr + size;
  while(ptr < end){
    unsigned int c = *(unsigned char *)ptr;
    if(!c){
      TCBITSTRMCAT(strm, 1);
    } else {
      c++;
      int plen = 8;
      while(plen > 0 && !(c & (1 << plen))){
        plen--;
      }
      int jlen = plen;
      while(jlen-- > 0){
        TCBITSTRMCAT(strm, 0);
      }
      while(plen >= 0){
        int sign = (c & (1 << plen)) > 0;
        TCBITSTRMCAT(strm, sign);
        plen--;
      }
    }
    ptr++;
  }
  TCBITSTRMSETEND(strm);
  return TCBITSTRMSIZE(strm);
}


/* Decode a region compressed with Elias gamma encoding.
   `ptr' specifies the pointer to the region.
   `size' specifies the size of the region.
   `obuf' specifies the pointer to the output buffer.
   The return value is the size of the output buffer. */
static int tcgammadecode(const char *ptr, int size, char *obuf){
  assert(ptr && size >= 0 && obuf);
  char *wp = obuf;
  TCBITSTRM strm;
  TCBITSTRMINITR(strm, ptr, size);
  int bnum = TCBITSTRMNUM(strm);
  while(bnum > 0){
    int sign;
    TCBITSTRMREAD(strm, sign);
    bnum--;
    if(sign){
      *(wp++) = 0;
    } else {
      int plen = 1;
      while(bnum > 0){
        TCBITSTRMREAD(strm, sign);
        bnum--;
        if(sign) break;
        plen++;
      }
      unsigned int c = 1;
      while(bnum > 0 && plen-- > 0){
        TCBITSTRMREAD(strm, sign);
        bnum--;
        c = (c << 1) + (sign > 0);
      }
      *(wp++) = c - 1;
    }
  }
  return wp - obuf;
}



// END OF FILE
/*
  Copyright (C) 1999, 2000, 2002 Aladdin Enterprises.  All rights reserved.

  This software is provided 'as-is', without any express or implied
  warranty.  In no event will the authors be held liable for any damages
  arising from the use of this software.

  Permission is granted to anyone to use this software for any purpose,
  including commercial applications, and to alter it and redistribute it
  freely, subject to the following restrictions:

  1. The origin of this software must not be misrepresented; you must not
     claim that you wrote the original software. If you use this software
     in a product, an acknowledgment in the product documentation would be
     appreciated but is not required.
  2. Altered source versions must be plainly marked as such, and must not be
     misrepresented as being the original software.
  3. This notice may not be removed or altered from any source distribution.

  L. Peter Deutsch
  ghost@aladdin.com

 */
/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */
/*
  Independent implementation of MD5 (RFC 1321).

  This code implements the MD5 Algorithm defined in RFC 1321, whose
  text is available at
        http://www.ietf.org/rfc/rfc1321.txt
  The code is derived from the text of the RFC, including the test suite
  (section A.5) but excluding the rest of Appendix A.  It does not include
  any code or documentation that is identified in the RFC as being
  copyrighted.

  The original and principal author of md5.c is L. Peter Deutsch
  <ghost@aladdin.com>.  Other authors are noted in the change history
  that follows (in reverse chronological order):

  2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order
        either statically or dynamically; added missing #include <string.h>
        in library.
  2002-03-11 lpd Corrected argument list for main(), and added int return
        type, in test program and T value program.
  2002-02-21 lpd Added missing #include <stdio.h> in test program.
  2000-07-03 lpd Patched to eliminate warnings about "constant is
        unsigned in ANSI C, signed in traditional"; made test program
        self-checking.
  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5).
  1999-05-03 lpd Original version.
 */

#include "md5.h"
#include <string.h>

#undef BYTE_ORDER       /* 1 = big-endian, -1 = little-endian, 0 = unknown */
#ifdef ARCH_IS_BIG_ENDIAN
#  define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)
#else
#  define BYTE_ORDER 0
#endif

#define T_MASK ((md5_word_t)~0)
#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)
#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)
#define T3    0x242070db
#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)
#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)
#define T6    0x4787c62a
#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)
#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)
#define T9    0x698098d8
#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)
#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)
#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)
#define T13    0x6b901122
#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)
#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)
#define T16    0x49b40821
#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)
#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)
#define T19    0x265e5a51
#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)
#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)
#define T22    0x02441453
#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)
#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)
#define T25    0x21e1cde6
#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)
#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)
#define T28    0x455a14ed
#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)
#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)
#define T31    0x676f02d9
#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)
#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)
#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)
#define T35    0x6d9d6122
#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)
#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)
#define T38    0x4bdecfa9
#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)
#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)
#define T41    0x289b7ec6
#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)
#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)
#define T44    0x04881d05
#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)
#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)
#define T47    0x1fa27cf8
#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)
#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)
#define T50    0x432aff97
#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)
#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)
#define T53    0x655b59c3
#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)
#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)
#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)
#define T57    0x6fa87e4f
#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)
#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)
#define T60    0x4e0811a1
#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)
#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)
#define T63    0x2ad7d2bb
#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)


static void
md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
{
    md5_word_t
        a = pms->abcd[0], b = pms->abcd[1],
        c = pms->abcd[2], d = pms->abcd[3];
    md5_word_t t;
#if BYTE_ORDER > 0
    /* Define storage only for big-endian CPUs. */
    md5_word_t X[16];
#else
    /* Define storage for little-endian or both types of CPUs. */
    md5_word_t xbuf[16];
    const md5_word_t *X;
#endif

    {
#if BYTE_ORDER == 0
        /*
         * Determine dynamically whether this is a big-endian or
         * little-endian machine, since we can use a more efficient
         * algorithm on the latter.
         */
        static const int w = 1;

        if (*((const md5_byte_t *)&w)) /* dynamic little-endian */
#endif
#if BYTE_ORDER <= 0             /* little-endian */
        {
            /*
             * On little-endian machines, we can process properly aligned
             * data without copying it.
             */
            if (!((data - (const md5_byte_t *)0) & 3)) {
                /* data are properly aligned */
                X = (const md5_word_t *)data;
            } else {
                /* not aligned */
                memcpy(xbuf, data, 64);
                X = xbuf;
            }
        }
#endif
#if BYTE_ORDER == 0
        else                    /* dynamic big-endian */
#endif
#if BYTE_ORDER >= 0             /* big-endian */
        {
            /*
             * On big-endian machines, we must arrange the bytes in the
             * right order.
             */
            const md5_byte_t *xp = data;
            int i;

#  if BYTE_ORDER == 0
            X = xbuf;           /* (dynamic only) */
#  else
#    define xbuf X              /* (static only) */
#  endif
            for (i = 0; i < 16; ++i, xp += 4)
                xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);
        }
#endif
    }

#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))

    /* Round 1. */
    /* Let [abcd k s i] denote the operation
       a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
#define SET(a, b, c, d, k, s, Ti)\
  t = a + F(b,c,d) + X[k] + Ti;\
  a = ROTATE_LEFT(t, s) + b
    /* Do the following 16 operations. */
    SET(a, b, c, d,  0,  7,  T1);
    SET(d, a, b, c,  1, 12,  T2);
    SET(c, d, a, b,  2, 17,  T3);
    SET(b, c, d, a,  3, 22,  T4);
    SET(a, b, c, d,  4,  7,  T5);
    SET(d, a, b, c,  5, 12,  T6);
    SET(c, d, a, b,  6, 17,  T7);
    SET(b, c, d, a,  7, 22,  T8);
    SET(a, b, c, d,  8,  7,  T9);
    SET(d, a, b, c,  9, 12, T10);
    SET(c, d, a, b, 10, 17, T11);
    SET(b, c, d, a, 11, 22, T12);
    SET(a, b, c, d, 12,  7, T13);
    SET(d, a, b, c, 13, 12, T14);
    SET(c, d, a, b, 14, 17, T15);
    SET(b, c, d, a, 15, 22, T16);
#undef SET

     /* Round 2. */
     /* Let [abcd k s i] denote the operation
          a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
#define SET(a, b, c, d, k, s, Ti)\
  t = a + G(b,c,d) + X[k] + Ti;\
  a = ROTATE_LEFT(t, s) + b
     /* Do the following 16 operations. */
    SET(a, b, c, d,  1,  5, T17);
    SET(d, a, b, c,  6,  9, T18);
    SET(c, d, a, b, 11, 14, T19);
    SET(b, c, d, a,  0, 20, T20);
    SET(a, b, c, d,  5,  5, T21);
    SET(d, a, b, c, 10,  9, T22);
    SET(c, d, a, b, 15, 14, T23);
    SET(b, c, d, a,  4, 20, T24);
    SET(a, b, c, d,  9,  5, T25);
    SET(d, a, b, c, 14,  9, T26);
    SET(c, d, a, b,  3, 14, T27);
    SET(b, c, d, a,  8, 20, T28);
    SET(a, b, c, d, 13,  5, T29);
    SET(d, a, b, c,  2,  9, T30);
    SET(c, d, a, b,  7, 14, T31);
    SET(b, c, d, a, 12, 20, T32);
#undef SET

     /* Round 3. */
     /* Let [abcd k s t] denote the operation
          a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
#define H(x, y, z) ((x) ^ (y) ^ (z))
#define SET(a, b, c, d, k, s, Ti)\
  t = a + H(b,c,d) + X[k] + Ti;\
  a = ROTATE_LEFT(t, s) + b
     /* Do the following 16 operations. */
    SET(a, b, c, d,  5,  4, T33);
    SET(d, a, b, c,  8, 11, T34);
    SET(c, d, a, b, 11, 16, T35);
    SET(b, c, d, a, 14, 23, T36);
    SET(a, b, c, d,  1,  4, T37);
    SET(d, a, b, c,  4, 11, T38);
    SET(c, d, a, b,  7, 16, T39);
    SET(b, c, d, a, 10, 23, T40);
    SET(a, b, c, d, 13,  4, T41);
    SET(d, a, b, c,  0, 11, T42);
    SET(c, d, a, b,  3, 16, T43);
    SET(b, c, d, a,  6, 23, T44);
    SET(a, b, c, d,  9,  4, T45);
    SET(d, a, b, c, 12, 11, T46);
    SET(c, d, a, b, 15, 16, T47);
    SET(b, c, d, a,  2, 23, T48);
#undef SET

     /* Round 4. */
     /* Let [abcd k s t] denote the operation
          a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
#define I(x, y, z) ((y) ^ ((x) | ~(z)))
#define SET(a, b, c, d, k, s, Ti)\
  t = a + I(b,c,d) + X[k] + Ti;\
  a = ROTATE_LEFT(t, s) + b
     /* Do the following 16 operations. */
    SET(a, b, c, d,  0,  6, T49);
    SET(d, a, b, c,  7, 10, T50);
    SET(c, d, a, b, 14, 15, T51);
    SET(b, c, d, a,  5, 21, T52);
    SET(a, b, c, d, 12,  6, T53);
    SET(d, a, b, c,  3, 10, T54);
    SET(c, d, a, b, 10, 15, T55);
    SET(b, c, d, a,  1, 21, T56);
    SET(a, b, c, d,  8,  6, T57);
    SET(d, a, b, c, 15, 10, T58);
    SET(c, d, a, b,  6, 15, T59);
    SET(b, c, d, a, 13, 21, T60);
    SET(a, b, c, d,  4,  6, T61);
    SET(d, a, b, c, 11, 10, T62);
    SET(c, d, a, b,  2, 15, T63);
    SET(b, c, d, a,  9, 21, T64);
#undef SET

     /* Then perform the following additions. (That is increment each
        of the four registers by the value it had before this block
        was started.) */
    pms->abcd[0] += a;
    pms->abcd[1] += b;
    pms->abcd[2] += c;
    pms->abcd[3] += d;
}

void
md5_init(md5_state_t *pms)
{
    pms->count[0] = pms->count[1] = 0;
    pms->abcd[0] = 0x67452301;
    pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476;
    pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301;
    pms->abcd[3] = 0x10325476;
}

void
md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
{
    const md5_byte_t *p = data;
    int left = nbytes;
    int offset = (pms->count[0] >> 3) & 63;
    md5_word_t nbits = (md5_word_t)(nbytes << 3);

    if (nbytes <= 0)
        return;

    /* Update the message length. */
    pms->count[1] += nbytes >> 29;
    pms->count[0] += nbits;
    if (pms->count[0] < nbits)
        pms->count[1]++;

    /* Process an initial partial block. */
    if (offset) {
        int copy = (offset + nbytes > 64 ? 64 - offset : nbytes);

        memcpy(pms->buf + offset, p, copy);
        if (offset + copy < 64)
            return;
        p += copy;
        left -= copy;
        md5_process(pms, pms->buf);
    }

    /* Process full blocks. */
    for (; left >= 64; p += 64, left -= 64)
        md5_process(pms, p);

    /* Process a final partial block. */
    if (left)
        memcpy(pms->buf, p, left);
}

void
md5_finish(md5_state_t *pms, md5_byte_t digest[16])
{
    static const md5_byte_t pad[64] = {
        0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
    };
    md5_byte_t data[8];
    int i;

    /* Save the length before padding. */
    for (i = 0; i < 8; ++i)
        data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
    /* Pad to 56 bytes mod 64. */
    md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
    /* Append the length. */
    md5_append(pms, data, 8);
    for (i = 0; i < 16; ++i)
        digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
}
/*************************************************************************************************
 * The hash database API of Tokyo Cabinet
 *                                                               Copyright (C) 2006-2012 FAL Labs
 * This file is part of Tokyo Cabinet.
 * Tokyo Cabinet 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 any later version.  Tokyo Cabinet 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 Tokyo
 * Cabinet; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307 USA.
 *************************************************************************************************/


#include "tcutil.h"
#include "tchdb.h"
#include "tcbdb.h"
#include "myconf.h"

#define HDBFILEMODE    00644             // permission of created files
#define HDBIOBUFSIZ    8192              // size of an I/O buffer

#define HDBMAGICDATA   "ToKyO CaBiNeT"   // magic data for identification
#define HDBHEADSIZ     256               // size of the reagion of the header
#define HDBTYPEOFF     32                // offset of the region for the database type
#define HDBFLAGSOFF    33                // offset of the region for the additional flags
#define HDBAPOWOFF     34                // offset of the region for the alignment power
#define HDBFPOWOFF     35                // offset of the region for the free block pool power
#define HDBOPTSOFF     36                // offset of the region for the options
#define HDBBNUMOFF     40                // offset of the region for the bucket number
#define HDBRNUMOFF     48                // offset of the region for the record number
#define HDBFSIZOFF     56                // offset of the region for the file size
#define HDBFRECOFF     64                // offset of the region for the first record offset
#define HDBOPAQUEOFF   128               // offset of the region for the opaque field

#define HDBDEFBNUM     131071            // default bucket number
#define HDBDEFAPOW     4                 // default alignment power
#define HDBMAXAPOW     16                // maximum alignment power
#define HDBDEFFPOW     10                // default free block pool power
#define HDBMAXFPOW     20                // maximum free block pool power
#define HDBDEFXMSIZ    (64LL<<20)        // default size of the extra mapped memory
#define HDBXFSIZINC    32768             // increment of extra file size
#define HDBMINRUNIT    48                // minimum record reading unit
#define HDBMAXHSIZ     32                // maximum record header size
#define HDBFBPALWRAT   2                 // allowance ratio of the free block pool
#define HDBFBPBSIZ     64                // base region size of the free block pool
#define HDBFBPESIZ     4                 // size of each region of the free block pool
#define HDBFBPMGFREQ   4096              // frequency to merge the free block pool
#define HDBDRPUNIT     65536             // unit size of the delayed record pool
#define HDBDRPLAT      2048              // latitude size of the delayed record pool
#define HDBDFRSRAT     2                 // step ratio of auto defragmentation
#define HDBFBMAXSIZ    (INT32_MAX/4)     // maximum size of a free block pool
#define HDBCACHEOUT    128               // number of records in a process of cacheout
#define HDBWALSUFFIX   "wal"             // suffix of write ahead logging file

typedef struct {                         // type of structure for a record
  uint64_t off;                          // offset of the record
  uint32_t rsiz;                         // size of the whole record
  uint8_t magic;                         // magic number
  uint8_t hash;                          // second hash value
  uint64_t left;                         // offset of the left child record
  uint64_t right;                        // offset of the right child record
  uint32_t ksiz;                         // size of the key
  uint32_t vsiz;                         // size of the value
  uint16_t psiz;                         // size of the padding
  const char *kbuf;                      // pointer to the key
  const char *vbuf;                      // pointer to the value
  uint64_t boff;                         // offset of the body
  char *bbuf;                            // buffer of the body
} TCHREC;

typedef struct {                         // type of structure for a free block
  uint64_t off;                          // offset of the block
  uint32_t rsiz;                         // size of the block
} HDBFB;

enum {                                   // enumeration for magic data
  HDBMAGICREC = 0xc8,                    // for data block
  HDBMAGICFB = 0xb0                      // for free block
};

enum {                                   // enumeration for duplication behavior
  HDBPDOVER,                             // overwrite an existing value
  HDBPDKEEP,                             // keep the existing value
  HDBPDCAT,                              // concatenate values
  HDBPDADDINT,                           // add an integer
  HDBPDADDDBL,                           // add a real number
  HDBPDPROC                              // process by a callback function
};

typedef struct {                         // type of structure for a duplication callback
  TCPDPROC proc;                         // function pointer
  void *op;                              // opaque pointer
} HDBPDPROCOP;


/* private macros */
#define HDBLOCKMETHOD(TC_hdb, TC_wr)                            \
  ((TC_hdb)->mmtx ? tchdblockmethod((TC_hdb), (TC_wr)) : true)
#define HDBUNLOCKMETHOD(TC_hdb)                         \
  ((TC_hdb)->mmtx ? tchdbunlockmethod(TC_hdb) : true)
#define HDBLOCKRECORD(TC_hdb, TC_bidx, TC_wr)                           \
  ((TC_hdb)->mmtx ? tchdblockrecord((TC_hdb), (uint8_t)(TC_bidx), (TC_wr)) : true)
#define HDBUNLOCKRECORD(TC_hdb, TC_bidx)                                \
  ((TC_hdb)->mmtx ? tchdbunlockrecord((TC_hdb), (uint8_t)(TC_bidx)) : true)
#define HDBLOCKALLRECORDS(TC_hdb, TC_wr)                                \
  ((TC_hdb)->mmtx ? tchdblockallrecords((TC_hdb), (TC_wr)) : true)
#define HDBUNLOCKALLRECORDS(TC_hdb)                             \
  ((TC_hdb)->mmtx ? tchdbunlockallrecords(TC_hdb) : true)
#define HDBLOCKDB(TC_hdb)                       \
  ((TC_hdb)->mmtx ? tchdblockdb(TC_hdb) : true)
#define HDBUNLOCKDB(TC_hdb)                             \
  ((TC_hdb)->mmtx ? tchdbunlockdb(TC_hdb) : true)
#define HDBLOCKWAL(TC_hdb)                              \
  ((TC_hdb)->mmtx ? tchdblockwal(TC_hdb) : true)
#define HDBUNLOCKWAL(TC_hdb)                            \
  ((TC_hdb)->mmtx ? tchdbunlockwal(TC_hdb) : true)
#define HDBTHREADYIELD(TC_hdb)                          \
  do { if((TC_hdb)->mmtx) sched_yield(); } while(false)


/* private function prototypes */
static uint64_t tcgetprime(uint64_t num);
static bool tchdbseekwrite(TCHDB *hdb, off_t off, const void *buf, size_t size);
static bool tchdbseekread(TCHDB *hdb, off_t off, void *buf, size_t size);
static bool tchdbseekreadtry(TCHDB *hdb, off_t off, void *buf, size_t size);
static void tchdbdumpmeta(TCHDB *hdb, char *hbuf);
static void tchdbloadmeta(TCHDB *hdb, const char *hbuf);
static void tchdbclear(TCHDB *hdb);
static int32_t tchdbpadsize(TCHDB *hdb, uint64_t off);
static void tchdbsetflag(TCHDB *hdb, int flag, bool sign);
static uint64_t tchdbbidx(TCHDB *hdb, const char *kbuf, int ksiz, uint8_t *hp);
static off_t tchdbgetbucket(TCHDB *hdb, uint64_t bidx);
static void tchdbsetbucket(TCHDB *hdb, uint64_t bidx, uint64_t off);
static bool tchdbsavefbp(TCHDB *hdb);
static bool tchdbloadfbp(TCHDB *hdb);
static void tcfbpsortbyoff(HDBFB *fbpool, int fbpnum);
static void tcfbpsortbyrsiz(HDBFB *fbpool, int fbpnum);
static void tchdbfbpmerge(TCHDB *hdb);
static void tchdbfbpinsert(TCHDB *hdb, uint64_t off, uint32_t rsiz);
static bool tchdbfbpsearch(TCHDB *hdb, TCHREC *rec);
static bool tchdbfbpsplice(TCHDB *hdb, TCHREC *rec, uint32_t nsiz);
static void tchdbfbptrim(TCHDB *hdb, uint64_t base, uint64_t next, uint64_t off, uint32_t rsiz);
static bool tchdbwritefb(TCHDB *hdb, uint64_t off, uint32_t rsiz);
static bool tchdbwriterec(TCHDB *hdb, TCHREC *rec, uint64_t bidx, off_t entoff);
static bool tchdbreadrec(TCHDB *hdb, TCHREC *rec, char *rbuf);
static bool tchdbreadrecbody(TCHDB *hdb, TCHREC *rec);
static bool tchdbremoverec(TCHDB *hdb, TCHREC *rec, char *rbuf, uint64_t bidx, off_t entoff);
static bool tchdbshiftrec(TCHDB *hdb, TCHREC *rec, char *rbuf, off_t destoff);
static int tcreckeycmp(const char *abuf, int asiz, const char *bbuf, int bsiz);
static bool tchdbflushdrp(TCHDB *hdb);
static void tchdbcacheadjust(TCHDB *hdb);
static bool tchdbwalinit(TCHDB *hdb);
static bool tchdbwalwrite(TCHDB *hdb, uint64_t off, int64_t size);
static int tchdbwalrestore(TCHDB *hdb, const char *path);
static bool tchdbwalremove(TCHDB *hdb, const char *path);
static bool tchdbopenimpl(TCHDB *hdb, const char *path, int omode);
static bool tchdbcloseimpl(TCHDB *hdb);
static bool tchdbputimpl(TCHDB *hdb, const char *kbuf, int ksiz, uint64_t bidx, uint8_t hash,
                         const char *vbuf, int vsiz, int dmode);
static void tchdbdrpappend(TCHDB *hdb, const char *kbuf, int ksiz, const char *vbuf, int vsiz,
                           uint8_t hash);
static bool tchdbputasyncimpl(TCHDB *hdb, const char *kbuf, int ksiz, uint64_t bidx,
                              uint8_t hash, const char *vbuf, int vsiz);
static bool tchdboutimpl(TCHDB *hdb, const char *kbuf, int ksiz, uint64_t bidx, uint8_t hash);
static char *tchdbgetimpl(TCHDB *hdb, const char *kbuf, int ksiz, uint64_t bidx, uint8_t hash,
                          int *sp);
static int tchdbgetintobuf(TCHDB *hdb, const char *kbuf, int ksiz, uint64_t bidx, uint8_t hash,
                           char *vbuf, int max);
static char *tchdbgetnextimpl(TCHDB *hdb, const char *kbuf, int ksiz, int *sp,
                              const char **vbp, int *vsp);
static int tchdbvsizimpl(TCHDB *hdb, const char *kbuf, int ksiz, uint64_t bidx, uint8_t hash);
static bool tchdbiterinitimpl(TCHDB *hdb);
static char *tchdbiternextimpl(TCHDB *hdb, int *sp);
static bool tchdbiternextintoxstr(TCHDB *hdb, TCXSTR *kxstr, TCXSTR *vxstr);
static bool tchdboptimizeimpl(TCHDB *hdb, int64_t bnum, int8_t apow, int8_t fpow, uint8_t opts);
static bool tchdbvanishimpl(TCHDB *hdb);
static bool tchdbcopyimpl(TCHDB *hdb, const char *path);
static bool tchdbdefragimpl(TCHDB *hdb, int64_t step);
static bool tchdbiterjumpimpl(TCHDB *hdb, const char *kbuf, int ksiz);
static bool tchdbforeachimpl(TCHDB *hdb, TCITER iter, void *op);
static bool tchdblockmethod(TCHDB *hdb, bool wr);
static bool tchdbunlockmethod(TCHDB *hdb);
static bool tchdblockrecord(TCHDB *hdb, uint8_t bidx, bool wr);
static bool tchdbunlockrecord(TCHDB *hdb, uint8_t bidx);
static bool tchdblockallrecords(TCHDB *hdb, bool wr);
static bool tchdbunlockallrecords(TCHDB *hdb);
static bool tchdblockdb(TCHDB *hdb);
static bool tchdbunlockdb(TCHDB *hdb);
static bool tchdblockwal(TCHDB *hdb);
static bool tchdbunlockwal(TCHDB *hdb);


/* debugging function prototypes */
void tchdbprintmeta(TCHDB *hdb);
void tchdbprintrec(TCHDB *hdb, TCHREC *rec);



/*************************************************************************************************
 * API
 *************************************************************************************************/


/* Get the message string corresponding to an error code. */
const char *tchdberrmsg(int ecode){
  return tcerrmsg(ecode);
}


/* Create a hash database object. */
TCHDB *tchdbnew(void){
  TCHDB *hdb;
  TCMALLOC(hdb, sizeof(*hdb));
  tchdbclear(hdb);
  return hdb;
}


/* Delete a hash database object. */
void tchdbdel(TCHDB *hdb){
  assert(hdb);
  if(hdb->fd >= 0) tchdbclose(hdb);
  if(hdb->mmtx){
    pthread_key_delete(*(pthread_key_t *)hdb->eckey);
    pthread_mutex_destroy(hdb->wmtx);
    pthread_mutex_destroy(hdb->dmtx);
    for(int i = UINT8_MAX; i >= 0; i--){
      pthread_rwlock_destroy((pthread_rwlock_t *)hdb->rmtxs + i);
    }
    pthread_rwlock_destroy(hdb->mmtx);
    TCFREE(hdb->eckey);
    TCFREE(hdb->wmtx);
    TCFREE(hdb->dmtx);
    TCFREE(hdb->rmtxs);
    TCFREE(hdb->mmtx);
  }
  TCFREE(hdb);
}


/* Get the last happened error code of a hash database object. */
int tchdbecode(TCHDB *hdb){
  assert(hdb);
  return hdb->mmtx ?
    (int)(intptr_t)pthread_getspecific(*(pthread_key_t *)hdb->eckey) : hdb->ecode;
}


/* Set mutual exclusion control of a hash database object for threading. */
bool tchdbsetmutex(TCHDB *hdb){
  assert(hdb);
  if(!TCUSEPTHREAD) return true;
  if(hdb->mmtx || hdb->fd >= 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  pthread_mutexattr_t rma;
  pthread_mutexattr_init(&rma);
  TCMALLOC(hdb->mmtx, sizeof(pthread_rwlock_t));
  TCMALLOC(hdb->rmtxs, (UINT8_MAX + 1) * sizeof(pthread_rwlock_t));
  TCMALLOC(hdb->dmtx, sizeof(pthread_mutex_t));
  TCMALLOC(hdb->wmtx, sizeof(pthread_mutex_t));
  TCMALLOC(hdb->eckey, sizeof(pthread_key_t));
  bool err = false;
  if(pthread_mutexattr_settype(&rma, PTHREAD_MUTEX_RECURSIVE) != 0) err = true;
  if(pthread_rwlock_init(hdb->mmtx, NULL) != 0) err = true;
  for(int i = 0; i <= UINT8_MAX; i++){
    if(pthread_rwlock_init((pthread_rwlock_t *)hdb->rmtxs + i, NULL) != 0) err = true;
  }
  if(pthread_mutex_init(hdb->dmtx, &rma) != 0) err = true;
  if(pthread_mutex_init(hdb->wmtx, NULL) != 0) err = true;
  if(pthread_key_create(hdb->eckey, NULL) != 0) err = true;
  if(err){
    tchdbsetecode(hdb, TCETHREAD, __FILE__, __LINE__, __func__);
    pthread_mutexattr_destroy(&rma);
    TCFREE(hdb->eckey);
    TCFREE(hdb->wmtx);
    TCFREE(hdb->dmtx);
    TCFREE(hdb->rmtxs);
    TCFREE(hdb->mmtx);
    hdb->eckey = NULL;
    hdb->wmtx = NULL;
    hdb->dmtx = NULL;
    hdb->rmtxs = NULL;
    hdb->mmtx = NULL;
    return false;
  }
  pthread_mutexattr_destroy(&rma);
  return true;
}


/* Set the tuning parameters of a hash database object. */
bool tchdbtune(TCHDB *hdb, int64_t bnum, int8_t apow, int8_t fpow, uint8_t opts){
  assert(hdb);
  if(hdb->fd >= 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  hdb->bnum = (bnum > 0) ? tcgetprime(bnum) : HDBDEFBNUM;
  hdb->apow = (apow >= 0) ? tclmin(apow, HDBMAXAPOW) : HDBDEFAPOW;
  hdb->fpow = (fpow >= 0) ? tclmin(fpow, HDBMAXFPOW) : HDBDEFFPOW;
  hdb->opts = opts;
  if(!_tc_deflate) hdb->opts &= ~HDBTDEFLATE;
  if(!_tc_bzcompress) hdb->opts &= ~HDBTBZIP;
  return true;
}


/* Set the caching parameters of a hash database object. */
bool tchdbsetcache(TCHDB *hdb, int32_t rcnum){
  assert(hdb);
  if(hdb->fd >= 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  hdb->rcnum = (rcnum > 0) ? tclmin(tclmax(rcnum, HDBCACHEOUT * 2), INT_MAX / 4) : 0;
  return true;
}


/* Set the size of the extra mapped memory of a hash database object. */
bool tchdbsetxmsiz(TCHDB *hdb, int64_t xmsiz){
  assert(hdb);
  if(hdb->fd >= 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  hdb->xmsiz = (xmsiz > 0) ? tcpagealign(xmsiz) : 0;
  return true;
}


/* Set the unit step number of auto defragmentation of a hash database object. */
bool tchdbsetdfunit(TCHDB *hdb, int32_t dfunit){
  assert(hdb);
  if(hdb->fd >= 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  hdb->dfunit = (dfunit > 0) ? dfunit : 0;
  return true;
}


/* Open a database file and connect a hash database object. */
bool tchdbopen(TCHDB *hdb, const char *path, int omode){
  assert(hdb && path);
  if(!HDBLOCKMETHOD(hdb, true)) return false;
  if(hdb->fd >= 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  char *rpath = tcrealpath(path);
  if(!rpath){
    int ecode = TCEOPEN;
    switch(errno){
      case EACCES: ecode = TCENOPERM; break;
      case ENOENT: ecode = TCENOFILE; break;
      case ENOTDIR: ecode = TCENOFILE; break;
    }
    tchdbsetecode(hdb, ecode, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(!tcpathlock(rpath)){
    tchdbsetecode(hdb, TCETHREAD, __FILE__, __LINE__, __func__);
    TCFREE(rpath);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  bool rv = tchdbopenimpl(hdb, path, omode);
  if(rv){
    hdb->rpath = rpath;
  } else {
    tcpathunlock(rpath);
    TCFREE(rpath);
  }
  HDBUNLOCKMETHOD(hdb);
  return rv;
}


/* Close a database object. */
bool tchdbclose(TCHDB *hdb){
  assert(hdb);
  if(!HDBLOCKMETHOD(hdb, true)) return false;
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  bool rv = tchdbcloseimpl(hdb);
  tcpathunlock(hdb->rpath);
  TCFREE(hdb->rpath);
  hdb->rpath = NULL;
  HDBUNLOCKMETHOD(hdb);
  return rv;
}


/* Store a record into a hash database object. */
bool tchdbput(TCHDB *hdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(hdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  if(!HDBLOCKMETHOD(hdb, false)) return false;
  uint8_t hash;
  uint64_t bidx = tchdbbidx(hdb, kbuf, ksiz, &hash);
  if(hdb->fd < 0 || !(hdb->omode & HDBOWRITER)){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(hdb->async && !tchdbflushdrp(hdb)){
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(!HDBLOCKRECORD(hdb, bidx, true)){
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(hdb->zmode){
    char *zbuf;
    if(hdb->opts & HDBTDEFLATE){
      zbuf = _tc_deflate(vbuf, vsiz, &vsiz, _TCZMRAW);
    } else if(hdb->opts & HDBTBZIP){
      zbuf = _tc_bzcompress(vbuf, vsiz, &vsiz);
    } else if(hdb->opts & HDBTTCBS){
      zbuf = tcbsencode(vbuf, vsiz, &vsiz);
    } else {
      zbuf = hdb->enc(vbuf, vsiz, &vsiz, hdb->encop);
    }
    if(!zbuf){
      tchdbsetecode(hdb, TCEMISC, __FILE__, __LINE__, __func__);
      HDBUNLOCKRECORD(hdb, bidx);
      HDBUNLOCKMETHOD(hdb);
      return false;
    }
    bool rv = tchdbputimpl(hdb, kbuf, ksiz, bidx, hash, zbuf, vsiz, HDBPDOVER);
    TCFREE(zbuf);
    HDBUNLOCKRECORD(hdb, bidx);
    HDBUNLOCKMETHOD(hdb);
    if(hdb->dfunit > 0 && hdb->dfcnt > hdb->dfunit &&
       !tchdbdefrag(hdb, hdb->dfunit * HDBDFRSRAT + 1)) rv = false;
    return rv;
  }
  bool rv = tchdbputimpl(hdb, kbuf, ksiz, bidx, hash, vbuf, vsiz, HDBPDOVER);
  HDBUNLOCKRECORD(hdb, bidx);
  HDBUNLOCKMETHOD(hdb);
  if(hdb->dfunit > 0 && hdb->dfcnt > hdb->dfunit &&
     !tchdbdefrag(hdb, hdb->dfunit * HDBDFRSRAT + 1)) rv = false;
  return rv;
}


/* Store a string record into a hash database object. */
bool tchdbput2(TCHDB *hdb, const char *kstr, const char *vstr){
  assert(hdb && kstr && vstr);
  return tchdbput(hdb, kstr, strlen(kstr), vstr, strlen(vstr));
}


/* Store a new record into a hash database object. */
bool tchdbputkeep(TCHDB *hdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(hdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  if(!HDBLOCKMETHOD(hdb, false)) return false;
  uint8_t hash;
  uint64_t bidx = tchdbbidx(hdb, kbuf, ksiz, &hash);
  if(hdb->fd < 0 || !(hdb->omode & HDBOWRITER)){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(hdb->async && !tchdbflushdrp(hdb)){
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(!HDBLOCKRECORD(hdb, bidx, true)){
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(hdb->zmode){
    char *zbuf;
    if(hdb->opts & HDBTDEFLATE){
      zbuf = _tc_deflate(vbuf, vsiz, &vsiz, _TCZMRAW);
    } else if(hdb->opts & HDBTBZIP){
      zbuf = _tc_bzcompress(vbuf, vsiz, &vsiz);
    } else if(hdb->opts & HDBTTCBS){
      zbuf = tcbsencode(vbuf, vsiz, &vsiz);
    } else {
      zbuf = hdb->enc(vbuf, vsiz, &vsiz, hdb->encop);
    }
    if(!zbuf){
      tchdbsetecode(hdb, TCEMISC, __FILE__, __LINE__, __func__);
      HDBUNLOCKRECORD(hdb, bidx);
      HDBUNLOCKMETHOD(hdb);
      return false;
    }
    bool rv = tchdbputimpl(hdb, kbuf, ksiz, bidx, hash, zbuf, vsiz, HDBPDKEEP);
    TCFREE(zbuf);
    HDBUNLOCKRECORD(hdb, bidx);
    HDBUNLOCKMETHOD(hdb);
    if(hdb->dfunit > 0 && hdb->dfcnt > hdb->dfunit &&
       !tchdbdefrag(hdb, hdb->dfunit * HDBDFRSRAT + 1)) rv = false;
    return rv;
  }
  bool rv = tchdbputimpl(hdb, kbuf, ksiz, bidx, hash, vbuf, vsiz, HDBPDKEEP);
  HDBUNLOCKRECORD(hdb, bidx);
  HDBUNLOCKMETHOD(hdb);
  if(hdb->dfunit > 0 && hdb->dfcnt > hdb->dfunit &&
     !tchdbdefrag(hdb, hdb->dfunit * HDBDFRSRAT + 1)) rv = false;
  return rv;
}


/* Store a new string record into a hash database object. */
bool tchdbputkeep2(TCHDB *hdb, const char *kstr, const char *vstr){
  return tchdbputkeep(hdb, kstr, strlen(kstr), vstr, strlen(vstr));
}


/* Concatenate a value at the end of the existing record in a hash database object. */
bool tchdbputcat(TCHDB *hdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(hdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  if(!HDBLOCKMETHOD(hdb, false)) return false;
  uint8_t hash;
  uint64_t bidx = tchdbbidx(hdb, kbuf, ksiz, &hash);
  if(hdb->fd < 0 || !(hdb->omode & HDBOWRITER)){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(hdb->async && !tchdbflushdrp(hdb)){
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(!HDBLOCKRECORD(hdb, bidx, true)){
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(hdb->zmode){
    char *zbuf;
    int osiz;
    char *obuf = tchdbgetimpl(hdb, kbuf, ksiz, bidx, hash, &osiz);
    if(obuf){
      TCREALLOC(obuf, obuf, osiz + vsiz + 1);
      memcpy(obuf + osiz, vbuf, vsiz);
      if(hdb->opts & HDBTDEFLATE){
        zbuf = _tc_deflate(obuf, osiz + vsiz, &vsiz, _TCZMRAW);
      } else if(hdb->opts & HDBTBZIP){
        zbuf = _tc_bzcompress(obuf, osiz + vsiz, &vsiz);
      } else if(hdb->opts & HDBTTCBS){
        zbuf = tcbsencode(obuf, osiz + vsiz, &vsiz);
      } else {
        zbuf = hdb->enc(obuf, osiz + vsiz, &vsiz, hdb->encop);
      }
      TCFREE(obuf);
    } else {
      if(hdb->opts & HDBTDEFLATE){
        zbuf = _tc_deflate(vbuf, vsiz, &vsiz, _TCZMRAW);
      } else if(hdb->opts & HDBTBZIP){
        zbuf = _tc_bzcompress(vbuf, vsiz, &vsiz);
      } else if(hdb->opts & HDBTTCBS){
        zbuf = tcbsencode(vbuf, vsiz, &vsiz);
      } else {
        zbuf = hdb->enc(vbuf, vsiz, &vsiz, hdb->encop);
      }
    }
    if(!zbuf){
      tchdbsetecode(hdb, TCEMISC, __FILE__, __LINE__, __func__);
      HDBUNLOCKRECORD(hdb, bidx);
      HDBUNLOCKMETHOD(hdb);
      return false;
    }
    bool rv = tchdbputimpl(hdb, kbuf, ksiz, bidx, hash, zbuf, vsiz, HDBPDOVER);
    TCFREE(zbuf);
    HDBUNLOCKRECORD(hdb, bidx);
    HDBUNLOCKMETHOD(hdb);
    if(hdb->dfunit > 0 && hdb->dfcnt > hdb->dfunit &&
       !tchdbdefrag(hdb, hdb->dfunit * HDBDFRSRAT + 1)) rv = false;
    return rv;
  }
  bool rv = tchdbputimpl(hdb, kbuf, ksiz, bidx, hash, vbuf, vsiz, HDBPDCAT);
  HDBUNLOCKRECORD(hdb, bidx);
  HDBUNLOCKMETHOD(hdb);
  if(hdb->dfunit > 0 && hdb->dfcnt > hdb->dfunit &&
     !tchdbdefrag(hdb, hdb->dfunit * HDBDFRSRAT + 1)) rv = false;
  return rv;
}


/* Concatenate a string value at the end of the existing record in a hash database object. */
bool tchdbputcat2(TCHDB *hdb, const char *kstr, const char *vstr){
  assert(hdb && kstr && vstr);
  return tchdbputcat(hdb, kstr, strlen(kstr), vstr, strlen(vstr));
}


/* Store a record into a hash database object in asynchronous fashion. */
bool tchdbputasync(TCHDB *hdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(hdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  if(!HDBLOCKMETHOD(hdb, true)) return false;
  uint8_t hash;
  uint64_t bidx = tchdbbidx(hdb, kbuf, ksiz, &hash);
  hdb->async = true;
  if(hdb->fd < 0 || !(hdb->omode & HDBOWRITER)){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(hdb->zmode){
    char *zbuf;
    if(hdb->opts & HDBTDEFLATE){
      zbuf = _tc_deflate(vbuf, vsiz, &vsiz, _TCZMRAW);
    } else if(hdb->opts & HDBTBZIP){
      zbuf = _tc_bzcompress(vbuf, vsiz, &vsiz);
    } else if(hdb->opts & HDBTTCBS){
      zbuf = tcbsencode(vbuf, vsiz, &vsiz);
    } else {
      zbuf = hdb->enc(vbuf, vsiz, &vsiz, hdb->encop);
    }
    if(!zbuf){
      tchdbsetecode(hdb, TCEMISC, __FILE__, __LINE__, __func__);
      HDBUNLOCKMETHOD(hdb);
      return false;
    }
    bool rv = tchdbputasyncimpl(hdb, kbuf, ksiz, bidx, hash, zbuf, vsiz);
    TCFREE(zbuf);
    HDBUNLOCKMETHOD(hdb);
    return rv;
  }
  bool rv = tchdbputasyncimpl(hdb, kbuf, ksiz, bidx, hash, vbuf, vsiz);
  HDBUNLOCKMETHOD(hdb);
  return rv;
}


/* Store a string record into a hash database object in asynchronous fashion. */
bool tchdbputasync2(TCHDB *hdb, const char *kstr, const char *vstr){
  assert(hdb && kstr && vstr);
  return tchdbputasync(hdb, kstr, strlen(kstr), vstr, strlen(vstr));
}


/* Remove a record of a hash database object. */
bool tchdbout(TCHDB *hdb, const void *kbuf, int ksiz){
  assert(hdb && kbuf && ksiz >= 0);
  if(!HDBLOCKMETHOD(hdb, false)) return false;
  uint8_t hash;
  uint64_t bidx = tchdbbidx(hdb, kbuf, ksiz, &hash);
  if(hdb->fd < 0 || !(hdb->omode & HDBOWRITER)){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(hdb->async && !tchdbflushdrp(hdb)){
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(!HDBLOCKRECORD(hdb, bidx, true)){
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  bool rv = tchdboutimpl(hdb, kbuf, ksiz, bidx, hash);
  HDBUNLOCKRECORD(hdb, bidx);
  HDBUNLOCKMETHOD(hdb);
  if(hdb->dfunit > 0 && hdb->dfcnt > hdb->dfunit &&
     !tchdbdefrag(hdb, hdb->dfunit * HDBDFRSRAT + 1)) rv = false;
  return rv;
}


/* Remove a string record of a hash database object. */
bool tchdbout2(TCHDB *hdb, const char *kstr){
  assert(hdb && kstr);
  return tchdbout(hdb, kstr, strlen(kstr));
}


/* Retrieve a record in a hash database object. */
void *tchdbget(TCHDB *hdb, const void *kbuf, int ksiz, int *sp){
  assert(hdb && kbuf && ksiz >= 0 && sp);
  if(!HDBLOCKMETHOD(hdb, false)) return NULL;
  uint8_t hash;
  uint64_t bidx = tchdbbidx(hdb, kbuf, ksiz, &hash);
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return NULL;
  }
  if(hdb->async && !tchdbflushdrp(hdb)){
    HDBUNLOCKMETHOD(hdb);
    return NULL;
  }
  if(!HDBLOCKRECORD(hdb, bidx, false)){
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  char *rv = tchdbgetimpl(hdb, kbuf, ksiz, bidx, hash, sp);
  HDBUNLOCKRECORD(hdb, bidx);
  HDBUNLOCKMETHOD(hdb);
  return rv;
}


/* Retrieve a string record in a hash database object. */
char *tchdbget2(TCHDB *hdb, const char *kstr){
  assert(hdb && kstr);
  int vsiz;
  return tchdbget(hdb, kstr, strlen(kstr), &vsiz);
}


/* Retrieve a record in a hash database object and write the value into a buffer. */
int tchdbget3(TCHDB *hdb, const void *kbuf, int ksiz, void *vbuf, int max){
  assert(hdb && kbuf && ksiz >= 0 && vbuf && max >= 0);
  if(!HDBLOCKMETHOD(hdb, false)) return -1;
  uint8_t hash;
  uint64_t bidx = tchdbbidx(hdb, kbuf, ksiz, &hash);
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return -1;
  }
  if(hdb->async && !tchdbflushdrp(hdb)){
    HDBUNLOCKMETHOD(hdb);
    return -1;
  }
  if(!HDBLOCKRECORD(hdb, bidx, false)){
    HDBUNLOCKMETHOD(hdb);
    return -1;
  }
  int rv = tchdbgetintobuf(hdb, kbuf, ksiz, bidx, hash, vbuf, max);
  HDBUNLOCKRECORD(hdb, bidx);
  HDBUNLOCKMETHOD(hdb);
  return rv;
}


/* Get the size of the value of a record in a hash database object. */
int tchdbvsiz(TCHDB *hdb, const void *kbuf, int ksiz){
  assert(hdb && kbuf && ksiz >= 0);
  if(!HDBLOCKMETHOD(hdb, false)) return -1;
  uint8_t hash;
  uint64_t bidx = tchdbbidx(hdb, kbuf, ksiz, &hash);
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return -1;
  }
  if(hdb->async && !tchdbflushdrp(hdb)){
    HDBUNLOCKMETHOD(hdb);
    return -1;
  }
  if(!HDBLOCKRECORD(hdb, bidx, false)){
    HDBUNLOCKMETHOD(hdb);
    return -1;
  }
  int rv = tchdbvsizimpl(hdb, kbuf, ksiz, bidx, hash);
  HDBUNLOCKRECORD(hdb, bidx);
  HDBUNLOCKMETHOD(hdb);
  return rv;
}


/* Get the size of the value of a string record in a hash database object. */
int tchdbvsiz2(TCHDB *hdb, const char *kstr){
  assert(hdb && kstr);
  return tchdbvsiz(hdb, kstr, strlen(kstr));
}


/* Initialize the iterator of a hash database object. */
bool tchdbiterinit(TCHDB *hdb){
  assert(hdb);
  if(!HDBLOCKMETHOD(hdb, true)) return false;
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(hdb->async && !tchdbflushdrp(hdb)){
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  bool rv = tchdbiterinitimpl(hdb);
  HDBUNLOCKMETHOD(hdb);
  return rv;
}


/* Get the next key of the iterator of a hash database object. */
void *tchdbiternext(TCHDB *hdb, int *sp){
  assert(hdb && sp);
  if(!HDBLOCKMETHOD(hdb, true)) return NULL;
  if(hdb->fd < 0 || hdb->iter < 1){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return NULL;
  }
  if(hdb->async && !tchdbflushdrp(hdb)){
    HDBUNLOCKMETHOD(hdb);
    return NULL;
  }
  char *rv = tchdbiternextimpl(hdb, sp);
  HDBUNLOCKMETHOD(hdb);
  return rv;
}


/* Get the next key string of the iterator of a hash database object. */
char *tchdbiternext2(TCHDB *hdb){
  assert(hdb);
  int vsiz;
  return tchdbiternext(hdb, &vsiz);
}


/* Get the next extensible objects of the iterator of a hash database object. */
bool tchdbiternext3(TCHDB *hdb, TCXSTR *kxstr, TCXSTR *vxstr){
  assert(hdb && kxstr && vxstr);
  if(!HDBLOCKMETHOD(hdb, true)) return false;
  if(hdb->fd < 0 || hdb->iter < 1){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(hdb->async && !tchdbflushdrp(hdb)){
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  bool rv = tchdbiternextintoxstr(hdb, kxstr, vxstr);
  HDBUNLOCKMETHOD(hdb);
  return rv;
}


/* Get forward matching keys in a hash database object. */
TCLIST *tchdbfwmkeys(TCHDB *hdb, const void *pbuf, int psiz, int max){
  assert(hdb && pbuf && psiz >= 0);
  TCLIST* keys = tclistnew();
  if(!HDBLOCKMETHOD(hdb, true)) return keys;
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return keys;
  }
  if(hdb->async && !tchdbflushdrp(hdb)){
    HDBUNLOCKMETHOD(hdb);
    return keys;
  }
  if(max < 0) max = INT_MAX;
  uint64_t iter = hdb->iter;
  tchdbiterinitimpl(hdb);
  char *kbuf;
  int ksiz;
  while(TCLISTNUM(keys) < max && (kbuf = tchdbiternextimpl(hdb, &ksiz)) != NULL){
    if(ksiz >= psiz && !memcmp(kbuf, pbuf, psiz)){
      tclistpushmalloc(keys, kbuf, ksiz);
    } else {
      TCFREE(kbuf);
    }
  }
  hdb->iter = iter;
  HDBUNLOCKMETHOD(hdb);
  return keys;
}


/* Get forward matching string keys in a hash database object. */
TCLIST *tchdbfwmkeys2(TCHDB *hdb, const char *pstr, int max){
  assert(hdb && pstr);
  return tchdbfwmkeys(hdb, pstr, strlen(pstr), max);
}


/* Add an integer to a record in a hash database object. */
int tchdbaddint(TCHDB *hdb, const void *kbuf, int ksiz, int num){
  assert(hdb && kbuf && ksiz >= 0);
  if(!HDBLOCKMETHOD(hdb, false)) return INT_MIN;
  uint8_t hash;
  uint64_t bidx = tchdbbidx(hdb, kbuf, ksiz, &hash);
  if(hdb->fd < 0 || !(hdb->omode & HDBOWRITER)){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return INT_MIN;
  }
  if(hdb->async && !tchdbflushdrp(hdb)){
    HDBUNLOCKMETHOD(hdb);
    return INT_MIN;
  }
  if(!HDBLOCKRECORD(hdb, bidx, true)){
    HDBUNLOCKMETHOD(hdb);
    return INT_MIN;
  }
  if(hdb->zmode){
    char *zbuf;
    int osiz;
    char *obuf = tchdbgetimpl(hdb, kbuf, ksiz, bidx, hash, &osiz);
    if(obuf){
      if(osiz != sizeof(num)){
        tchdbsetecode(hdb, TCEKEEP, __FILE__, __LINE__, __func__);
        TCFREE(obuf);
        HDBUNLOCKRECORD(hdb, bidx);
        HDBUNLOCKMETHOD(hdb);
        return INT_MIN;
      }
      num += *(int *)obuf;
      TCFREE(obuf);
    }
    int zsiz;
    if(hdb->opts & HDBTDEFLATE){
      zbuf = _tc_deflate((char *)&num, sizeof(num), &zsiz, _TCZMRAW);
    } else if(hdb->opts & HDBTBZIP){
      zbuf = _tc_bzcompress((char *)&num, sizeof(num), &zsiz);
    } else if(hdb->opts & HDBTTCBS){
      zbuf = tcbsencode((char *)&num, sizeof(num), &zsiz);
    } else {
      zbuf = hdb->enc((char *)&num, sizeof(num), &zsiz, hdb->encop);
    }
    if(!zbuf){
      tchdbsetecode(hdb, TCEMISC, __FILE__, __LINE__, __func__);
      HDBUNLOCKRECORD(hdb, bidx);
      HDBUNLOCKMETHOD(hdb);
      return INT_MIN;
    }
    bool rv = tchdbputimpl(hdb, kbuf, ksiz, bidx, hash, zbuf, zsiz, HDBPDOVER);
    TCFREE(zbuf);
    HDBUNLOCKRECORD(hdb, bidx);
    HDBUNLOCKMETHOD(hdb);
    if(hdb->dfunit > 0 && hdb->dfcnt > hdb->dfunit &&
       !tchdbdefrag(hdb, hdb->dfunit * HDBDFRSRAT + 1)) rv = false;
    return rv ? num : INT_MIN;
  }
  bool rv = tchdbputimpl(hdb, kbuf, ksiz, bidx, hash, (char *)&num, sizeof(num), HDBPDADDINT);
  HDBUNLOCKRECORD(hdb, bidx);
  HDBUNLOCKMETHOD(hdb);
  if(hdb->dfunit > 0 && hdb->dfcnt > hdb->dfunit &&
     !tchdbdefrag(hdb, hdb->dfunit * HDBDFRSRAT + 1)) rv = false;
  return rv ? num : INT_MIN;
}


/* Add a real number to a record in a hash database object. */
double tchdbadddouble(TCHDB *hdb, const void *kbuf, int ksiz, double num){
  assert(hdb && kbuf && ksiz >= 0);
  if(!HDBLOCKMETHOD(hdb, false)) return nan("");
  uint8_t hash;
  uint64_t bidx = tchdbbidx(hdb, kbuf, ksiz, &hash);
  if(hdb->fd < 0 || !(hdb->omode & HDBOWRITER)){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return nan("");
  }
  if(hdb->async && !tchdbflushdrp(hdb)){
    HDBUNLOCKMETHOD(hdb);
    return nan("");
  }
  if(!HDBLOCKRECORD(hdb, bidx, true)){
    HDBUNLOCKMETHOD(hdb);
    return nan("");
  }
  if(hdb->zmode){
    char *zbuf;
    int osiz;
    char *obuf = tchdbgetimpl(hdb, kbuf, ksiz, bidx, hash, &osiz);
    if(obuf){
      if(osiz != sizeof(num)){
        tchdbsetecode(hdb, TCEKEEP, __FILE__, __LINE__, __func__);
        TCFREE(obuf);
        HDBUNLOCKRECORD(hdb, bidx);
        HDBUNLOCKMETHOD(hdb);
        return nan("");
      }
      num += *(double *)obuf;
      TCFREE(obuf);
    }
    int zsiz;
    if(hdb->opts & HDBTDEFLATE){
      zbuf = _tc_deflate((char *)&num, sizeof(num), &zsiz, _TCZMRAW);
    } else if(hdb->opts & HDBTBZIP){
      zbuf = _tc_bzcompress((char *)&num, sizeof(num), &zsiz);
    } else if(hdb->opts & HDBTTCBS){
      zbuf = tcbsencode((char *)&num, sizeof(num), &zsiz);
    } else {
      zbuf = hdb->enc((char *)&num, sizeof(num), &zsiz, hdb->encop);
    }
    if(!zbuf){
      tchdbsetecode(hdb, TCEMISC, __FILE__, __LINE__, __func__);
      HDBUNLOCKRECORD(hdb, bidx);
      HDBUNLOCKMETHOD(hdb);
      return nan("");
    }
    bool rv = tchdbputimpl(hdb, kbuf, ksiz, bidx, hash, zbuf, zsiz, HDBPDOVER);
    TCFREE(zbuf);
    HDBUNLOCKRECORD(hdb, bidx);
    HDBUNLOCKMETHOD(hdb);
    if(hdb->dfunit > 0 && hdb->dfcnt > hdb->dfunit &&
       !tchdbdefrag(hdb, hdb->dfunit * HDBDFRSRAT + 1)) rv = false;
    return rv ? num : nan("");
  }
  bool rv = tchdbputimpl(hdb, kbuf, ksiz, bidx, hash, (char *)&num, sizeof(num), HDBPDADDDBL);
  HDBUNLOCKRECORD(hdb, bidx);
  HDBUNLOCKMETHOD(hdb);
  if(hdb->dfunit > 0 && hdb->dfcnt > hdb->dfunit &&
     !tchdbdefrag(hdb, hdb->dfunit * HDBDFRSRAT + 1)) rv = false;
  return rv ? num : nan("");
}


/* Synchronize updated contents of a hash database object with the file and the device. */
bool tchdbsync(TCHDB *hdb){
  assert(hdb);
  if(!HDBLOCKMETHOD(hdb, true)) return false;
  if(hdb->fd < 0 || !(hdb->omode & HDBOWRITER) || hdb->tran){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(hdb->async && !tchdbflushdrp(hdb)){
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  bool rv = tchdbmemsync(hdb, true);
  HDBUNLOCKMETHOD(hdb);
  return rv;
}


/* Optimize the file of a hash database object. */
bool tchdboptimize(TCHDB *hdb, int64_t bnum, int8_t apow, int8_t fpow, uint8_t opts){
  assert(hdb);
  if(!HDBLOCKMETHOD(hdb, true)) return false;
  if(hdb->fd < 0 || !(hdb->omode & HDBOWRITER) || hdb->tran){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(hdb->async && !tchdbflushdrp(hdb)){
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  HDBTHREADYIELD(hdb);
  bool rv = tchdboptimizeimpl(hdb, bnum, apow, fpow, opts);
  HDBUNLOCKMETHOD(hdb);
  return rv;
}


/* Remove all records of a hash database object. */
bool tchdbvanish(TCHDB *hdb){
  assert(hdb);
  if(!HDBLOCKMETHOD(hdb, true)) return false;
  if(hdb->fd < 0 || !(hdb->omode & HDBOWRITER) || hdb->tran){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(hdb->async && !tchdbflushdrp(hdb)){
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  HDBTHREADYIELD(hdb);
  bool rv = tchdbvanishimpl(hdb);
  HDBUNLOCKMETHOD(hdb);
  return rv;
}


/* Copy the database file of a hash database object. */
bool tchdbcopy(TCHDB *hdb, const char *path){
  assert(hdb && path);
  if(!HDBLOCKMETHOD(hdb, false)) return false;
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(hdb->async && !tchdbflushdrp(hdb)){
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(!HDBLOCKALLRECORDS(hdb, false)){
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  HDBTHREADYIELD(hdb);
  bool rv = tchdbcopyimpl(hdb, path);
  HDBUNLOCKALLRECORDS(hdb);
  HDBUNLOCKMETHOD(hdb);
  return rv;
}


/* Begin the transaction of a hash database object. */
bool tchdbtranbegin(TCHDB *hdb){
  assert(hdb);
  for(double wsec = 1.0 / sysconf(_SC_CLK_TCK); true; wsec *= 2){
    if(!HDBLOCKMETHOD(hdb, true)) return false;
    if(hdb->fd < 0 || !(hdb->omode & HDBOWRITER) || hdb->fatal){
      tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
      HDBUNLOCKMETHOD(hdb);
      return false;
    }
    if(!hdb->tran) break;
    HDBUNLOCKMETHOD(hdb);
    if(wsec > 1.0) wsec = 1.0;
    tcsleep(wsec);
  }
  if(hdb->async && !tchdbflushdrp(hdb)){
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(!tchdbmemsync(hdb, false)){
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if((hdb->omode & HDBOTSYNC) && fsync(hdb->fd) == -1){
    tchdbsetecode(hdb, TCESYNC, __FILE__, __LINE__, __func__);
    return false;
  }
  if(hdb->walfd < 0){
    char *tpath = tcsprintf("%s%c%s", hdb->path, MYEXTCHR, HDBWALSUFFIX);
    int walfd = open(tpath, O_RDWR | O_CREAT | O_TRUNC, HDBFILEMODE);
    TCFREE(tpath);
    if(walfd < 0){
      int ecode = TCEOPEN;
      switch(errno){
        case EACCES: ecode = TCENOPERM; break;
        case ENOENT: ecode = TCENOFILE; break;
        case ENOTDIR: ecode = TCENOFILE; break;
      }
      tchdbsetecode(hdb, ecode, __FILE__, __LINE__, __func__);
      HDBUNLOCKMETHOD(hdb);
      return false;
    }
    hdb->walfd = walfd;
  }
  tchdbsetflag(hdb, HDBFOPEN, false);
  if(!tchdbwalinit(hdb)){
    tchdbsetflag(hdb, HDBFOPEN, true);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  tchdbsetflag(hdb, HDBFOPEN, true);
  hdb->tran = true;
  HDBUNLOCKMETHOD(hdb);
  return true;
}


/* Commit the transaction of a hash database object. */
bool tchdbtrancommit(TCHDB *hdb){
  assert(hdb);
  if(!HDBLOCKMETHOD(hdb, true)) return false;
  if(hdb->fd < 0 || !(hdb->omode & HDBOWRITER) || hdb->fatal || !hdb->tran){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  bool err = false;
  if(hdb->async && !tchdbflushdrp(hdb)) err = true;
  if(!tchdbmemsync(hdb, hdb->omode & HDBOTSYNC)) err = true;
  if(!err && ftruncate(hdb->walfd, 0) == -1){
    tchdbsetecode(hdb, TCETRUNC, __FILE__, __LINE__, __func__);
    err = true;
  }
  hdb->tran = false;
  HDBUNLOCKMETHOD(hdb);
  return !err;
}


/* Abort the transaction of a hash database object. */
bool tchdbtranabort(TCHDB *hdb){
  assert(hdb);
  if(!HDBLOCKMETHOD(hdb, true)) return false;
  if(hdb->fd < 0 || !(hdb->omode & HDBOWRITER) || !hdb->tran){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  bool err = false;
  if(hdb->async && !tchdbflushdrp(hdb)) err = true;
  if(!tchdbmemsync(hdb, false)) err = true;
  if(!tchdbwalrestore(hdb, hdb->path)) err = true;
  char hbuf[HDBHEADSIZ];
  if(lseek(hdb->fd, 0, SEEK_SET) == -1){
    tchdbsetecode(hdb, TCESEEK, __FILE__, __LINE__, __func__);
    err = false;
  } else if(!tcread(hdb->fd, hbuf, HDBHEADSIZ)){
    tchdbsetecode(hdb, TCEREAD, __FILE__, __LINE__, __func__);
    err = false;
  } else {
    tchdbloadmeta(hdb, hbuf);
  }
  hdb->dfcur = hdb->frec;
  hdb->iter = 0;
  hdb->xfsiz = 0;
  hdb->fbpnum = 0;
  if(hdb->recc) tcmdbvanish(hdb->recc);
  hdb->tran = false;
  HDBUNLOCKMETHOD(hdb);
  return !err;
}


/* Get the file path of a hash database object. */
const char *tchdbpath(TCHDB *hdb){
  assert(hdb);
  if(!HDBLOCKMETHOD(hdb, false)) return NULL;
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return NULL;
  }
  const char *rv = hdb->path;
  HDBUNLOCKMETHOD(hdb);
  return rv;
}


/* Get the number of records of a hash database object. */
uint64_t tchdbrnum(TCHDB *hdb){
  assert(hdb);
  if(!HDBLOCKMETHOD(hdb, false)) return 0;
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return 0;
  }
  uint64_t rv = hdb->rnum;
  HDBUNLOCKMETHOD(hdb);
  return rv;
}


/* Get the size of the database file of a hash database object. */
uint64_t tchdbfsiz(TCHDB *hdb){
  assert(hdb);
  if(!HDBLOCKMETHOD(hdb, false)) return 0;
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return 0;
  }
  uint64_t rv = hdb->fsiz;
  HDBUNLOCKMETHOD(hdb);
  return rv;
}



/*************************************************************************************************
 * features for experts
 *************************************************************************************************/


/* Set the error code of a hash database object. */
void tchdbsetecode(TCHDB *hdb, int ecode, const char *filename, int line, const char *func){
  assert(hdb && filename && line >= 1 && func);
  int myerrno = errno;
  if(!hdb->fatal){
    if(hdb->mmtx){
      pthread_setspecific(*(pthread_key_t *)hdb->eckey, (void *)(intptr_t)ecode);
    } else {
      hdb->ecode = ecode;
    }
  }
  if(ecode != TCESUCCESS && ecode != TCEINVALID && ecode != TCEKEEP && ecode != TCENOREC){
    hdb->fatal = true;
    if(hdb->fd >= 0 && (hdb->omode & HDBOWRITER)) tchdbsetflag(hdb, HDBFFATAL, true);
  }
  if(hdb->dbgfd >= 0 && (hdb->dbgfd != UINT16_MAX || hdb->fatal)){
    int dbgfd = (hdb->dbgfd == UINT16_MAX) ? 1 : hdb->dbgfd;
    char obuf[HDBIOBUFSIZ];
    int osiz = sprintf(obuf, "ERROR:%s:%d:%s:%s:%d:%s:%d:%s\n", filename, line, func,
                       hdb->path ? hdb->path : "-", ecode, tchdberrmsg(ecode),
                       myerrno, strerror(myerrno));
    tcwrite(dbgfd, obuf, osiz);
  }
}


/* Set the type of a hash database object. */
void tchdbsettype(TCHDB *hdb, uint8_t type){
  assert(hdb);
  if(hdb->fd >= 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return;
  }
  hdb->type = type;
}


/* Set the file descriptor for debugging output. */
void tchdbsetdbgfd(TCHDB *hdb, int fd){
  assert(hdb && fd >= 0);
  hdb->dbgfd = fd;
}


/* Get the file descriptor for debugging output. */
int tchdbdbgfd(TCHDB *hdb){
  assert(hdb);
  return hdb->dbgfd;
}


/* Check whether mutual exclusion control is set to a hash database object. */
bool tchdbhasmutex(TCHDB *hdb){
  assert(hdb);
  return hdb->mmtx != NULL;
}


/* Synchronize updating contents on memory of a hash database object. */
bool tchdbmemsync(TCHDB *hdb, bool phys){
  assert(hdb);
  if(hdb->fd < 0 || !(hdb->omode & HDBOWRITER)){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  bool err = false;
  char hbuf[HDBHEADSIZ];
  tchdbdumpmeta(hdb, hbuf);
  memcpy(hdb->map, hbuf, HDBOPAQUEOFF);
  if(phys){
    size_t xmsiz = (hdb->xmsiz > hdb->msiz) ? hdb->xmsiz : hdb->msiz;
    if(msync(hdb->map, xmsiz, MS_SYNC) == -1){
      tchdbsetecode(hdb, TCEMMAP, __FILE__, __LINE__, __func__);
      err = true;
    }
    if(fsync(hdb->fd) == -1){
      tchdbsetecode(hdb, TCESYNC, __FILE__, __LINE__, __func__);
      err = true;
    }
  }
  return !err;
}


/* Get the number of elements of the bucket array of a hash database object. */
uint64_t tchdbbnum(TCHDB *hdb){
  assert(hdb);
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return hdb->bnum;
}


/* Get the record alignment a hash database object. */
uint32_t tchdbalign(TCHDB *hdb){
  assert(hdb);
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return hdb->align;
}


/* Get the maximum number of the free block pool of a a hash database object. */
uint32_t tchdbfbpmax(TCHDB *hdb){
  assert(hdb);
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return hdb->fbpmax;
}


/* Get the size of the extra mapped memory of a hash database object. */
uint64_t tchdbxmsiz(TCHDB *hdb){
  assert(hdb);
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return hdb->xmsiz;
}


/* Get the inode number of the database file of a hash database object. */
uint64_t tchdbinode(TCHDB *hdb){
  assert(hdb);
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return hdb->inode;
}


/* Get the modification time of the database file of a hash database object. */
time_t tchdbmtime(TCHDB *hdb){
  assert(hdb);
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return hdb->mtime;
}


/* Get the connection mode of a hash database object. */
int tchdbomode(TCHDB *hdb){
  assert(hdb);
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return hdb->omode;
}


/* Get the database type of a hash database object. */
uint8_t tchdbtype(TCHDB *hdb){
  assert(hdb);
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return hdb->type;
}


/* Get the additional flags of a hash database object. */
uint8_t tchdbflags(TCHDB *hdb){
  assert(hdb);
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return hdb->flags;
}


/* Get the options of a hash database object. */
uint8_t tchdbopts(TCHDB *hdb){
  assert(hdb);
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return hdb->opts;
}


/* Get the pointer to the opaque field of a hash database object. */
char *tchdbopaque(TCHDB *hdb){
  assert(hdb);
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return NULL;
  }
  return hdb->map + HDBOPAQUEOFF;
}


/* Get the number of used elements of the bucket array of a hash database object. */
uint64_t tchdbbnumused(TCHDB *hdb){
  assert(hdb);
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  uint64_t unum = 0;
  if(hdb->ba64){
    uint64_t *buckets = hdb->ba64;
    for(int i = 0; i < hdb->bnum; i++){
      if(buckets[i]) unum++;
    }
  } else {
    uint32_t *buckets = hdb->ba32;
    for(int i = 0; i < hdb->bnum; i++){
      if(buckets[i]) unum++;
    }
  }
  return unum;
}


/* Set the custom codec functions of a hash database object. */
bool tchdbsetcodecfunc(TCHDB *hdb, TCCODEC enc, void *encop, TCCODEC dec, void *decop){
  assert(hdb && enc && dec);
  if(!HDBLOCKMETHOD(hdb, true)) return false;
  if(hdb->fd >= 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  hdb->enc = enc;
  hdb->encop = encop;
  hdb->dec = dec;
  hdb->decop = decop;
  HDBUNLOCKMETHOD(hdb);
  return true;
}


/* Get the unit step number of auto defragmentation of a hash database object. */
uint32_t tchdbdfunit(TCHDB *hdb){
  assert(hdb);
  return hdb->dfunit;
}


/* Perform dynamic defragmentation of a hash database object. */
bool tchdbdefrag(TCHDB *hdb, int64_t step){
  assert(hdb);
  if(step > 0){
    if(!HDBLOCKMETHOD(hdb, true)) return false;
    if(hdb->fd < 0 || !(hdb->omode & HDBOWRITER)){
      tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
      HDBUNLOCKMETHOD(hdb);
      return false;
    }
    if(hdb->async && !tchdbflushdrp(hdb)){
      HDBUNLOCKMETHOD(hdb);
      return false;
    }
    bool rv = tchdbdefragimpl(hdb, step);
    HDBUNLOCKMETHOD(hdb);
    return rv;
  }
  if(!HDBLOCKMETHOD(hdb, false)) return false;
  if(hdb->fd < 0 || !(hdb->omode & HDBOWRITER)){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(hdb->async && !tchdbflushdrp(hdb)){
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  bool err = false;
  if(HDBLOCKALLRECORDS(hdb, true)){
    hdb->dfcur = hdb->frec;
    HDBUNLOCKALLRECORDS(hdb);
  } else {
    err = true;
  }
  bool stop = false;
  while(!err && !stop){
    if(HDBLOCKALLRECORDS(hdb, true)){
      uint64_t cur = hdb->dfcur;
      if(!tchdbdefragimpl(hdb, UINT8_MAX)) err = true;
      if(hdb->dfcur <= cur) stop = true;
      HDBUNLOCKALLRECORDS(hdb);
      HDBTHREADYIELD(hdb);
    } else {
      err = true;
    }
  }
  HDBUNLOCKMETHOD(hdb);
  return !err;
}


/* Clear the cache of a hash tree database object. */
bool tchdbcacheclear(TCHDB *hdb){
  assert(hdb);
  if(!HDBLOCKMETHOD(hdb, true)) return false;
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  HDBTHREADYIELD(hdb);
  if(hdb->recc) tcmdbvanish(hdb->recc);
  HDBUNLOCKMETHOD(hdb);
  return true;
}


/* Store a record into a hash database object with a duplication handler. */
bool tchdbputproc(TCHDB *hdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz,
                  TCPDPROC proc, void *op){
  assert(hdb && kbuf && ksiz >= 0 && proc);
  if(!HDBLOCKMETHOD(hdb, false)) return false;
  uint8_t hash;
  uint64_t bidx = tchdbbidx(hdb, kbuf, ksiz, &hash);
  if(hdb->fd < 0 || !(hdb->omode & HDBOWRITER)){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(hdb->async && !tchdbflushdrp(hdb)){
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(!HDBLOCKRECORD(hdb, bidx, true)){
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(hdb->zmode){
    char *zbuf;
    int osiz;
    char *obuf = tchdbgetimpl(hdb, kbuf, ksiz, bidx, hash, &osiz);
    if(obuf){
      int nsiz;
      char *nbuf = proc(obuf, osiz, &nsiz, op);
      if(nbuf == (void *)-1){
        bool rv = tchdboutimpl(hdb, kbuf, ksiz, bidx, hash);
        TCFREE(obuf);
        HDBUNLOCKRECORD(hdb, bidx);
        HDBUNLOCKMETHOD(hdb);
        return rv;
      } else if(nbuf){
        if(hdb->opts & HDBTDEFLATE){
          zbuf = _tc_deflate(nbuf, nsiz, &vsiz, _TCZMRAW);
        } else if(hdb->opts & HDBTBZIP){
          zbuf = _tc_bzcompress(nbuf, nsiz, &vsiz);
        } else if(hdb->opts & HDBTTCBS){
          zbuf = tcbsencode(nbuf, nsiz, &vsiz);
        } else {
          zbuf = hdb->enc(nbuf, nsiz, &vsiz, hdb->encop);
        }
        TCFREE(nbuf);
      } else {
        zbuf = NULL;
      }
      TCFREE(obuf);
    } else if(vbuf){
      if(hdb->opts & HDBTDEFLATE){
        zbuf = _tc_deflate(vbuf, vsiz, &vsiz, _TCZMRAW);
      } else if(hdb->opts & HDBTBZIP){
        zbuf = _tc_bzcompress(vbuf, vsiz, &vsiz);
      } else if(hdb->opts & HDBTTCBS){
        zbuf = tcbsencode(vbuf, vsiz, &vsiz);
      } else {
        zbuf = hdb->enc(vbuf, vsiz, &vsiz, hdb->encop);
      }
    } else {
      tchdbsetecode(hdb, TCENOREC, __FILE__, __LINE__, __func__);
      HDBUNLOCKRECORD(hdb, bidx);
      HDBUNLOCKMETHOD(hdb);
      return false;
    }
    if(!zbuf){
      tchdbsetecode(hdb, TCEKEEP, __FILE__, __LINE__, __func__);
      HDBUNLOCKRECORD(hdb, bidx);
      HDBUNLOCKMETHOD(hdb);
      return false;
    }
    bool rv = tchdbputimpl(hdb, kbuf, ksiz, bidx, hash, zbuf, vsiz, HDBPDOVER);
    TCFREE(zbuf);
    HDBUNLOCKRECORD(hdb, bidx);
    HDBUNLOCKMETHOD(hdb);
    if(hdb->dfunit > 0 && hdb->dfcnt > hdb->dfunit &&
       !tchdbdefrag(hdb, hdb->dfunit * HDBDFRSRAT + 1)) rv = false;
    return rv;
  }
  HDBPDPROCOP procop;
  procop.proc = proc;
  procop.op = op;
  HDBPDPROCOP *procptr = &procop;
  tcgeneric_t stack[(TCNUMBUFSIZ*2)/sizeof(tcgeneric_t)+1];
  char *rbuf;
  if(ksiz <= sizeof(stack) - sizeof(procptr)){
    rbuf = (char *)stack;
  } else {
    TCMALLOC(rbuf, ksiz + sizeof(procptr));
  }
  char *wp = rbuf;
  memcpy(wp, &procptr, sizeof(procptr));
  wp += sizeof(procptr);
  memcpy(wp, kbuf, ksiz);
  kbuf = rbuf + sizeof(procptr);
  bool rv = tchdbputimpl(hdb, kbuf, ksiz, bidx, hash, vbuf, vsiz, HDBPDPROC);
  if(rbuf != (char *)stack) TCFREE(rbuf);
  HDBUNLOCKRECORD(hdb, bidx);
  HDBUNLOCKMETHOD(hdb);
  if(hdb->dfunit > 0 && hdb->dfcnt > hdb->dfunit &&
     !tchdbdefrag(hdb, hdb->dfunit * HDBDFRSRAT + 1)) rv = false;
  return rv;
}


/* Get the custom codec functions of a hash database object. */
void tchdbcodecfunc(TCHDB *hdb, TCCODEC *ep, void **eop, TCCODEC *dp, void **dop){
  assert(hdb && ep && eop && dp && dop);
  *ep = hdb->enc;
  *eop = hdb->encop;
  *dp = hdb->dec;
  *dop = hdb->decop;
}


/* Retrieve the next record of a record in a hash database object. */
void *tchdbgetnext(TCHDB *hdb, const void *kbuf, int ksiz, int *sp){
  assert(hdb && sp);
  if(!HDBLOCKMETHOD(hdb, true)) return NULL;
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return NULL;
  }
  if(hdb->async && !tchdbflushdrp(hdb)){
    HDBUNLOCKMETHOD(hdb);
    return NULL;
  }
  char *rv = tchdbgetnextimpl(hdb, kbuf, ksiz, sp, NULL, NULL);
  HDBUNLOCKMETHOD(hdb);
  return rv;
}


/* Retrieve the next record of a string record in a hash database object. */
char *tchdbgetnext2(TCHDB *hdb, const char *kstr){
  assert(hdb);
  int vsiz;
  return tchdbgetnext(hdb, kstr, strlen(kstr), &vsiz);
}


/* Retrieve the key and the value of the next record of a record in a hash database object. */
char *tchdbgetnext3(TCHDB *hdb, const char *kbuf, int ksiz, int *sp, const char **vbp, int *vsp){
  assert(hdb && sp && vbp && vsp);
  if(!HDBLOCKMETHOD(hdb, true)) return NULL;
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return NULL;
  }
  if(hdb->async && !tchdbflushdrp(hdb)){
    HDBUNLOCKMETHOD(hdb);
    return NULL;
  }
  char *rv = tchdbgetnextimpl(hdb, kbuf, ksiz, sp, vbp, vsp);
  HDBUNLOCKMETHOD(hdb);
  return rv;
}


/* Move the iterator to the record corresponding a key of a hash database object. */
bool tchdbiterinit2(TCHDB *hdb, const void *kbuf, int ksiz){
  assert(hdb && kbuf && ksiz >= 0);
  if(!HDBLOCKMETHOD(hdb, true)) return false;
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(hdb->async && !tchdbflushdrp(hdb)){
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  bool rv = tchdbiterjumpimpl(hdb, kbuf, ksiz);
  HDBUNLOCKMETHOD(hdb);
  return rv;
}


/* Move the iterator to the record corresponding a key string of a hash database object. */
bool tchdbiterinit3(TCHDB *hdb, const char *kstr){
  assert(hdb && kstr);
  return tchdbiterinit2(hdb, kstr, strlen(kstr));
}


/* Process each record atomically of a hash database object. */
bool tchdbforeach(TCHDB *hdb, TCITER iter, void *op){
  assert(hdb && iter);
  if(!HDBLOCKMETHOD(hdb, false)) return false;
  if(hdb->fd < 0){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(hdb->async && !tchdbflushdrp(hdb)){
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  if(!HDBLOCKALLRECORDS(hdb, false)){
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  HDBTHREADYIELD(hdb);
  bool rv = tchdbforeachimpl(hdb, iter, op);
  HDBUNLOCKALLRECORDS(hdb);
  HDBUNLOCKMETHOD(hdb);
  return rv;
}


/* Void the transaction of a hash database object. */
bool tchdbtranvoid(TCHDB *hdb){
  assert(hdb);
  if(!HDBLOCKMETHOD(hdb, true)) return false;
  if(hdb->fd < 0 || !(hdb->omode & HDBOWRITER) || hdb->fatal || !hdb->tran){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    HDBUNLOCKMETHOD(hdb);
    return false;
  }
  hdb->tran = false;
  HDBUNLOCKMETHOD(hdb);
  return true;
}



/*************************************************************************************************
 * private features
 *************************************************************************************************/


/* Get a natural prime number not less than a floor number.
   `num' specified the floor number.
   The return value is a prime number not less than the floor number. */
static uint64_t tcgetprime(uint64_t num){
  uint64_t primes[] = {
    1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 43, 47, 53, 59, 61, 71, 79, 83,
    89, 103, 109, 113, 127, 139, 157, 173, 191, 199, 223, 239, 251, 283, 317, 349,
    383, 409, 443, 479, 509, 571, 631, 701, 761, 829, 887, 953, 1021, 1151, 1279,
    1399, 1531, 1663, 1789, 1913, 2039, 2297, 2557, 2803, 3067, 3323, 3583, 3833,
    4093, 4603, 5119, 5623, 6143, 6653, 7159, 7673, 8191, 9209, 10223, 11261,
    12281, 13309, 14327, 15359, 16381, 18427, 20479, 22511, 24571, 26597, 28669,
    30713, 32749, 36857, 40949, 45053, 49139, 53239, 57331, 61417, 65521, 73727,
    81919, 90107, 98299, 106487, 114679, 122869, 131071, 147451, 163819, 180221,
    196597, 212987, 229373, 245759, 262139, 294911, 327673, 360439, 393209, 425977,
    458747, 491503, 524287, 589811, 655357, 720887, 786431, 851957, 917503, 982981,
    1048573, 1179641, 1310719, 1441771, 1572853, 1703903, 1835003, 1966079,
    2097143, 2359267, 2621431, 2883577, 3145721, 3407857, 3670013, 3932153,
    4194301, 4718579, 5242877, 5767129, 6291449, 6815741, 7340009, 7864301,
    8388593, 9437179, 10485751, 11534329, 12582893, 13631477, 14680063, 15728611,
    16777213, 18874367, 20971507, 23068667, 25165813, 27262931, 29360087, 31457269,
    33554393, 37748717, 41943023, 46137319, 50331599, 54525917, 58720253, 62914549,
    67108859, 75497467, 83886053, 92274671, 100663291, 109051903, 117440509,
    125829103, 134217689, 150994939, 167772107, 184549373, 201326557, 218103799,
    234881011, 251658227, 268435399, 301989881, 335544301, 369098707, 402653171,
    436207613, 469762043, 503316469, 536870909, 603979769, 671088637, 738197503,
    805306357, 872415211, 939524087, 1006632947, 1073741789, 1207959503,
    1342177237, 1476394991, 1610612711, 1744830457, 1879048183, 2013265907,
    2576980349, 3092376431, 3710851741, 4718021527, 6133428047, 7973456459,
    10365493393, 13475141413, 17517683831, 22772988923, 29604885677, 38486351381,
    50032256819, 65041933867, 84554514043, 109920868241, 153889215497, 0
  };
  int i;
  for(i = 0; primes[i] > 0; i++){
    if(num <= primes[i]) return primes[i];
  }
  return primes[i-1];
}


/* Seek and write data into a file.
   `hdb' specifies the hash database object.
   `off' specifies the offset of the region to seek.
   `buf' specifies the buffer to store into.
   `size' specifies the size of the buffer.
   The return value is true if successful, else, it is false. */
static bool tchdbseekwrite(TCHDB *hdb, off_t off, const void *buf, size_t size){
  assert(hdb && off >= 0 && buf && size >= 0);
  if(hdb->tran && !tchdbwalwrite(hdb, off, size)) return false;
  off_t end = off + size;
  if(end <= hdb->xmsiz){
    if(end >= hdb->fsiz && end >= hdb->xfsiz){
      uint64_t xfsiz = end + HDBXFSIZINC;
      if(ftruncate(hdb->fd, xfsiz) == -1){
        tchdbsetecode(hdb, TCETRUNC, __FILE__, __LINE__, __func__);
        return false;
      }
      hdb->xfsiz = xfsiz;
    }
    memcpy(hdb->map + off, buf, size);
    return true;
  }
  if(!TCUBCACHE && off < hdb->xmsiz){
    if(end >= hdb->fsiz && end >= hdb->xfsiz){
      uint64_t xfsiz = end + HDBXFSIZINC;
      if(ftruncate(hdb->fd, xfsiz) == -1){
        tchdbsetecode(hdb, TCETRUNC, __FILE__, __LINE__, __func__);
        return false;
      }
      hdb->xfsiz = xfsiz;
    }
    int head = hdb->xmsiz - off;
    memcpy(hdb->map + off, buf, head);
    off += head;
    buf = (char *)buf + head;
    size -= head;
  }
  while(true){
    int wb = pwrite(hdb->fd, buf, size, off);
    if(wb >= size){
      return true;
    } else if(wb > 0){
      buf = (char *)buf + wb;
      size -= wb;
      off += wb;
    } else if(wb == -1){
      if(errno != EINTR){
        tchdbsetecode(hdb, TCEWRITE, __FILE__, __LINE__, __func__);
        return false;
      }
    } else {
      if(size > 0){
        tchdbsetecode(hdb, TCEWRITE, __FILE__, __LINE__, __func__);
        return false;
      }
    }
  }
  return true;
}


/* Seek and read data from a file.
   `hdb' specifies the hash database object.
   `off' specifies the offset of the region to seek.
   `buf' specifies the buffer to store into.
   `size' specifies the size of the buffer.
   The return value is true if successful, else, it is false. */
static bool tchdbseekread(TCHDB *hdb, off_t off, void *buf, size_t size){
  assert(hdb && off >= 0 && buf && size >= 0);
  if(off + size <= hdb->xmsiz){
    memcpy(buf, hdb->map + off, size);
    return true;
  }
  if(!TCUBCACHE && off < hdb->xmsiz){
    int head = hdb->xmsiz - off;
    memcpy(buf, hdb->map + off, head);
    off += head;
    buf = (char *)buf + head;
    size -= head;
  }
  while(true){
    int rb = pread(hdb->fd, buf, size, off);
    if(rb >= size){
      break;
    } else if(rb > 0){
      buf = (char *)buf + rb;
      size -= rb;
      off += rb;
    } else if(rb == -1){
      if(errno != EINTR){
        tchdbsetecode(hdb, TCEREAD, __FILE__, __LINE__, __func__);
        return false;
      }
    } else {
      if(size > 0){
        tchdbsetecode(hdb, TCEREAD, __FILE__, __LINE__, __func__);
        return false;
      }
    }
  }
  return true;
}


/* Try to seek and read data from a file.
   `hdb' specifies the hash database object.
   `off' specifies the offset of the region to seek.
   `buf' specifies the buffer to store into.
   `size' specifies the size of the buffer.
   The return value is true if successful, else, it is false. */
static bool tchdbseekreadtry(TCHDB *hdb, off_t off, void *buf, size_t size){
  assert(hdb && off >= 0 && buf && size >= 0);
  off_t end = off + size;
  if(end > hdb->fsiz) return false;
  if(end <= hdb->xmsiz){
    memcpy(buf, hdb->map + off, size);
    return true;
  }
  if(!TCUBCACHE && off < hdb->xmsiz){
    int head = hdb->xmsiz - off;
    memcpy(buf, hdb->map + off, head);
    off += head;
    buf = (char *)buf + head;
    size -= head;
  }
  int rb = pread(hdb->fd, buf, size, off);
  if(rb == size) return true;
  if(rb == -1) tchdbsetecode(hdb, TCEREAD, __FILE__, __LINE__, __func__);
  return false;
}


/* Serialize meta data into a buffer.
   `hdb' specifies the hash database object.
   `hbuf' specifies the buffer. */
static void tchdbdumpmeta(TCHDB *hdb, char *hbuf){
  memset(hbuf, 0, HDBHEADSIZ);
  sprintf(hbuf, "%s\n%s:%d\n", HDBMAGICDATA, _TC_FORMATVER, _TC_LIBVER);
  memcpy(hbuf + HDBTYPEOFF, &(hdb->type), sizeof(hdb->type));
  memcpy(hbuf + HDBFLAGSOFF, &(hdb->flags), sizeof(hdb->flags));
  memcpy(hbuf + HDBAPOWOFF, &(hdb->apow), sizeof(hdb->apow));
  memcpy(hbuf + HDBFPOWOFF, &(hdb->fpow), sizeof(hdb->fpow));
  memcpy(hbuf + HDBOPTSOFF, &(hdb->opts), sizeof(hdb->opts));
  uint64_t llnum;
  llnum = hdb->bnum;
  llnum = TCHTOILL(llnum);
  memcpy(hbuf + HDBBNUMOFF, &llnum, sizeof(llnum));
  llnum = hdb->rnum;
  llnum = TCHTOILL(llnum);
  memcpy(hbuf + HDBRNUMOFF, &llnum, sizeof(llnum));
  llnum = hdb->fsiz;
  llnum = TCHTOILL(llnum);
  memcpy(hbuf + HDBFSIZOFF, &llnum, sizeof(llnum));
  llnum = hdb->frec;
  llnum = TCHTOILL(llnum);
  memcpy(hbuf + HDBFRECOFF, &llnum, sizeof(llnum));
}


/* Deserialize meta data from a buffer.
   `hdb' specifies the hash database object.
   `hbuf' specifies the buffer. */
static void tchdbloadmeta(TCHDB *hdb, const char *hbuf){
  memcpy(&(hdb->type), hbuf + HDBTYPEOFF, sizeof(hdb->type));
  memcpy(&(hdb->flags), hbuf + HDBFLAGSOFF, sizeof(hdb->flags));
  memcpy(&(hdb->apow), hbuf + HDBAPOWOFF, sizeof(hdb->apow));
  memcpy(&(hdb->fpow), hbuf + HDBFPOWOFF, sizeof(hdb->fpow));
  memcpy(&(hdb->opts), hbuf + HDBOPTSOFF, sizeof(hdb->opts));
  uint64_t llnum;
  memcpy(&llnum, hbuf + HDBBNUMOFF, sizeof(llnum));
  hdb->bnum = TCITOHLL(llnum);
  memcpy(&llnum, hbuf + HDBRNUMOFF, sizeof(llnum));
  hdb->rnum = TCITOHLL(llnum);
  memcpy(&llnum, hbuf + HDBFSIZOFF, sizeof(llnum));
  hdb->fsiz = TCITOHLL(llnum);
  memcpy(&llnum, hbuf + HDBFRECOFF, sizeof(llnum));
  hdb->frec = TCITOHLL(llnum);
}


/* Clear all members.
   `hdb' specifies the hash database object. */
static void tchdbclear(TCHDB *hdb){
  assert(hdb);
  hdb->mmtx = NULL;
  hdb->rmtxs = NULL;
  hdb->dmtx = NULL;
  hdb->wmtx = NULL;
  hdb->eckey = NULL;
  hdb->rpath = NULL;
  hdb->type = TCDBTHASH;
  hdb->flags = 0;
  hdb->bnum = HDBDEFBNUM;
  hdb->apow = HDBDEFAPOW;
  hdb->fpow = HDBDEFFPOW;
  hdb->opts = 0;
  hdb->path = NULL;
  hdb->fd = -1;
  hdb->omode = 0;
  hdb->rnum = 0;
  hdb->fsiz = 0;
  hdb->frec = 0;
  hdb->dfcur = 0;
  hdb->iter = 0;
  hdb->map = NULL;
  hdb->msiz = 0;
  hdb->xmsiz = HDBDEFXMSIZ;
  hdb->xfsiz = 0;
  hdb->ba32 = NULL;
  hdb->ba64 = NULL;
  hdb->align = 0;
  hdb->runit = 0;
  hdb->zmode = false;
  hdb->fbpmax = 0;
  hdb->fbpool = NULL;
  hdb->fbpnum = 0;
  hdb->fbpmis = 0;
  hdb->async = false;
  hdb->drpool = NULL;
  hdb->drpdef = NULL;
  hdb->drpoff = 0;
  hdb->recc = NULL;
  hdb->rcnum = 0;
  hdb->enc = NULL;
  hdb->encop = NULL;
  hdb->dec = NULL;
  hdb->decop = NULL;
  hdb->ecode = TCESUCCESS;
  hdb->fatal = false;
  hdb->inode = 0;
  hdb->mtime = 0;
  hdb->dfunit = 0;
  hdb->dfcnt = 0;
  hdb->tran = false;
  hdb->walfd = -1;
  hdb->walend = 0;
  hdb->dbgfd = -1;
  hdb->cnt_writerec = -1;
  hdb->cnt_reuserec = -1;
  hdb->cnt_moverec = -1;
  hdb->cnt_readrec = -1;
  hdb->cnt_searchfbp = -1;
  hdb->cnt_insertfbp = -1;
  hdb->cnt_splicefbp = -1;
  hdb->cnt_dividefbp = -1;
  hdb->cnt_mergefbp = -1;
  hdb->cnt_reducefbp = -1;
  hdb->cnt_appenddrp = -1;
  hdb->cnt_deferdrp = -1;
  hdb->cnt_flushdrp = -1;
  hdb->cnt_adjrecc = -1;
  hdb->cnt_defrag = -1;
  hdb->cnt_shiftrec = -1;
  hdb->cnt_trunc = -1;
  TCDODEBUG(hdb->cnt_writerec = 0);
  TCDODEBUG(hdb->cnt_reuserec = 0);
  TCDODEBUG(hdb->cnt_moverec = 0);
  TCDODEBUG(hdb->cnt_readrec = 0);
  TCDODEBUG(hdb->cnt_searchfbp = 0);
  TCDODEBUG(hdb->cnt_insertfbp = 0);
  TCDODEBUG(hdb->cnt_splicefbp = 0);
  TCDODEBUG(hdb->cnt_dividefbp = 0);
  TCDODEBUG(hdb->cnt_mergefbp = 0);
  TCDODEBUG(hdb->cnt_reducefbp = 0);
  TCDODEBUG(hdb->cnt_appenddrp = 0);
  TCDODEBUG(hdb->cnt_deferdrp = 0);
  TCDODEBUG(hdb->cnt_flushdrp = 0);
  TCDODEBUG(hdb->cnt_adjrecc = 0);
  TCDODEBUG(hdb->cnt_defrag = 0);
  TCDODEBUG(hdb->cnt_shiftrec = 0);
  TCDODEBUG(hdb->cnt_trunc = 0);
}


/* Get the padding size to record alignment.
   `hdb' specifies the hash database object.
   `off' specifies the current offset.
   The return value is the padding size. */
static int32_t tchdbpadsize(TCHDB *hdb, uint64_t off){
  assert(hdb);
  int32_t diff = off & (hdb->align - 1);
  return (diff > 0) ? hdb->align - diff : 0;
}


/* Set the open flag.
   `hdb' specifies the hash database object.
   `flag' specifies the flag value.
   `sign' specifies the sign. */
static void tchdbsetflag(TCHDB *hdb, int flag, bool sign){
  assert(hdb);
  char *fp = (char *)hdb->map + HDBFLAGSOFF;
  if(sign){
    *fp |= (uint8_t)flag;
  } else {
    *fp &= ~(uint8_t)flag;
  }
  hdb->flags = *fp;
}


/* Get the bucket index of a record.
   `hdb' specifies the hash database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   `hp' specifies the pointer to the variable into which the second hash value is assigned.
   The return value is the bucket index. */
static uint64_t tchdbbidx(TCHDB *hdb, const char *kbuf, int ksiz, uint8_t *hp){
  assert(hdb && kbuf && ksiz >= 0 && hp);
  uint64_t idx = 19780211;
  uint32_t hash = 751;
  const char *rp = kbuf + ksiz;
  while(ksiz--){
    idx = idx * 37 + *(uint8_t *)kbuf++;
    hash = (hash * 31) ^ *(uint8_t *)--rp;
  }
  *hp = hash;
  return idx % hdb->bnum;
}


/* Get the offset of the record of a bucket element.
   `hdb' specifies the hash database object.
   `bidx' specifies the index of the bucket.
   The return value is the offset of the record. */
static off_t tchdbgetbucket(TCHDB *hdb, uint64_t bidx){
  assert(hdb && bidx >= 0);
  if(hdb->ba64){
    uint64_t llnum = hdb->ba64[bidx];
    return TCITOHLL(llnum) << hdb->apow;
  }
  uint32_t lnum = hdb->ba32[bidx];
  return (off_t)TCITOHL(lnum) << hdb->apow;
}


/* Get the offset of the record of a bucket element.
   `hdb' specifies the hash database object.
   `bidx' specifies the index of the record.
   `off' specifies the offset of the record. */
static void tchdbsetbucket(TCHDB *hdb, uint64_t bidx, uint64_t off){
  assert(hdb && bidx >= 0);
  if(hdb->ba64){
    uint64_t llnum = off >> hdb->apow;
    if(hdb->tran) tchdbwalwrite(hdb, HDBHEADSIZ + bidx * sizeof(llnum), sizeof(llnum));
    hdb->ba64[bidx] = TCHTOILL(llnum);
  } else {
    uint32_t lnum = off >> hdb->apow;
    if(hdb->tran) tchdbwalwrite(hdb, HDBHEADSIZ + bidx * sizeof(lnum), sizeof(lnum));
    hdb->ba32[bidx] = TCHTOIL(lnum);
  }
}


/* Load the free block pool from the file.
   The return value is true if successful, else, it is false. */
static bool tchdbsavefbp(TCHDB *hdb){
  assert(hdb);
  if(hdb->fbpnum > hdb->fbpmax){
    tchdbfbpmerge(hdb);
  } else if(hdb->fbpnum > 1){
    tcfbpsortbyoff(hdb->fbpool, hdb->fbpnum);
  }
  int bsiz = hdb->frec - hdb->msiz;
  char *buf;
  TCMALLOC(buf, bsiz);
  char *wp = buf;
  HDBFB *cur = hdb->fbpool;
  HDBFB *end = cur + hdb->fbpnum;
  uint64_t base = 0;
  bsiz -= sizeof(HDBFB) + sizeof(uint8_t) + sizeof(uint8_t);
  while(cur < end && bsiz > 0){
    uint64_t noff = cur->off >> hdb->apow;
    int step;
    uint64_t llnum = noff - base;
    TCSETVNUMBUF64(step, wp, llnum);
    wp += step;
    bsiz -= step;
    uint32_t lnum = cur->rsiz >> hdb->apow;
    TCSETVNUMBUF(step, wp, lnum);
    wp += step;
    bsiz -= step;
    base = noff;
    cur++;
  }
  *(wp++) = '\0';
  *(wp++) = '\0';
  if(!tchdbseekwrite(hdb, hdb->msiz, buf, wp - buf)){
    TCFREE(buf);
    return false;
  }
  TCFREE(buf);
  return true;
}


/* Save the free block pool into the file.
   The return value is true if successful, else, it is false. */
static bool tchdbloadfbp(TCHDB *hdb){
  int bsiz = hdb->frec - hdb->msiz;
  char *buf;
  TCMALLOC(buf, bsiz);
  if(!tchdbseekread(hdb, hdb->msiz, buf, bsiz)){
    TCFREE(buf);
    return false;
  }
  const char *rp = buf;
  HDBFB *cur = hdb->fbpool;
  HDBFB *end = cur + hdb->fbpmax * HDBFBPALWRAT;
  uint64_t base = 0;
  while(cur < end && *rp != '\0'){
    int step;
    uint64_t llnum;
    TCREADVNUMBUF64(rp, llnum, step);
    base += llnum << hdb->apow;
    cur->off = base;
    rp += step;
    uint32_t lnum;
    TCREADVNUMBUF(rp, lnum, step);
    cur->rsiz = lnum << hdb->apow;
    rp += step;
    cur++;
  }
  hdb->fbpnum = cur - (HDBFB *)hdb->fbpool;
  TCFREE(buf);
  tcfbpsortbyrsiz(hdb->fbpool, hdb->fbpnum);
  return true;
}


/* Sort the free block pool by offset.
   `fbpool' specifies the free block pool.
   `fbpnum' specifies the number of blocks. */
static void tcfbpsortbyoff(HDBFB *fbpool, int fbpnum){
  assert(fbpool && fbpnum >= 0);
  fbpnum--;
  int bottom = fbpnum / 2 + 1;
  int top = fbpnum;
  while(bottom > 0){
    bottom--;
    int mybot = bottom;
    int i = mybot * 2;
    while(i <= top){
      if(i < top && fbpool[i+1].off > fbpool[i].off) i++;
      if(fbpool[mybot].off >= fbpool[i].off) break;
      HDBFB swap = fbpool[mybot];
      fbpool[mybot] = fbpool[i];
      fbpool[i] = swap;
      mybot = i;
      i = mybot * 2;
    }
  }
  while(top > 0){
    HDBFB swap = fbpool[0];
    fbpool[0] = fbpool[top];
    fbpool[top] = swap;
    top--;
    int mybot = bottom;
    int i = mybot * 2;
    while(i <= top){
      if(i < top && fbpool[i+1].off > fbpool[i].off) i++;
      if(fbpool[mybot].off >= fbpool[i].off) break;
      swap = fbpool[mybot];
      fbpool[mybot] = fbpool[i];
      fbpool[i] = swap;
      mybot = i;
      i = mybot * 2;
    }
  }
}


/* Sort the free block pool by record size.
   `fbpool' specifies the free block pool.
   `fbpnum' specifies the number of blocks. */
static void tcfbpsortbyrsiz(HDBFB *fbpool, int fbpnum){
  assert(fbpool && fbpnum >= 0);
  fbpnum--;
  int bottom = fbpnum / 2 + 1;
  int top = fbpnum;
  while(bottom > 0){
    bottom--;
    int mybot = bottom;
    int i = mybot * 2;
    while(i <= top){
      if(i < top && fbpool[i+1].rsiz > fbpool[i].rsiz) i++;
      if(fbpool[mybot].rsiz >= fbpool[i].rsiz) break;
      HDBFB swap = fbpool[mybot];
      fbpool[mybot] = fbpool[i];
      fbpool[i] = swap;
      mybot = i;
      i = mybot * 2;
    }
  }
  while(top > 0){
    HDBFB swap = fbpool[0];
    fbpool[0] = fbpool[top];
    fbpool[top] = swap;
    top--;
    int mybot = bottom;
    int i = mybot * 2;
    while(i <= top){
      if(i < top && fbpool[i+1].rsiz > fbpool[i].rsiz) i++;
      if(fbpool[mybot].rsiz >= fbpool[i].rsiz) break;
      swap = fbpool[mybot];
      fbpool[mybot] = fbpool[i];
      fbpool[i] = swap;
      mybot = i;
      i = mybot * 2;
    }
  }
}


/* Merge successive records in the free block pool.
   `hdb' specifies the hash database object. */
static void tchdbfbpmerge(TCHDB *hdb){
  assert(hdb);
  TCDODEBUG(hdb->cnt_mergefbp++);
  tcfbpsortbyoff(hdb->fbpool, hdb->fbpnum);
  HDBFB *wp = hdb->fbpool;
  HDBFB *cur = wp;
  HDBFB *end = wp + hdb->fbpnum - 1;
  while(cur < end){
    if(cur->off > 0){
      HDBFB *next = cur + 1;
      if(cur->off + cur->rsiz == next->off && cur->rsiz + next->rsiz <= HDBFBMAXSIZ){
        if(hdb->dfcur == next->off) hdb->dfcur += next->rsiz;
        if(hdb->iter == next->off) hdb->iter += next->rsiz;
        cur->rsiz += next->rsiz;
        next->off = 0;
      }
      *(wp++) = *cur;
    }
    cur++;
  }
  if(end->off > 0) *(wp++) = *end;
  hdb->fbpnum = wp - (HDBFB *)hdb->fbpool;
  hdb->fbpmis = hdb->fbpnum * -1;
}


/* Insert a block into the free block pool.
   `hdb' specifies the hash database object.
   `off' specifies the offset of the block.
   `rsiz' specifies the size of the block. */
static void tchdbfbpinsert(TCHDB *hdb, uint64_t off, uint32_t rsiz){
  assert(hdb && off > 0 && rsiz > 0);
  TCDODEBUG(hdb->cnt_insertfbp++);
  hdb->dfcnt++;
  if(hdb->fpow < 1) return;
  HDBFB *pv = hdb->fbpool;
  if(hdb->fbpnum >= hdb->fbpmax * HDBFBPALWRAT){
    tchdbfbpmerge(hdb);
    tcfbpsortbyrsiz(hdb->fbpool, hdb->fbpnum);
    int diff = hdb->fbpnum - hdb->fbpmax;
    if(diff > 0){
      TCDODEBUG(hdb->cnt_reducefbp++);
      memmove(pv, pv + diff, (hdb->fbpnum - diff) * sizeof(*pv));
      hdb->fbpnum -= diff;
    }
    hdb->fbpmis = 0;
  }
  int num = hdb->fbpnum;
  int left = 0;
  int right = num;
  int i = (left + right) / 2;
  int cand = -1;
  while(right >= left && i < num){
    int rv = (int)rsiz - (int)pv[i].rsiz;
    if(rv == 0){
      cand = i;
      break;
    } else if(rv <= 0){
      cand = i;
      right = i - 1;
    } else {
      left = i + 1;
    }
    i = (left + right) / 2;
  }
  if(cand >= 0){
    pv += cand;
    memmove(pv + 1, pv, sizeof(*pv) * (num - cand));
  } else {
    pv += num;
  }
  pv->off = off;
  pv->rsiz = rsiz;
  hdb->fbpnum++;
}


/* Search the free block pool for the minimum region.
   `hdb' specifies the hash database object.
   `rec' specifies the record object to be stored.
   The return value is true if successful, else, it is false. */
static bool tchdbfbpsearch(TCHDB *hdb, TCHREC *rec){
  assert(hdb && rec);
  TCDODEBUG(hdb->cnt_searchfbp++);
  if(hdb->fbpnum < 1){
    rec->off = hdb->fsiz;
    rec->rsiz = 0;
    return true;
  }
  uint32_t rsiz = rec->rsiz;
  HDBFB *pv = hdb->fbpool;
  int num = hdb->fbpnum;
  int left = 0;
  int right = num;
  int i = (left + right) / 2;
  int cand = -1;
  while(right >= left && i < num){
    int rv = (int)rsiz - (int)pv[i].rsiz;
    if(rv == 0){
      cand = i;
      break;
    } else if(rv <= 0){
      cand = i;
      right = i - 1;
    } else {
      left = i + 1;
    }
    i = (left + right) / 2;
  }
  if(cand >= 0){
    pv += cand;
    if(pv->rsiz > rsiz * 2){
      uint32_t psiz = tchdbpadsize(hdb, pv->off + rsiz);
      uint64_t noff = pv->off + rsiz + psiz;
      if(pv->rsiz >= (noff - pv->off) * 2){
        TCDODEBUG(hdb->cnt_dividefbp++);
        rec->off = pv->off;
        rec->rsiz = noff - pv->off;
        pv->off = noff;
        pv->rsiz -= rec->rsiz;
        return tchdbwritefb(hdb, pv->off, pv->rsiz);
      }
    }
    rec->off = pv->off;
    rec->rsiz = pv->rsiz;
    memmove(pv, pv + 1, sizeof(*pv) * (num - cand - 1));
    hdb->fbpnum--;
    return true;
  }
  rec->off = hdb->fsiz;
  rec->rsiz = 0;
  hdb->fbpmis++;
  if(hdb->fbpmis >= HDBFBPMGFREQ){
    tchdbfbpmerge(hdb);
    tcfbpsortbyrsiz(hdb->fbpool, hdb->fbpnum);
  }
  return true;
}


/* Splice the trailing free block
   `hdb' specifies the hash database object.
   `rec' specifies the record object to be stored.
   `nsiz' specifies the needed size.
   The return value is whether splicing succeeded or not. */
static bool tchdbfbpsplice(TCHDB *hdb, TCHREC *rec, uint32_t nsiz){
  assert(hdb && rec && nsiz > 0);
  if(hdb->mmtx){
    if(hdb->fbpnum < 1) return false;
    uint64_t off = rec->off + rec->rsiz;
    uint32_t rsiz = rec->rsiz;
    uint8_t magic;
    if(tchdbseekreadtry(hdb, off, &magic, sizeof(magic)) && magic != HDBMAGICFB) return false;
    HDBFB *pv = hdb->fbpool;
    HDBFB *ep = pv + hdb->fbpnum;
    while(pv < ep){
      if(pv->off == off && rsiz + pv->rsiz >= nsiz){
        if(hdb->dfcur == pv->off) hdb->dfcur += pv->rsiz;
        if(hdb->iter == pv->off) hdb->iter += pv->rsiz;
        rec->rsiz += pv->rsiz;
        memmove(pv, pv + 1, sizeof(*pv) * ((ep - pv) - 1));
        hdb->fbpnum--;
        return true;
      }
      pv++;
    }
    return false;
  }
  uint64_t off = rec->off + rec->rsiz;
  TCHREC nrec;
  char nbuf[HDBIOBUFSIZ];
  while(off < hdb->fsiz){
    nrec.off = off;
    if(!tchdbreadrec(hdb, &nrec, nbuf)) return false;
    if(nrec.magic != HDBMAGICFB) break;
    if(hdb->dfcur == off) hdb->dfcur += nrec.rsiz;
    if(hdb->iter == off) hdb->iter += nrec.rsiz;
    off += nrec.rsiz;
  }
  uint32_t jsiz = off - rec->off;
  if(jsiz < nsiz) return false;
  rec->rsiz = jsiz;
  uint64_t base = rec->off;
  HDBFB *wp = hdb->fbpool;
  HDBFB *cur = wp;
  HDBFB *end = wp + hdb->fbpnum;
  while(cur < end){
    if(cur->off < base || cur->off > off) *(wp++) = *cur;
    cur++;
  }
  hdb->fbpnum = wp - (HDBFB *)hdb->fbpool;
  if(jsiz > nsiz * 2){
    uint32_t psiz = tchdbpadsize(hdb, rec->off + nsiz);
    uint64_t noff = rec->off + nsiz + psiz;
    if(jsiz >= (noff - rec->off) * 2){
      TCDODEBUG(hdb->cnt_dividefbp++);
      nsiz = off - noff;
      if(!tchdbwritefb(hdb, noff, nsiz)) return false;
      rec->rsiz = noff - rec->off;
      tchdbfbpinsert(hdb, noff, nsiz);
    }
  }
  return true;
}


/* Remove blocks of a region from the free block pool.
   `hdb' specifies the hash database object.
   `base' specifies the base offset of the region.
   `next' specifies the offset of the next region.
   `off' specifies the offset of the block.
   `rsiz' specifies the size of the block. */
static void tchdbfbptrim(TCHDB *hdb, uint64_t base, uint64_t next, uint64_t off, uint32_t rsiz){
  assert(hdb && base > 0 && next > 0);
  if(hdb->fpow < 1) return;
  if(hdb->fbpnum < 1){
    if(off > 0){
      HDBFB *fbpool = hdb->fbpool;
      fbpool->off = off;
      fbpool->rsiz = rsiz;
      hdb->fbpnum = 1;
    }
    return;
  }
  HDBFB *wp = hdb->fbpool;
  HDBFB *cur = wp;
  HDBFB *end = wp + hdb->fbpnum;
  if(hdb->fbpnum >= hdb->fbpmax * HDBFBPALWRAT) cur++;
  while(cur < end){
    if(cur->rsiz >= rsiz && off > 0){
      TCDODEBUG(hdb->cnt_insertfbp++);
      wp->off = off;
      wp->rsiz = rsiz;
      wp++;
      off = 0;
    } else if(cur->off < base || cur->off >= next){
      *(wp++) = *cur;
    }
    cur++;
  }
  if(off > 0){
    TCDODEBUG(hdb->cnt_insertfbp++);
    wp->off = off;
    wp->rsiz = rsiz;
    wp++;
    off = 0;
  }
  hdb->fbpnum = wp - (HDBFB *)hdb->fbpool;
}


/* Write a free block into the file.
   `hdb' specifies the hash database object.
   `off' specifies the offset of the block.
   `rsiz' specifies the size of the block.
   The return value is true if successful, else, it is false. */
static bool tchdbwritefb(TCHDB *hdb, uint64_t off, uint32_t rsiz){
  assert(hdb && off > 0 && rsiz > 0);
  char rbuf[HDBMAXHSIZ];
  char *wp = rbuf;
  *(uint8_t *)(wp++) = HDBMAGICFB;
  uint32_t lnum = TCHTOIL(rsiz);
  memcpy(wp, &lnum, sizeof(lnum));
  wp += sizeof(lnum);
  if(!tchdbseekwrite(hdb, off, rbuf, wp - rbuf)) return false;
  return true;
}


/* Write a record into the file.
   `hdb' specifies the hash database object.
   `rec' specifies the record object.
   `bidx' specifies the index of the bucket.
   `entoff' specifies the offset of the tree entry.
   The return value is true if successful, else, it is false. */
static bool tchdbwriterec(TCHDB *hdb, TCHREC *rec, uint64_t bidx, off_t entoff){
  assert(hdb && rec);
  TCDODEBUG(hdb->cnt_writerec++);
  char stack[HDBIOBUFSIZ];
  int bsiz = (rec->rsiz > 0) ? rec->rsiz : HDBMAXHSIZ + rec->ksiz + rec->vsiz + hdb->align;
  char *rbuf;
  if(bsiz <= HDBIOBUFSIZ){
    rbuf = stack;
  } else {
    TCMALLOC(rbuf, bsiz);
  }
  char *wp = rbuf;
  *(uint8_t *)(wp++) = HDBMAGICREC;
  *(uint8_t *)(wp++) = rec->hash;
  if(hdb->ba64){
    uint64_t llnum;
    llnum = rec->left >> hdb->apow;
    llnum = TCHTOILL(llnum);
    memcpy(wp, &llnum, sizeof(llnum));
    wp += sizeof(llnum);
    llnum = rec->right >> hdb->apow;
    llnum = TCHTOILL(llnum);
    memcpy(wp, &llnum, sizeof(llnum));
    wp += sizeof(llnum);
  } else {
    uint32_t lnum;
    lnum = rec->left >> hdb->apow;
    lnum = TCHTOIL(lnum);
    memcpy(wp, &lnum, sizeof(lnum));
    wp += sizeof(lnum);
    lnum = rec->right >> hdb->apow;
    lnum = TCHTOIL(lnum);
    memcpy(wp, &lnum, sizeof(lnum));
    wp += sizeof(lnum);
  }
  uint16_t snum;
  char *pwp = wp;
  wp += sizeof(snum);
  int step;
  TCSETVNUMBUF(step, wp, rec->ksiz);
  wp += step;
  TCSETVNUMBUF(step, wp, rec->vsiz);
  wp += step;
  int32_t hsiz = wp - rbuf;
  int32_t rsiz = hsiz + rec->ksiz + rec->vsiz;
  int32_t finc = 0;
  if(rec->rsiz < 1){
    uint16_t psiz = tchdbpadsize(hdb, hdb->fsiz + rsiz);
    rec->rsiz = rsiz + psiz;
    rec->psiz = psiz;
    finc = rec->rsiz;
  } else if(rsiz > rec->rsiz){
    if(rbuf != stack) TCFREE(rbuf);
    if(!HDBLOCKDB(hdb)) return false;
    if(tchdbfbpsplice(hdb, rec, rsiz)){
      TCDODEBUG(hdb->cnt_splicefbp++);
      bool rv = tchdbwriterec(hdb, rec, bidx, entoff);
      HDBUNLOCKDB(hdb);
      return rv;
    }
    TCDODEBUG(hdb->cnt_moverec++);
    if(!tchdbwritefb(hdb, rec->off, rec->rsiz)){
      HDBUNLOCKDB(hdb);
      return false;
    }
    tchdbfbpinsert(hdb, rec->off, rec->rsiz);
    rec->rsiz = rsiz;
    if(!tchdbfbpsearch(hdb, rec)){
      HDBUNLOCKDB(hdb);
      return false;
    }
    bool rv = tchdbwriterec(hdb, rec, bidx, entoff);
    HDBUNLOCKDB(hdb);
    return rv;
  } else {
    TCDODEBUG(hdb->cnt_reuserec++);
    uint32_t psiz = rec->rsiz - rsiz;
    if(psiz > UINT16_MAX){
      TCDODEBUG(hdb->cnt_dividefbp++);
      psiz = tchdbpadsize(hdb, rec->off + rsiz);
      uint64_t noff = rec->off + rsiz + psiz;
      uint32_t nsiz = rec->rsiz - rsiz - psiz;
      rec->rsiz = noff - rec->off;
      rec->psiz = psiz;
      if(!tchdbwritefb(hdb, noff, nsiz)){
        if(rbuf != stack) TCFREE(rbuf);
        return false;
      }
      if(!HDBLOCKDB(hdb)){
        if(rbuf != stack) TCFREE(rbuf);
        return false;
      }
      tchdbfbpinsert(hdb, noff, nsiz);
      HDBUNLOCKDB(hdb);
    }
    rec->psiz = psiz;
  }
  snum = rec->psiz;
  snum = TCHTOIS(snum);
  memcpy(pwp, &snum, sizeof(snum));
  rsiz = rec->rsiz;
  rsiz -= hsiz;
  memcpy(wp, rec->kbuf, rec->ksiz);
  wp += rec->ksiz;
  rsiz -= rec->ksiz;
  memcpy(wp, rec->vbuf, rec->vsiz);
  wp += rec->vsiz;
  rsiz -= rec->vsiz;
  memset(wp, 0, rsiz);
  if(!tchdbseekwrite(hdb, rec->off, rbuf, rec->rsiz)){
    if(rbuf != stack) TCFREE(rbuf);
    return false;
  }
  if(finc != 0){
    hdb->fsiz += finc;
    uint64_t llnum = hdb->fsiz;
    llnum = TCHTOILL(llnum);
    memcpy(hdb->map + HDBFSIZOFF, &llnum, sizeof(llnum));
  }
  if(rbuf != stack) TCFREE(rbuf);
  if(entoff > 0){
    if(hdb->ba64){
      uint64_t llnum = rec->off >> hdb->apow;
      llnum = TCHTOILL(llnum);
      if(!tchdbseekwrite(hdb, entoff, &llnum, sizeof(uint64_t))) return false;
    } else {
      uint32_t lnum = rec->off >> hdb->apow;
      lnum = TCHTOIL(lnum);
      if(!tchdbseekwrite(hdb, entoff, &lnum, sizeof(uint32_t))) return false;
    }
  } else {
    tchdbsetbucket(hdb, bidx, rec->off);
  }
  return true;
}


/* Read a record from the file.
   `hdb' specifies the hash database object.
   `rec' specifies the record object.
   `rbuf' specifies the buffer for reading.
   The return value is true if successful, else, it is false. */
static bool tchdbreadrec(TCHDB *hdb, TCHREC *rec, char *rbuf){
  assert(hdb && rec && rbuf);
  TCDODEBUG(hdb->cnt_readrec++);
  int rsiz = hdb->runit;
  if(!tchdbseekreadtry(hdb, rec->off, rbuf, rsiz)){
    if(!HDBLOCKDB(hdb)) return false;
    rsiz = hdb->fsiz - rec->off;
    if(rsiz > hdb->runit){
      rsiz = hdb->runit;
    } else if(rsiz < (int)(sizeof(uint8_t) + sizeof(uint32_t))){
      tchdbsetecode(hdb, TCERHEAD, __FILE__, __LINE__, __func__);
      HDBUNLOCKDB(hdb);
      return false;
    }
    if(!tchdbseekread(hdb, rec->off, rbuf, rsiz)){
      HDBUNLOCKDB(hdb);
      return false;
    }
    HDBUNLOCKDB(hdb);
  }
  const char *rp = rbuf;
  rec->magic = *(uint8_t *)(rp++);
  if(rec->magic == HDBMAGICFB){
    uint32_t lnum;
    memcpy(&lnum, rp, sizeof(lnum));
    rec->rsiz = TCITOHL(lnum);
    return true;
  } else if(rec->magic != HDBMAGICREC){
    tchdbsetecode(hdb, TCERHEAD, __FILE__, __LINE__, __func__);
    return false;
  }
  rec->hash = *(uint8_t *)(rp++);
  if(hdb->ba64){
    uint64_t llnum;
    memcpy(&llnum, rp, sizeof(llnum));
    rec->left = TCITOHLL(llnum) << hdb->apow;
    rp += sizeof(llnum);
    memcpy(&llnum, rp, sizeof(llnum));
    rec->right = TCITOHLL(llnum) << hdb->apow;
    rp += sizeof(llnum);
  } else {
    uint32_t lnum;
    memcpy(&lnum, rp, sizeof(lnum));
    rec->left = (uint64_t)TCITOHL(lnum) << hdb->apow;
    rp += sizeof(lnum);
    memcpy(&lnum, rp, sizeof(lnum));
    rec->right = (uint64_t)TCITOHL(lnum) << hdb->apow;
    rp += sizeof(lnum);
  }
  uint16_t snum;
  memcpy(&snum, rp, sizeof(snum));
  rec->psiz = TCITOHS(snum);
  rp += sizeof(snum);
  uint32_t lnum;
  int step;
  TCREADVNUMBUF(rp, lnum, step);
  rec->ksiz = lnum;
  rp += step;
  TCREADVNUMBUF(rp, lnum, step);
  rec->vsiz = lnum;
  rp += step;
  int32_t hsiz = rp - rbuf;
  rec->rsiz = hsiz + rec->ksiz + rec->vsiz + rec->psiz;
  rec->kbuf = NULL;
  rec->vbuf = NULL;
  rec->boff = rec->off + hsiz;
  rec->bbuf = NULL;
  rsiz -= hsiz;
  if(rsiz >= rec->ksiz){
    rec->kbuf = rp;
    rsiz -= rec->ksiz;
    rp += rec->ksiz;
    if(rsiz >= rec->vsiz) rec->vbuf = rp;
  }
  return true;
}


/* Read the body of a record from the file.
   `hdb' specifies the hash database object.
   `rec' specifies the record object.
   The return value is true if successful, else, it is false. */
static bool tchdbreadrecbody(TCHDB *hdb, TCHREC *rec){
  assert(hdb && rec);
  int32_t bsiz = rec->ksiz + rec->vsiz;
  TCMALLOC(rec->bbuf, bsiz + 1);
  if(!tchdbseekread(hdb, rec->boff, rec->bbuf, bsiz)) return false;
  rec->kbuf = rec->bbuf;
  rec->vbuf = rec->bbuf + rec->ksiz;
  return true;
}


/* Remove a record from the file.
   `hdb' specifies the hash database object.
   `rec' specifies the record object.
   `rbuf' specifies the buffer for reading.
   `bidx' specifies the index of the bucket.
   `entoff' specifies the offset of the tree entry.
   The return value is true if successful, else, it is false. */
static bool tchdbremoverec(TCHDB *hdb, TCHREC *rec, char *rbuf, uint64_t bidx, off_t entoff){
  assert(hdb && rec);
  if(!tchdbwritefb(hdb, rec->off, rec->rsiz)) return false;
  if(!HDBLOCKDB(hdb)) return false;
  tchdbfbpinsert(hdb, rec->off, rec->rsiz);
  HDBUNLOCKDB(hdb);
  uint64_t child;
  if(rec->left > 0 && rec->right < 1){
    child = rec->left;
  } else if(rec->left < 1 && rec->right > 0){
    child = rec->right;
  } else if(rec->left < 1){
    child = 0;
  } else {
    child = rec->left;
    uint64_t right = rec->right;
    rec->right = child;
    while(rec->right > 0){
      rec->off = rec->right;
      if(!tchdbreadrec(hdb, rec, rbuf)) return false;
    }
    if(hdb->ba64){
      off_t toff = rec->off + (sizeof(uint8_t) + sizeof(uint8_t) + sizeof(uint64_t));
      uint64_t llnum = right >> hdb->apow;
      llnum = TCHTOILL(llnum);
      if(!tchdbseekwrite(hdb, toff, &llnum, sizeof(uint64_t))) return false;
    } else {
      off_t toff = rec->off + (sizeof(uint8_t) + sizeof(uint8_t) + sizeof(uint32_t));
      uint32_t lnum = right >> hdb->apow;
      lnum = TCHTOIL(lnum);
      if(!tchdbseekwrite(hdb, toff, &lnum, sizeof(uint32_t))) return false;
    }
  }
  if(entoff > 0){
    if(hdb->ba64){
      uint64_t llnum = child >> hdb->apow;
      llnum = TCHTOILL(llnum);
      if(!tchdbseekwrite(hdb, entoff, &llnum, sizeof(uint64_t))) return false;
    } else {
      uint32_t lnum = child >> hdb->apow;
      lnum = TCHTOIL(lnum);
      if(!tchdbseekwrite(hdb, entoff, &lnum, sizeof(uint32_t))) return false;
    }
  } else {
    tchdbsetbucket(hdb, bidx, child);
  }
  if(!HDBLOCKDB(hdb)) return false;
  hdb->rnum--;
  uint64_t llnum = hdb->rnum;
  llnum = TCHTOILL(llnum);
  memcpy(hdb->map + HDBRNUMOFF, &llnum, sizeof(llnum));
  HDBUNLOCKDB(hdb);
  return true;
}


/* Remove a record from the file.
   `hdb' specifies the hash database object.
   `rec' specifies the record object.
   `rbuf' specifies the buffer for reading.
   `destoff' specifies the offset of the destination.
   The return value is true if successful, else, it is false. */
static bool tchdbshiftrec(TCHDB *hdb, TCHREC *rec, char *rbuf, off_t destoff){
  assert(hdb && rec && rbuf && destoff >= 0);
  TCDODEBUG(hdb->cnt_shiftrec++);
  if(!rec->vbuf && !tchdbreadrecbody(hdb, rec)) return false;
  uint8_t hash;
  uint64_t bidx = tchdbbidx(hdb, rec->kbuf, rec->ksiz, &hash);
  off_t off = tchdbgetbucket(hdb, bidx);
  if(rec->off == off){
    bool err = false;
    rec->off = destoff;
    if(!tchdbwriterec(hdb, rec, bidx, 0)) err = true;
    TCFREE(rec->bbuf);
    rec->kbuf = NULL;
    rec->vbuf = NULL;
    rec->bbuf = NULL;
    return !err;
  }
  TCHREC trec;
  char tbuf[HDBIOBUFSIZ];
  char *bbuf = rec->bbuf;
  const char *kbuf = rec->kbuf;
  int ksiz = rec->ksiz;
  const char *vbuf = rec->vbuf;
  int vsiz = rec->vsiz;
  rec->kbuf = NULL;
  rec->vbuf = NULL;
  rec->bbuf = NULL;
  off_t entoff = 0;
  while(off > 0){
    trec.off = off;
    if(!tchdbreadrec(hdb, &trec, tbuf)){
      TCFREE(bbuf);
      return false;
    }
    if(hash > trec.hash){
      off = trec.left;
      entoff = trec.off + (sizeof(uint8_t) + sizeof(uint8_t));
    } else if(hash < trec.hash){
      off = trec.right;
      entoff = trec.off + (sizeof(uint8_t) + sizeof(uint8_t)) +
        (hdb->ba64 ? sizeof(uint64_t) : sizeof(uint32_t));
    } else {
      if(!trec.kbuf && !tchdbreadrecbody(hdb, &trec)){
        TCFREE(bbuf);
        return false;
      }
      int kcmp = tcreckeycmp(kbuf, ksiz, trec.kbuf, trec.ksiz);
      if(kcmp > 0){
        off = trec.left;
        TCFREE(trec.bbuf);
        trec.kbuf = NULL;
        trec.bbuf = NULL;
        entoff = trec.off + (sizeof(uint8_t) + sizeof(uint8_t));
      } else if(kcmp < 0){
        off = trec.right;
        TCFREE(trec.bbuf);
        trec.kbuf = NULL;
        trec.bbuf = NULL;
        entoff = trec.off + (sizeof(uint8_t) + sizeof(uint8_t)) +
          (hdb->ba64 ? sizeof(uint64_t) : sizeof(uint32_t));
      } else {
        TCFREE(trec.bbuf);
        trec.bbuf = NULL;
        bool err = false;
        rec->off = destoff;
        rec->kbuf = kbuf;
        rec->ksiz = ksiz;
        rec->vbuf = vbuf;
        rec->vsiz = vsiz;
        if(!tchdbwriterec(hdb, rec, bidx, entoff)) err = true;
        TCFREE(bbuf);
        return !err;
      }
    }
  }
  TCFREE(bbuf);
  tchdbsetecode(hdb, TCENOREC, __FILE__, __LINE__, __func__);
  return false;
}


/* Compare keys of two records.
   `abuf' specifies the pointer to the region of the former.
   `asiz' specifies the size of the region.
   `bbuf' specifies the pointer to the region of the latter.
   `bsiz' specifies the size of the region.
   The return value is 0 if two equals, positive if the formar is big, else, negative. */
static int tcreckeycmp(const char *abuf, int asiz, const char *bbuf, int bsiz){
  assert(abuf && asiz >= 0 && bbuf && bsiz >= 0);
  if(asiz > bsiz) return 1;
  if(asiz < bsiz) return -1;
  return memcmp(abuf, bbuf, asiz);
}


/* Flush the delayed record pool.
   `hdb' specifies the hash database object.
   The return value is true if successful, else, it is false. */
static bool tchdbflushdrp(TCHDB *hdb){
  assert(hdb);
  if(!HDBLOCKDB(hdb)) return false;
  if(!hdb->drpool){
    HDBUNLOCKDB(hdb);
    return true;
  }
  TCDODEBUG(hdb->cnt_flushdrp++);
  if(!tchdbseekwrite(hdb, hdb->drpoff, TCXSTRPTR(hdb->drpool), TCXSTRSIZE(hdb->drpool))){
    HDBUNLOCKDB(hdb);
    return false;
  }
  const char *rp = TCXSTRPTR(hdb->drpdef);
  int size = TCXSTRSIZE(hdb->drpdef);
  while(size > 0){
    int ksiz, vsiz;
    memcpy(&ksiz, rp, sizeof(int));
    rp += sizeof(int);
    memcpy(&vsiz, rp, sizeof(int));
    rp += sizeof(int);
    const char *kbuf = rp;
    rp += ksiz;
    const char *vbuf = rp;
    rp += vsiz;
    uint8_t hash;
    uint64_t bidx = tchdbbidx(hdb, kbuf, ksiz, &hash);
    if(!tchdbputimpl(hdb, kbuf, ksiz, bidx, hash, vbuf, vsiz, HDBPDOVER)){
      tcxstrdel(hdb->drpdef);
      tcxstrdel(hdb->drpool);
      hdb->drpool = NULL;
      hdb->drpdef = NULL;
      hdb->drpoff = 0;
      HDBUNLOCKDB(hdb);
      return false;
    }
    size -= sizeof(int) * 2 + ksiz + vsiz;
  }
  tcxstrdel(hdb->drpdef);
  tcxstrdel(hdb->drpool);
  hdb->drpool = NULL;
  hdb->drpdef = NULL;
  hdb->drpoff = 0;
  uint64_t llnum;
  llnum = hdb->rnum;
  llnum = TCHTOILL(llnum);
  memcpy(hdb->map + HDBRNUMOFF, &llnum, sizeof(llnum));
  llnum = hdb->fsiz;
  llnum = TCHTOILL(llnum);
  memcpy(hdb->map + HDBFSIZOFF, &llnum, sizeof(llnum));
  HDBUNLOCKDB(hdb);
  return true;
}


/* Adjust the caches for leaves and nodes.
   `hdb' specifies the hash tree database object. */
static void tchdbcacheadjust(TCHDB *hdb){
  assert(hdb);
  TCDODEBUG(hdb->cnt_adjrecc++);
  tcmdbcutfront(hdb->recc, HDBCACHEOUT);
}


/* Initialize the write ahead logging file.
   `hdb' specifies the hash database object.
   If successful, the return value is true, else, it is false. */
static bool tchdbwalinit(TCHDB *hdb){
  assert(hdb);
  if(lseek(hdb->walfd, 0, SEEK_SET) == -1){
    tchdbsetecode(hdb, TCESEEK, __FILE__, __LINE__, __func__);
    return false;
  }
  if(ftruncate(hdb->walfd, 0) == -1){
    tchdbsetecode(hdb, TCETRUNC, __FILE__, __LINE__, __func__);
    return false;
  }
  uint64_t llnum = hdb->fsiz;
  llnum = TCHTOILL(llnum);
  if(!tcwrite(hdb->walfd, &llnum, sizeof(llnum))){
    tchdbsetecode(hdb, TCEWRITE, __FILE__, __LINE__, __func__);
    return false;
  }
  hdb->walend = hdb->fsiz;
  if(!tchdbwalwrite(hdb, 0, HDBHEADSIZ)) return false;
  return true;
}


/* Write an event into the write ahead logging file.
   `hdb' specifies the hash database object.
   `off' specifies the offset of the region to be updated.
   `size' specifies the size of the region.
   If successful, the return value is true, else, it is false. */
static bool tchdbwalwrite(TCHDB *hdb, uint64_t off, int64_t size){
  assert(hdb && off >= 0 && size >= 0);
  if(off + size > hdb->walend) size = hdb->walend - off;
  if(size < 1) return true;
  char stack[HDBIOBUFSIZ];
  char *buf;
  if(size + sizeof(off) + sizeof(size) <= HDBIOBUFSIZ){
    buf = stack;
  } else {
    TCMALLOC(buf, size + sizeof(off) + sizeof(size));
  }
  char *wp = buf;
  uint64_t llnum = TCHTOILL(off);
  memcpy(wp, &llnum, sizeof(llnum));
  wp += sizeof(llnum);
  uint32_t lnum = TCHTOIL(size);
  memcpy(wp, &lnum, sizeof(lnum));
  wp += sizeof(lnum);
  if(!tchdbseekread(hdb, off, wp, size)){
    if(buf != stack) TCFREE(buf);
    return false;
  }
  wp += size;
  if(!HDBLOCKWAL(hdb)) return false;
  if(!tcwrite(hdb->walfd, buf, wp - buf)){
    tchdbsetecode(hdb, TCEWRITE, __FILE__, __LINE__, __func__);
    if(buf != stack) TCFREE(buf);
    HDBUNLOCKWAL(hdb);
    return false;
  }
  if(buf != stack) TCFREE(buf);
  if((hdb->omode & HDBOTSYNC) && fsync(hdb->walfd) == -1){
    tchdbsetecode(hdb, TCESYNC, __FILE__, __LINE__, __func__);
    HDBUNLOCKWAL(hdb);
    return false;
  }
  HDBUNLOCKWAL(hdb);
  return true;
}


/* Restore the database from the write ahead logging file.
   `hdb' specifies the hash database object.
   `path' specifies the path of the database file.
   If successful, the return value is true, else, it is false. */
static int tchdbwalrestore(TCHDB *hdb, const char *path){
  assert(hdb && path);
  char *tpath = tcsprintf("%s%c%s", path, MYEXTCHR, HDBWALSUFFIX);
  int walfd = open(tpath, O_RDONLY, HDBFILEMODE);
  TCFREE(tpath);
  if(walfd < 0) return false;
  bool err = false;
  uint64_t walsiz = 0;
  struct stat sbuf;
  if(fstat(walfd, &sbuf) == 0){
    walsiz = sbuf.st_size;
  } else {
    tchdbsetecode(hdb, TCESTAT, __FILE__, __LINE__, __func__);
    err = true;
  }
  if(walsiz >= sizeof(walsiz) + HDBHEADSIZ){
    int dbfd = hdb->fd;
    int tfd = -1;
    if(!(hdb->omode & HDBOWRITER)){
      tfd = open(path, O_WRONLY, HDBFILEMODE);
      if(tfd >= 0){
        dbfd = tfd;
      } else {
        int ecode = TCEOPEN;
        switch(errno){
          case EACCES: ecode = TCENOPERM; break;
          case ENOENT: ecode = TCENOFILE; break;
          case ENOTDIR: ecode = TCENOFILE; break;
        }
        tchdbsetecode(hdb, ecode, __FILE__, __LINE__, __func__);
        err = true;
      }
    }
    uint64_t fsiz = 0;
    if(tcread(walfd, &fsiz, sizeof(fsiz))){
      fsiz = TCITOHLL(fsiz);
    } else {
      tchdbsetecode(hdb, TCEREAD, __FILE__, __LINE__, __func__);
      err = true;
    }
    TCLIST *list = tclistnew();
    uint64_t waloff = sizeof(fsiz);
    char stack[HDBIOBUFSIZ];
    while(waloff < walsiz){
      uint64_t off;
      uint32_t size;
      if(!tcread(walfd, stack, sizeof(off) + sizeof(size))){
        tchdbsetecode(hdb, TCEREAD, __FILE__, __LINE__, __func__);
        err = true;
        break;
      }
      memcpy(&off, stack, sizeof(off));
      off = TCITOHLL(off);
      memcpy(&size, stack + sizeof(off), sizeof(size));
      size = TCITOHL(size);
      char *buf;
      if(sizeof(off) + size <= HDBIOBUFSIZ){
        buf = stack;
      } else {
        TCMALLOC(buf, sizeof(off) + size);
      }
      *(uint64_t *)buf = off;
      if(!tcread(walfd, buf + sizeof(off), size)){
        tchdbsetecode(hdb, TCEREAD, __FILE__, __LINE__, __func__);
        err = true;
        if(buf != stack) TCFREE(buf);
        break;
      }
      TCLISTPUSH(list, buf, sizeof(off) + size);
      if(buf != stack) TCFREE(buf);
      waloff += sizeof(off) + sizeof(size) + size;
    }
    size_t xmsiz = 0;
    if(hdb->fd >= 0 && hdb->map) xmsiz = (hdb->xmsiz > hdb->msiz) ? hdb->xmsiz : hdb->msiz;
    for(int i = TCLISTNUM(list) - 1; i >= 0; i--){
      const char *rec;
      int size;
      TCLISTVAL(rec, list, i, size);
      uint64_t off = *(uint64_t *)rec;
      rec += sizeof(off);
      size -= sizeof(off);
      if(lseek(dbfd, off, SEEK_SET) == -1){
        tchdbsetecode(hdb, TCESEEK, __FILE__, __LINE__, __func__);
        err = true;
        break;
      }
      if(!tcwrite(dbfd, rec, size)){
        tchdbsetecode(hdb, TCEWRITE, __FILE__, __LINE__, __func__);
        err = true;
        break;
      }
      if(!TCUBCACHE && off < xmsiz){
        size = (size <= xmsiz - off) ? size : xmsiz - off;
        memcpy(hdb->map + off, rec, size);
      }
    }
    tclistdel(list);
    if(ftruncate(dbfd, fsiz) == -1){
      tchdbsetecode(hdb, TCETRUNC, __FILE__, __LINE__, __func__);
      err = true;
    }
    if((hdb->omode & HDBOTSYNC) && fsync(dbfd) == -1){
      tchdbsetecode(hdb, TCESYNC, __FILE__, __LINE__, __func__);
      err = true;
    }
    if(tfd >= 0 && close(tfd) == -1){
      tchdbsetecode(hdb, TCECLOSE, __FILE__, __LINE__, __func__);
      err = true;
    }
  } else {
    err = true;
  }
  if(close(walfd) == -1){
    tchdbsetecode(hdb, TCECLOSE, __FILE__, __LINE__, __func__);
    err = true;
  }
  return !err;
}


/* Remove the write ahead logging file.
   `hdb' specifies the hash database object.
   `path' specifies the path of the database file.
   If successful, the return value is true, else, it is false. */
static bool tchdbwalremove(TCHDB *hdb, const char *path){
  assert(hdb && path);
  char *tpath = tcsprintf("%s%c%s", path, MYEXTCHR, HDBWALSUFFIX);
  bool err = false;
  if(unlink(tpath) == -1 && errno != ENOENT){
    tchdbsetecode(hdb, TCEUNLINK, __FILE__, __LINE__, __func__);
    err = true;
  }
  TCFREE(tpath);
  return !err;
}


/* Open a database file and connect a hash database object.
   `hdb' specifies the hash database object.
   `path' specifies the path of the database file.
   `omode' specifies the connection mode.
   If successful, the return value is true, else, it is false. */
static bool tchdbopenimpl(TCHDB *hdb, const char *path, int omode){
  assert(hdb && path);
  int mode = O_RDONLY;
  if(omode & HDBOWRITER){
    mode = O_RDWR;
    if(omode & HDBOCREAT) mode |= O_CREAT;
  }
  int fd = open(path, mode, HDBFILEMODE);
  if(fd < 0){
    int ecode = TCEOPEN;
    switch(errno){
      case EACCES: ecode = TCENOPERM; break;
      case ENOENT: ecode = TCENOFILE; break;
      case ENOTDIR: ecode = TCENOFILE; break;
    }
    tchdbsetecode(hdb, ecode, __FILE__, __LINE__, __func__);
    return false;
  }
  if(!(omode & HDBONOLCK)){
    if(!tclock(fd, omode & HDBOWRITER, omode & HDBOLCKNB)){
      tchdbsetecode(hdb, TCELOCK, __FILE__, __LINE__, __func__);
      close(fd);
      return false;
    }
  }
  if((omode & HDBOWRITER) && (omode & HDBOTRUNC)){
    if(ftruncate(fd, 0) == -1){
      tchdbsetecode(hdb, TCETRUNC, __FILE__, __LINE__, __func__);
      close(fd);
      return false;
    }
    if(!tchdbwalremove(hdb, path)){
      close(fd);
      return false;
    }
  }
  struct stat sbuf;
  if(fstat(fd, &sbuf) == -1 || !S_ISREG(sbuf.st_mode)){
    tchdbsetecode(hdb, TCESTAT, __FILE__, __LINE__, __func__);
    close(fd);
    return false;
  }
  char hbuf[HDBHEADSIZ];
  if((omode & HDBOWRITER) && sbuf.st_size < 1){
    hdb->flags = 0;
    hdb->rnum = 0;
    uint32_t fbpmax = 1 << hdb->fpow;
    uint32_t fbpsiz = HDBFBPBSIZ + fbpmax * HDBFBPESIZ;
    int besiz = (hdb->opts & HDBTLARGE) ? sizeof(int64_t) : sizeof(int32_t);
    hdb->align = 1 << hdb->apow;
    hdb->fsiz = HDBHEADSIZ + besiz * hdb->bnum + fbpsiz;
    hdb->fsiz += tchdbpadsize(hdb, hdb->fsiz);
    hdb->frec = hdb->fsiz;
    tchdbdumpmeta(hdb, hbuf);
    bool err = false;
    if(!tcwrite(fd, hbuf, HDBHEADSIZ)) err = true;
    char pbuf[HDBIOBUFSIZ];
    memset(pbuf, 0, HDBIOBUFSIZ);
    uint64_t psiz = hdb->fsiz - HDBHEADSIZ;
    while(psiz > 0){
      if(psiz > HDBIOBUFSIZ){
        if(!tcwrite(fd, pbuf, HDBIOBUFSIZ)) err = true;
        psiz -= HDBIOBUFSIZ;
      } else {
        if(!tcwrite(fd, pbuf, psiz)) err = true;
        psiz = 0;
      }
    }
    if(err){
      tchdbsetecode(hdb, TCEWRITE, __FILE__, __LINE__, __func__);
      close(fd);
      return false;
    }
    sbuf.st_size = hdb->fsiz;
  }
  if(lseek(fd, 0, SEEK_SET) == -1){
    tchdbsetecode(hdb, TCESEEK, __FILE__, __LINE__, __func__);
    close(fd);
    return false;
  }
  if(!tcread(fd, hbuf, HDBHEADSIZ)){
    tchdbsetecode(hdb, TCEREAD, __FILE__, __LINE__, __func__);
    close(fd);
    return false;
  }
  int type = hdb->type;
  tchdbloadmeta(hdb, hbuf);
  if((hdb->flags & HDBFOPEN) && tchdbwalrestore(hdb, path)){
    if(lseek(fd, 0, SEEK_SET) == -1){
      tchdbsetecode(hdb, TCESEEK, __FILE__, __LINE__, __func__);
      close(fd);
      return false;
    }
    if(!tcread(fd, hbuf, HDBHEADSIZ)){
      tchdbsetecode(hdb, TCEREAD, __FILE__, __LINE__, __func__);
      close(fd);
      return false;
    }
    tchdbloadmeta(hdb, hbuf);
    if(!tchdbwalremove(hdb, path)){
      close(fd);
      return false;
    }
  }
  int besiz = (hdb->opts & HDBTLARGE) ? sizeof(int64_t) : sizeof(int32_t);
  size_t msiz = HDBHEADSIZ + hdb->bnum * besiz;
  if(!(omode & HDBONOLCK)){
    if(memcmp(hbuf, HDBMAGICDATA, strlen(HDBMAGICDATA)) || hdb->type != type ||
       hdb->frec < msiz + HDBFBPBSIZ || hdb->frec > hdb->fsiz || sbuf.st_size < hdb->fsiz){
      tchdbsetecode(hdb, TCEMETA, __FILE__, __LINE__, __func__);
      close(fd);
      return false;
    }
  }
  if(((hdb->opts & HDBTDEFLATE) && !_tc_deflate) ||
     ((hdb->opts & HDBTBZIP) && !_tc_bzcompress) || ((hdb->opts & HDBTEXCODEC) && !hdb->enc)){
    tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
    close(fd);
    return false;
  }
  size_t xmsiz = (hdb->xmsiz > msiz) ? hdb->xmsiz : msiz;
  if(!(omode & HDBOWRITER) && xmsiz > hdb->fsiz) xmsiz = hdb->fsiz;
  void *map = mmap(0, xmsiz, PROT_READ | ((omode & HDBOWRITER) ? PROT_WRITE : 0),
                   MAP_SHARED, fd, 0);
  if(map == MAP_FAILED){
    tchdbsetecode(hdb, TCEMMAP, __FILE__, __LINE__, __func__);
    close(fd);
    return false;
  }
  hdb->fbpmax = 1 << hdb->fpow;
  if(omode & HDBOWRITER){
    TCMALLOC(hdb->fbpool, hdb->fbpmax * HDBFBPALWRAT * sizeof(HDBFB));
  } else {
    hdb->fbpool = NULL;
  }
  hdb->fbpnum = 0;
  hdb->fbpmis = 0;
  hdb->async = false;
  hdb->drpool = NULL;
  hdb->drpdef = NULL;
  hdb->drpoff = 0;
  hdb->recc = (hdb->rcnum > 0) ? tcmdbnew2(hdb->rcnum * 2 + 1) : NULL;
  hdb->path = tcstrdup(path);
  hdb->fd = fd;
  hdb->omode = omode;
  hdb->dfcur = hdb->frec;
  hdb->iter = 0;
  hdb->map = map;
  hdb->msiz = msiz;
  hdb->xfsiz = 0;
  if(hdb->opts & HDBTLARGE){
    hdb->ba32 = NULL;
    hdb->ba64 = (uint64_t *)((char *)map + HDBHEADSIZ);
  } else {
    hdb->ba32 = (uint32_t *)((char *)map + HDBHEADSIZ);
    hdb->ba64 = NULL;
  }
  hdb->align = 1 << hdb->apow;
  hdb->runit = tclmin(tclmax(hdb->align, HDBMINRUNIT), HDBIOBUFSIZ);
  hdb->zmode = (hdb->opts & HDBTDEFLATE) || (hdb->opts & HDBTBZIP) ||
    (hdb->opts & HDBTTCBS) || (hdb->opts & HDBTEXCODEC);
  hdb->ecode = TCESUCCESS;
  hdb->fatal = false;
  hdb->inode = (uint64_t)sbuf.st_ino;
  hdb->mtime = sbuf.st_mtime;
  hdb->dfcnt = 0;
  hdb->tran = false;
  hdb->walfd = -1;
  hdb->walend = 0;
  if(hdb->omode & HDBOWRITER){
    bool err = false;
    if(!(hdb->flags & HDBFOPEN) && !tchdbloadfbp(hdb)) err = true;
    memset(hbuf, 0, 2);
    if(!tchdbseekwrite(hdb, hdb->msiz, hbuf, 2)) err = true;
    if(err){
      TCFREE(hdb->path);
      TCFREE(hdb->fbpool);
      munmap(hdb->map, xmsiz);
      close(fd);
      hdb->fd = -1;
      return false;
    }
    tchdbsetflag(hdb, HDBFOPEN, true);
  }
  return true;
}


/* Close a hash database object.
   `hdb' specifies the hash database object.
   If successful, the return value is true, else, it is false. */
static bool tchdbcloseimpl(TCHDB *hdb){
  assert(hdb);
  bool err = false;
  if(hdb->recc){
    tcmdbdel(hdb->recc);
    hdb->recc = NULL;
  }
  if(hdb->omode & HDBOWRITER){
    if(!tchdbflushdrp(hdb)) err = true;
    if(hdb->tran) hdb->fbpnum = 0;
    if(!tchdbsavefbp(hdb)) err = true;
    TCFREE(hdb->fbpool);
    tchdbsetflag(hdb, HDBFOPEN, false);
  }
  if((hdb->omode & HDBOWRITER) && !tchdbmemsync(hdb, false)) err = true;
  size_t xmsiz = (hdb->xmsiz > hdb->msiz) ? hdb->xmsiz : hdb->msiz;
  if(!(hdb->omode & HDBOWRITER) && xmsiz > hdb->fsiz) xmsiz = hdb->fsiz;
  if(munmap(hdb->map, xmsiz) == -1){
    tchdbsetecode(hdb, TCEMMAP, __FILE__, __LINE__, __func__);
    err = true;
  }
  hdb->map = NULL;
  if((hdb->omode & HDBOWRITER) && ftruncate(hdb->fd, hdb->fsiz) == -1){
    tchdbsetecode(hdb, TCETRUNC, __FILE__, __LINE__, __func__);
    err = true;
  }
  if(hdb->tran){
    if(!tchdbwalrestore(hdb, hdb->path)) err = true;
    hdb->tran = false;
  }
  if(hdb->walfd >= 0){
    if(close(hdb->walfd) == -1){
      tchdbsetecode(hdb, TCECLOSE, __FILE__, __LINE__, __func__);
      err = true;
    }
    if(!hdb->fatal && !tchdbwalremove(hdb, hdb->path)) err = true;
  }
  if(close(hdb->fd) == -1){
    tchdbsetecode(hdb, TCECLOSE, __FILE__, __LINE__, __func__);
    err = true;
  }
  TCFREE(hdb->path);
  hdb->path = NULL;
  hdb->fd = -1;
  return !err;
}


/* Store a record.
   `hdb' specifies the hash database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   `bidx' specifies the index of the bucket array.
   `hash' specifies the hash value for the collision tree.
   `vbuf' specifies the pointer to the region of the value.
   `vsiz' specifies the size of the region of the value.
   `dmode' specifies behavior when the key overlaps.
   If successful, the return value is true, else, it is false. */
static bool tchdbputimpl(TCHDB *hdb, const char *kbuf, int ksiz, uint64_t bidx, uint8_t hash,
                         const char *vbuf, int vsiz, int dmode){
  assert(hdb && kbuf && ksiz >= 0);
  if(hdb->recc) tcmdbout(hdb->recc, kbuf, ksiz);
  off_t off = tchdbgetbucket(hdb, bidx);
  off_t entoff = 0;
  TCHREC rec;
  char rbuf[HDBIOBUFSIZ];
  while(off > 0){
    rec.off = off;
    if(!tchdbreadrec(hdb, &rec, rbuf)) return false;
    if(hash > rec.hash){
      off = rec.left;
      entoff = rec.off + (sizeof(uint8_t) + sizeof(uint8_t));
    } else if(hash < rec.hash){
      off = rec.right;
      entoff = rec.off + (sizeof(uint8_t) + sizeof(uint8_t)) +
        (hdb->ba64 ? sizeof(uint64_t) : sizeof(uint32_t));
    } else {
      if(!rec.kbuf && !tchdbreadrecbody(hdb, &rec)) return false;
      int kcmp = tcreckeycmp(kbuf, ksiz, rec.kbuf, rec.ksiz);
      if(kcmp > 0){
        off = rec.left;
        TCFREE(rec.bbuf);
        rec.kbuf = NULL;
        rec.bbuf = NULL;
        entoff = rec.off + (sizeof(uint8_t) + sizeof(uint8_t));
      } else if(kcmp < 0){
        off = rec.right;
        TCFREE(rec.bbuf);
        rec.kbuf = NULL;
        rec.bbuf = NULL;
        entoff = rec.off + (sizeof(uint8_t) + sizeof(uint8_t)) +
          (hdb->ba64 ? sizeof(uint64_t) : sizeof(uint32_t));
      } else {
        bool rv;
        int nvsiz;
        char *nvbuf;
        HDBPDPROCOP *procptr;
        switch(dmode){
          case HDBPDKEEP:
            tchdbsetecode(hdb, TCEKEEP, __FILE__, __LINE__, __func__);
            TCFREE(rec.bbuf);
            return false;
          case HDBPDCAT:
            if(vsiz < 1){
              TCFREE(rec.bbuf);
              return true;
            }
            if(!rec.vbuf && !tchdbreadrecbody(hdb, &rec)){
              TCFREE(rec.bbuf);
              return false;
            }
            nvsiz = rec.vsiz + vsiz;
            if(rec.bbuf){
              TCREALLOC(rec.bbuf, rec.bbuf, rec.ksiz + nvsiz);
              memcpy(rec.bbuf + rec.ksiz + rec.vsiz, vbuf, vsiz);
              rec.kbuf = rec.bbuf;
              rec.vbuf = rec.kbuf + rec.ksiz;
              rec.vsiz = nvsiz;
            } else {
              TCMALLOC(rec.bbuf, nvsiz + 1);
              memcpy(rec.bbuf, rec.vbuf, rec.vsiz);
              memcpy(rec.bbuf + rec.vsiz, vbuf, vsiz);
              rec.vbuf = rec.bbuf;
              rec.vsiz = nvsiz;
            }
            rv = tchdbwriterec(hdb, &rec, bidx, entoff);
            TCFREE(rec.bbuf);
            return rv;
          case HDBPDADDINT:
            if(rec.vsiz != sizeof(int)){
              tchdbsetecode(hdb, TCEKEEP, __FILE__, __LINE__, __func__);
              TCFREE(rec.bbuf);
              return false;
            }
            if(!rec.vbuf && !tchdbreadrecbody(hdb, &rec)){
              TCFREE(rec.bbuf);
              return false;
            }
            int lnum;
            memcpy(&lnum, rec.vbuf, sizeof(lnum));
            if(*(int *)vbuf == 0){
              TCFREE(rec.bbuf);
              *(int *)vbuf = lnum;
              return true;
            }
            lnum += *(int *)vbuf;
            rec.vbuf = (char *)&lnum;
            *(int *)vbuf = lnum;
            rv = tchdbwriterec(hdb, &rec, bidx, entoff);
            TCFREE(rec.bbuf);
            return rv;
          case HDBPDADDDBL:
            if(rec.vsiz != sizeof(double)){
              tchdbsetecode(hdb, TCEKEEP, __FILE__, __LINE__, __func__);
              TCFREE(rec.bbuf);
              return false;
            }
            if(!rec.vbuf && !tchdbreadrecbody(hdb, &rec)){
              TCFREE(rec.bbuf);
              return false;
            }
            double dnum;
            memcpy(&dnum, rec.vbuf, sizeof(dnum));
            if(*(double *)vbuf == 0.0){
              TCFREE(rec.bbuf);
              *(double *)vbuf = dnum;
              return true;
            }
            dnum += *(double *)vbuf;
            rec.vbuf = (char *)&dnum;
            *(double *)vbuf = dnum;
            rv = tchdbwriterec(hdb, &rec, bidx, entoff);
            TCFREE(rec.bbuf);
            return rv;
          case HDBPDPROC:
            if(!rec.vbuf && !tchdbreadrecbody(hdb, &rec)){
              TCFREE(rec.bbuf);
              return false;
            }
            procptr = *(HDBPDPROCOP **)((char *)kbuf - sizeof(procptr));
            nvbuf = procptr->proc(rec.vbuf, rec.vsiz, &nvsiz, procptr->op);
            TCFREE(rec.bbuf);
            if(nvbuf == (void *)-1){
              return tchdbremoverec(hdb, &rec, rbuf, bidx, entoff);
            } else if(nvbuf){
              rec.kbuf = kbuf;
              rec.ksiz = ksiz;
              rec.vbuf = nvbuf;
              rec.vsiz = nvsiz;
              rv = tchdbwriterec(hdb, &rec, bidx, entoff);
              TCFREE(nvbuf);
              return rv;
            }
            tchdbsetecode(hdb, TCEKEEP, __FILE__, __LINE__, __func__);
            return false;
          default:
            break;
        }
        TCFREE(rec.bbuf);
        rec.ksiz = ksiz;
        rec.vsiz = vsiz;
        rec.kbuf = kbuf;
        rec.vbuf = vbuf;
        return tchdbwriterec(hdb, &rec, bidx, entoff);
      }
    }
  }
  if(!vbuf){
    tchdbsetecode(hdb, TCENOREC, __FILE__, __LINE__, __func__);
    return false;
  }
  if(!HDBLOCKDB(hdb)) return false;
  rec.rsiz = hdb->ba64 ? sizeof(uint8_t) * 2 + sizeof(uint64_t) * 2 + sizeof(uint16_t) :
    sizeof(uint8_t) * 2 + sizeof(uint32_t) * 2 + sizeof(uint16_t);
  if(ksiz < (1U << 7)){
    rec.rsiz += 1;
  } else if(ksiz < (1U << 14)){
    rec.rsiz += 2;
  } else if(ksiz < (1U << 21)){
    rec.rsiz += 3;
  } else if(ksiz < (1U << 28)){
    rec.rsiz += 4;
  } else {
    rec.rsiz += 5;
  }
  if(vsiz < (1U << 7)){
    rec.rsiz += 1;
  } else if(vsiz < (1U << 14)){
    rec.rsiz += 2;
  } else if(vsiz < (1U << 21)){
    rec.rsiz += 3;
  } else if(vsiz < (1U << 28)){
    rec.rsiz += 4;
  } else {
    rec.rsiz += 5;
  }
  if(!tchdbfbpsearch(hdb, &rec)){
    HDBUNLOCKDB(hdb);
    return false;
  }
  rec.hash = hash;
  rec.left = 0;
  rec.right = 0;
  rec.ksiz = ksiz;
  rec.vsiz = vsiz;
  rec.psiz = 0;
  rec.kbuf = kbuf;
  rec.vbuf = vbuf;
  if(!tchdbwriterec(hdb, &rec, bidx, entoff)){
    HDBUNLOCKDB(hdb);
    return false;
  }
  hdb->rnum++;
  uint64_t llnum = hdb->rnum;
  llnum = TCHTOILL(llnum);
  memcpy(hdb->map + HDBRNUMOFF, &llnum, sizeof(llnum));
  HDBUNLOCKDB(hdb);
  return true;
}


/* Append a record to the delayed record pool.
   `hdb' specifies the hash database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   `vbuf' specifies the pointer to the region of the value.
   `vsiz' specifies the size of the region of the value.
   `hash' specifies the second hash value. */
static void tchdbdrpappend(TCHDB *hdb, const char *kbuf, int ksiz, const char *vbuf, int vsiz,
                           uint8_t hash){
  assert(hdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  TCDODEBUG(hdb->cnt_appenddrp++);
  char rbuf[HDBIOBUFSIZ];
  char *wp = rbuf;
  *(uint8_t *)(wp++) = HDBMAGICREC;
  *(uint8_t *)(wp++) = hash;
  if(hdb->ba64){
    memset(wp, 0, sizeof(uint64_t) * 2);
    wp += sizeof(uint64_t) * 2;
  } else {
    memset(wp, 0, sizeof(uint32_t) * 2);
    wp += sizeof(uint32_t) * 2;
  }
  uint16_t snum;
  char *pwp = wp;
  wp += sizeof(snum);
  int step;
  TCSETVNUMBUF(step, wp, ksiz);
  wp += step;
  TCSETVNUMBUF(step, wp, vsiz);
  wp += step;
  int32_t hsiz = wp - rbuf;
  int32_t rsiz = hsiz + ksiz + vsiz;
  uint16_t psiz = tchdbpadsize(hdb, hdb->fsiz + rsiz);
  hdb->fsiz += rsiz + psiz;
  snum = TCHTOIS(psiz);
  memcpy(pwp, &snum, sizeof(snum));
  TCXSTR *drpool = hdb->drpool;
  TCXSTRCAT(drpool, rbuf, hsiz);
  TCXSTRCAT(drpool, kbuf, ksiz);
  TCXSTRCAT(drpool, vbuf, vsiz);
  if(psiz > 0){
    char pbuf[psiz];
    memset(pbuf, 0, psiz);
    TCXSTRCAT(drpool, pbuf, psiz);
  }
}


/* Store a record in asynchronus fashion.
   `hdb' specifies the hash database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   `bidx' specifies the index of the bucket array.
   `hash' specifies the hash value for the collision tree.
   `vbuf' specifies the pointer to the region of the value.
   `vsiz' specifies the size of the region of the value.
   If successful, the return value is true, else, it is false. */
static bool tchdbputasyncimpl(TCHDB *hdb, const char *kbuf, int ksiz, uint64_t bidx,
                              uint8_t hash, const char *vbuf, int vsiz){
  assert(hdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  if(hdb->recc) tcmdbout(hdb->recc, kbuf, ksiz);
  if(!hdb->drpool){
    hdb->drpool = tcxstrnew3(HDBDRPUNIT + HDBDRPLAT);
    hdb->drpdef = tcxstrnew3(HDBDRPUNIT);
    hdb->drpoff = hdb->fsiz;
  }
  off_t off = tchdbgetbucket(hdb, bidx);
  off_t entoff = 0;
  TCHREC rec;
  char rbuf[HDBIOBUFSIZ];
  while(off > 0){
    if(off >= hdb->drpoff - hdb->runit){
      TCDODEBUG(hdb->cnt_deferdrp++);
      TCXSTR *drpdef = hdb->drpdef;
      TCXSTRCAT(drpdef, &ksiz, sizeof(ksiz));
      TCXSTRCAT(drpdef, &vsiz, sizeof(vsiz));
      TCXSTRCAT(drpdef, kbuf, ksiz);
      TCXSTRCAT(drpdef, vbuf, vsiz);
      if(TCXSTRSIZE(hdb->drpdef) > HDBDRPUNIT && !tchdbflushdrp(hdb)) return false;
      return true;
    }
    rec.off = off;
    if(!tchdbreadrec(hdb, &rec, rbuf)) return false;
    if(hash > rec.hash){
      off = rec.left;
      entoff = rec.off + (sizeof(uint8_t) + sizeof(uint8_t));
    } else if(hash < rec.hash){
      off = rec.right;
      entoff = rec.off + (sizeof(uint8_t) + sizeof(uint8_t)) +
        (hdb->ba64 ? sizeof(uint64_t) : sizeof(uint32_t));
    } else {
      TCDODEBUG(hdb->cnt_deferdrp++);
      TCXSTR *drpdef = hdb->drpdef;
      TCXSTRCAT(drpdef, &ksiz, sizeof(ksiz));
      TCXSTRCAT(drpdef, &vsiz, sizeof(vsiz));
      TCXSTRCAT(drpdef, kbuf, ksiz);
      TCXSTRCAT(drpdef, vbuf, vsiz);
      if(TCXSTRSIZE(hdb->drpdef) > HDBDRPUNIT && !tchdbflushdrp(hdb)) return false;
      return true;
    }
  }
  if(entoff > 0){
    if(hdb->ba64){
      uint64_t llnum = hdb->fsiz >> hdb->apow;
      llnum = TCHTOILL(llnum);
      if(!tchdbseekwrite(hdb, entoff, &llnum, sizeof(uint64_t))) return false;
    } else {
      uint32_t lnum = hdb->fsiz >> hdb->apow;
      lnum = TCHTOIL(lnum);
      if(!tchdbseekwrite(hdb, entoff, &lnum, sizeof(uint32_t))) return false;
    }
  } else {
    tchdbsetbucket(hdb, bidx, hdb->fsiz);
  }
  tchdbdrpappend(hdb, kbuf, ksiz, vbuf, vsiz, hash);
  hdb->rnum++;
  if(TCXSTRSIZE(hdb->drpool) > HDBDRPUNIT && !tchdbflushdrp(hdb)) return false;
  return true;
}


/* Remove a record of a hash database object.
   `hdb' specifies the hash database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   `bidx' specifies the index of the bucket array.
   `hash' specifies the hash value for the collision tree.
   If successful, the return value is true, else, it is false. */
static bool tchdboutimpl(TCHDB *hdb, const char *kbuf, int ksiz, uint64_t bidx, uint8_t hash){
  assert(hdb && kbuf && ksiz >= 0);
  if(hdb->recc) tcmdbout(hdb->recc, kbuf, ksiz);
  off_t off = tchdbgetbucket(hdb, bidx);
  off_t entoff = 0;
  TCHREC rec;
  char rbuf[HDBIOBUFSIZ];
  while(off > 0){
    rec.off = off;
    if(!tchdbreadrec(hdb, &rec, rbuf)) return false;
    if(hash > rec.hash){
      off = rec.left;
      entoff = rec.off + (sizeof(uint8_t) + sizeof(uint8_t));
    } else if(hash < rec.hash){
      off = rec.right;
      entoff = rec.off + (sizeof(uint8_t) + sizeof(uint8_t)) +
        (hdb->ba64 ? sizeof(uint64_t) : sizeof(uint32_t));
    } else {
      if(!rec.kbuf && !tchdbreadrecbody(hdb, &rec)) return false;
      int kcmp = tcreckeycmp(kbuf, ksiz, rec.kbuf, rec.ksiz);
      if(kcmp > 0){
        off = rec.left;
        TCFREE(rec.bbuf);
        rec.kbuf = NULL;
        rec.bbuf = NULL;
        entoff = rec.off + (sizeof(uint8_t) + sizeof(uint8_t));
      } else if(kcmp < 0){
        off = rec.right;
        TCFREE(rec.bbuf);
        rec.kbuf = NULL;
        rec.bbuf = NULL;
        entoff = rec.off + (sizeof(uint8_t) + sizeof(uint8_t)) +
          (hdb->ba64 ? sizeof(uint64_t) : sizeof(uint32_t));
      } else {
        TCFREE(rec.bbuf);
        rec.bbuf = NULL;
        return tchdbremoverec(hdb, &rec, rbuf, bidx, entoff);
      }
    }
  }
  tchdbsetecode(hdb, TCENOREC, __FILE__, __LINE__, __func__);
  return false;
}


/* Retrieve a record in a hash database object.
   `hdb' specifies the hash database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   `bidx' specifies the index of the bucket array.
   `hash' specifies the hash value for the collision tree.
   `sp' specifies the pointer to the variable into which the size of the region of the return
   value is assigned.
   If successful, the return value is the pointer to the region of the value of the corresponding
   record. */
static char *tchdbgetimpl(TCHDB *hdb, const char *kbuf, int ksiz, uint64_t bidx, uint8_t hash,
                          int *sp){
  assert(hdb && kbuf && ksiz >= 0 && sp);
  if(hdb->recc){
    int tvsiz;
    char *tvbuf = tcmdbget(hdb->recc, kbuf, ksiz, &tvsiz);
    if(tvbuf){
      if(*tvbuf == '*'){
        tchdbsetecode(hdb, TCENOREC, __FILE__, __LINE__, __func__);
        TCFREE(tvbuf);
        return NULL;
      }
      *sp = tvsiz - 1;
      memmove(tvbuf, tvbuf + 1, tvsiz);
      return tvbuf;
    }
  }
  off_t off = tchdbgetbucket(hdb, bidx);
  TCHREC rec;
  char rbuf[HDBIOBUFSIZ];
  while(off > 0){
    rec.off = off;
    if(!tchdbreadrec(hdb, &rec, rbuf)) return NULL;
    if(hash > rec.hash){
      off = rec.left;
    } else if(hash < rec.hash){
      off = rec.right;
    } else {
      if(!rec.kbuf && !tchdbreadrecbody(hdb, &rec)) return NULL;
      int kcmp = tcreckeycmp(kbuf, ksiz, rec.kbuf, rec.ksiz);
      if(kcmp > 0){
        off = rec.left;
        TCFREE(rec.bbuf);
        rec.kbuf = NULL;
        rec.bbuf = NULL;
      } else if(kcmp < 0){
        off = rec.right;
        TCFREE(rec.bbuf);
        rec.kbuf = NULL;
        rec.bbuf = NULL;
      } else {
        if(!rec.vbuf && !tchdbreadrecbody(hdb, &rec)) return NULL;
        if(hdb->zmode){
          int zsiz;
          char *zbuf;
          if(hdb->opts & HDBTDEFLATE){
            zbuf = _tc_inflate(rec.vbuf, rec.vsiz, &zsiz, _TCZMRAW);
          } else if(hdb->opts & HDBTBZIP){
            zbuf = _tc_bzdecompress(rec.vbuf, rec.vsiz, &zsiz);
          } else if(hdb->opts & HDBTTCBS){
            zbuf = tcbsdecode(rec.vbuf, rec.vsiz, &zsiz);
          } else {
            zbuf = hdb->dec(rec.vbuf, rec.vsiz, &zsiz, hdb->decop);
          }
          TCFREE(rec.bbuf);
          if(!zbuf){
            tchdbsetecode(hdb, TCEMISC, __FILE__, __LINE__, __func__);
            return NULL;
          }
          if(hdb->recc){
            if(tcmdbrnum(hdb->recc) >= hdb->rcnum) tchdbcacheadjust(hdb);
            tcmdbput4(hdb->recc, kbuf, ksiz, "=", 1, zbuf, zsiz);
          }
          *sp = zsiz;
          return zbuf;
        }
        if(hdb->recc){
          if(tcmdbrnum(hdb->recc) >= hdb->rcnum) tchdbcacheadjust(hdb);
          tcmdbput4(hdb->recc, kbuf, ksiz, "=", 1, rec.vbuf, rec.vsiz);
        }
        if(rec.bbuf){
          memmove(rec.bbuf, rec.vbuf, rec.vsiz);
          rec.bbuf[rec.vsiz] = '\0';
          *sp = rec.vsiz;
          return rec.bbuf;
        }
        *sp = rec.vsiz;
        char *rv;
        TCMEMDUP(rv, rec.vbuf, rec.vsiz);
        return rv;
      }
    }
  }
  if(hdb->recc){
    if(tcmdbrnum(hdb->recc) >= hdb->rcnum) tchdbcacheadjust(hdb);
    tcmdbput(hdb->recc, kbuf, ksiz, "*", 1);
  }
  tchdbsetecode(hdb, TCENOREC, __FILE__, __LINE__, __func__);
  return NULL;
}


/* Retrieve a record in a hash database object and write the value into a buffer.
   `hdb' specifies the hash database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   `bidx' specifies the index of the bucket array.
   `hash' specifies the hash value for the collision tree.
   `vbuf' specifies the pointer to the buffer into which the value of the corresponding record is
   written.
   `max' specifies the size of the buffer.
   If successful, the return value is the size of the written data, else, it is -1. */
static int tchdbgetintobuf(TCHDB *hdb, const char *kbuf, int ksiz, uint64_t bidx, uint8_t hash,
                           char *vbuf, int max){
  assert(hdb && kbuf && ksiz >= 0 && vbuf && max >= 0);
  if(hdb->recc){
    int tvsiz;
    char *tvbuf = tcmdbget(hdb->recc, kbuf, ksiz, &tvsiz);
    if(tvbuf){
      if(*tvbuf == '*'){
        tchdbsetecode(hdb, TCENOREC, __FILE__, __LINE__, __func__);
        TCFREE(tvbuf);
        return -1;
      }
      tvsiz = tclmin(tvsiz - 1, max);
      memcpy(vbuf, tvbuf + 1, tvsiz);
      TCFREE(tvbuf);
      return tvsiz;
    }
  }
  off_t off = tchdbgetbucket(hdb, bidx);
  TCHREC rec;
  char rbuf[HDBIOBUFSIZ];
  while(off > 0){
    rec.off = off;
    if(!tchdbreadrec(hdb, &rec, rbuf)) return -1;
    if(hash > rec.hash){
      off = rec.left;
    } else if(hash < rec.hash){
      off = rec.right;
    } else {
      if(!rec.kbuf && !tchdbreadrecbody(hdb, &rec)) return -1;
      int kcmp = tcreckeycmp(kbuf, ksiz, rec.kbuf, rec.ksiz);
      if(kcmp > 0){
        off = rec.left;
        TCFREE(rec.bbuf);
        rec.kbuf = NULL;
        rec.bbuf = NULL;
      } else if(kcmp < 0){
        off = rec.right;
        TCFREE(rec.bbuf);
        rec.kbuf = NULL;
        rec.bbuf = NULL;
      } else {
        if(!rec.vbuf && !tchdbreadrecbody(hdb, &rec)) return -1;
        if(hdb->zmode){
          int zsiz;
          char *zbuf;
          if(hdb->opts & HDBTDEFLATE){
            zbuf = _tc_inflate(rec.vbuf, rec.vsiz, &zsiz, _TCZMRAW);
          } else if(hdb->opts & HDBTBZIP){
            zbuf = _tc_bzdecompress(rec.vbuf, rec.vsiz, &zsiz);
          } else if(hdb->opts & HDBTTCBS){
            zbuf = tcbsdecode(rec.vbuf, rec.vsiz, &zsiz);
          } else {
            zbuf = hdb->dec(rec.vbuf, rec.vsiz, &zsiz, hdb->decop);
          }
          TCFREE(rec.bbuf);
          if(!zbuf){
            tchdbsetecode(hdb, TCEMISC, __FILE__, __LINE__, __func__);
            return -1;
          }
          if(hdb->recc){
            if(tcmdbrnum(hdb->recc) >= hdb->rcnum) tchdbcacheadjust(hdb);
            tcmdbput4(hdb->recc, kbuf, ksiz, "=", 1, zbuf, zsiz);
          }
          zsiz = tclmin(zsiz, max);
          memcpy(vbuf, zbuf, zsiz);
          TCFREE(zbuf);
          return zsiz;
        }
        if(hdb->recc){
          if(tcmdbrnum(hdb->recc) >= hdb->rcnum) tchdbcacheadjust(hdb);
          tcmdbput4(hdb->recc, kbuf, ksiz, "=", 1, rec.vbuf, rec.vsiz);
        }
        int vsiz = tclmin(rec.vsiz, max);
        memcpy(vbuf, rec.vbuf, vsiz);
        TCFREE(rec.bbuf);
        return vsiz;
      }
    }
  }
  if(hdb->recc){
    if(tcmdbrnum(hdb->recc) >= hdb->rcnum) tchdbcacheadjust(hdb);
    tcmdbput(hdb->recc, kbuf, ksiz, "*", 1);
  }
  tchdbsetecode(hdb, TCENOREC, __FILE__, __LINE__, __func__);
  return -1;
}


/* Retrieve the next record of a record in a hash database object.
   `hdb' specifies the hash database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   `sp' specifies the pointer to the variable into which the size of the region of the return
   value is assigned.
   `vbp' specifies the pointer to the variable into which the pointer to the value is assigned.
   `vsp' specifies the pointer to the variable into which the size of the value is assigned.
   If successful, the return value is the pointer to the region of the value of the next
   record. */
static char *tchdbgetnextimpl(TCHDB *hdb, const char *kbuf, int ksiz, int *sp,
                              const char **vbp, int *vsp){
  assert(hdb && sp);
  if(!kbuf){
    uint64_t iter = hdb->frec;
    TCHREC rec;
    char rbuf[HDBIOBUFSIZ];
    while(iter < hdb->fsiz){
      rec.off = iter;
      if(!tchdbreadrec(hdb, &rec, rbuf)) return NULL;
      iter += rec.rsiz;
      if(rec.magic == HDBMAGICREC){
        if(vbp){
          if(hdb->zmode){
            if(!tchdbreadrecbody(hdb, &rec)) return NULL;
            int zsiz;
            char *zbuf;
            if(hdb->opts & HDBTDEFLATE){
              zbuf = _tc_inflate(rec.vbuf, rec.vsiz, &zsiz, _TCZMRAW);
            } else if(hdb->opts & HDBTBZIP){
              zbuf = _tc_bzdecompress(rec.vbuf, rec.vsiz, &zsiz);
            } else if(hdb->opts & HDBTTCBS){
              zbuf = tcbsdecode(rec.vbuf, rec.vsiz, &zsiz);
            } else {
              zbuf = hdb->dec(rec.vbuf, rec.vsiz, &zsiz, hdb->decop);
            }
            if(!zbuf){
              tchdbsetecode(hdb, TCEMISC, __FILE__, __LINE__, __func__);
              TCFREE(rec.bbuf);
              return NULL;
            }
            char *rv;
            TCMALLOC(rv, rec.ksiz + zsiz + 1);
            memcpy(rv, rec.kbuf, rec.ksiz);
            memcpy(rv + rec.ksiz, zbuf, zsiz);
            *sp = rec.ksiz;
            *vbp = rv + rec.ksiz;
            *vsp = zsiz;
            TCFREE(zbuf);
            TCFREE(rec.bbuf);
            return rv;
          }
          if(rec.vbuf){
            char *rv;
            TCMALLOC(rv, rec.ksiz + rec.vsiz + 1);
            memcpy(rv, rec.kbuf, rec.ksiz);
            memcpy(rv + rec.ksiz, rec.vbuf, rec.vsiz);
            *sp = rec.ksiz;
            *vbp = rv + rec.ksiz;
            *vsp = rec.vsiz;
            return rv;
          }
          if(!tchdbreadrecbody(hdb, &rec)) return NULL;
          *sp = rec.ksiz;
          *vbp = rec.vbuf;
          *vsp = rec.vsiz;
          return rec.bbuf;
        }
        if(rec.kbuf){
          *sp = rec.ksiz;
          char *rv;
          TCMEMDUP(rv, rec.kbuf, rec.ksiz);
          return rv;
        }
        if(!tchdbreadrecbody(hdb, &rec)) return NULL;
        rec.bbuf[rec.ksiz] = '\0';
        *sp = rec.ksiz;
        return rec.bbuf;
      }
    }
    tchdbsetecode(hdb, TCENOREC, __FILE__, __LINE__, __func__);
    return NULL;
  }
  uint8_t hash;
  uint64_t bidx = tchdbbidx(hdb, kbuf, ksiz, &hash);
  off_t off = tchdbgetbucket(hdb, bidx);
  TCHREC rec;
  char rbuf[HDBIOBUFSIZ];
  while(off > 0){
    rec.off = off;
    if(!tchdbreadrec(hdb, &rec, rbuf)) return NULL;
    if(hash > rec.hash){
      off = rec.left;
    } else if(hash < rec.hash){
      off = rec.right;
    } else {
      if(!rec.kbuf && !tchdbreadrecbody(hdb, &rec)) return NULL;
      int kcmp = tcreckeycmp(kbuf, ksiz, rec.kbuf, rec.ksiz);
      if(kcmp > 0){
        off = rec.left;
        TCFREE(rec.bbuf);
        rec.kbuf = NULL;
        rec.bbuf = NULL;
      } else if(kcmp < 0){
        off = rec.right;
        TCFREE(rec.bbuf);
        rec.kbuf = NULL;
        rec.bbuf = NULL;
      } else {
        uint64_t iter = rec.off + rec.rsiz;
        TCFREE(rec.bbuf);
        rec.kbuf = NULL;
        rec.bbuf = NULL;
        while(iter < hdb->fsiz){
          rec.off = iter;
          if(!tchdbreadrec(hdb, &rec, rbuf)) return NULL;
          iter += rec.rsiz;
          if(rec.magic == HDBMAGICREC){
            if(vbp){
              if(hdb->zmode){
                if(!tchdbreadrecbody(hdb, &rec)) return NULL;
                int zsiz;
                char *zbuf;
                if(hdb->opts & HDBTDEFLATE){
                  zbuf = _tc_inflate(rec.vbuf, rec.vsiz, &zsiz, _TCZMRAW);
                } else if(hdb->opts & HDBTBZIP){
                  zbuf = _tc_bzdecompress(rec.vbuf, rec.vsiz, &zsiz);
                } else if(hdb->opts & HDBTTCBS){
                  zbuf = tcbsdecode(rec.vbuf, rec.vsiz, &zsiz);
                } else {
                  zbuf = hdb->dec(rec.vbuf, rec.vsiz, &zsiz, hdb->decop);
                }
                if(!zbuf){
                  tchdbsetecode(hdb, TCEMISC, __FILE__, __LINE__, __func__);
                  TCFREE(rec.bbuf);
                  return NULL;
                }
                char *rv;
                TCMALLOC(rv, rec.ksiz + zsiz + 1);
                memcpy(rv, rec.kbuf, rec.ksiz);
                memcpy(rv + rec.ksiz, zbuf, zsiz);
                *sp = rec.ksiz;
                *vbp = rv + rec.ksiz;
                *vsp = zsiz;
                TCFREE(zbuf);
                TCFREE(rec.bbuf);
                return rv;
              }
              if(rec.vbuf){
                char *rv;
                TCMALLOC(rv, rec.ksiz + rec.vsiz + 1);
                memcpy(rv, rec.kbuf, rec.ksiz);
                memcpy(rv + rec.ksiz, rec.vbuf, rec.vsiz);
                *sp = rec.ksiz;
                *vbp = rv + rec.ksiz;
                *vsp = rec.vsiz;
                return rv;
              }
              if(!tchdbreadrecbody(hdb, &rec)) return NULL;
              *sp = rec.ksiz;
              *vbp = rec.vbuf;
              *vsp = rec.vsiz;
              return rec.bbuf;
            }
            if(rec.kbuf){
              *sp = rec.ksiz;
              char *rv;
              TCMEMDUP(rv, rec.kbuf, rec.ksiz);
              return rv;
            }
            if(!tchdbreadrecbody(hdb, &rec)) return NULL;
            rec.bbuf[rec.ksiz] = '\0';
            *sp = rec.ksiz;
            return rec.bbuf;
          }
        }
        tchdbsetecode(hdb, TCENOREC, __FILE__, __LINE__, __func__);
        return NULL;
      }
    }
  }
  tchdbsetecode(hdb, TCENOREC, __FILE__, __LINE__, __func__);
  return NULL;
}


/* Get the size of the value of a record in a hash database object.
   `hdb' specifies the hash database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   `bidx' specifies the index of the bucket array.
   `hash' specifies the hash value for the collision tree.
   If successful, the return value is the size of the value of the corresponding record, else,
   it is -1. */
static int tchdbvsizimpl(TCHDB *hdb, const char *kbuf, int ksiz, uint64_t bidx, uint8_t hash){
  assert(hdb && kbuf && ksiz >= 0);
  if(hdb->recc){
    int tvsiz;
    char *tvbuf = tcmdbget(hdb->recc, kbuf, ksiz, &tvsiz);
    if(tvbuf){
      if(*tvbuf == '*'){
        tchdbsetecode(hdb, TCENOREC, __FILE__, __LINE__, __func__);
        TCFREE(tvbuf);
        return -1;
      }
      TCFREE(tvbuf);
      return tvsiz - 1;
    }
  }
  off_t off = tchdbgetbucket(hdb, bidx);
  TCHREC rec;
  char rbuf[HDBIOBUFSIZ];
  while(off > 0){
    rec.off = off;
    if(!tchdbreadrec(hdb, &rec, rbuf)) return -1;
    if(hash > rec.hash){
      off = rec.left;
    } else if(hash < rec.hash){
      off = rec.right;
    } else {
      if(!rec.kbuf && !tchdbreadrecbody(hdb, &rec)) return -1;
      int kcmp = tcreckeycmp(kbuf, ksiz, rec.kbuf, rec.ksiz);
      if(kcmp > 0){
        off = rec.left;
        TCFREE(rec.bbuf);
        rec.kbuf = NULL;
        rec.bbuf = NULL;
      } else if(kcmp < 0){
        off = rec.right;
        TCFREE(rec.bbuf);
        rec.kbuf = NULL;
        rec.bbuf = NULL;
      } else {
        if(hdb->zmode){
          if(!rec.vbuf && !tchdbreadrecbody(hdb, &rec)) return -1;
          int zsiz;
          char *zbuf;
          if(hdb->opts & HDBTDEFLATE){
            zbuf = _tc_inflate(rec.vbuf, rec.vsiz, &zsiz, _TCZMRAW);
          } else if(hdb->opts & HDBTBZIP){
            zbuf = _tc_bzdecompress(rec.vbuf, rec.vsiz, &zsiz);
          } else if(hdb->opts & HDBTTCBS){
            zbuf = tcbsdecode(rec.vbuf, rec.vsiz, &zsiz);
          } else {
            zbuf = hdb->dec(rec.vbuf, rec.vsiz, &zsiz, hdb->decop);
          }
          TCFREE(rec.bbuf);
          if(!zbuf){
            tchdbsetecode(hdb, TCEMISC, __FILE__, __LINE__, __func__);
            return -1;
          }
          if(hdb->recc){
            if(tcmdbrnum(hdb->recc) >= hdb->rcnum) tchdbcacheadjust(hdb);
            tcmdbput4(hdb->recc, kbuf, ksiz, "=", 1, zbuf, zsiz);
          }
          TCFREE(zbuf);
          return zsiz;
        }
        if(hdb->recc && rec.vbuf){
          if(tcmdbrnum(hdb->recc) >= hdb->rcnum) tchdbcacheadjust(hdb);
          tcmdbput4(hdb->recc, kbuf, ksiz, "=", 1, rec.vbuf, rec.vsiz);
        }
        TCFREE(rec.bbuf);
        return rec.vsiz;
      }
    }
  }
  if(hdb->recc){
    if(tcmdbrnum(hdb->recc) >= hdb->rcnum) tchdbcacheadjust(hdb);
    tcmdbput(hdb->recc, kbuf, ksiz, "*", 1);
  }
  tchdbsetecode(hdb, TCENOREC, __FILE__, __LINE__, __func__);
  return -1;
}


/* Initialize the iterator of a hash database object.
   `hdb' specifies the hash database object.
   If successful, the return value is true, else, it is false. */
static bool tchdbiterinitimpl(TCHDB *hdb){
  assert(hdb);
  hdb->iter = hdb->frec;
  return true;
}


/* Get the next key of the iterator of a hash database object.
   `hdb' specifies the hash database object.
   `sp' specifies the pointer to the variable into which the size of the region of the return
   value is assigned.
   If successful, the return value is the pointer to the region of the next key, else, it is
   `NULL'. */
static char *tchdbiternextimpl(TCHDB *hdb, int *sp){
  assert(hdb && sp);
  TCHREC rec;
  char rbuf[HDBIOBUFSIZ];
  while(hdb->iter < hdb->fsiz){
    rec.off = hdb->iter;
    if(!tchdbreadrec(hdb, &rec, rbuf)) return NULL;
    hdb->iter += rec.rsiz;
    if(rec.magic == HDBMAGICREC){
      if(rec.kbuf){
        *sp = rec.ksiz;
        char *rv;
        TCMEMDUP(rv, rec.kbuf, rec.ksiz);
        return rv;
      }
      if(!tchdbreadrecbody(hdb, &rec)) return NULL;
      rec.bbuf[rec.ksiz] = '\0';
      *sp = rec.ksiz;
      return rec.bbuf;
    }
  }
  tchdbsetecode(hdb, TCENOREC, __FILE__, __LINE__, __func__);
  return NULL;
}


/* Get the next extensible objects of the iterator of a hash database object. */
static bool tchdbiternextintoxstr(TCHDB *hdb, TCXSTR *kxstr, TCXSTR *vxstr){
  assert(hdb && kxstr && vxstr);
  TCHREC rec;
  char rbuf[HDBIOBUFSIZ];
  while(hdb->iter < hdb->fsiz){
    rec.off = hdb->iter;
    if(!tchdbreadrec(hdb, &rec, rbuf)) return false;
    hdb->iter += rec.rsiz;
    if(rec.magic == HDBMAGICREC){
      if(!rec.vbuf && !tchdbreadrecbody(hdb, &rec)) return false;
      tcxstrclear(kxstr);
      TCXSTRCAT(kxstr, rec.kbuf, rec.ksiz);
      tcxstrclear(vxstr);
      if(hdb->zmode){
        int zsiz;
        char *zbuf;
        if(hdb->opts & HDBTDEFLATE){
          zbuf = _tc_inflate(rec.vbuf, rec.vsiz, &zsiz, _TCZMRAW);
        } else if(hdb->opts & HDBTBZIP){
          zbuf = _tc_bzdecompress(rec.vbuf, rec.vsiz, &zsiz);
        } else if(hdb->opts & HDBTTCBS){
          zbuf = tcbsdecode(rec.vbuf, rec.vsiz, &zsiz);
        } else {
          zbuf = hdb->dec(rec.vbuf, rec.vsiz, &zsiz, hdb->decop);
        }
        if(!zbuf){
          tchdbsetecode(hdb, TCEMISC, __FILE__, __LINE__, __func__);
          TCFREE(rec.bbuf);
          return false;
        }
        TCXSTRCAT(vxstr, zbuf, zsiz);
        TCFREE(zbuf);
      } else {
        TCXSTRCAT(vxstr, rec.vbuf, rec.vsiz);
      }
      TCFREE(rec.bbuf);
      return true;
    }
  }
  tchdbsetecode(hdb, TCENOREC, __FILE__, __LINE__, __func__);
  return false;
}


/* Optimize the file of a hash database object.
   `hdb' specifies the hash database object.
   `bnum' specifies the number of elements of the bucket array.
   `apow' specifies the size of record alignment by power of 2.
   `fpow' specifies the maximum number of elements of the free block pool by power of 2.
   `opts' specifies options by bitwise-or.
   If successful, the return value is true, else, it is false. */
static bool tchdboptimizeimpl(TCHDB *hdb, int64_t bnum, int8_t apow, int8_t fpow, uint8_t opts){
  assert(hdb);
  char *tpath = tcsprintf("%s%ctmp%c%llu", hdb->path, MYEXTCHR, MYEXTCHR, hdb->inode);
  TCHDB *thdb = tchdbnew();
  thdb->dbgfd = hdb->dbgfd;
  thdb->enc = hdb->enc;
  thdb->encop = hdb->encop;
  thdb->dec = hdb->dec;
  thdb->decop = hdb->decop;
  if(bnum < 1){
    bnum = hdb->rnum * 2 + 1;
    if(bnum < HDBDEFBNUM) bnum = HDBDEFBNUM;
  }
  if(apow < 0) apow = hdb->apow;
  if(fpow < 0) fpow = hdb->fpow;
  if(opts == UINT8_MAX) opts = hdb->opts;
  tchdbtune(thdb, bnum, apow, fpow, opts);
  if(!tchdbopen(thdb, tpath, HDBOWRITER | HDBOCREAT | HDBOTRUNC)){
    tchdbsetecode(hdb, thdb->ecode, __FILE__, __LINE__, __func__);
    tchdbdel(thdb);
    TCFREE(tpath);
    return false;
  }
  memcpy(tchdbopaque(thdb), tchdbopaque(hdb), HDBHEADSIZ - HDBOPAQUEOFF);
  bool err = false;
  uint64_t off = hdb->frec;
  TCHREC rec;
  char rbuf[HDBIOBUFSIZ];
  while(off < hdb->fsiz){
    rec.off = off;
    if(!tchdbreadrec(hdb, &rec, rbuf)){
      err = true;
      break;
    }
    off += rec.rsiz;
    if(rec.magic == HDBMAGICREC){
      if(!rec.vbuf && !tchdbreadrecbody(hdb, &rec)){
        TCFREE(rec.bbuf);
        err = true;
      } else {
        if(hdb->zmode){
          int zsiz;
          char *zbuf;
          if(hdb->opts & HDBTDEFLATE){
            zbuf = _tc_inflate(rec.vbuf, rec.vsiz, &zsiz, _TCZMRAW);
          } else if(hdb->opts & HDBTBZIP){
            zbuf = _tc_bzdecompress(rec.vbuf, rec.vsiz, &zsiz);
          } else if(hdb->opts & HDBTTCBS){
            zbuf = tcbsdecode(rec.vbuf, rec.vsiz, &zsiz);
          } else {
            zbuf = hdb->dec(rec.vbuf, rec.vsiz, &zsiz, hdb->decop);
          }
          if(zbuf){
            if(!tchdbput(thdb, rec.kbuf, rec.ksiz, zbuf, zsiz)){
              tchdbsetecode(hdb, thdb->ecode, __FILE__, __LINE__, __func__);
              err = true;
            }
            TCFREE(zbuf);
          } else {
            tchdbsetecode(hdb, TCEMISC, __FILE__, __LINE__, __func__);
            err = true;
          }
        } else {
          if(!tchdbput(thdb, rec.kbuf, rec.ksiz, rec.vbuf, rec.vsiz)){
            tchdbsetecode(hdb, thdb->ecode, __FILE__, __LINE__, __func__);
            err = true;
          }
        }
      }
      TCFREE(rec.bbuf);
    }
  }
  if(!tchdbclose(thdb)){
    tchdbsetecode(hdb, thdb->ecode, __FILE__, __LINE__, __func__);
    err = true;
  }
  bool esc = false;
  if(err && (hdb->omode & HDBONOLCK) && !thdb->fatal){
    err = false;
    esc = true;
  }
  tchdbdel(thdb);
  if(err){
    TCFREE(tpath);
    return false;
  }
  if(esc){
    char *bpath = tcsprintf("%s%cbroken", tpath, MYEXTCHR);
    if(rename(hdb->path, bpath) == -1){
      tchdbsetecode(hdb, TCEUNLINK, __FILE__, __LINE__, __func__);
      err = true;
    }
    TCFREE(bpath);
  } else {
    if(unlink(hdb->path) == -1){
      tchdbsetecode(hdb, TCEUNLINK, __FILE__, __LINE__, __func__);
      err = true;
    }
  }
  if(rename(tpath, hdb->path) == -1){
    tchdbsetecode(hdb, TCERENAME, __FILE__, __LINE__, __func__);
    err = true;
  }
  TCFREE(tpath);
  if(err) return false;
  tpath = tcstrdup(hdb->path);
  int omode = (hdb->omode & ~HDBOCREAT) & ~HDBOTRUNC;
  if(!tchdbcloseimpl(hdb)){
    TCFREE(tpath);
    return false;
  }
  bool rv = tchdbopenimpl(hdb, tpath, omode);
  TCFREE(tpath);
  return rv;
}


/* Remove all records of a hash database object.
   `hdb' specifies the hash database object.
   If successful, the return value is true, else, it is false. */
static bool tchdbvanishimpl(TCHDB *hdb){
  assert(hdb);
  char *path = tcstrdup(hdb->path);
  int omode = hdb->omode;
  bool err = false;
  if(!tchdbcloseimpl(hdb)) err = true;
  if(!tchdbopenimpl(hdb, path, HDBOTRUNC | omode)){
    tcpathunlock(hdb->rpath);
    TCFREE(hdb->rpath);
    err = true;
  }
  TCFREE(path);
  return !err;
}


/* Copy the database file of a hash database object.
   `hdb' specifies the hash database object.
   `path' specifies the path of the destination file.
   If successful, the return value is true, else, it is false. */
static bool tchdbcopyimpl(TCHDB *hdb, const char *path){
  assert(hdb && path);
  bool err = false;
  if(hdb->omode & HDBOWRITER){
    if(!tchdbsavefbp(hdb)) err = true;
    if(!tchdbmemsync(hdb, false)) err = true;
    tchdbsetflag(hdb, HDBFOPEN, false);
  }
  if(*path == '@'){
    char tsbuf[TCNUMBUFSIZ];
    sprintf(tsbuf, "%llu", (unsigned long long)(tctime() * 1000000));
    const char *args[3];
    args[0] = path + 1;
    args[1] = hdb->path;
    args[2] = tsbuf;
    if(tcsystem(args, sizeof(args) / sizeof(*args)) != 0) err = true;
  } else {
    if(!tccopyfile(hdb->path, path)){
      tchdbsetecode(hdb, TCEMISC, __FILE__, __LINE__, __func__);
      err = true;
    }
  }
  if(hdb->omode & HDBOWRITER) tchdbsetflag(hdb, HDBFOPEN, true);
  return !err;
}


/* Perform dynamic defragmentation of a hash database object.
   `hdb' specifies the hash database object connected.
   `step' specifie the number of steps.
   If successful, the return value is true, else, it is false. */
static bool tchdbdefragimpl(TCHDB *hdb, int64_t step){
  assert(hdb && step >= 0);
  TCDODEBUG(hdb->cnt_defrag++);
  hdb->dfcnt = 0;
  TCHREC rec;
  char rbuf[HDBIOBUFSIZ];
  while(true){
    if(hdb->dfcur >= hdb->fsiz){
      hdb->dfcur = hdb->frec;
      return true;
    }
    if(step-- < 1) return true;
    rec.off = hdb->dfcur;
    if(!tchdbreadrec(hdb, &rec, rbuf)) return false;
    if(rec.magic == HDBMAGICFB) break;
    hdb->dfcur += rec.rsiz;
  }
  uint32_t align = hdb->align;
  uint64_t base = hdb->dfcur;
  uint64_t dest = base;
  uint64_t cur = base;
  if(hdb->iter == cur) hdb->iter += rec.rsiz;
  cur += rec.rsiz;
  uint64_t fbsiz = cur - dest;
  step++;
  while(step > 0 && cur < hdb->fsiz){
    rec.off = cur;
    if(!tchdbreadrec(hdb, &rec, rbuf)) return false;
    uint32_t rsiz = rec.rsiz;
    if(rec.magic == HDBMAGICREC){
      if(rec.psiz >= align){
        int diff = rec.psiz - rec.psiz % align;
        rec.psiz -= diff;
        rec.rsiz -= diff;
        fbsiz += diff;
      }
      if(!tchdbshiftrec(hdb, &rec, rbuf, dest)) return false;
      if(hdb->iter == cur) hdb->iter = dest;
      dest += rec.rsiz;
      step--;
    } else {
      if(hdb->iter == cur) hdb->iter += rec.rsiz;
      fbsiz += rec.rsiz;
    }
    cur += rsiz;
  }
  if(cur < hdb->fsiz){
    if(fbsiz > HDBFBMAXSIZ){
      tchdbfbptrim(hdb, base, cur, 0, 0);
      uint64_t off = dest;
      uint64_t size = fbsiz;
      while(size > 0){
        uint32_t rsiz = (size > HDBFBMAXSIZ) ? HDBFBMAXSIZ : size;
        if(size - rsiz < HDBMINRUNIT) rsiz = size;
        tchdbfbpinsert(hdb, off, rsiz);
        if(!tchdbwritefb(hdb, off, rsiz)) return false;
        off += rsiz;
        size -= rsiz;
      }
    } else {
      tchdbfbptrim(hdb, base, cur, dest, fbsiz);
      if(!tchdbwritefb(hdb, dest, fbsiz)) return false;
    }
    hdb->dfcur = cur - fbsiz;
  } else {
    TCDODEBUG(hdb->cnt_trunc++);
    if(hdb->tran && !tchdbwalwrite(hdb, dest, fbsiz)) return false;
    tchdbfbptrim(hdb, base, cur, 0, 0);
    hdb->dfcur = hdb->frec;
    hdb->fsiz = dest;
    uint64_t llnum = hdb->fsiz;
    llnum = TCHTOILL(llnum);
    memcpy(hdb->map + HDBFSIZOFF, &llnum, sizeof(llnum));
    if(hdb->iter >= hdb->fsiz) hdb->iter = UINT64_MAX;
    if(!hdb->tran){
      if(ftruncate(hdb->fd, hdb->fsiz) == -1){
        tchdbsetecode(hdb, TCETRUNC, __FILE__, __LINE__, __func__);
        return false;
      }
      hdb->xfsiz = 0;
    }
  }
  return true;
}


/* Move the iterator to the record corresponding a key of a hash database object.
   `hdb' specifies the hash database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   If successful, the return value is true, else, it is false. */
static bool tchdbiterjumpimpl(TCHDB *hdb, const char *kbuf, int ksiz){
  assert(hdb && kbuf && ksiz);
  uint8_t hash;
  uint64_t bidx = tchdbbidx(hdb, kbuf, ksiz, &hash);
  off_t off = tchdbgetbucket(hdb, bidx);
  TCHREC rec;
  char rbuf[HDBIOBUFSIZ];
  while(off > 0){
    rec.off = off;
    if(!tchdbreadrec(hdb, &rec, rbuf)) return false;
    if(hash > rec.hash){
      off = rec.left;
    } else if(hash < rec.hash){
      off = rec.right;
    } else {
      if(!rec.kbuf && !tchdbreadrecbody(hdb, &rec)) return false;
      int kcmp = tcreckeycmp(kbuf, ksiz, rec.kbuf, rec.ksiz);
      if(kcmp > 0){
        off = rec.left;
        TCFREE(rec.bbuf);
        rec.kbuf = NULL;
        rec.bbuf = NULL;
      } else if(kcmp < 0){
        off = rec.right;
        TCFREE(rec.bbuf);
        rec.kbuf = NULL;
        rec.bbuf = NULL;
      } else {
        hdb->iter = off;
        return true;
      }
    }
  }
  tchdbsetecode(hdb, TCENOREC, __FILE__, __LINE__, __func__);
  return false;
}


/* Process each record atomically of a hash database object.
   `hdb' specifies the hash database object.
   `func' specifies the pointer to the iterator function called for each record.
   `op' specifies an arbitrary pointer to be given as a parameter of the iterator function.
   If successful, the return value is true, else, it is false. */
static bool tchdbforeachimpl(TCHDB *hdb, TCITER iter, void *op){
  assert(hdb && iter);
  bool err = false;
  uint64_t off = hdb->frec;
  TCHREC rec;
  char rbuf[HDBIOBUFSIZ];
  bool cont = true;
  while(cont && off < hdb->fsiz){
    rec.off = off;
    if(!tchdbreadrec(hdb, &rec, rbuf)){
      err = true;
      break;
    }
    off += rec.rsiz;
    if(rec.magic == HDBMAGICREC){
      if(!rec.vbuf && !tchdbreadrecbody(hdb, &rec)){
        TCFREE(rec.bbuf);
        err = true;
      } else {
        if(hdb->zmode){
          int zsiz;
          char *zbuf;
          if(hdb->opts & HDBTDEFLATE){
            zbuf = _tc_inflate(rec.vbuf, rec.vsiz, &zsiz, _TCZMRAW);
          } else if(hdb->opts & HDBTBZIP){
            zbuf = _tc_bzdecompress(rec.vbuf, rec.vsiz, &zsiz);
          } else if(hdb->opts & HDBTTCBS){
            zbuf = tcbsdecode(rec.vbuf, rec.vsiz, &zsiz);
          } else {
            zbuf = hdb->dec(rec.vbuf, rec.vsiz, &zsiz, hdb->decop);
          }
          if(zbuf){
            cont = iter(rec.kbuf, rec.ksiz, zbuf, zsiz, op);
            TCFREE(zbuf);
          } else {
            tchdbsetecode(hdb, TCEMISC, __FILE__, __LINE__, __func__);
            err = true;
          }
        } else {
          cont = iter(rec.kbuf, rec.ksiz, rec.vbuf, rec.vsiz, op);
        }
      }
      TCFREE(rec.bbuf);
    }
  }
  return !err;
}


/* Lock a method of the hash database object.
   `hdb' specifies the hash database object.
   `wr' specifies whether the lock is writer or not.
   If successful, the return value is true, else, it is false. */
static bool tchdblockmethod(TCHDB *hdb, bool wr){
  assert(hdb);
  if(wr ? pthread_rwlock_wrlock(hdb->mmtx) != 0 : pthread_rwlock_rdlock(hdb->mmtx) != 0){
    tchdbsetecode(hdb, TCETHREAD, __FILE__, __LINE__, __func__);
    return false;
  }
  TCTESTYIELD();
  return true;
}


/* Unlock a method of the hash database object.
   `hdb' specifies the hash database object.
   If successful, the return value is true, else, it is false. */
static bool tchdbunlockmethod(TCHDB *hdb){
  assert(hdb);
  if(pthread_rwlock_unlock(hdb->mmtx) != 0){
    tchdbsetecode(hdb, TCETHREAD, __FILE__, __LINE__, __func__);
    return false;
  }
  TCTESTYIELD();
  return true;
}


/* Lock a record of the hash database object.
   `hdb' specifies the hash database object.
   `bidx' specifies the bucket index of the record.
   `wr' specifies whether the lock is writer or not.
   If successful, the return value is true, else, it is false. */
static bool tchdblockrecord(TCHDB *hdb, uint8_t bidx, bool wr){
  assert(hdb);
  if(wr ? pthread_rwlock_wrlock((pthread_rwlock_t *)hdb->rmtxs + bidx) != 0 :
     pthread_rwlock_rdlock((pthread_rwlock_t *)hdb->rmtxs + bidx) != 0){
    tchdbsetecode(hdb, TCETHREAD, __FILE__, __LINE__, __func__);
    return false;
  }
  TCTESTYIELD();
  return true;
}


/* Unlock a record of the hash database object.
   `hdb' specifies the hash database object.
   `bidx' specifies the bucket index of the record.
   If successful, the return value is true, else, it is false. */
static bool tchdbunlockrecord(TCHDB *hdb, uint8_t bidx){
  assert(hdb);
  if(pthread_rwlock_unlock((pthread_rwlock_t *)hdb->rmtxs + bidx) != 0){
    tchdbsetecode(hdb, TCETHREAD, __FILE__, __LINE__, __func__);
    return false;
  }
  TCTESTYIELD();
  return true;
}


/* Lock all records of the hash database object.
   `hdb' specifies the hash database object.
   `wr' specifies whether the lock is writer or not.
   If successful, the return value is true, else, it is false. */
static bool tchdblockallrecords(TCHDB *hdb, bool wr){
  assert(hdb);
  for(int i = 0; i <= UINT8_MAX; i++){
    if(wr ? pthread_rwlock_wrlock((pthread_rwlock_t *)hdb->rmtxs + i) != 0 :
       pthread_rwlock_rdlock((pthread_rwlock_t *)hdb->rmtxs + i) != 0){
      tchdbsetecode(hdb, TCETHREAD, __FILE__, __LINE__, __func__);
      while(--i >= 0){
        pthread_rwlock_unlock((pthread_rwlock_t *)hdb->rmtxs + i);
      }
      return false;
    }
  }
  TCTESTYIELD();
  return true;
}


/* Unlock all records of the hash database object.
   `hdb' specifies the hash database object.
   If successful, the return value is true, else, it is false. */
static bool tchdbunlockallrecords(TCHDB *hdb){
  assert(hdb);
  bool err = false;
  for(int i = UINT8_MAX; i >= 0; i--){
    if(pthread_rwlock_unlock((pthread_rwlock_t *)hdb->rmtxs + i)) err = true;
  }
  TCTESTYIELD();
  if(err){
    tchdbsetecode(hdb, TCETHREAD, __FILE__, __LINE__, __func__);
    return false;
  }
  return true;
}


/* Lock the whole database of the hash database object.
   `hdb' specifies the hash database object.
   If successful, the return value is true, else, it is false. */
static bool tchdblockdb(TCHDB *hdb){
  assert(hdb);
  if(pthread_mutex_lock(hdb->dmtx) != 0){
    tchdbsetecode(hdb, TCETHREAD, __FILE__, __LINE__, __func__);
    return false;
  }
  TCTESTYIELD();
  return true;
}


/* Unlock the whole database of the hash database object.
   `hdb' specifies the hash database object.
   If successful, the return value is true, else, it is false. */
static bool tchdbunlockdb(TCHDB *hdb){
  assert(hdb);
  if(pthread_mutex_unlock(hdb->dmtx) != 0){
    tchdbsetecode(hdb, TCETHREAD, __FILE__, __LINE__, __func__);
    return false;
  }
  TCTESTYIELD();
  return true;
}


/* Lock the write ahead logging file of the hash database object.
   `hdb' specifies the hash database object.
   If successful, the return value is true, else, it is false. */
static bool tchdblockwal(TCHDB *hdb){
  assert(hdb);
  if(pthread_mutex_lock(hdb->wmtx) != 0){
    tchdbsetecode(hdb, TCETHREAD, __FILE__, __LINE__, __func__);
    return false;
  }
  TCTESTYIELD();
  return true;
}


/* Unlock the write ahead logging file of the hash database object.
   `hdb' specifies the hash database object.
   If successful, the return value is true, else, it is false. */
static bool tchdbunlockwal(TCHDB *hdb){
  assert(hdb);
  if(pthread_mutex_unlock(hdb->wmtx) != 0){
    tchdbsetecode(hdb, TCETHREAD, __FILE__, __LINE__, __func__);
    return false;
  }
  TCTESTYIELD();
  return true;
}



/*************************************************************************************************
 * debugging functions
 *************************************************************************************************/


/* Print meta data of the header into the debugging output.
   `hdb' specifies the hash database object. */
void tchdbprintmeta(TCHDB *hdb){
  assert(hdb);
  if(hdb->dbgfd < 0) return;
  int dbgfd = (hdb->dbgfd == UINT16_MAX) ? 1 : hdb->dbgfd;
  char buf[HDBIOBUFSIZ];
  char *wp = buf;
  wp += sprintf(wp, "META:");
  wp += sprintf(wp, " mmtx=%p", (void *)hdb->mmtx);
  wp += sprintf(wp, " rmtxs=%p", (void *)hdb->rmtxs);
  wp += sprintf(wp, " dmtx=%p", (void *)hdb->dmtx);
  wp += sprintf(wp, " wmtx=%p", (void *)hdb->wmtx);
  wp += sprintf(wp, " eckey=%p", (void *)hdb->eckey);
  wp += sprintf(wp, " rpath=%s", hdb->rpath ? hdb->rpath : "-");
  wp += sprintf(wp, " type=%02X", hdb->type);
  wp += sprintf(wp, " flags=%02X", hdb->flags);
  wp += sprintf(wp, " bnum=%llu", (unsigned long long)hdb->bnum);
  wp += sprintf(wp, " apow=%u", hdb->apow);
  wp += sprintf(wp, " fpow=%u", hdb->fpow);
  wp += sprintf(wp, " opts=%u", hdb->opts);
  wp += sprintf(wp, " path=%s", hdb->path ? hdb->path : "-");
  wp += sprintf(wp, " fd=%d", hdb->fd);
  wp += sprintf(wp, " omode=%u", hdb->omode);
  wp += sprintf(wp, " rnum=%llu", (unsigned long long)hdb->rnum);
  wp += sprintf(wp, " fsiz=%llu", (unsigned long long)hdb->fsiz);
  wp += sprintf(wp, " frec=%llu", (unsigned long long)hdb->frec);
  wp += sprintf(wp, " dfcur=%llu", (unsigned long long)hdb->dfcur);
  wp += sprintf(wp, " iter=%llu", (unsigned long long)hdb->iter);
  wp += sprintf(wp, " map=%p", (void *)hdb->map);
  wp += sprintf(wp, " msiz=%llu", (unsigned long long)hdb->msiz);
  wp += sprintf(wp, " ba32=%p", (void *)hdb->ba32);
  wp += sprintf(wp, " ba64=%p", (void *)hdb->ba64);
  wp += sprintf(wp, " align=%u", hdb->align);
  wp += sprintf(wp, " runit=%u", hdb->runit);
  wp += sprintf(wp, " zmode=%u", hdb->zmode);
  wp += sprintf(wp, " fbpmax=%d", hdb->fbpmax);
  wp += sprintf(wp, " fbpool=%p", (void *)hdb->fbpool);
  wp += sprintf(wp, " fbpnum=%d", hdb->fbpnum);
  wp += sprintf(wp, " fbpmis=%d", hdb->fbpmis);
  wp += sprintf(wp, " drpool=%p", (void *)hdb->drpool);
  wp += sprintf(wp, " drpdef=%p", (void *)hdb->drpdef);
  wp += sprintf(wp, " drpoff=%llu", (unsigned long long)hdb->drpoff);
  wp += sprintf(wp, " recc=%p", (void *)hdb->recc);
  wp += sprintf(wp, " rcnum=%u", hdb->rcnum);
  wp += sprintf(wp, " ecode=%d", hdb->ecode);
  wp += sprintf(wp, " fatal=%u", hdb->fatal);
  wp += sprintf(wp, " inode=%llu", (unsigned long long)(uint64_t)hdb->inode);
  wp += sprintf(wp, " mtime=%llu", (unsigned long long)(uint64_t)hdb->mtime);
  wp += sprintf(wp, " dfunit=%u", hdb->dfunit);
  wp += sprintf(wp, " dfcnt=%u", hdb->dfcnt);
  wp += sprintf(wp, " tran=%d", hdb->tran);
  wp += sprintf(wp, " walfd=%d", hdb->walfd);
  wp += sprintf(wp, " walend=%llu", (unsigned long long)hdb->walend);
  wp += sprintf(wp, " dbgfd=%d", hdb->dbgfd);
  wp += sprintf(wp, " cnt_writerec=%lld", (long long)hdb->cnt_writerec);
  wp += sprintf(wp, " cnt_reuserec=%lld", (long long)hdb->cnt_reuserec);
  wp += sprintf(wp, " cnt_moverec=%lld", (long long)hdb->cnt_moverec);
  wp += sprintf(wp, " cnt_readrec=%lld", (long long)hdb->cnt_readrec);
  wp += sprintf(wp, " cnt_searchfbp=%lld", (long long)hdb->cnt_searchfbp);
  wp += sprintf(wp, " cnt_insertfbp=%lld", (long long)hdb->cnt_insertfbp);
  wp += sprintf(wp, " cnt_splicefbp=%lld", (long long)hdb->cnt_splicefbp);
  wp += sprintf(wp, " cnt_dividefbp=%lld", (long long)hdb->cnt_dividefbp);
  wp += sprintf(wp, " cnt_mergefbp=%lld", (long long)hdb->cnt_mergefbp);
  wp += sprintf(wp, " cnt_reducefbp=%lld", (long long)hdb->cnt_reducefbp);
  wp += sprintf(wp, " cnt_appenddrp=%lld", (long long)hdb->cnt_appenddrp);
  wp += sprintf(wp, " cnt_deferdrp=%lld", (long long)hdb->cnt_deferdrp);
  wp += sprintf(wp, " cnt_flushdrp=%lld", (long long)hdb->cnt_flushdrp);
  wp += sprintf(wp, " cnt_adjrecc=%lld", (long long)hdb->cnt_adjrecc);
  wp += sprintf(wp, " cnt_defrag=%lld", (long long)hdb->cnt_defrag);
  wp += sprintf(wp, " cnt_shiftrec=%lld", (long long)hdb->cnt_shiftrec);
  wp += sprintf(wp, " cnt_trunc=%lld", (long long)hdb->cnt_trunc);
  *(wp++) = '\n';
  tcwrite(dbgfd, buf, wp - buf);
}


/* Print a record information into the debugging output.
   `hdb' specifies the hash database object.
   `rec' specifies the record. */
void tchdbprintrec(TCHDB *hdb, TCHREC *rec){
  assert(hdb && rec);
  if(hdb->dbgfd < 0) return;
  int dbgfd = (hdb->dbgfd == UINT16_MAX) ? 1 : hdb->dbgfd;
  char buf[HDBIOBUFSIZ];
  char *wp = buf;
  wp += sprintf(wp, "REC:");
  wp += sprintf(wp, " off=%llu", (unsigned long long)rec->off);
  wp += sprintf(wp, " rsiz=%u", rec->rsiz);
  wp += sprintf(wp, " magic=%02X", rec->magic);
  wp += sprintf(wp, " hash=%02X", rec->hash);
  wp += sprintf(wp, " left=%llu", (unsigned long long)rec->left);
  wp += sprintf(wp, " right=%llu", (unsigned long long)rec->right);
  wp += sprintf(wp, " ksiz=%u", rec->ksiz);
  wp += sprintf(wp, " vsiz=%u", rec->vsiz);
  wp += sprintf(wp, " psiz=%u", rec->psiz);
  wp += sprintf(wp, " kbuf=%p", (void *)rec->kbuf);
  wp += sprintf(wp, " vbuf=%p", (void *)rec->vbuf);
  wp += sprintf(wp, " boff=%llu", (unsigned long long)rec->boff);
  wp += sprintf(wp, " bbuf=%p", (void *)rec->bbuf);
  *(wp++) = '\n';
  tcwrite(dbgfd, buf, wp - buf);
}



// END OF FILE
/*************************************************************************************************
 * The B+ tree database API of Tokyo Cabinet
 *                                                               Copyright (C) 2006-2012 FAL Labs
 * This file is part of Tokyo Cabinet.
 * Tokyo Cabinet 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 any later version.  Tokyo Cabinet 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 Tokyo
 * Cabinet; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307 USA.
 *************************************************************************************************/


#include "tcutil.h"
#include "tchdb.h"
#include "tcbdb.h"
#include "myconf.h"

#define BDBOPAQUESIZ   64                // size of using opaque field
#define BDBLEFTOPQSIZ  64                // size of left opaque field
#define BDBPAGEBUFSIZ  32768             // size of a buffer to read each page
#define BDBNODEIDBASE  ((1LL<<48)+1)     // base number of node ID
#define BDBLEVELMAX    64                // max level of B+ tree
#define BDBCACHEOUT    8                 // number of pages in a process of cacheout

#define BDBDEFLMEMB    128               // default number of members in each leaf
#define BDBMINLMEMB    4                 // minimum number of members in each leaf
#define BDBDEFNMEMB    256               // default number of members in each node
#define BDBMINNMEMB    4                 // minimum number of members in each node
#define BDBDEFBNUM     32749             // default bucket number
#define BDBDEFAPOW     8                 // default alignment power
#define BDBDEFFPOW     10                // default free block pool power
#define BDBDEFLCNUM    1024              // default number of leaf cache
#define BDBDEFNCNUM    512               // default number of node cache
#define BDBDEFLSMAX    16384             // default maximum size of each leaf
#define BDBMINLSMAX    512               // minimum maximum size of each leaf

typedef struct {                         // type of structure for a record
  int ksiz;                              // size of the key region
  int vsiz;                              // size of the value region
  TCLIST *rest;                          // list of value objects
} BDBREC;

typedef struct {                         // type of structure for a leaf page
  uint64_t id;                           // ID number of the leaf
  TCPTRLIST *recs;                       // list of records
  int size;                              // predicted size of serialized buffer
  uint64_t prev;                         // ID number of the previous leaf
  uint64_t next;                         // ID number of the next leaf
  bool dirty;                            // whether to be written back
  bool dead;                             // whether to be removed
} BDBLEAF;

typedef struct {                         // type of structure for a page index
  uint64_t pid;                          // ID number of the referring page
  int ksiz;                              // size of the key region
} BDBIDX;

typedef struct {                         // type of structure for a node page
  uint64_t id;                           // ID number of the node
  uint64_t heir;                         // ID of the child before the first index
  TCPTRLIST *idxs;                       // list of indices
  bool dirty;                            // whether to be written back
  bool dead;                             // whether to be removed
} BDBNODE;

enum {                                   // enumeration for duplication behavior
  BDBPDOVER,                             // overwrite an existing value
  BDBPDKEEP,                             // keep the existing value
  BDBPDCAT,                              // concatenate values
  BDBPDDUP,                              // allow duplication of keys
  BDBPDDUPB,                             // allow backward duplication
  BDBPDADDINT,                           // add an integer
  BDBPDADDDBL,                           // add a real number
  BDBPDPROC                              // process by a callback function
};

typedef struct {                         // type of structure for a duplication callback
  TCPDPROC proc;                         // function pointer
  void *op;                              // opaque pointer
} BDBPDPROCOP;


/* private macros */
#define BDBLOCKMETHOD(TC_bdb, TC_wr)                            \
  ((TC_bdb)->mmtx ? tcbdblockmethod((TC_bdb), (TC_wr)) : true)
#define BDBUNLOCKMETHOD(TC_bdb)                         \
  ((TC_bdb)->mmtx ? tcbdbunlockmethod(TC_bdb) : true)
#define BDBLOCKCACHE(TC_bdb)                            \
  ((TC_bdb)->mmtx ? tcbdblockcache(TC_bdb) : true)
#define BDBUNLOCKCACHE(TC_bdb)                          \
  ((TC_bdb)->mmtx ? tcbdbunlockcache(TC_bdb) : true)
#define BDBTHREADYIELD(TC_bdb)                          \
  do { if((TC_bdb)->mmtx) sched_yield(); } while(false)


/* private function prototypes */
static void tcbdbclear(TCBDB *bdb);
static void tcbdbdumpmeta(TCBDB *bdb);
static void tcbdbloadmeta(TCBDB *bdb);
static BDBLEAF *tcbdbleafnew(TCBDB *bdb, uint64_t prev, uint64_t next);
static bool tcbdbleafcacheout(TCBDB *bdb, BDBLEAF *leaf);
static bool tcbdbleafsave(TCBDB *bdb, BDBLEAF *leaf);
static BDBLEAF *tcbdbleafload(TCBDB *bdb, uint64_t id);
static bool tcbdbleafcheck(TCBDB *bdb, uint64_t id);
static BDBLEAF *tcbdbgethistleaf(TCBDB *bdb, const char *kbuf, int ksiz, uint64_t id);
static bool tcbdbleafaddrec(TCBDB *bdb, BDBLEAF *leaf, int dmode,
                            const char *kbuf, int ksiz, const char *vbuf, int vsiz);
static BDBLEAF *tcbdbleafdivide(TCBDB *bdb, BDBLEAF *leaf);
static bool tcbdbleafkill(TCBDB *bdb, BDBLEAF *leaf);
static BDBNODE *tcbdbnodenew(TCBDB *bdb, uint64_t heir);
static bool tcbdbnodecacheout(TCBDB *bdb, BDBNODE *node);
static bool tcbdbnodesave(TCBDB *bdb, BDBNODE *node);
static BDBNODE *tcbdbnodeload(TCBDB *bdb, uint64_t id);
static void tcbdbnodeaddidx(TCBDB *bdb, BDBNODE *node, bool order, uint64_t pid,
                            const char *kbuf, int ksiz);
static bool tcbdbnodesubidx(TCBDB *bdb, BDBNODE *node, uint64_t pid);
static uint64_t tcbdbsearchleaf(TCBDB *bdb, const char *kbuf, int ksiz);
static BDBREC *tcbdbsearchrec(TCBDB *bdb, BDBLEAF *leaf, const char *kbuf, int ksiz, int *ip);
static void tcbdbremoverec(TCBDB *bdb, BDBLEAF *leaf, BDBREC *rec, int ri);
static bool tcbdbcacheadjust(TCBDB *bdb);
static void tcbdbcachepurge(TCBDB *bdb);
static bool tcbdbopenimpl(TCBDB *bdb, const char *path, int omode);
static bool tcbdbcloseimpl(TCBDB *bdb);
static bool tcbdbputimpl(TCBDB *bdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz,
                         int dmode);
static bool tcbdboutimpl(TCBDB *bdb, const char *kbuf, int ksiz);
static bool tcbdboutlist(TCBDB *bdb, const char *kbuf, int ksiz);
static const char *tcbdbgetimpl(TCBDB *bdb, const char *kbuf, int ksiz, int *sp);
static int tcbdbgetnum(TCBDB *bdb, const char *kbuf, int ksiz);
static TCLIST *tcbdbgetlist(TCBDB *bdb, const char *kbuf, int ksiz);
static bool tcbdbrangeimpl(TCBDB *bdb, const char *bkbuf, int bksiz, bool binc,
                           const char *ekbuf, int eksiz, bool einc, int max, TCLIST *keys);
static bool tcbdbrangefwm(TCBDB *bdb, const char *pbuf, int psiz, int max, TCLIST *keys);
static bool tcbdboptimizeimpl(TCBDB *bdb, int32_t lmemb, int32_t nmemb,
                              int64_t bnum, int8_t apow, int8_t fpow, uint8_t opts);
static bool tcbdbvanishimpl(TCBDB *bdb);
static bool tcbdblockmethod(TCBDB *bdb, bool wr);
static bool tcbdbunlockmethod(TCBDB *bdb);
static bool tcbdblockcache(TCBDB *bdb);
static bool tcbdbunlockcache(TCBDB *bdb);
static bool tcbdbcurfirstimpl(BDBCUR *cur);
static bool tcbdbcurlastimpl(BDBCUR *cur);
static bool tcbdbcurjumpimpl(BDBCUR *cur, const char *kbuf, int ksiz, bool forward);
static bool tcbdbcuradjust(BDBCUR *cur, bool forward);
static bool tcbdbcurprevimpl(BDBCUR *cur);
static bool tcbdbcurnextimpl(BDBCUR *cur);
static bool tcbdbcurputimpl(BDBCUR *cur, const char *vbuf, int vsiz, int mode);
static bool tcbdbcuroutimpl(BDBCUR *cur);
static bool tcbdbcurrecimpl(BDBCUR *cur, const char **kbp, int *ksp, const char **vbp, int *vsp);
static bool tcbdbforeachimpl(TCBDB *bdb, TCITER iter, void *op);


/* debugging function prototypes */
void tcbdbprintmeta(TCBDB *bdb);
void tcbdbprintleaf(TCBDB *bdb, BDBLEAF *leaf);
void tcbdbprintnode(TCBDB *bdb, BDBNODE *node);



/*************************************************************************************************
 * API
 *************************************************************************************************/


/* Get the message string corresponding to an error code. */
const char *tcbdberrmsg(int ecode){
  return tcerrmsg(ecode);
}


/* Create a B+ tree database object. */
TCBDB *tcbdbnew(void){
  TCBDB *bdb;
  TCMALLOC(bdb, sizeof(*bdb));
  tcbdbclear(bdb);
  bdb->hdb = tchdbnew();
  TCMALLOC(bdb->hist, sizeof(*bdb->hist) * BDBLEVELMAX);
  tchdbtune(bdb->hdb, BDBDEFBNUM, BDBDEFAPOW, BDBDEFFPOW, 0);
  tchdbsetxmsiz(bdb->hdb, 0);
  return bdb;
}


/* Delete a B+ tree database object. */
void tcbdbdel(TCBDB *bdb){
  assert(bdb);
  if(bdb->open) tcbdbclose(bdb);
  TCFREE(bdb->hist);
  tchdbdel(bdb->hdb);
  if(bdb->mmtx){
    pthread_mutex_destroy(bdb->cmtx);
    pthread_rwlock_destroy(bdb->mmtx);
    TCFREE(bdb->cmtx);
    TCFREE(bdb->mmtx);
  }
  TCFREE(bdb);
}


/* Get the last happened error code of a B+ tree database object. */
int tcbdbecode(TCBDB *bdb){
  assert(bdb);
  return tchdbecode(bdb->hdb);
}


/* Set mutual exclusion control of a B+ tree database object for threading. */
bool tcbdbsetmutex(TCBDB *bdb){
  assert(bdb);
  if(!TCUSEPTHREAD) return true;
  if(bdb->mmtx || bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  TCMALLOC(bdb->mmtx, sizeof(pthread_rwlock_t));
  TCMALLOC(bdb->cmtx, sizeof(pthread_mutex_t));
  bool err = false;
  if(pthread_rwlock_init(bdb->mmtx, NULL) != 0) err = true;
  if(pthread_mutex_init(bdb->cmtx, NULL) != 0) err = true;
  if(err){
    TCFREE(bdb->cmtx);
    TCFREE(bdb->mmtx);
    bdb->cmtx = NULL;
    bdb->mmtx = NULL;
    return false;
  }
  return tchdbsetmutex(bdb->hdb);
}


/* Set the custom comparison function of a B+ tree database object. */
bool tcbdbsetcmpfunc(TCBDB *bdb, TCCMP cmp, void *cmpop){
  assert(bdb && cmp);
  if(bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  bdb->cmp = cmp;
  bdb->cmpop = cmpop;
  return true;
}


/* Set the tuning parameters of a B+ tree database object. */
bool tcbdbtune(TCBDB *bdb, int32_t lmemb, int32_t nmemb,
               int64_t bnum, int8_t apow, int8_t fpow, uint8_t opts){
  assert(bdb);
  if(bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  bdb->lmemb = (lmemb > 0) ? tclmax(lmemb, BDBMINLMEMB) : BDBDEFLMEMB;
  bdb->nmemb = (nmemb > 0) ? tclmax(nmemb, BDBMINNMEMB) : BDBDEFNMEMB;
  bdb->opts = opts;
  uint8_t hopts = 0;
  if(opts & BDBTLARGE) hopts |= HDBTLARGE;
  if(opts & BDBTDEFLATE) hopts |= HDBTDEFLATE;
  if(opts & BDBTBZIP) hopts |= HDBTBZIP;
  if(opts & BDBTTCBS) hopts |= HDBTTCBS;
  if(opts & BDBTEXCODEC) hopts |= HDBTEXCODEC;
  bnum = (bnum > 0) ? bnum : BDBDEFBNUM;
  apow = (apow >= 0) ? apow : BDBDEFAPOW;
  fpow = (fpow >= 0) ? fpow : BDBDEFFPOW;
  return tchdbtune(bdb->hdb, bnum, apow, fpow, hopts);
}


/* Set the caching parameters of a B+ tree database object. */
bool tcbdbsetcache(TCBDB *bdb, int32_t lcnum, int32_t ncnum){
  assert(bdb);
  if(bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  if(lcnum > 0) bdb->lcnum = tclmax(lcnum, BDBLEVELMAX);
  if(ncnum > 0) bdb->ncnum = tclmax(ncnum, BDBLEVELMAX);
  return true;
}


/* Set the size of the extra mapped memory of a B+ tree database object. */
bool tcbdbsetxmsiz(TCBDB *bdb, int64_t xmsiz){
  assert(bdb);
  if(bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  return tchdbsetxmsiz(bdb->hdb, xmsiz);
}


/* Set the unit step number of auto defragmentation of a B+ tree database object. */
bool tcbdbsetdfunit(TCBDB *bdb, int32_t dfunit){
  assert(bdb);
  if(bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  return tchdbsetdfunit(bdb->hdb, dfunit);
}


/* Open a database file and connect a B+ tree database object. */
bool tcbdbopen(TCBDB *bdb, const char *path, int omode){
  assert(bdb && path);
  if(!BDBLOCKMETHOD(bdb, true)) return false;
  if(bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  bool rv = tcbdbopenimpl(bdb, path, omode);
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Close a B+ tree database object. */
bool tcbdbclose(TCBDB *bdb){
  assert(bdb);
  if(!BDBLOCKMETHOD(bdb, true)) return false;
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  bool rv = tcbdbcloseimpl(bdb);
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Store a record into a B+ tree database object. */
bool tcbdbput(TCBDB *bdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(bdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  if(!BDBLOCKMETHOD(bdb, true)) return false;
  if(!bdb->open || !bdb->wmode){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  bool rv = tcbdbputimpl(bdb, kbuf, ksiz, vbuf, vsiz, BDBPDOVER);
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Store a string record into a B+ tree database object. */
bool tcbdbput2(TCBDB *bdb, const char *kstr, const char *vstr){
  assert(bdb && kstr && vstr);
  return tcbdbput(bdb, kstr, strlen(kstr), vstr, strlen(vstr));
}


/* Store a new record into a B+ tree database object. */
bool tcbdbputkeep(TCBDB *bdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(bdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  if(!BDBLOCKMETHOD(bdb, true)) return false;
  if(!bdb->open || !bdb->wmode){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  bool rv = tcbdbputimpl(bdb, kbuf, ksiz, vbuf, vsiz, BDBPDKEEP);
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Store a new string record into a B+ tree database object. */
bool tcbdbputkeep2(TCBDB *bdb, const char *kstr, const char *vstr){
  assert(bdb && kstr && vstr);
  return tcbdbputkeep(bdb, kstr, strlen(kstr), vstr, strlen(vstr));
}


/* Concatenate a value at the end of the existing record in a B+ tree database object. */
bool tcbdbputcat(TCBDB *bdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(bdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  if(!BDBLOCKMETHOD(bdb, true)) return false;
  if(!bdb->open || !bdb->wmode){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  bool rv = tcbdbputimpl(bdb, kbuf, ksiz, vbuf, vsiz, BDBPDCAT);
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Concatenate a string value at the end of the existing record in a B+ tree database object. */
bool tcbdbputcat2(TCBDB *bdb, const char *kstr, const char *vstr){
  assert(bdb && kstr && vstr);
  return tcbdbputcat(bdb, kstr, strlen(kstr), vstr, strlen(vstr));
}


/* Store a record into a B+ tree database object with allowing duplication of keys. */
bool tcbdbputdup(TCBDB *bdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(bdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  if(!BDBLOCKMETHOD(bdb, true)) return false;
  if(!bdb->open || !bdb->wmode){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  bool rv = tcbdbputimpl(bdb, kbuf, ksiz, vbuf, vsiz, BDBPDDUP);
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Store a string record into a B+ tree database object with allowing duplication of keys. */
bool tcbdbputdup2(TCBDB *bdb, const char *kstr, const char *vstr){
  assert(bdb && kstr && vstr);
  return tcbdbputdup(bdb, kstr, strlen(kstr), vstr, strlen(vstr));
}


/* Store records into a B+ tree database object with allowing duplication of keys. */
bool tcbdbputdup3(TCBDB *bdb, const void *kbuf, int ksiz, const TCLIST *vals){
  assert(bdb && kbuf && ksiz >= 0 && vals);
  if(!BDBLOCKMETHOD(bdb, true)) return false;
  if(!bdb->open || !bdb->wmode){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  bool err = false;
  int ln = TCLISTNUM(vals);
  for(int i = 0; i < ln; i++){
    const char *vbuf;
    int vsiz;
    TCLISTVAL(vbuf, vals, i, vsiz);
    if(!tcbdbputimpl(bdb, kbuf, ksiz, vbuf, vsiz, BDBPDDUP)) err = true;
  }
  BDBUNLOCKMETHOD(bdb);
  return !err;
}


/* Remove a record of a B+ tree database object. */
bool tcbdbout(TCBDB *bdb, const void *kbuf, int ksiz){
  assert(bdb && kbuf && ksiz >= 0);
  if(!BDBLOCKMETHOD(bdb, true)) return false;
  if(!bdb->open || !bdb->wmode){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  bool rv = tcbdboutimpl(bdb, kbuf, ksiz);
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Remove a string record of a B+ tree database object. */
bool tcbdbout2(TCBDB *bdb, const char *kstr){
  assert(bdb && kstr);
  return tcbdbout(bdb, kstr, strlen(kstr));
}


/* Remove records of a B+ tree database object. */
bool tcbdbout3(TCBDB *bdb, const void *kbuf, int ksiz){
  assert(bdb && kbuf && ksiz >= 0);
  if(!BDBLOCKMETHOD(bdb, true)) return false;
  if(!bdb->open || !bdb->wmode){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  bool rv = tcbdboutlist(bdb, kbuf, ksiz);
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Retrieve a record in a B+ tree database object. */
void *tcbdbget(TCBDB *bdb, const void *kbuf, int ksiz, int *sp){
  assert(bdb && kbuf && ksiz >= 0 && sp);
  if(!BDBLOCKMETHOD(bdb, false)) return NULL;
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return NULL;
  }
  const char *vbuf = tcbdbgetimpl(bdb, kbuf, ksiz, sp);
  char *rv;
  if(vbuf){
    TCMEMDUP(rv, vbuf, *sp);
  } else {
    rv = NULL;
  }
  bool adj = TCMAPRNUM(bdb->leafc) > bdb->lcnum || TCMAPRNUM(bdb->nodec) > bdb->ncnum;
  BDBUNLOCKMETHOD(bdb);
  if(adj && BDBLOCKMETHOD(bdb, true)){
    if(!bdb->tran && !tcbdbcacheadjust(bdb)){
      TCFREE(rv);
      rv = NULL;
    }
    BDBUNLOCKMETHOD(bdb);
  }
  return rv;
}


/* Retrieve a string record in a B+ tree database object. */
char *tcbdbget2(TCBDB *bdb, const char *kstr){
  assert(bdb && kstr);
  int vsiz;
  return tcbdbget(bdb, kstr, strlen(kstr), &vsiz);
}


/* Retrieve a record in a B+ tree database object and write the value into a buffer. */
const void *tcbdbget3(TCBDB *bdb, const void *kbuf, int ksiz, int *sp){
  assert(bdb && kbuf && ksiz >= 0 && sp);
  if(!BDBLOCKMETHOD(bdb, false)) return NULL;
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return NULL;
  }
  const char *rv = tcbdbgetimpl(bdb, kbuf, ksiz, sp);
  bool adj = TCMAPRNUM(bdb->leafc) > bdb->lcnum || TCMAPRNUM(bdb->nodec) > bdb->ncnum;
  BDBUNLOCKMETHOD(bdb);
  if(adj && BDBLOCKMETHOD(bdb, true)){
    if(!bdb->tran && !tcbdbcacheadjust(bdb)) rv = NULL;
    BDBUNLOCKMETHOD(bdb);
  }
  return rv;
}


/* Retrieve records in a B+ tree database object. */
TCLIST *tcbdbget4(TCBDB *bdb, const void *kbuf, int ksiz){
  assert(bdb && kbuf && ksiz >= 0);
  if(!BDBLOCKMETHOD(bdb, false)) return NULL;
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return NULL;
  }
  TCLIST *rv = tcbdbgetlist(bdb, kbuf, ksiz);
  bool adj = TCMAPRNUM(bdb->leafc) > bdb->lcnum || TCMAPRNUM(bdb->nodec) > bdb->ncnum;
  BDBUNLOCKMETHOD(bdb);
  if(adj && BDBLOCKMETHOD(bdb, true)){
    if(!bdb->tran && !tcbdbcacheadjust(bdb)){
      if(rv) tclistdel(rv);
      rv = NULL;
    }
    BDBUNLOCKMETHOD(bdb);
  }
  return rv;
}


/* Get the number of records corresponding a key in a B+ tree database object. */
int tcbdbvnum(TCBDB *bdb, const void *kbuf, int ksiz){
  assert(bdb && kbuf && ksiz >= 0);
  if(!BDBLOCKMETHOD(bdb, false)) return 0;
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return 0;
  }
  int rv = tcbdbgetnum(bdb, kbuf, ksiz);
  bool adj = TCMAPRNUM(bdb->leafc) > bdb->lcnum || TCMAPRNUM(bdb->nodec) > bdb->ncnum;
  BDBUNLOCKMETHOD(bdb);
  if(adj && BDBLOCKMETHOD(bdb, true)){
    if(!bdb->tran && !tcbdbcacheadjust(bdb)) rv = 0;
    BDBUNLOCKMETHOD(bdb);
  }
  return rv;
}


/* Get the number of records corresponding a string key in a B+ tree database object. */
int tcbdbvnum2(TCBDB *bdb, const char *kstr){
  assert(bdb && kstr);
  return tcbdbvnum(bdb, kstr, strlen(kstr));
}


/* Get the size of the value of a record in a B+ tree database object. */
int tcbdbvsiz(TCBDB *bdb, const void *kbuf, int ksiz){
  assert(bdb && kbuf && ksiz >= 0);
  int vsiz;
  if(!tcbdbget3(bdb, kbuf, ksiz, &vsiz)) return -1;
  return vsiz;
}


/* Get the size of the value of a string record in a B+ tree database object. */
int tcbdbvsiz2(TCBDB *bdb, const char *kstr){
  assert(bdb && kstr);
  return tcbdbvsiz(bdb, kstr, strlen(kstr));
}


/* Get keys of ranged records in a B+ tree database object. */
TCLIST *tcbdbrange(TCBDB *bdb, const void *bkbuf, int bksiz, bool binc,
                   const void *ekbuf, int eksiz, bool einc, int max){
  assert(bdb);
  TCLIST *keys = tclistnew();
  if(!BDBLOCKMETHOD(bdb, false)) return keys;
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return keys;
  }
  tcbdbrangeimpl(bdb, bkbuf, bksiz, binc, ekbuf, eksiz, einc, max, keys);
  bool adj = TCMAPRNUM(bdb->leafc) > bdb->lcnum || TCMAPRNUM(bdb->nodec) > bdb->ncnum;
  BDBUNLOCKMETHOD(bdb);
  if(adj && BDBLOCKMETHOD(bdb, true)){
    tcbdbcacheadjust(bdb);
    BDBUNLOCKMETHOD(bdb);
  }
  return keys;
}


/* Get string keys of ranged records in a B+ tree database object. */
TCLIST *tcbdbrange2(TCBDB *bdb, const char *bkstr, bool binc,
                    const char *ekstr, bool einc, int max){
  assert(bdb);
  return tcbdbrange(bdb, bkstr, bkstr ? strlen(bkstr) : 0, binc,
                    ekstr, ekstr ? strlen(ekstr) : 0, einc, max);
}


/* Get forward matching keys in a B+ tree database object. */
TCLIST *tcbdbfwmkeys(TCBDB *bdb, const void *pbuf, int psiz, int max){
  assert(bdb && pbuf && psiz >= 0);
  TCLIST *keys = tclistnew();
  if(!BDBLOCKMETHOD(bdb, false)) return keys;
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return keys;
  }
  tcbdbrangefwm(bdb, pbuf, psiz, max, keys);
  bool adj = TCMAPRNUM(bdb->leafc) > bdb->lcnum || TCMAPRNUM(bdb->nodec) > bdb->ncnum;
  BDBUNLOCKMETHOD(bdb);
  if(adj && BDBLOCKMETHOD(bdb, true)){
    tcbdbcacheadjust(bdb);
    BDBUNLOCKMETHOD(bdb);
  }
  return keys;
}


/* Get forward matching string keys in a B+ tree database object. */
TCLIST *tcbdbfwmkeys2(TCBDB *bdb, const char *pstr, int max){
  assert(bdb && pstr);
  return tcbdbfwmkeys(bdb, pstr, strlen(pstr), max);
}


/* Add an integer to a record in a B+ tree database object. */
int tcbdbaddint(TCBDB *bdb, const void *kbuf, int ksiz, int num){
  assert(bdb && kbuf && ksiz >= 0);
  if(!BDBLOCKMETHOD(bdb, true)) return INT_MIN;
  if(!bdb->open || !bdb->wmode){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return INT_MIN;
  }
  bool rv = tcbdbputimpl(bdb, kbuf, ksiz, (char *)&num, sizeof(num), BDBPDADDINT);
  BDBUNLOCKMETHOD(bdb);
  return rv ? num : INT_MIN;
}


/* Add a real number to a record in a B+ tree database object. */
double tcbdbadddouble(TCBDB *bdb, const void *kbuf, int ksiz, double num){
  assert(bdb && kbuf && ksiz >= 0);
  if(!BDBLOCKMETHOD(bdb, true)) return nan("");
  if(!bdb->open || !bdb->wmode){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return nan("");
  }
  bool rv = tcbdbputimpl(bdb, kbuf, ksiz, (char *)&num, sizeof(num), BDBPDADDDBL);
  BDBUNLOCKMETHOD(bdb);
  return rv ? num : nan("");
}


/* Synchronize updated contents of a B+ tree database object with the file and the device. */
bool tcbdbsync(TCBDB *bdb){
  assert(bdb);
  if(!BDBLOCKMETHOD(bdb, true)) return false;
  if(!bdb->open || !bdb->wmode || bdb->tran){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  bool rv = tcbdbmemsync(bdb, true);
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Optimize the file of a B+ tree database object. */
bool tcbdboptimize(TCBDB *bdb, int32_t lmemb, int32_t nmemb,
                   int64_t bnum, int8_t apow, int8_t fpow, uint8_t opts){
  assert(bdb);
  if(!BDBLOCKMETHOD(bdb, true)) return false;
  if(!bdb->open || !bdb->wmode || bdb->tran){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  BDBTHREADYIELD(bdb);
  bool rv = tcbdboptimizeimpl(bdb, lmemb, nmemb, bnum, apow, fpow, opts);
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Remove all records of a B+ tree database object. */
bool tcbdbvanish(TCBDB *bdb){
  assert(bdb);
  if(!BDBLOCKMETHOD(bdb, true)) return false;
  if(!bdb->open || !bdb->wmode || bdb->tran){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  BDBTHREADYIELD(bdb);
  bool rv = tcbdbvanishimpl(bdb);
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Copy the database file of a B+ tree database object. */
bool tcbdbcopy(TCBDB *bdb, const char *path){
  assert(bdb && path);
  if(!BDBLOCKMETHOD(bdb, true)) return false;
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  BDBTHREADYIELD(bdb);
  TCLIST *lids = tclistnew();
  TCLIST *nids = tclistnew();
  const char *vbuf;
  int vsiz;
  TCMAP *leafc = bdb->leafc;
  tcmapiterinit(leafc);
  while((vbuf = tcmapiternext(leafc, &vsiz)) != NULL){
    TCLISTPUSH(lids, vbuf, vsiz);
  }
  TCMAP *nodec = bdb->nodec;
  tcmapiterinit(nodec);
  while((vbuf = tcmapiternext(nodec, &vsiz)) != NULL){
    TCLISTPUSH(nids, vbuf, vsiz);
  }
  BDBUNLOCKMETHOD(bdb);
  bool err = false;
  int ln = TCLISTNUM(lids);
  for(int i = 0; i < ln; i++){
    vbuf = TCLISTVALPTR(lids, i);
    if(BDBLOCKMETHOD(bdb, true)){
      BDBTHREADYIELD(bdb);
      if(bdb->open){
        int rsiz;
        BDBLEAF *leaf = (BDBLEAF *)tcmapget(bdb->leafc, vbuf, sizeof(leaf->id), &rsiz);
        if(leaf && leaf->dirty && !tcbdbleafsave(bdb, leaf)) err = true;
      } else {
        err = true;
      }
      BDBUNLOCKMETHOD(bdb);
    } else {
      err = true;
    }
  }
  ln = TCLISTNUM(nids);
  for(int i = 0; i < ln; i++){
    vbuf = TCLISTVALPTR(nids, i);
    if(BDBLOCKMETHOD(bdb, true)){
      if(bdb->open){
        int rsiz;
        BDBNODE *node = (BDBNODE *)tcmapget(bdb->nodec, vbuf, sizeof(node->id), &rsiz);
        if(node && node->dirty && !tcbdbnodesave(bdb, node)) err = true;
      } else {
        err = true;
      }
      BDBUNLOCKMETHOD(bdb);
    } else {
      err = true;
    }
  }
  tclistdel(nids);
  tclistdel(lids);
  if(!tcbdbtranbegin(bdb)) err = true;
  if(BDBLOCKMETHOD(bdb, false)){
    BDBTHREADYIELD(bdb);
    if(!tchdbcopy(bdb->hdb, path)) err = true;
    BDBUNLOCKMETHOD(bdb);
  } else {
    err = true;
  }
  if(!tcbdbtrancommit(bdb)) err = true;
  return !err;
}


/* Begin the transaction of a B+ tree database object. */
bool tcbdbtranbegin(TCBDB *bdb){
  assert(bdb);
  for(double wsec = 1.0 / sysconf(_SC_CLK_TCK); true; wsec *= 2){
    if(!BDBLOCKMETHOD(bdb, true)) return false;
    if(!bdb->open || !bdb->wmode){
      tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
      BDBUNLOCKMETHOD(bdb);
      return false;
    }
    if(!bdb->tran) break;
    BDBUNLOCKMETHOD(bdb);
    if(wsec > 1.0) wsec = 1.0;
    tcsleep(wsec);
  }
  if(!tcbdbmemsync(bdb, false)){
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  if(!tchdbtranbegin(bdb->hdb)){
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  bdb->tran = true;
  TCMEMDUP(bdb->rbopaque, bdb->opaque, BDBOPAQUESIZ);
  BDBUNLOCKMETHOD(bdb);
  return true;
}


/* Commit the transaction of a B+ tree database object. */
bool tcbdbtrancommit(TCBDB *bdb){
  assert(bdb);
  if(!BDBLOCKMETHOD(bdb, true)) return false;
  if(!bdb->open || !bdb->wmode || !bdb->tran){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  TCFREE(bdb->rbopaque);
  bdb->tran = false;
  bdb->rbopaque = NULL;
  bool err = false;
  if(!tcbdbmemsync(bdb, false)) err = true;
  if(!tcbdbcacheadjust(bdb)) err = true;
  if(err){
    tchdbtranabort(bdb->hdb);
  } else if(!tchdbtrancommit(bdb->hdb)){
    err = true;
  }
  BDBUNLOCKMETHOD(bdb);
  return !err;
}


/* Abort the transaction of a B+ tree database object. */
bool tcbdbtranabort(TCBDB *bdb){
  assert(bdb);
  if(!BDBLOCKMETHOD(bdb, true)) return false;
  if(!bdb->open || !bdb->wmode || !bdb->tran){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  tcbdbcachepurge(bdb);
  memcpy(bdb->opaque, bdb->rbopaque, BDBOPAQUESIZ);
  tcbdbloadmeta(bdb);
  TCFREE(bdb->rbopaque);
  bdb->tran = false;
  bdb->rbopaque = NULL;
  bdb->hleaf = 0;
  bdb->lleaf = 0;
  bdb->clock++;
  bool err = false;
  if(!tcbdbcacheadjust(bdb)) err = true;
  if(!tchdbtranvoid(bdb->hdb)) err = true;
  BDBUNLOCKMETHOD(bdb);
  return !err;
}


/* Get the file path of a B+ tree database object. */
const char *tcbdbpath(TCBDB *bdb){
  assert(bdb);
  if(!BDBLOCKMETHOD(bdb, false)) return NULL;
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return NULL;
  }
  const char *rv = tchdbpath(bdb->hdb);
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Get the number of records of a B+ tree database object. */
uint64_t tcbdbrnum(TCBDB *bdb){
  assert(bdb);
  if(!BDBLOCKMETHOD(bdb, false)) return 0;
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return 0;
  }
  uint64_t rv = bdb->rnum;
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Get the size of the database file of a B+ tree database object. */
uint64_t tcbdbfsiz(TCBDB *bdb){
  assert(bdb);
  if(!BDBLOCKMETHOD(bdb, false)) return 0;
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return 0;
  }
  uint64_t rv = tchdbfsiz(bdb->hdb);
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Create a cursor object. */
BDBCUR *tcbdbcurnew(TCBDB *bdb){
  assert(bdb);
  BDBCUR *cur;
  TCMALLOC(cur, sizeof(*cur));
  cur->bdb = bdb;
  cur->clock = 0;
  cur->id = 0;
  cur->kidx = 0;
  cur->vidx = 0;
  return cur;
}


/* Delete a cursor object. */
void tcbdbcurdel(BDBCUR *cur){
  assert(cur);
  TCFREE(cur);
}


/* Move a cursor object to the first record. */
bool tcbdbcurfirst(BDBCUR *cur){
  assert(cur);
  TCBDB *bdb = cur->bdb;
  if(!BDBLOCKMETHOD(bdb, false)) return false;
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  bool rv = tcbdbcurfirstimpl(cur);
  bool adj = TCMAPRNUM(bdb->leafc) > bdb->lcnum || TCMAPRNUM(bdb->nodec) > bdb->ncnum;
  BDBUNLOCKMETHOD(bdb);
  if(adj && BDBLOCKMETHOD(bdb, true)){
    if(!bdb->tran && !tcbdbcacheadjust(bdb)) rv = false;
    BDBUNLOCKMETHOD(bdb);
  }
  return rv;
}


/* Move a cursor object to the last record. */
bool tcbdbcurlast(BDBCUR *cur){
  assert(cur);
  TCBDB *bdb = cur->bdb;
  if(!BDBLOCKMETHOD(bdb, false)) return false;
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  bool rv = tcbdbcurlastimpl(cur);
  bool adj = TCMAPRNUM(bdb->leafc) > bdb->lcnum || TCMAPRNUM(bdb->nodec) > bdb->ncnum;
  BDBUNLOCKMETHOD(bdb);
  if(adj && BDBLOCKMETHOD(bdb, true)){
    if(!bdb->tran && !tcbdbcacheadjust(bdb)) rv = false;
    BDBUNLOCKMETHOD(bdb);
  }
  return rv;
}


/* Move a cursor object to the front of records corresponding a key. */
bool tcbdbcurjump(BDBCUR *cur, const void *kbuf, int ksiz){
  assert(cur && kbuf && ksiz >= 0);
  TCBDB *bdb = cur->bdb;
  if(!BDBLOCKMETHOD(bdb, false)) return false;
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  bool rv = tcbdbcurjumpimpl(cur, kbuf, ksiz, true);
  bool adj = TCMAPRNUM(bdb->leafc) > bdb->lcnum || TCMAPRNUM(bdb->nodec) > bdb->ncnum;
  BDBUNLOCKMETHOD(bdb);
  if(adj && BDBLOCKMETHOD(bdb, true)){
    if(!bdb->tran && !tcbdbcacheadjust(bdb)) rv = false;
    BDBUNLOCKMETHOD(bdb);
  }
  return rv;
}


/* Move a cursor object to the front of records corresponding a key string. */
bool tcbdbcurjump2(BDBCUR *cur, const char *kstr){
  assert(cur && kstr);
  return tcbdbcurjump(cur, kstr, strlen(kstr));
}


/* Move a cursor object to the previous record. */
bool tcbdbcurprev(BDBCUR *cur){
  assert(cur);
  TCBDB *bdb = cur->bdb;
  if(!BDBLOCKMETHOD(bdb, false)) return false;
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  if(cur->id < 1){
    tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  bool rv = tcbdbcurprevimpl(cur);
  bool adj = TCMAPRNUM(bdb->leafc) > bdb->lcnum || TCMAPRNUM(bdb->nodec) > bdb->ncnum;
  BDBUNLOCKMETHOD(bdb);
  if(adj && BDBLOCKMETHOD(bdb, true)){
    if(!bdb->tran && !tcbdbcacheadjust(bdb)) rv = false;
    BDBUNLOCKMETHOD(bdb);
  }
  return rv;
}


/* Move a cursor object to the next record. */
bool tcbdbcurnext(BDBCUR *cur){
  assert(cur);
  TCBDB *bdb = cur->bdb;
  if(!BDBLOCKMETHOD(bdb, false)) return false;
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  if(cur->id < 1){
    tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  bool rv = tcbdbcurnextimpl(cur);
  bool adj = TCMAPRNUM(bdb->leafc) > bdb->lcnum || TCMAPRNUM(bdb->nodec) > bdb->ncnum;
  BDBUNLOCKMETHOD(bdb);
  if(adj && BDBLOCKMETHOD(bdb, true)){
    if(!bdb->tran && !tcbdbcacheadjust(bdb)) rv = false;
    BDBUNLOCKMETHOD(bdb);
  }
  return rv;
}


/* Insert a record around a cursor object. */
bool tcbdbcurput(BDBCUR *cur, const void *vbuf, int vsiz, int cpmode){
  assert(cur && vbuf && vsiz >= 0);
  TCBDB *bdb = cur->bdb;
  if(!BDBLOCKMETHOD(bdb, true)) return false;
  if(!bdb->open || !bdb->wmode){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  if(cur->id < 1){
    tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  bool rv = tcbdbcurputimpl(cur, vbuf, vsiz, cpmode);
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Insert a string record around a cursor object. */
bool tcbdbcurput2(BDBCUR *cur, const char *vstr, int cpmode){
  assert(cur && vstr);
  return tcbdbcurput(cur, vstr, strlen(vstr), cpmode);
}


/* Delete the record where a cursor object is. */
bool tcbdbcurout(BDBCUR *cur){
  assert(cur);
  TCBDB *bdb = cur->bdb;
  if(!BDBLOCKMETHOD(bdb, true)) return false;
  if(!bdb->open || !bdb->wmode){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  if(cur->id < 1){
    tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  bool rv = tcbdbcuroutimpl(cur);
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Get the key of the record where the cursor object is. */
void *tcbdbcurkey(BDBCUR *cur, int *sp){
  assert(cur && sp);
  TCBDB *bdb = cur->bdb;
  if(!BDBLOCKMETHOD(bdb, false)) return false;
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  if(cur->id < 1){
    tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  const char *kbuf, *vbuf;
  int ksiz, vsiz;
  char *rv;
  if(tcbdbcurrecimpl(cur, &kbuf, &ksiz, &vbuf, &vsiz)){
    TCMEMDUP(rv, kbuf, ksiz);
    *sp = ksiz;
  } else {
    rv = NULL;
  }
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Get the key string of the record where the cursor object is. */
char *tcbdbcurkey2(BDBCUR *cur){
  assert(cur);
  int ksiz;
  return tcbdbcurkey(cur, &ksiz);
}


/* Get the key of the record where the cursor object is, as a volatile buffer. */
const void *tcbdbcurkey3(BDBCUR *cur, int *sp){
  assert(cur && sp);
  TCBDB *bdb = cur->bdb;
  if(!BDBLOCKMETHOD(bdb, false)) return false;
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  if(cur->id < 1){
    tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  const char *kbuf, *vbuf;
  int ksiz, vsiz;
  const char *rv;
  if(tcbdbcurrecimpl(cur, &kbuf, &ksiz, &vbuf, &vsiz)){
    rv = kbuf;
    *sp = ksiz;
  } else {
    rv = NULL;
  }
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Get the value of the record where the cursor object is. */
void *tcbdbcurval(BDBCUR *cur, int *sp){
  assert(cur && sp);
  TCBDB *bdb = cur->bdb;
  if(!BDBLOCKMETHOD(bdb, false)) return false;
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  if(cur->id < 1){
    tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  const char *kbuf, *vbuf;
  int ksiz, vsiz;
  char *rv;
  if(tcbdbcurrecimpl(cur, &kbuf, &ksiz, &vbuf, &vsiz)){
    TCMEMDUP(rv, vbuf, vsiz);
    *sp = vsiz;
  } else {
    rv = NULL;
  }
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Get the value string of the record where the cursor object is. */
char *tcbdbcurval2(BDBCUR *cur){
  assert(cur);
  int vsiz;
  return tcbdbcurval(cur, &vsiz);
}


/* Get the value of the record where the cursor object is, as a volatile buffer. */
const void *tcbdbcurval3(BDBCUR *cur, int *sp){
  assert(cur && sp);
  TCBDB *bdb = cur->bdb;
  if(!BDBLOCKMETHOD(bdb, false)) return false;
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  if(cur->id < 1){
    tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  const char *kbuf, *vbuf;
  int ksiz, vsiz;
  const char *rv;
  if(tcbdbcurrecimpl(cur, &kbuf, &ksiz, &vbuf, &vsiz)){
    rv = vbuf;
    *sp = vsiz;
  } else {
    rv = NULL;
  }
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Get the key and the value of the record where the cursor object is. */
bool tcbdbcurrec(BDBCUR *cur, TCXSTR *kxstr, TCXSTR *vxstr){
  assert(cur && kxstr && vxstr);
  TCBDB *bdb = cur->bdb;
  if(!BDBLOCKMETHOD(bdb, false)) return false;
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  if(cur->id < 1){
    tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  const char *kbuf, *vbuf;
  int ksiz, vsiz;
  bool rv;
  if(tcbdbcurrecimpl(cur, &kbuf, &ksiz, &vbuf, &vsiz)){
    tcxstrclear(kxstr);
    TCXSTRCAT(kxstr, kbuf, ksiz);
    tcxstrclear(vxstr);
    TCXSTRCAT(vxstr, vbuf, vsiz);
    rv = true;
  } else {
    rv = false;
  }
  BDBUNLOCKMETHOD(bdb);
  return rv;
}



/*************************************************************************************************
 * features for experts
 *************************************************************************************************/


/* Set the error code of a B+ tree database object. */
void tcbdbsetecode(TCBDB *bdb, int ecode, const char *filename, int line, const char *func){
  assert(bdb && filename && line >= 1 && func);
  tchdbsetecode(bdb->hdb, ecode, filename, line, func);
}


/* Set the file descriptor for debugging output. */
void tcbdbsetdbgfd(TCBDB *bdb, int fd){
  assert(bdb && fd >= 0);
  tchdbsetdbgfd(bdb->hdb, fd);
}


/* Get the file descriptor for debugging output. */
int tcbdbdbgfd(TCBDB *bdb){
  assert(bdb);
  return tchdbdbgfd(bdb->hdb);
}


/* Check whether mutual exclusion control is set to a B+ tree database object. */
bool tcbdbhasmutex(TCBDB *bdb){
  assert(bdb);
  return bdb->mmtx != NULL;
}


/* Synchronize updating contents on memory of a B+ tree database object. */
bool tcbdbmemsync(TCBDB *bdb, bool phys){
  assert(bdb);
  if(!bdb->open || !bdb->wmode){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  bool err = false;
  bool clk = BDBLOCKCACHE(bdb);
  const char *vbuf;
  int vsiz;
  TCMAP *leafc = bdb->leafc;
  tcmapiterinit(leafc);
  while((vbuf = tcmapiternext(leafc, &vsiz)) != NULL){
    int rsiz;
    BDBLEAF *leaf = (BDBLEAF *)tcmapiterval(vbuf, &rsiz);
    if(leaf->dirty && !tcbdbleafsave(bdb, leaf)) err = true;
  }
  TCMAP *nodec = bdb->nodec;
  tcmapiterinit(nodec);
  while((vbuf = tcmapiternext(nodec, &vsiz)) != NULL){
    int rsiz;
    BDBNODE *node = (BDBNODE *)tcmapiterval(vbuf, &rsiz);
    if(node->dirty && !tcbdbnodesave(bdb, node)) err = true;
  }
  if(clk) BDBUNLOCKCACHE(bdb);
  tcbdbdumpmeta(bdb);
  if(!tchdbmemsync(bdb->hdb, phys)) err = true;
  return !err;
}


/* Get the comparison function of a B+ tree database object. */
TCCMP tcbdbcmpfunc(TCBDB *bdb){
  assert(bdb);
  return bdb->cmp;
}


/* Get the opaque object for the comparison function of a B+ tree database object. */
void *tcbdbcmpop(TCBDB *bdb){
  assert(bdb);
  return bdb->cmpop;
}


/* Get the maximum number of cached leaf nodes of a B+ tree database object. */
uint32_t tcbdblmemb(TCBDB *bdb){
  assert(bdb);
  return bdb->lmemb;
}


/* Get the maximum number of cached non-leaf nodes of a B+ tree database object. */
uint32_t tcbdbnmemb(TCBDB *bdb){
  assert(bdb);
  return bdb->nmemb;
}


/* Get the number of the leaf nodes of B+ tree database object. */
uint64_t tcbdblnum(TCBDB *bdb){
  assert(bdb);
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return bdb->lnum;
}


/* Get the number of the non-leaf nodes of B+ tree database object. */
uint64_t tcbdbnnum(TCBDB *bdb){
  assert(bdb);
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return bdb->nnum;
}


/* Get the number of elements of the bucket array of a B+ tree database object. */
uint64_t tcbdbbnum(TCBDB *bdb){
  assert(bdb);
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return tchdbbnum(bdb->hdb);
}


/* Get the record alignment of a B+ tree database object. */
uint32_t tcbdbalign(TCBDB *bdb){
  assert(bdb);
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return tchdbalign(bdb->hdb);
}


/* Get the maximum number of the free block pool of a B+ tree database object. */
uint32_t tcbdbfbpmax(TCBDB *bdb){
  assert(bdb);
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return tchdbfbpmax(bdb->hdb);
}


/* Get the inode number of the database file of a B+ tree database object. */
uint64_t tcbdbinode(TCBDB *bdb){
  assert(bdb);
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return tchdbinode(bdb->hdb);
}


/* Get the modification time of the database file of a B+ tree database object. */
time_t tcbdbmtime(TCBDB *bdb){
  assert(bdb);
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return tchdbmtime(bdb->hdb);
}


/* Get the additional flags of a B+ tree database object. */
uint8_t tcbdbflags(TCBDB *bdb){
  assert(bdb);
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return tchdbflags(bdb->hdb);
}


/* Get the options of a B+ tree database object. */
uint8_t tcbdbopts(TCBDB *bdb){
  assert(bdb);
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return bdb->opts;
}


/* Get the pointer to the opaque field of a B+ tree database object. */
char *tcbdbopaque(TCBDB *bdb){
  assert(bdb);
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return NULL;
  }
  return bdb->opaque + BDBOPAQUESIZ;
}


/* Get the number of used elements of the bucket array of a B+ tree database object. */
uint64_t tcbdbbnumused(TCBDB *bdb){
  assert(bdb);
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return tchdbbnumused(bdb->hdb);
}


/* Set the maximum size of each leaf node. */
bool tcbdbsetlsmax(TCBDB *bdb, uint32_t lsmax){
  assert(bdb);
  if(bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  bdb->lsmax = (lsmax > 0) ? tclmax(lsmax, BDBMINLSMAX) : BDBDEFLSMAX;
  return true;
}


/* Set the capacity number of records. */
bool tcbdbsetcapnum(TCBDB *bdb, uint64_t capnum){
  assert(bdb);
  if(bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  bdb->capnum = capnum;
  return true;
}


/* Set the custom codec functions of a B+ tree database object. */
bool tcbdbsetcodecfunc(TCBDB *bdb, TCCODEC enc, void *encop, TCCODEC dec, void *decop){
  assert(bdb && enc && dec);
  if(!BDBLOCKMETHOD(bdb, true)) return false;
  if(bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  bool rv = tchdbsetcodecfunc(bdb->hdb, enc, encop, dec, decop);
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Get the unit step number of auto defragmentation of a B+ tree database object. */
uint32_t tcbdbdfunit(TCBDB *bdb){
  assert(bdb);
  return tchdbdfunit(bdb->hdb);
}


/* Perform dynamic defragmentation of a B+ tree database object. */
bool tcbdbdefrag(TCBDB *bdb, int64_t step){
  assert(bdb);
  if(!BDBLOCKMETHOD(bdb, false)) return false;
  if(!bdb->open || !bdb->wmode){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  bool rv = tchdbdefrag(bdb->hdb, step);
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Clear the cache of a B+ tree database object. */
bool tcbdbcacheclear(TCBDB *bdb){
  assert(bdb);
  if(!BDBLOCKMETHOD(bdb, true)) return false;
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  BDBTHREADYIELD(bdb);
  bool err = false;
  bool tran = bdb->tran;
  if(TCMAPRNUM(bdb->leafc) > 0){
    bool clk = BDBLOCKCACHE(bdb);
    TCMAP *leafc = bdb->leafc;
    tcmapiterinit(leafc);
    int rsiz;
    const void *buf;
    while((buf = tcmapiternext(leafc, &rsiz)) != NULL){
      BDBLEAF *leaf = (BDBLEAF *)tcmapiterval(buf, &rsiz);
      if(!(tran && leaf->dirty) && !tcbdbleafcacheout(bdb, leaf)) err = true;
    }
    if(clk) BDBUNLOCKCACHE(bdb);
  }
  if(TCMAPRNUM(bdb->nodec) > 0){
    bool clk = BDBLOCKCACHE(bdb);
    TCMAP *nodec = bdb->nodec;
    tcmapiterinit(nodec);
    int rsiz;
    const void *buf;
    while((buf = tcmapiternext(nodec, &rsiz)) != NULL){
      BDBNODE *node = (BDBNODE *)tcmapiterval(buf, &rsiz);
      if(!(tran && node->dirty) && !tcbdbnodecacheout(bdb, node)) err = true;
    }
    if(clk) BDBUNLOCKCACHE(bdb);
  }
  BDBUNLOCKMETHOD(bdb);
  return !err;
}


/* Store a new record into a B+ tree database object with backward duplication. */
bool tcbdbputdupback(TCBDB *bdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(bdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  if(!BDBLOCKMETHOD(bdb, true)) return false;
  if(!bdb->open || !bdb->wmode){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  bool rv = tcbdbputimpl(bdb, kbuf, ksiz, vbuf, vsiz, BDBPDDUPB);
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Store a record into a B+ tree database object with a duplication handler. */
bool tcbdbputproc(TCBDB *bdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz,
                  TCPDPROC proc, void *op){
  assert(bdb && kbuf && ksiz >= 0 && proc);
  if(!BDBLOCKMETHOD(bdb, true)) return false;
  if(!bdb->open || !bdb->wmode){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  BDBPDPROCOP procop;
  procop.proc = proc;
  procop.op = op;
  BDBPDPROCOP *procptr = &procop;
  tcgeneric_t stack[(TCNUMBUFSIZ*2)/sizeof(tcgeneric_t)+1];
  char *rbuf;
  if(ksiz <= sizeof(stack) - sizeof(procptr)){
    rbuf = (char *)stack;
  } else {
    TCMALLOC(rbuf, ksiz + sizeof(procptr));
  }
  char *wp = rbuf;
  memcpy(wp, &procptr, sizeof(procptr));
  wp += sizeof(procptr);
  memcpy(wp, kbuf, ksiz);
  kbuf = rbuf + sizeof(procptr);
  bool rv = tcbdbputimpl(bdb, kbuf, ksiz, vbuf, vsiz, BDBPDPROC);
  if(rbuf != (char *)stack) TCFREE(rbuf);
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Store a new string record into a B+ tree database object with backward duplication. */
bool tcbdbputdupback2(TCBDB *bdb, const char *kstr, const char *vstr){
  assert(bdb && kstr && vstr);
  return tcbdbputdupback(bdb, kstr, strlen(kstr), vstr, strlen(vstr));
}


/* Move a cursor object to the rear of records corresponding a key. */
bool tcbdbcurjumpback(BDBCUR *cur, const void *kbuf, int ksiz){
  assert(cur && kbuf && ksiz >= 0);
  TCBDB *bdb = cur->bdb;
  if(!BDBLOCKMETHOD(bdb, false)) return false;
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  bool rv = tcbdbcurjumpimpl(cur, kbuf, ksiz, false);
  BDBUNLOCKMETHOD(bdb);
  return rv;
}


/* Move a cursor object to the rear of records corresponding a key string. */
bool tcbdbcurjumpback2(BDBCUR *cur, const char *kstr){
  assert(cur && kstr);
  return tcbdbcurjumpback(cur, kstr, strlen(kstr));
}


/* Process each record atomically of a B+ tree database object. */
bool tcbdbforeach(TCBDB *bdb, TCITER iter, void *op){
  assert(bdb && iter);
  if(!BDBLOCKMETHOD(bdb, true)) return false;
  if(!bdb->open){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    BDBUNLOCKMETHOD(bdb);
    return false;
  }
  BDBTHREADYIELD(bdb);
  bool rv = tcbdbforeachimpl(bdb, iter, op);
  BDBUNLOCKMETHOD(bdb);
  return rv;
}



/*************************************************************************************************
 * private features
 *************************************************************************************************/


/* Clear all members.
   `bdb' specifies the B+ tree database object. */
static void tcbdbclear(TCBDB *bdb){
  assert(bdb);
  bdb->mmtx = NULL;
  bdb->cmtx = NULL;
  bdb->hdb = NULL;
  bdb->opaque = NULL;
  bdb->open = false;
  bdb->wmode = false;
  bdb->lmemb = BDBDEFLMEMB;
  bdb->nmemb = BDBDEFNMEMB;
  bdb->opts = 0;
  bdb->root = 0;
  bdb->first = 0;
  bdb->last = 0;
  bdb->lnum = 0;
  bdb->nnum = 0;
  bdb->rnum = 0;
  bdb->leafc = NULL;
  bdb->nodec = NULL;
  bdb->cmp = NULL;
  bdb->cmpop = NULL;
  bdb->lcnum = BDBDEFLCNUM;
  bdb->ncnum = BDBDEFNCNUM;
  bdb->lsmax = BDBDEFLSMAX;
  bdb->lschk = 0;
  bdb->capnum = 0;
  bdb->hist = NULL;
  bdb->hnum = 0;
  bdb->hleaf = 0;
  bdb->lleaf = 0;
  bdb->tran = false;
  bdb->rbopaque = NULL;
  bdb->clock = 0;
  bdb->cnt_saveleaf = -1;
  bdb->cnt_loadleaf = -1;
  bdb->cnt_killleaf = -1;
  bdb->cnt_adjleafc = -1;
  bdb->cnt_savenode = -1;
  bdb->cnt_loadnode = -1;
  bdb->cnt_adjnodec = -1;
  TCDODEBUG(bdb->cnt_saveleaf = 0);
  TCDODEBUG(bdb->cnt_loadleaf = 0);
  TCDODEBUG(bdb->cnt_killleaf = 0);
  TCDODEBUG(bdb->cnt_adjleafc = 0);
  TCDODEBUG(bdb->cnt_savenode = 0);
  TCDODEBUG(bdb->cnt_loadnode = 0);
  TCDODEBUG(bdb->cnt_adjnodec = 0);
}


/* Serialize meta data into the opaque field.
   `bdb' specifies the B+ tree database object. */
static void tcbdbdumpmeta(TCBDB *bdb){
  assert(bdb);
  memset(bdb->opaque, 0, 64);
  char *wp = bdb->opaque;
  if(bdb->cmp == tccmplexical){
    *(uint8_t *)(wp++) = 0x0;
  } else if(bdb->cmp == tccmpdecimal){
    *(uint8_t *)(wp++) = 0x1;
  } else if(bdb->cmp == tccmpint32){
    *(uint8_t *)(wp++) = 0x2;
  } else if(bdb->cmp == tccmpint64){
    *(uint8_t *)(wp++) = 0x3;
  } else {
    *(uint8_t *)(wp++) = 0xff;
  }
  wp += 7;
  uint32_t lnum;
  lnum = bdb->lmemb;
  lnum = TCHTOIL(lnum);
  memcpy(wp, &lnum, sizeof(lnum));
  wp += sizeof(lnum);
  lnum = bdb->nmemb;
  lnum = TCHTOIL(lnum);
  memcpy(wp, &lnum, sizeof(lnum));
  wp += sizeof(lnum);
  uint64_t llnum;
  llnum = bdb->root;
  llnum = TCHTOILL(llnum);
  memcpy(wp, &llnum, sizeof(llnum));
  wp += sizeof(llnum);
  llnum = bdb->first;
  llnum = TCHTOILL(llnum);
  memcpy(wp, &llnum, sizeof(llnum));
  wp += sizeof(llnum);
  llnum = bdb->last;
  llnum = TCHTOILL(llnum);
  memcpy(wp, &llnum, sizeof(llnum));
  wp += sizeof(llnum);
  llnum = bdb->lnum;
  llnum = TCHTOILL(llnum);
  memcpy(wp, &llnum, sizeof(llnum));
  wp += sizeof(llnum);
  llnum = bdb->nnum;
  llnum = TCHTOILL(llnum);
  memcpy(wp, &llnum, sizeof(llnum));
  wp += sizeof(llnum);
  llnum = bdb->rnum;
  llnum = TCHTOILL(llnum);
  memcpy(wp, &llnum, sizeof(llnum));
  wp += sizeof(llnum);
}


/* Deserialize meta data from the opaque field.
   `bdb' specifies the B+ tree database object. */
static void tcbdbloadmeta(TCBDB *bdb){
  const char *rp = bdb->opaque;
  uint8_t cnum = *(uint8_t *)(rp++);
  if(cnum == 0x0){
    bdb->cmp = tccmplexical;
  } else if(cnum == 0x1){
    bdb->cmp = tccmpdecimal;
  } else if(cnum == 0x2){
    bdb->cmp = tccmpint32;
  } else if(cnum == 0x3){
    bdb->cmp = tccmpint64;
  }
  rp += 7;
  uint32_t lnum;
  memcpy(&lnum, rp, sizeof(lnum));
  rp += sizeof(lnum);
  bdb->lmemb = TCITOHL(lnum);
  memcpy(&lnum, rp, sizeof(lnum));
  rp += sizeof(lnum);
  bdb->nmemb = TCITOHL(lnum);
  uint64_t llnum;
  memcpy(&llnum, rp, sizeof(llnum));
  bdb->root = TCITOHLL(llnum);
  rp += sizeof(llnum);
  memcpy(&llnum, rp, sizeof(llnum));
  bdb->first = TCITOHLL(llnum);
  rp += sizeof(llnum);
  memcpy(&llnum, rp, sizeof(llnum));
  bdb->last = TCITOHLL(llnum);
  rp += sizeof(llnum);
  memcpy(&llnum, rp, sizeof(llnum));
  bdb->lnum = TCITOHLL(llnum);
  rp += sizeof(llnum);
  memcpy(&llnum, rp, sizeof(llnum));
  bdb->nnum = TCITOHLL(llnum);
  rp += sizeof(llnum);
  memcpy(&llnum, rp, sizeof(llnum));
  bdb->rnum = TCITOHLL(llnum);
  rp += sizeof(llnum);
}


/* Create a new leaf.
   `bdb' specifies the B+ tree database object.
   `prev' specifies the ID number of the previous leaf.
   `next' specifies the ID number of the next leaf.
   The return value is the new leaf object. */
static BDBLEAF *tcbdbleafnew(TCBDB *bdb, uint64_t prev, uint64_t next){
  assert(bdb);
  BDBLEAF lent;
  lent.id = ++bdb->lnum;
  lent.recs = tcptrlistnew2(bdb->lmemb + 1);
  lent.size = 0;
  lent.prev = prev;
  lent.next = next;
  lent.dirty = true;
  lent.dead = false;
  tcmapputkeep(bdb->leafc, &(lent.id), sizeof(lent.id), &lent, sizeof(lent));
  int rsiz;
  return (BDBLEAF *)tcmapget(bdb->leafc, &(lent.id), sizeof(lent.id), &rsiz);
}


/* Remove a leaf from the cache.
   `bdb' specifies the B+ tree database object.
   `leaf' specifies the leaf object.
   If successful, the return value is true, else, it is false. */
static bool tcbdbleafcacheout(TCBDB *bdb, BDBLEAF *leaf){
  assert(bdb && leaf);
  bool err = false;
  if(leaf->dirty && !tcbdbleafsave(bdb, leaf)) err = true;
  TCPTRLIST *recs = leaf->recs;
  int ln = TCPTRLISTNUM(recs);
  for(int i = 0; i < ln; i++){
    BDBREC *rec = TCPTRLISTVAL(recs, i);
    if(rec->rest) tclistdel(rec->rest);
    TCFREE(rec);
  }
  tcptrlistdel(recs);
  tcmapout(bdb->leafc, &(leaf->id), sizeof(leaf->id));
  return !err;
}


/* Save a leaf into the internal database.
   `bdb' specifies the B+ tree database object.
   `leaf' specifies the leaf object.
   If successful, the return value is true, else, it is false. */
static bool tcbdbleafsave(TCBDB *bdb, BDBLEAF *leaf){
  assert(bdb && leaf);
  TCDODEBUG(bdb->cnt_saveleaf++);
  TCXSTR *rbuf = tcxstrnew3(BDBPAGEBUFSIZ);
  char hbuf[(sizeof(uint64_t)+1)*3];
  char *wp = hbuf;
  uint64_t llnum;
  int step;
  llnum = leaf->prev;
  TCSETVNUMBUF64(step, wp, llnum);
  wp += step;
  llnum = leaf->next;
  TCSETVNUMBUF64(step, wp, llnum);
  wp += step;
  TCXSTRCAT(rbuf, hbuf, wp - hbuf);
  TCPTRLIST *recs = leaf->recs;
  int ln = TCPTRLISTNUM(recs);
  for(int i = 0; i < ln; i++){
    BDBREC *rec = TCPTRLISTVAL(recs, i);
    char *dbuf = (char *)rec + sizeof(*rec);
    int lnum;
    wp = hbuf;
    lnum = rec->ksiz;
    TCSETVNUMBUF(step, wp, lnum);
    wp += step;
    lnum = rec->vsiz;
    TCSETVNUMBUF(step, wp, lnum);
    wp += step;
    TCLIST *rest = rec->rest;
    int rnum = rest ? TCLISTNUM(rest) : 0;
    TCSETVNUMBUF(step, wp, rnum);
    wp += step;
    TCXSTRCAT(rbuf, hbuf, wp - hbuf);
    TCXSTRCAT(rbuf, dbuf, rec->ksiz);
    TCXSTRCAT(rbuf, dbuf + rec->ksiz + TCALIGNPAD(rec->ksiz), rec->vsiz);
    for(int j = 0; j < rnum; j++){
      const char *vbuf;
      int vsiz;
      TCLISTVAL(vbuf, rest, j, vsiz);
      TCSETVNUMBUF(step, hbuf, vsiz);
      TCXSTRCAT(rbuf, hbuf, step);
      TCXSTRCAT(rbuf, vbuf, vsiz);
    }
  }
  bool err = false;
  step = sprintf(hbuf, "%llx", (unsigned long long)leaf->id);
  if(ln < 1 && !tchdbout(bdb->hdb, hbuf, step) && tchdbecode(bdb->hdb) != TCENOREC)
    err = true;
  if(!leaf->dead && !tchdbput(bdb->hdb, hbuf, step, TCXSTRPTR(rbuf), TCXSTRSIZE(rbuf)))
    err = true;
  tcxstrdel(rbuf);
  leaf->dirty = false;
  leaf->dead = false;
  return !err;
}


/* Load a leaf from the internal database.
   `bdb' specifies the B+ tree database object.
   `id' specifies the ID number of the leaf.
   The return value is the leaf object or `NULL' on failure. */
static BDBLEAF *tcbdbleafload(TCBDB *bdb, uint64_t id){
  assert(bdb && id > 0);
  bool clk = BDBLOCKCACHE(bdb);
  int rsiz;
  BDBLEAF *leaf = (BDBLEAF *)tcmapget3(bdb->leafc, &id, sizeof(id), &rsiz);
  if(leaf){
    if(clk) BDBUNLOCKCACHE(bdb);
    return leaf;
  }
  if(clk) BDBUNLOCKCACHE(bdb);
  TCDODEBUG(bdb->cnt_loadleaf++);
  char hbuf[(sizeof(uint64_t)+1)*3];
  int step;
  step = sprintf(hbuf, "%llx", (unsigned long long)id);
  char *rbuf = NULL;
  char wbuf[BDBPAGEBUFSIZ];
  const char *rp = NULL;
  rsiz = tchdbget3(bdb->hdb, hbuf, step, wbuf, BDBPAGEBUFSIZ);
  if(rsiz < 1){
    tcbdbsetecode(bdb, TCEMISC, __FILE__, __LINE__, __func__);
    return false;
  } else if(rsiz < BDBPAGEBUFSIZ){
    rp = wbuf;
  } else {
    if(!(rbuf = tchdbget(bdb->hdb, hbuf, step, &rsiz))){
      tcbdbsetecode(bdb, TCEMISC, __FILE__, __LINE__, __func__);
      return false;
    }
    rp = rbuf;
  }
  BDBLEAF lent;
  lent.id = id;
  uint64_t llnum;
  TCREADVNUMBUF64(rp, llnum, step);
  lent.prev = llnum;
  rp += step;
  rsiz -= step;
  TCREADVNUMBUF64(rp, llnum, step);
  lent.next = llnum;
  rp += step;
  rsiz -= step;
  lent.dirty = false;
  lent.dead = false;
  lent.recs = tcptrlistnew2(bdb->lmemb + 1);
  lent.size = 0;
  bool err = false;
  while(rsiz >= 3){
    int ksiz;
    TCREADVNUMBUF(rp, ksiz, step);
    rp += step;
    rsiz -= step;
    int vsiz;
    TCREADVNUMBUF(rp, vsiz, step);
    rp += step;
    rsiz -= step;
    int rnum;
    TCREADVNUMBUF(rp, rnum, step);
    rp += step;
    rsiz -= step;
    if(rsiz < ksiz + vsiz + rnum){
      err = true;
      break;
    }
    int psiz = TCALIGNPAD(ksiz);
    BDBREC *nrec;
    TCMALLOC(nrec, sizeof(*nrec) + ksiz + psiz + vsiz + 1);
    char *dbuf = (char *)nrec + sizeof(*nrec);
    memcpy(dbuf, rp, ksiz);
    dbuf[ksiz] = '\0';
    nrec->ksiz = ksiz;
    rp += ksiz;
    rsiz -= ksiz;
    memcpy(dbuf + ksiz + psiz, rp, vsiz);
    dbuf[ksiz+psiz+vsiz] = '\0';
    nrec->vsiz = vsiz;
    rp += vsiz;
    rsiz -= vsiz;
    lent.size += ksiz;
    lent.size += vsiz;
    if(rnum > 0){
      nrec->rest = tclistnew2(rnum);
      while(rnum-- > 0 && rsiz > 0){
        TCREADVNUMBUF(rp, vsiz, step);
        rp += step;
        rsiz -= step;
        if(rsiz < vsiz){
          err = true;
          break;
        }
        TCLISTPUSH(nrec->rest, rp, vsiz);
        rp += vsiz;
        rsiz -= vsiz;
        lent.size += vsiz;
      }
    } else {
      nrec->rest = NULL;
    }
    TCPTRLISTPUSH(lent.recs, nrec);
  }
  TCFREE(rbuf);
  if(err || rsiz != 0){
    tcbdbsetecode(bdb, TCEMISC, __FILE__, __LINE__, __func__);
    return NULL;
  }
  clk = BDBLOCKCACHE(bdb);
  if(!tcmapputkeep(bdb->leafc, &(lent.id), sizeof(lent.id), &lent, sizeof(lent))){
    int ln = TCPTRLISTNUM(lent.recs);
    for(int i = 0; i < ln; i++){
      BDBREC *rec = TCPTRLISTVAL(lent.recs, i);
      if(rec->rest) tclistdel(rec->rest);
      TCFREE(rec);
    }
    tcptrlistdel(lent.recs);
  }
  leaf = (BDBLEAF *)tcmapget(bdb->leafc, &(lent.id), sizeof(lent.id), &rsiz);
  if(clk) BDBUNLOCKCACHE(bdb);
  return leaf;
}


/* Check existence of a leaf in the internal database.
   `bdb' specifies the B+ tree database object.
   `id' specifies the ID number of the leaf.
   The return value is true if the leaf exists, else, it is false. */
static bool tcbdbleafcheck(TCBDB *bdb, uint64_t id){
  assert(bdb && id > 0);
  bool clk = BDBLOCKCACHE(bdb);
  int rsiz;
  BDBLEAF *leaf = (BDBLEAF *)tcmapget(bdb->leafc, &id, sizeof(id), &rsiz);
  if(clk) BDBUNLOCKCACHE(bdb);
  if(leaf) return true;
  char hbuf[(sizeof(uint64_t)+1)*3];
  int step = sprintf(hbuf, "%llx", (unsigned long long)id);
  return tchdbvsiz(bdb->hdb, hbuf, step) > 0;
}


/* Load the historical leaf from the internal database.
   `bdb' specifies the B+ tree database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   `id' specifies the ID number of the historical leaf.
   If successful, the return value is the pointer to the leaf, else, it is `NULL'. */
static BDBLEAF *tcbdbgethistleaf(TCBDB *bdb, const char *kbuf, int ksiz, uint64_t id){
  assert(bdb && kbuf && ksiz >= 0 && id > 0);
  BDBLEAF *leaf = tcbdbleafload(bdb, id);
  if(!leaf) return NULL;
  int ln = TCPTRLISTNUM(leaf->recs);
  if(ln < 2) return NULL;
  BDBREC *rec = TCPTRLISTVAL(leaf->recs, 0);
  char *dbuf = (char *)rec + sizeof(*rec);
  int rv;
  if(bdb->cmp == tccmplexical){
    TCCMPLEXICAL(rv, kbuf, ksiz, dbuf, rec->ksiz);
  } else {
    rv = bdb->cmp(kbuf, ksiz, dbuf, rec->ksiz, bdb->cmpop);
  }
  if(rv == 0) return leaf;
  if(rv < 0) return NULL;
  rec = TCPTRLISTVAL(leaf->recs, ln - 1);
  dbuf = (char *)rec + sizeof(*rec);
  if(bdb->cmp == tccmplexical){
    TCCMPLEXICAL(rv, kbuf, ksiz, dbuf, rec->ksiz);
  } else {
    rv = bdb->cmp(kbuf, ksiz, dbuf, rec->ksiz, bdb->cmpop);
  }
  if(rv <= 0 || leaf->next < 1) return leaf;
  return NULL;
}


/* Add a record to a leaf.
   `bdb' specifies the B+ tree database object.
   `leaf' specifies the leaf object.
   `dmode' specifies behavior when the key overlaps.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   `vbuf' specifies the pointer to the region of the value.
   `vsiz' specifies the size of the region of the value.
   If successful, the return value is true, else, it is false. */
static bool tcbdbleafaddrec(TCBDB *bdb, BDBLEAF *leaf, int dmode,
                            const char *kbuf, int ksiz, const char *vbuf, int vsiz){
  assert(bdb && leaf && kbuf && ksiz >= 0);
  TCCMP cmp = bdb->cmp;
  void *cmpop = bdb->cmpop;
  TCPTRLIST *recs = leaf->recs;
  int ln = TCPTRLISTNUM(recs);
  int left = 0;
  int right = ln;
  int i = (left + right) / 2;
  while(right >= left && i < ln){
    BDBREC *rec = TCPTRLISTVAL(recs, i);
    char *dbuf = (char *)rec + sizeof(*rec);
    int rv;
    if(cmp == tccmplexical){
      TCCMPLEXICAL(rv, kbuf, ksiz, dbuf, rec->ksiz);
    } else {
      rv = cmp(kbuf, ksiz, dbuf, rec->ksiz, cmpop);
    }
    if(rv == 0){
      break;
    } else if(rv <= 0){
      right = i - 1;
    } else {
      left = i + 1;
    }
    i = (left + right) / 2;
  }
  while(i < ln){
    BDBREC *rec = TCPTRLISTVAL(recs, i);
    char *dbuf = (char *)rec + sizeof(*rec);
    int rv;
    if(cmp == tccmplexical){
      TCCMPLEXICAL(rv, kbuf, ksiz, dbuf, rec->ksiz);
    } else {
      rv = cmp(kbuf, ksiz, dbuf, rec->ksiz, cmpop);
    }
    if(rv == 0){
      int psiz = TCALIGNPAD(rec->ksiz);
      BDBREC *orec = rec;
      BDBPDPROCOP *procptr;
      int nvsiz;
      char *nvbuf;
      switch(dmode){
        case BDBPDKEEP:
          tcbdbsetecode(bdb, TCEKEEP, __FILE__, __LINE__, __func__);
          return false;
        case BDBPDCAT:
          leaf->size += vsiz;
          TCREALLOC(rec, rec, sizeof(*rec) + rec->ksiz + psiz + rec->vsiz + vsiz + 1);
          if(rec != orec){
            tcptrlistover(recs, i, rec);
            dbuf = (char *)rec + sizeof(*rec);
          }
          memcpy(dbuf + rec->ksiz + psiz + rec->vsiz, vbuf, vsiz);
          rec->vsiz += vsiz;
          dbuf[rec->ksiz+psiz+rec->vsiz] = '\0';
          break;
        case BDBPDDUP:
          leaf->size += vsiz;
          if(!rec->rest) rec->rest = tclistnew2(1);
          TCLISTPUSH(rec->rest, vbuf, vsiz);
          bdb->rnum++;
          break;
        case BDBPDDUPB:
          leaf->size += vsiz;
          if(!rec->rest) rec->rest = tclistnew2(1);
          tclistunshift(rec->rest, dbuf + rec->ksiz + psiz, rec->vsiz);
          if(vsiz > rec->vsiz){
            TCREALLOC(rec, rec, sizeof(*rec) + rec->ksiz + psiz + vsiz + 1);
            if(rec != orec){
              tcptrlistover(recs, i, rec);
              dbuf = (char *)rec + sizeof(*rec);
            }
          }
          memcpy(dbuf + rec->ksiz + psiz, vbuf, vsiz);
          dbuf[rec->ksiz+psiz+vsiz] = '\0';
          rec->vsiz = vsiz;
          bdb->rnum++;
          break;
        case BDBPDADDINT:
          if(rec->vsiz != sizeof(int)){
            tcbdbsetecode(bdb, TCEKEEP, __FILE__, __LINE__, __func__);
            return false;
          }
          if(*(int *)vbuf == 0){
            *(int *)vbuf = *(int *)(dbuf + rec->ksiz + psiz);
            return true;
          }
          *(int *)(dbuf + rec->ksiz + psiz) += *(int *)vbuf;
          *(int *)vbuf = *(int *)(dbuf + rec->ksiz + psiz);
          break;
        case BDBPDADDDBL:
          if(rec->vsiz != sizeof(double)){
            tcbdbsetecode(bdb, TCEKEEP, __FILE__, __LINE__, __func__);
            return false;
          }
          if(*(double *)vbuf == 0.0){
            *(double *)vbuf = *(double *)(dbuf + rec->ksiz + psiz);
            return true;
          }
          *(double *)(dbuf + rec->ksiz + psiz) += *(double *)vbuf;
          *(double *)vbuf = *(double *)(dbuf + rec->ksiz + psiz);
          break;
        case BDBPDPROC:
          procptr = *(BDBPDPROCOP **)((char *)kbuf - sizeof(procptr));
          nvbuf = procptr->proc(dbuf + rec->ksiz + psiz, rec->vsiz, &nvsiz, procptr->op);
          if(nvbuf == (void *)-1){
            tcbdbremoverec(bdb, leaf, rec, i);
          } else if(nvbuf){
            leaf->size += nvsiz - rec->vsiz;
            if(nvsiz > rec->vsiz){
              TCREALLOC(rec, rec, sizeof(*rec) + rec->ksiz + psiz + nvsiz + 1);
              if(rec != orec){
                tcptrlistover(recs, i, rec);
                dbuf = (char *)rec + sizeof(*rec);
              }
            }
            memcpy(dbuf + rec->ksiz + psiz, nvbuf, nvsiz);
            dbuf[rec->ksiz+psiz+nvsiz] = '\0';
            rec->vsiz = nvsiz;
            TCFREE(nvbuf);
          } else {
            tcbdbsetecode(bdb, TCEKEEP, __FILE__, __LINE__, __func__);
            return false;
          }
          break;
        default:
          leaf->size += vsiz - rec->vsiz;
          if(vsiz > rec->vsiz){
            TCREALLOC(rec, rec, sizeof(*rec) + rec->ksiz + psiz + vsiz + 1);
            if(rec != orec){
              tcptrlistover(recs, i, rec);
              dbuf = (char *)rec + sizeof(*rec);
            }
          }
          memcpy(dbuf + rec->ksiz + psiz, vbuf, vsiz);
          dbuf[rec->ksiz+psiz+vsiz] = '\0';
          rec->vsiz = vsiz;
          break;
      }
      break;
    } else if(rv < 0){
      if(!vbuf){
        tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
        return false;
      }
      leaf->size += ksiz + vsiz;
      int psiz = TCALIGNPAD(ksiz);
      BDBREC *nrec;
      TCMALLOC(nrec, sizeof(*nrec) + ksiz + psiz + vsiz + 1);
      char *dbuf = (char *)nrec + sizeof(*nrec);
      memcpy(dbuf, kbuf, ksiz);
      dbuf[ksiz] = '\0';
      nrec->ksiz = ksiz;
      memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
      dbuf[ksiz+psiz+vsiz] = '\0';
      nrec->vsiz = vsiz;
      nrec->rest = NULL;
      TCPTRLISTINSERT(recs, i, nrec);
      bdb->rnum++;
      break;
    }
    i++;
  }
  if(i >= ln){
    if(!vbuf){
      tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
      return false;
    }
    leaf->size += ksiz + vsiz;
    int psiz = TCALIGNPAD(ksiz);
    BDBREC *nrec;
    TCMALLOC(nrec, sizeof(*nrec) + ksiz + psiz + vsiz + 1);
    char *dbuf = (char *)nrec + sizeof(*nrec);
    memcpy(dbuf, kbuf, ksiz);
    dbuf[ksiz] = '\0';
    nrec->ksiz = ksiz;
    memcpy(dbuf + ksiz + psiz, vbuf, vsiz);
    dbuf[ksiz+psiz+vsiz] = '\0';
    nrec->vsiz = vsiz;
    nrec->rest = NULL;
    TCPTRLISTPUSH(recs, nrec);
    bdb->rnum++;
  }
  leaf->dirty = true;
  return true;
}


/* Divide a leaf into two.
   `bdb' specifies the B+ tree database object.
   `leaf' specifies the leaf object.
   The return value is the new leaf object or `NULL' on failure. */
static BDBLEAF *tcbdbleafdivide(TCBDB *bdb, BDBLEAF *leaf){
  assert(bdb && leaf);
  bdb->hleaf = 0;
  TCPTRLIST *recs = leaf->recs;
  int mid = TCPTRLISTNUM(recs) / 2;
  BDBLEAF *newleaf = tcbdbleafnew(bdb, leaf->id, leaf->next);
  if(newleaf->next > 0){
    BDBLEAF *nextleaf = tcbdbleafload(bdb, newleaf->next);
    if(!nextleaf) return NULL;
    nextleaf->prev = newleaf->id;
    nextleaf->dirty = true;
  }
  leaf->next = newleaf->id;
  leaf->dirty = true;
  int ln = TCPTRLISTNUM(recs);
  TCPTRLIST *newrecs = newleaf->recs;
  int nsiz = 0;
  for(int i = mid; i < ln; i++){
    BDBREC *rec = TCPTRLISTVAL(recs, i);
    nsiz += rec->ksiz + rec->vsiz;
    if(rec->rest){
      TCLIST *rest = rec->rest;
      int rnum = TCLISTNUM(rest);
      for(int j = 0; j < rnum; j++){
        nsiz += TCLISTVALSIZ(rest, j);
      }
    }
    TCPTRLISTPUSH(newrecs, rec);
  }
  TCPTRLISTTRUNC(recs, TCPTRLISTNUM(recs) - TCPTRLISTNUM(newrecs));
  leaf->size -= nsiz;
  newleaf->size = nsiz;
  return newleaf;
}


/* Cut off the path to a leaf and mark it dead.
   `bdb' specifies the B+ tree database object.
   `leaf' specifies the leaf object.
   If successful, the return value is true, else, it is false. */
static bool tcbdbleafkill(TCBDB *bdb, BDBLEAF *leaf){
  assert(bdb && leaf);
  BDBNODE *node = tcbdbnodeload(bdb, bdb->hist[--bdb->hnum]);
  if(!node) return false;
  if(tcbdbnodesubidx(bdb, node, leaf->id)){
    TCDODEBUG(bdb->cnt_killleaf++);
    if(bdb->hleaf == leaf->id) bdb->hleaf = 0;
    if(leaf->prev > 0){
      BDBLEAF *tleaf = tcbdbleafload(bdb, leaf->prev);
      if(!tleaf) return false;
      tleaf->next = leaf->next;
      tleaf->dirty = true;
      if(bdb->last == leaf->id) bdb->last = leaf->prev;
    }
    if(leaf->next > 0){
      BDBLEAF *tleaf = tcbdbleafload(bdb, leaf->next);
      if(!tleaf) return false;
      tleaf->prev = leaf->prev;
      tleaf->dirty = true;
      if(bdb->first == leaf->id) bdb->first = leaf->next;
    }
    leaf->dead = true;
  }
  bdb->clock++;
  return true;
}


/* Create a new node.
   `bdb' specifies the B+ tree database object.
   `heir' specifies the ID of the child before the first index.
   The return value is the new node object. */
static BDBNODE *tcbdbnodenew(TCBDB *bdb, uint64_t heir){
  assert(bdb && heir > 0);
  BDBNODE nent;
  nent.id = ++bdb->nnum + BDBNODEIDBASE;
  nent.idxs = tcptrlistnew2(bdb->nmemb + 1);
  nent.heir = heir;
  nent.dirty = true;
  nent.dead = false;
  tcmapputkeep(bdb->nodec, &(nent.id), sizeof(nent.id), &nent, sizeof(nent));
  int rsiz;
  return (BDBNODE *)tcmapget(bdb->nodec, &(nent.id), sizeof(nent.id), &rsiz);
}


/* Remove a node from the cache.
   `bdb' specifies the B+ tree database object.
   `node' specifies the node object.
   If successful, the return value is true, else, it is false. */
static bool tcbdbnodecacheout(TCBDB *bdb, BDBNODE *node){
  assert(bdb && node);
  bool err = false;
  if(node->dirty && !tcbdbnodesave(bdb, node)) err = true;
  TCPTRLIST *idxs = node->idxs;
  int ln = TCPTRLISTNUM(idxs);
  for(int i = 0; i < ln; i++){
    BDBIDX *idx = TCPTRLISTVAL(idxs, i);
    TCFREE(idx);
  }
  tcptrlistdel(idxs);
  tcmapout(bdb->nodec, &(node->id), sizeof(node->id));
  return !err;
}


/* Save a node into the internal database.
   `bdb' specifies the B+ tree database object.
   `node' specifies the node object.
   If successful, the return value is true, else, it is false. */
static bool tcbdbnodesave(TCBDB *bdb, BDBNODE *node){
  assert(bdb && node);
  TCDODEBUG(bdb->cnt_savenode++);
  TCXSTR *rbuf = tcxstrnew3(BDBPAGEBUFSIZ);
  char hbuf[(sizeof(uint64_t)+1)*2];
  uint64_t llnum;
  int step;
  llnum = node->heir;
  TCSETVNUMBUF64(step, hbuf, llnum);
  TCXSTRCAT(rbuf, hbuf, step);
  TCPTRLIST *idxs = node->idxs;
  int ln = TCPTRLISTNUM(idxs);
  for(int i = 0; i < ln; i++){
    BDBIDX *idx = TCPTRLISTVAL(idxs, i);
    char *ebuf = (char *)idx + sizeof(*idx);
    char *wp = hbuf;
    llnum = idx->pid;
    TCSETVNUMBUF64(step, wp, llnum);
    wp += step;
    uint32_t lnum = idx->ksiz;
    TCSETVNUMBUF(step, wp, lnum);
    wp += step;
    TCXSTRCAT(rbuf, hbuf, wp - hbuf);
    TCXSTRCAT(rbuf, ebuf, idx->ksiz);
  }
  bool err = false;
  step = sprintf(hbuf, "#%llx", (unsigned long long)(node->id - BDBNODEIDBASE));
  if(ln < 1 && !tchdbout(bdb->hdb, hbuf, step) && tchdbecode(bdb->hdb) != TCENOREC)
    err = true;
  if(!node->dead && !tchdbput(bdb->hdb, hbuf, step, TCXSTRPTR(rbuf), TCXSTRSIZE(rbuf)))
    err = true;
  tcxstrdel(rbuf);
  node->dirty = false;
  node->dead = false;
  return !err;
}


/* Load a node from the internal database.
   `bdb' specifies the B+ tree database object.
   `id' specifies the ID number of the node.
   The return value is the node object or `NULL' on failure. */
static BDBNODE *tcbdbnodeload(TCBDB *bdb, uint64_t id){
  assert(bdb && id > BDBNODEIDBASE);
  bool clk = BDBLOCKCACHE(bdb);
  int rsiz;
  BDBNODE *node = (BDBNODE *)tcmapget3(bdb->nodec, &id, sizeof(id), &rsiz);
  if(node){
    if(clk) BDBUNLOCKCACHE(bdb);
    return node;
  }
  if(clk) BDBUNLOCKCACHE(bdb);
  TCDODEBUG(bdb->cnt_loadnode++);
  char hbuf[(sizeof(uint64_t)+1)*2];
  int step;
  step = sprintf(hbuf, "#%llx", (unsigned long long)(id - BDBNODEIDBASE));
  char *rbuf = NULL;
  char wbuf[BDBPAGEBUFSIZ];
  const char *rp = NULL;
  rsiz = tchdbget3(bdb->hdb, hbuf, step, wbuf, BDBPAGEBUFSIZ);
  if(rsiz < 1){
    tcbdbsetecode(bdb, TCEMISC, __FILE__, __LINE__, __func__);
    return NULL;
  } else if(rsiz < BDBPAGEBUFSIZ){
    rp = wbuf;
  } else {
    if(!(rbuf = tchdbget(bdb->hdb, hbuf, step, &rsiz))){
      tcbdbsetecode(bdb, TCEMISC, __FILE__, __LINE__, __func__);
      return NULL;
    }
    rp = rbuf;
  }
  BDBNODE nent;
  nent.id = id;
  uint64_t llnum;
  TCREADVNUMBUF64(rp, llnum, step);
  nent.heir = llnum;
  rp += step;
  rsiz -= step;
  nent.dirty = false;
  nent.dead = false;
  nent.idxs = tcptrlistnew2(bdb->nmemb + 1);
  bool err = false;
  while(rsiz >= 2){
    uint64_t pid;
    TCREADVNUMBUF64(rp, pid, step);
    rp += step;
    rsiz -= step;
    int ksiz;
    TCREADVNUMBUF(rp, ksiz, step);
    rp += step;
    rsiz -= step;
    if(rsiz < ksiz){
      err = true;
      break;
    }
    BDBIDX *nidx;
    TCMALLOC(nidx, sizeof(*nidx) + ksiz + 1);
    nidx->pid = pid;
    char *ebuf = (char *)nidx + sizeof(*nidx);
    memcpy(ebuf, rp, ksiz);
    ebuf[ksiz] = '\0';
    nidx->ksiz = ksiz;
    rp += ksiz;
    rsiz -= ksiz;
    TCPTRLISTPUSH(nent.idxs, nidx);
  }
  TCFREE(rbuf);
  if(err || rsiz != 0){
    tcbdbsetecode(bdb, TCEMISC, __FILE__, __LINE__, __func__);
    return NULL;
  }
  clk = BDBLOCKCACHE(bdb);
  if(!tcmapputkeep(bdb->nodec, &(nent.id), sizeof(nent.id), &nent, sizeof(nent))){
    int ln = TCPTRLISTNUM(nent.idxs);
    for(int i = 0; i < ln; i++){
      BDBIDX *idx = TCPTRLISTVAL(nent.idxs, i);
      TCFREE(idx);
    }
    tcptrlistdel(nent.idxs);
  }
  node = (BDBNODE *)tcmapget(bdb->nodec, &(nent.id), sizeof(nent.id), &rsiz);
  if(clk) BDBUNLOCKCACHE(bdb);
  return node;
}


/* Add an index to a node.
   `bdb' specifies the B+ tree database object.
   `node' specifies the node object.
   `order' specifies whether the calling sequence is orderd or not.
   `pid' specifies the ID number of referred page.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key. */
static void tcbdbnodeaddidx(TCBDB *bdb, BDBNODE *node, bool order, uint64_t pid,
                            const char *kbuf, int ksiz){
  assert(bdb && node && pid > 0 && kbuf && ksiz >= 0);
  BDBIDX *nidx;
  TCMALLOC(nidx, sizeof(*nidx) + ksiz + 1);
  nidx->pid = pid;
  char *ebuf = (char *)nidx + sizeof(*nidx);
  memcpy(ebuf, kbuf, ksiz);
  ebuf[ksiz] = '\0';
  nidx->ksiz = ksiz;
  TCCMP cmp = bdb->cmp;
  void *cmpop = bdb->cmpop;
  TCPTRLIST *idxs = node->idxs;
  if(order){
    TCPTRLISTPUSH(idxs, nidx);
  } else {
    int ln = TCPTRLISTNUM(idxs);
    int left = 0;
    int right = ln;
    int i = (left + right) / 2;
    while(right >= left && i < ln){
      BDBIDX *idx = TCPTRLISTVAL(idxs, i);
      char *ebuf = (char *)idx + sizeof(*idx);
      int rv;
      if(cmp == tccmplexical){
        TCCMPLEXICAL(rv, kbuf, ksiz, ebuf, idx->ksiz);
      } else {
        rv = cmp(kbuf, ksiz, ebuf, idx->ksiz, cmpop);
      }
      if(rv == 0){
        break;
      } else if(rv <= 0){
        right = i - 1;
      } else {
        left = i + 1;
      }
      i = (left + right) / 2;
    }
    while(i < ln){
      BDBIDX *idx = TCPTRLISTVAL(idxs, i);
      char *ebuf = (char *)idx + sizeof(*idx);
      int rv;
      if(cmp == tccmplexical){
        TCCMPLEXICAL(rv, kbuf, ksiz, ebuf, idx->ksiz);
      } else {
        rv = cmp(kbuf, ksiz, ebuf, idx->ksiz, cmpop);
      }
      if(rv < 0){
        TCPTRLISTINSERT(idxs, i, nidx);
        break;
      }
      i++;
    }
    if(i >= ln) TCPTRLISTPUSH(idxs, nidx);
  }
  node->dirty = true;
}


/* Subtract an index from a node.
   `bdb' specifies the B+ tree database object.
   `node' specifies the node object.
   `pid' specifies the ID number of referred page.
   The return value is whether the subtraction is completed. */
static bool tcbdbnodesubidx(TCBDB *bdb, BDBNODE *node, uint64_t pid){
  assert(bdb && node && pid > 0);
  node->dirty = true;
  TCPTRLIST *idxs = node->idxs;
  if(node->heir == pid){
    if(TCPTRLISTNUM(idxs) > 0){
      BDBIDX *idx = tcptrlistshift(idxs);
      assert(idx);
      node->heir = idx->pid;
      TCFREE(idx);
      return true;
    } else if(bdb->hnum > 0){
      BDBNODE *pnode = tcbdbnodeload(bdb, bdb->hist[--bdb->hnum]);
      if(!pnode){
        tcbdbsetecode(bdb, TCEMISC, __FILE__, __LINE__, __func__);
        return false;
      }
      node->dead = true;
      return tcbdbnodesubidx(bdb, pnode, node->id);
    }
    node->dead = true;
    bdb->root = pid;
    while(pid > BDBNODEIDBASE){
      node = tcbdbnodeload(bdb, pid);
      if(!node){
        tcbdbsetecode(bdb, TCEMISC, __FILE__, __LINE__, __func__);
        return false;
      }
      if(node->dead){
        pid = node->heir;
        bdb->root = pid;
      } else {
        pid = 0;
      }
    }
    return false;
  }
  int ln = TCPTRLISTNUM(idxs);
  for(int i = 0; i < ln; i++){
    BDBIDX *idx = TCPTRLISTVAL(idxs, i);
    if(idx->pid == pid){
      TCFREE(tcptrlistremove(idxs, i));
      return true;
    }
  }
  tcbdbsetecode(bdb, TCEMISC, __FILE__, __LINE__, __func__);
  return false;
}


/* Search the leaf object corresponding to a key.
   `bdb' specifies the B+ tree database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   The return value is the ID number of the leaf object or 0 on failure. */
static uint64_t tcbdbsearchleaf(TCBDB *bdb, const char *kbuf, int ksiz){
  assert(bdb && kbuf && ksiz >= 0);
  TCCMP cmp = bdb->cmp;
  void *cmpop = bdb->cmpop;
  uint64_t *hist = bdb->hist;
  uint64_t pid = bdb->root;
  int hnum = 0;
  bdb->hleaf = 0;
  while(pid > BDBNODEIDBASE){
    BDBNODE *node = tcbdbnodeload(bdb, pid);
    if(!node){
      tcbdbsetecode(bdb, TCEMISC, __FILE__, __LINE__, __func__);
      return 0;
    }
    hist[hnum++] = node->id;
    TCPTRLIST *idxs = node->idxs;
    int ln = TCPTRLISTNUM(idxs);
    if(ln > 0){
      int left = 0;
      int right = ln;
      int i = (left + right) / 2;
      BDBIDX *idx = NULL;
      while(right >= left && i < ln){
        idx = TCPTRLISTVAL(idxs, i);
        char *ebuf = (char *)idx + sizeof(*idx);
        int rv;
        if(cmp == tccmplexical){
          TCCMPLEXICAL(rv, kbuf, ksiz, ebuf, idx->ksiz);
        } else {
          rv = cmp(kbuf, ksiz, ebuf, idx->ksiz, cmpop);
        }
        if(rv == 0){
          break;
        } else if(rv <= 0){
          right = i - 1;
        } else {
          left = i + 1;
        }
        i = (left + right) / 2;
      }
      if(i > 0) i--;
      while(i < ln){
        idx = TCPTRLISTVAL(idxs, i);
        char *ebuf = (char *)idx + sizeof(*idx);
        int rv;
        if(cmp == tccmplexical){
          TCCMPLEXICAL(rv, kbuf, ksiz, ebuf, idx->ksiz);
        } else {
          rv = cmp(kbuf, ksiz, ebuf, idx->ksiz, cmpop);
        }
        if(rv < 0){
          if(i == 0){
            pid = node->heir;
            break;
          }
          idx = TCPTRLISTVAL(idxs, i - 1);
          pid = idx->pid;
          break;
        }
        i++;
      }
      if(i >= ln) pid = idx->pid;
    } else {
      pid = node->heir;
    }
  }
  if(bdb->lleaf == pid) bdb->hleaf = pid;
  bdb->lleaf = pid;
  bdb->hnum = hnum;
  return pid;
}


/* Search a record of a leaf.
   `bdb' specifies the B+ tree database object.
   `leaf' specifies the leaf object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   `ip' specifies the pointer to a variable to fetch the index of the correspnding record.
   The return value is the pointer to a corresponding record or `NULL' on failure. */
static BDBREC *tcbdbsearchrec(TCBDB *bdb, BDBLEAF *leaf, const char *kbuf, int ksiz, int *ip){
  assert(bdb && leaf && kbuf && ksiz >= 0);
  TCCMP cmp = bdb->cmp;
  void *cmpop = bdb->cmpop;
  TCPTRLIST *recs = leaf->recs;
  int ln = TCPTRLISTNUM(recs);
  int left = 0;
  int right = ln;
  int i = (left + right) / 2;
  while(right >= left && i < ln){
    BDBREC *rec = TCPTRLISTVAL(recs, i);
    char *dbuf = (char *)rec + sizeof(*rec);
    int rv;
    if(cmp == tccmplexical){
      TCCMPLEXICAL(rv, kbuf, ksiz, dbuf, rec->ksiz);
    } else {
      rv = cmp(kbuf, ksiz, dbuf, rec->ksiz, cmpop);
    }
    if(rv == 0){
      if(ip) *ip = i;
      return rec;
    } else if(rv <= 0){
      right = i - 1;
    } else {
      left = i + 1;
    }
    i = (left + right) / 2;
  }
  if(ip) *ip = i;
  return NULL;
}


/* Remove a record from a leaf.
   `bdb' specifies the B+ tree database object.
   `rec' specifies the record object.
   `ri' specifies the index of the record. */
static void tcbdbremoverec(TCBDB *bdb, BDBLEAF *leaf, BDBREC *rec, int ri){
  assert(bdb && leaf && rec && ri >= 0);
  if(rec->rest){
    leaf->size -= rec->vsiz;
    int vsiz;
    char *vbuf = tclistshift(rec->rest, &vsiz);
    int psiz = TCALIGNPAD(rec->ksiz);
    if(vsiz > rec->vsiz){
      BDBREC *orec = rec;
      TCREALLOC(rec, rec, sizeof(*rec) + rec->ksiz + psiz + vsiz + 1);
      if(rec != orec) tcptrlistover(leaf->recs, ri, rec);
    }
    char *dbuf = (char *)rec + sizeof(*rec);
    memcpy(dbuf + rec->ksiz + psiz, vbuf, vsiz);
    dbuf[rec->ksiz+psiz+vsiz] = '\0';
    rec->vsiz = vsiz;
    TCFREE(vbuf);
    if(TCLISTNUM(rec->rest) < 1){
      tclistdel(rec->rest);
      rec->rest = NULL;
    }
  } else {
    leaf->size -= rec->ksiz + rec->vsiz;
    TCFREE(tcptrlistremove(leaf->recs, ri));
  }
  bdb->rnum--;
}


/* Adjust the caches for leaves and nodes.
   `bdb' specifies the B+ tree database object.
   The return value is true if successful, else, it is false. */
static bool tcbdbcacheadjust(TCBDB *bdb){
  bool err = false;
  if(TCMAPRNUM(bdb->leafc) > bdb->lcnum){
    TCDODEBUG(bdb->cnt_adjleafc++);
    int ecode = tchdbecode(bdb->hdb);
    bool clk = BDBLOCKCACHE(bdb);
    TCMAP *leafc = bdb->leafc;
    tcmapiterinit(leafc);
    int dnum = tclmax(TCMAPRNUM(bdb->leafc) - bdb->lcnum, BDBCACHEOUT);
    for(int i = 0; i < dnum; i++){
      int rsiz;
      if(!tcbdbleafcacheout(bdb, (BDBLEAF *)tcmapiterval(tcmapiternext(leafc, &rsiz), &rsiz)))
        err = true;
    }
    if(clk) BDBUNLOCKCACHE(bdb);
    if(!err && tchdbecode(bdb->hdb) != ecode)
      tcbdbsetecode(bdb, ecode, __FILE__, __LINE__, __func__);
  }
  if(TCMAPRNUM(bdb->nodec) > bdb->ncnum){
    TCDODEBUG(bdb->cnt_adjnodec++);
    int ecode = tchdbecode(bdb->hdb);
    bool clk = BDBLOCKCACHE(bdb);
    TCMAP *nodec = bdb->nodec;
    tcmapiterinit(nodec);
    int dnum = tclmax(TCMAPRNUM(bdb->nodec) - bdb->ncnum, BDBCACHEOUT);
    for(int i = 0; i < dnum; i++){
      int rsiz;
      if(!tcbdbnodecacheout(bdb, (BDBNODE *)tcmapiterval(tcmapiternext(nodec, &rsiz), &rsiz)))
        err = true;
    }
    if(clk) BDBUNLOCKCACHE(bdb);
    if(!err && tchdbecode(bdb->hdb) != ecode)
      tcbdbsetecode(bdb, ecode, __FILE__, __LINE__, __func__);
  }
  return !err;
}


/* Purge dirty pages of caches for leaves and nodes.
   `bdb' specifies the B+ tree database object. */
static void tcbdbcachepurge(TCBDB *bdb){
  bool clk = BDBLOCKCACHE(bdb);
  int tsiz;
  const char *tmp;
  tcmapiterinit(bdb->leafc);
  while((tmp = tcmapiternext(bdb->leafc, &tsiz)) != NULL){
    int lsiz;
    BDBLEAF *leaf = (BDBLEAF *)tcmapiterval(tmp, &lsiz);
    if(!leaf->dirty) continue;
    TCPTRLIST *recs = leaf->recs;
    int ln = TCPTRLISTNUM(recs);
    for(int i = 0; i < ln; i++){
      BDBREC *rec = TCPTRLISTVAL(recs, i);
      if(rec->rest) tclistdel(rec->rest);
      TCFREE(rec);
    }
    tcptrlistdel(recs);
    tcmapout(bdb->leafc, tmp, tsiz);
  }
  tcmapiterinit(bdb->nodec);
  while((tmp = tcmapiternext(bdb->nodec, &tsiz)) != NULL){
    int nsiz;
    BDBNODE *node = (BDBNODE *)tcmapiterval(tmp, &nsiz);
    if(!node->dirty) continue;
    TCPTRLIST *idxs = node->idxs;
    int ln = TCPTRLISTNUM(idxs);
    for(int i = 0; i < ln; i++){
      BDBIDX *idx = TCPTRLISTVAL(idxs, i);
      TCFREE(idx);
    }
    tcptrlistdel(idxs);
    tcmapout(bdb->nodec, tmp, tsiz);
  }
  if(clk) BDBUNLOCKCACHE(bdb);
}


/* Open a database file and connect a B+ tree database object.
   `bdb' specifies the B+ tree database object.
   `path' specifies the path of the internal database file.
   `omode' specifies the connection mode.
   If successful, the return value is true, else, it is false. */
static bool tcbdbopenimpl(TCBDB *bdb, const char *path, int omode){
  assert(bdb && path);
  int homode = HDBOREADER;
  if(omode & BDBOWRITER){
    homode = HDBOWRITER;
    if(omode & BDBOCREAT) homode |= HDBOCREAT;
    if(omode & BDBOTRUNC) homode |= HDBOTRUNC;
    bdb->wmode = true;
  } else {
    bdb->wmode = false;
  }
  if(omode & BDBONOLCK) homode |= HDBONOLCK;
  if(omode & BDBOLCKNB) homode |= HDBOLCKNB;
  if(omode & BDBOTSYNC) homode |= HDBOTSYNC;
  tchdbsettype(bdb->hdb, TCDBTBTREE);
  if(!tchdbopen(bdb->hdb, path, homode)) return false;
  bdb->root = 0;
  bdb->first = 0;
  bdb->last = 0;
  bdb->lnum = 0;
  bdb->nnum = 0;
  bdb->rnum = 0;
  bdb->opaque = tchdbopaque(bdb->hdb);
  bdb->leafc = tcmapnew2(bdb->lcnum * 2 + 1);
  bdb->nodec = tcmapnew2(bdb->ncnum * 2 + 1);
  if(bdb->wmode && tchdbrnum(bdb->hdb) < 1){
    BDBLEAF *leaf = tcbdbleafnew(bdb, 0, 0);
    bdb->root = leaf->id;
    bdb->first = leaf->id;
    bdb->last = leaf->id;
    bdb->lnum = 1;
    bdb->nnum = 0;
    bdb->rnum = 0;
    if(!bdb->cmp){
      bdb->cmp = tccmplexical;
      bdb->cmpop = NULL;
    }
    tcbdbdumpmeta(bdb);
    if(!tcbdbleafsave(bdb, leaf)){
      tcmapdel(bdb->nodec);
      tcmapdel(bdb->leafc);
      tchdbclose(bdb->hdb);
      return false;
    }
  }
  tcbdbloadmeta(bdb);
  if(!bdb->cmp){
    tcbdbsetecode(bdb, TCEINVALID, __FILE__, __LINE__, __func__);
    tcmapdel(bdb->nodec);
    tcmapdel(bdb->leafc);
    tchdbclose(bdb->hdb);
    return false;
  }
  if(bdb->lmemb < BDBMINLMEMB || bdb->nmemb < BDBMINNMEMB ||
     bdb->root < 1 || bdb->first < 1 || bdb->last < 1 ||
     bdb->lnum < 0 || bdb->nnum < 0 || bdb->rnum < 0){
    tcbdbsetecode(bdb, TCEMETA, __FILE__, __LINE__, __func__);
    tcmapdel(bdb->nodec);
    tcmapdel(bdb->leafc);
    tchdbclose(bdb->hdb);
    return false;
  }
  bdb->open = true;
  uint8_t hopts = tchdbopts(bdb->hdb);
  uint8_t opts = 0;
  if(hopts & HDBTLARGE) opts |= BDBTLARGE;
  if(hopts & HDBTDEFLATE) opts |= BDBTDEFLATE;
  if(hopts & HDBTBZIP) opts |= BDBTBZIP;
  if(hopts & HDBTTCBS) opts |= BDBTTCBS;
  if(hopts & HDBTEXCODEC) opts |= BDBTEXCODEC;
  bdb->opts = opts;
  bdb->hleaf = 0;
  bdb->lleaf = 0;
  bdb->tran = false;
  bdb->rbopaque = NULL;
  bdb->clock = 1;
  return true;
}


/* Close a B+ tree database object.
   `bdb' specifies the B+ tree database object.
   If successful, the return value is true, else, it is false. */
static bool tcbdbcloseimpl(TCBDB *bdb){
  assert(bdb);
  bool err = false;
  if(bdb->tran){
    tcbdbcachepurge(bdb);
    memcpy(bdb->opaque, bdb->rbopaque, BDBOPAQUESIZ);
    tcbdbloadmeta(bdb);
    TCFREE(bdb->rbopaque);
    bdb->tran = false;
    bdb->rbopaque = NULL;
    if(!tchdbtranvoid(bdb->hdb)) err = true;
  }
  bdb->open = false;
  const char *vbuf;
  int vsiz;
  TCMAP *leafc = bdb->leafc;
  tcmapiterinit(leafc);
  while((vbuf = tcmapiternext(leafc, &vsiz)) != NULL){
    if(!tcbdbleafcacheout(bdb, (BDBLEAF *)tcmapiterval(vbuf, &vsiz))) err = true;
  }
  TCMAP *nodec = bdb->nodec;
  tcmapiterinit(nodec);
  while((vbuf = tcmapiternext(nodec, &vsiz)) != NULL){
    if(!tcbdbnodecacheout(bdb, (BDBNODE *)tcmapiterval(vbuf, &vsiz))) err = true;
  }
  if(bdb->wmode) tcbdbdumpmeta(bdb);
  tcmapdel(bdb->nodec);
  tcmapdel(bdb->leafc);
  if(!tchdbclose(bdb->hdb)) err = true;
  return !err;
}


/* Store a record into a B+ tree database object.
   `bdb' specifies the B+ tree database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   `vbuf' specifies the pointer to the region of the value.
   `vsiz' specifies the size of the region of the value.
   `dmode' specifies behavior when the key overlaps.
   If successful, the return value is true, else, it is false. */
static bool tcbdbputimpl(TCBDB *bdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz,
                         int dmode){
  assert(bdb && kbuf && ksiz >= 0);
  BDBLEAF *leaf = NULL;
  uint64_t hlid = bdb->hleaf;
  if(hlid < 1 || !(leaf = tcbdbgethistleaf(bdb, kbuf, ksiz, hlid))){
    uint64_t pid = tcbdbsearchleaf(bdb, kbuf, ksiz);
    if(pid < 1) return false;
    if(!(leaf = tcbdbleafload(bdb, pid))) return false;
    hlid = 0;
  }
  if(!tcbdbleafaddrec(bdb, leaf, dmode, kbuf, ksiz, vbuf, vsiz)){
    if(!bdb->tran) tcbdbcacheadjust(bdb);
    return false;
  }
  int rnum = TCPTRLISTNUM(leaf->recs);
  if(rnum > bdb->lmemb || (rnum > 1 && leaf->size > bdb->lsmax)){
    if(hlid > 0 && hlid != tcbdbsearchleaf(bdb, kbuf, ksiz)) return false;
    bdb->lschk = 0;
    BDBLEAF *newleaf = tcbdbleafdivide(bdb, leaf);
    if(!newleaf) return false;
    if(leaf->id == bdb->last) bdb->last = newleaf->id;
    uint64_t heir = leaf->id;
    uint64_t pid = newleaf->id;
    BDBREC *rec = TCPTRLISTVAL(newleaf->recs, 0);
    char *dbuf = (char *)rec + sizeof(*rec);
    int ksiz = rec->ksiz;
    char *kbuf;
    TCMEMDUP(kbuf, dbuf, ksiz);
    while(true){
      BDBNODE *node;
      if(bdb->hnum < 1){
        node = tcbdbnodenew(bdb, heir);
        tcbdbnodeaddidx(bdb, node, true, pid, kbuf, ksiz);
        bdb->root = node->id;
        TCFREE(kbuf);
        break;
      }
      uint64_t parent = bdb->hist[--bdb->hnum];
      if(!(node = tcbdbnodeload(bdb, parent))){
        TCFREE(kbuf);
        return false;
      }
      tcbdbnodeaddidx(bdb, node, false, pid, kbuf, ksiz);
      TCFREE(kbuf);
      TCPTRLIST *idxs = node->idxs;
      int ln = TCPTRLISTNUM(idxs);
      if(ln <= bdb->nmemb) break;
      int mid = ln / 2;
      BDBIDX *idx = TCPTRLISTVAL(idxs, mid);
      BDBNODE *newnode = tcbdbnodenew(bdb, idx->pid);
      heir = node->id;
      pid = newnode->id;
      char *ebuf = (char *)idx + sizeof(*idx);
      TCMEMDUP(kbuf, ebuf, idx->ksiz);
      ksiz = idx->ksiz;
      for(int i = mid + 1; i < ln; i++){
        idx = TCPTRLISTVAL(idxs, i);
        char *ebuf = (char *)idx + sizeof(*idx);
        tcbdbnodeaddidx(bdb, newnode, true, idx->pid, ebuf, idx->ksiz);
      }
      ln = TCPTRLISTNUM(newnode->idxs);
      for(int i = 0; i <= ln; i++){
        idx = tcptrlistpop(idxs);
        TCFREE(idx);
      }
      node->dirty = true;
    }
    if(bdb->capnum > 0 && bdb->rnum > bdb->capnum){
      uint64_t xnum = bdb->rnum - bdb->capnum;
      BDBCUR *cur = tcbdbcurnew(bdb);
      while((xnum--) > 0){
        if((cur->id < 1 || cur->clock != bdb->clock) && !tcbdbcurfirstimpl(cur)){
          tcbdbcurdel(cur);
          return false;
        }
        if(!tcbdbcuroutimpl(cur)){
          tcbdbcurdel(cur);
          return false;
        }
      }
      tcbdbcurdel(cur);
    }
  } else if(rnum < 1){
    if(hlid > 0 && hlid != tcbdbsearchleaf(bdb, kbuf, ksiz)) return false;
    if(bdb->hnum > 0 && !tcbdbleafkill(bdb, leaf)) return false;
  }
  if(!bdb->tran && !tcbdbcacheadjust(bdb)) return false;
  return true;
}


/* Remove a record of a B+ tree database object.
   `bdb' specifies the B+ tree database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   If successful, the return value is true, else, it is false. */
static bool tcbdboutimpl(TCBDB *bdb, const char *kbuf, int ksiz){
  assert(bdb && kbuf && ksiz >= 0);
  BDBLEAF *leaf = NULL;
  uint64_t hlid = bdb->hleaf;
  if(hlid < 1 || !(leaf = tcbdbgethistleaf(bdb, kbuf, ksiz, hlid))){
    uint64_t pid = tcbdbsearchleaf(bdb, kbuf, ksiz);
    if(pid < 1) return false;
    if(!(leaf = tcbdbleafload(bdb, pid))) return false;
    hlid = 0;
  }
  int ri;
  BDBREC *rec = tcbdbsearchrec(bdb, leaf, kbuf, ksiz, &ri);
  if(!rec){
    tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
    return false;
  }
  tcbdbremoverec(bdb, leaf, rec, ri);
  leaf->dirty = true;
  if(TCPTRLISTNUM(leaf->recs) < 1){
    if(hlid > 0 && hlid != tcbdbsearchleaf(bdb, kbuf, ksiz)) return false;
    if(bdb->hnum > 0 && !tcbdbleafkill(bdb, leaf)) return false;
  }
  if(!bdb->tran && !tcbdbcacheadjust(bdb)) return false;
  return true;
}


/* Remove records of a B+ tree database object.
   `bdb' specifies the B+ tree database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   If successful, the return value is true, else, it is false. */
static bool tcbdboutlist(TCBDB *bdb, const char *kbuf, int ksiz){
  assert(bdb && kbuf && ksiz >= 0);
  BDBLEAF *leaf = NULL;
  uint64_t hlid = bdb->hleaf;
  if(hlid < 1 || !(leaf = tcbdbgethistleaf(bdb, kbuf, ksiz, hlid))){
    uint64_t pid = tcbdbsearchleaf(bdb, kbuf, ksiz);
    if(pid < 1) return false;
    if(!(leaf = tcbdbleafload(bdb, pid))) return false;
    hlid = 0;
  }
  int ri;
  BDBREC *rec = tcbdbsearchrec(bdb, leaf, kbuf, ksiz, &ri);
  if(!rec){
    tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
    return false;
  }
  int rnum = 1;
  int rsiz = rec->ksiz + rec->vsiz;
  if(rec->rest){
    TCLIST *rest = rec->rest;
    int ln = TCLISTNUM(rec->rest);
    rnum += ln;
    for(int i = 0; i < ln; i++){
      rsiz += TCLISTVALSIZ(rest, i);
    }
    tclistdel(rest);
  }
  TCFREE(tcptrlistremove(leaf->recs, ri));
  leaf->size -= rsiz;
  leaf->dirty = true;
  bdb->rnum -= rnum;
  if(TCPTRLISTNUM(leaf->recs) < 1){
    if(hlid > 0 && hlid != tcbdbsearchleaf(bdb, kbuf, ksiz)) return false;
    if(bdb->hnum > 0 && !tcbdbleafkill(bdb, leaf)) return false;
  }
  if(!bdb->tran && !tcbdbcacheadjust(bdb)) return false;
  return true;
}


/* Retrieve a record in a B+ tree database object.
   `bdb' specifies the B+ tree database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   `sp' specifies the pointer to the variable into which the size of the region of the return
   value is assigned.
   If successful, the return value is the pointer to the region of the value of the corresponding
   record. */
static const char *tcbdbgetimpl(TCBDB *bdb, const char *kbuf, int ksiz, int *sp){
  assert(bdb && kbuf && ksiz >= 0 && sp);
  BDBLEAF *leaf = NULL;
  uint64_t hlid = bdb->hleaf;
  if(hlid < 1 || !(leaf = tcbdbgethistleaf(bdb, kbuf, ksiz, hlid))){
    uint64_t pid = tcbdbsearchleaf(bdb, kbuf, ksiz);
    if(pid < 1) return NULL;
    if(!(leaf = tcbdbleafload(bdb, pid))) return NULL;
  }
  BDBREC *rec = tcbdbsearchrec(bdb, leaf, kbuf, ksiz, NULL);
  if(!rec){
    tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
    return NULL;
  }
  *sp = rec->vsiz;
  return (char *)rec + sizeof(*rec) + rec->ksiz + TCALIGNPAD(rec->ksiz);
}


/* Get the number of records corresponding a key in a B+ tree database object.
   `bdb' specifies the B+ tree database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   If successful, the return value is the number of the corresponding records, else, it is 0. */
static int tcbdbgetnum(TCBDB *bdb, const char *kbuf, int ksiz){
  assert(bdb && kbuf && ksiz >= 0);
  BDBLEAF *leaf = NULL;
  uint64_t hlid = bdb->hleaf;
  if(hlid < 1 || !(leaf = tcbdbgethistleaf(bdb, kbuf, ksiz, hlid))){
    uint64_t pid = tcbdbsearchleaf(bdb, kbuf, ksiz);
    if(pid < 1) return 0;
    if(!(leaf = tcbdbleafload(bdb, pid))) return 0;
  }
  BDBREC *rec = tcbdbsearchrec(bdb, leaf, kbuf, ksiz, NULL);
  if(!rec){
    tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
    return 0;
  }
  return rec->rest ? TCLISTNUM(rec->rest) + 1 : 1;
}


/* Retrieve records in a B+ tree database object.
   `bdb' specifies the B+ tree database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   If successful, the return value is a list object of the values of the corresponding records. */
static TCLIST *tcbdbgetlist(TCBDB *bdb, const char *kbuf, int ksiz){
  assert(bdb && kbuf && ksiz >= 0);
  BDBLEAF *leaf = NULL;
  uint64_t hlid = bdb->hleaf;
  if(hlid < 1 || !(leaf = tcbdbgethistleaf(bdb, kbuf, ksiz, hlid))){
    uint64_t pid = tcbdbsearchleaf(bdb, kbuf, ksiz);
    if(pid < 1) return NULL;
    if(!(leaf = tcbdbleafload(bdb, pid))) return NULL;
  }
  BDBREC *rec = tcbdbsearchrec(bdb, leaf, kbuf, ksiz, NULL);
  if(!rec){
    tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
    return NULL;
  }
  TCLIST *vals;
  TCLIST *rest = rec->rest;
  if(rest){
    int ln = TCLISTNUM(rest);
    vals = tclistnew2(ln + 1);
    TCLISTPUSH(vals, (char *)rec + sizeof(*rec) + rec->ksiz + TCALIGNPAD(rec->ksiz), rec->vsiz);
    for(int i = 0; i < ln; i++){
      const char *vbuf;
      int vsiz;
      TCLISTVAL(vbuf, rest, i, vsiz);
      TCLISTPUSH(vals, vbuf, vsiz);
    }
  } else {
    vals = tclistnew2(1);
    TCLISTPUSH(vals, (char *)rec + sizeof(*rec) + rec->ksiz + TCALIGNPAD(rec->ksiz), rec->vsiz);
  }
  return vals;
}


/* Get keys of ranged records in a B+ tree database object.
   `bdb' specifies the B+ tree database object.
   `bkbuf' specifies the pointer to the region of the key of the beginning border.
   `bksiz' specifies the size of the region of the beginning key.
   `binc' specifies whether the beginning border is inclusive or not.
   `ekbuf' specifies the pointer to the region of the key of the ending border.
   `eksiz' specifies the size of the region of the ending key.
   `einc' specifies whether the ending border is inclusive or not.
   `max' specifies the maximum number of keys to be fetched.
   `keys' specifies a list object to store the result.
   If successful, the return value is true, else, it is false. */
static bool tcbdbrangeimpl(TCBDB *bdb, const char *bkbuf, int bksiz, bool binc,
                           const char *ekbuf, int eksiz, bool einc, int max, TCLIST *keys){
  assert(bdb && keys);
  bool err = false;
  BDBCUR *cur = tcbdbcurnew(bdb);
  if(bkbuf){
    tcbdbcurjumpimpl(cur, bkbuf, bksiz, true);
  } else {
    tcbdbcurfirstimpl(cur);
  }
  TCCMP cmp = bdb->cmp;
  void *cmpop = bdb->cmpop;
  const char *lbuf = NULL;
  int lsiz = 0;
  while(cur->id > 0){
    const char *kbuf, *vbuf;
    int ksiz, vsiz;
    if(!tcbdbcurrecimpl(cur, &kbuf, &ksiz, &vbuf, &vsiz)){
      if(tchdbecode(bdb->hdb) != TCEINVALID && tchdbecode(bdb->hdb) != TCENOREC) err = true;
      break;
    }
    if(bkbuf && !binc){
      if(cmp(kbuf, ksiz, bkbuf, bksiz, cmpop) == 0){
        tcbdbcurnextimpl(cur);
        continue;
      }
      bkbuf = NULL;
    }
    if(ekbuf){
      if(einc){
        if(cmp(kbuf, ksiz, ekbuf, eksiz, cmpop) > 0) break;
      } else {
        if(cmp(kbuf, ksiz, ekbuf, eksiz, cmpop) >= 0) break;
      }
    }
    if(!lbuf || lsiz != ksiz || memcmp(kbuf, lbuf, ksiz)){
      TCLISTPUSH(keys, kbuf, ksiz);
      if(max >= 0 && TCLISTNUM(keys) >= max) break;
      lbuf = kbuf;
      lsiz = ksiz;
    }
    tcbdbcurnextimpl(cur);
  }
  tcbdbcurdel(cur);
  return !err;
}


/* Get forward matching keys in a B+ tree database object.
   `bdb' specifies the B+ tree database object.
   `pbuf' specifies the pointer to the region of the prefix.
   `psiz' specifies the size of the region of the prefix.
   `max' specifies the maximum number of keys to be fetched.
   `keys' specifies a list object to store the result.
   If successful, the return value is true, else, it is false. */
static bool tcbdbrangefwm(TCBDB *bdb, const char *pbuf, int psiz, int max, TCLIST *keys){
  assert(bdb && pbuf && psiz >= 0 && keys);
  bool err = false;
  if(max < 0) max = INT_MAX;
  if(max < 1) return true;
  BDBCUR *cur = tcbdbcurnew(bdb);
  tcbdbcurjumpimpl(cur, pbuf, psiz, true);
  const char *lbuf = NULL;
  int lsiz = 0;
  while(cur->id > 0){
    const char *kbuf, *vbuf;
    int ksiz, vsiz;
    if(!tcbdbcurrecimpl(cur, &kbuf, &ksiz, &vbuf, &vsiz)){
      if(tchdbecode(bdb->hdb) != TCEINVALID && tchdbecode(bdb->hdb) != TCENOREC) err = true;
      break;
    }
    if(ksiz < psiz || memcmp(kbuf, pbuf, psiz)) break;
    if(!lbuf || lsiz != ksiz || memcmp(kbuf, lbuf, ksiz)){
      TCLISTPUSH(keys, kbuf, ksiz);
      if(TCLISTNUM(keys) >= max) break;
      lbuf = kbuf;
      lsiz = ksiz;
    }
    tcbdbcurnextimpl(cur);
  }
  tcbdbcurdel(cur);
  return !err;
}


/* Optimize the file of a B+ tree database object.
   `bdb' specifies the B+ tree database object.
   `lmemb' specifies the number of members in each leaf page.
   `nmemb' specifies the number of members in each non-leaf page.
   `bnum' specifies the number of elements of the bucket array.
   `apow' specifies the size of record alignment by power of 2.
   `fpow' specifies the maximum number of elements of the free block pool by power of 2.
   `opts' specifies options by bitwise-or.
   If successful, the return value is true, else, it is false. */
static bool tcbdboptimizeimpl(TCBDB *bdb, int32_t lmemb, int32_t nmemb,
                              int64_t bnum, int8_t apow, int8_t fpow, uint8_t opts){
  assert(bdb);
  const char *path = tchdbpath(bdb->hdb);
  char *tpath = tcsprintf("%s%ctmp%c%llu", path, MYEXTCHR, MYEXTCHR, tchdbinode(bdb->hdb));
  TCBDB *tbdb = tcbdbnew();
  int dbgfd = tchdbdbgfd(bdb->hdb);
  if(dbgfd >= 0) tcbdbsetdbgfd(tbdb, dbgfd);
  tcbdbsetcmpfunc(tbdb, bdb->cmp, bdb->cmpop);
  TCCODEC enc, dec;
  void *encop, *decop;
  tchdbcodecfunc(bdb->hdb, &enc, &encop, &dec, &decop);
  if(enc && dec) tcbdbsetcodecfunc(tbdb, enc, encop, dec, decop);
  if(lmemb < 1) lmemb = bdb->lmemb;
  if(nmemb < 1) nmemb = bdb->nmemb;
  if(bnum < 1) bnum = tchdbrnum(bdb->hdb) * 2 + 1;
  if(apow < 0) apow = tclog2l(tchdbalign(bdb->hdb));
  if(fpow < 0) fpow = tclog2l(tchdbfbpmax(bdb->hdb));
  if(opts == UINT8_MAX) opts = bdb->opts;
  tcbdbtune(tbdb, lmemb, nmemb, bnum, apow, fpow, opts);
  tcbdbsetcache(tbdb, 1, 1);
  tcbdbsetlsmax(tbdb, bdb->lsmax);
  uint32_t lcnum = bdb->lcnum;
  uint32_t ncnum = bdb->ncnum;
  bdb->lcnum = BDBLEVELMAX;
  bdb->ncnum = BDBCACHEOUT * 2;
  tbdb->lcnum = BDBLEVELMAX;
  tbdb->ncnum = BDBCACHEOUT * 2;
  if(!tcbdbopen(tbdb, tpath, BDBOWRITER | BDBOCREAT | BDBOTRUNC)){
    tcbdbsetecode(bdb, tcbdbecode(tbdb), __FILE__, __LINE__, __func__);
    tcbdbdel(tbdb);
    TCFREE(tpath);
    return false;
  }
  memcpy(tcbdbopaque(tbdb), tcbdbopaque(bdb), BDBLEFTOPQSIZ);
  bool err = false;
  BDBCUR *cur = tcbdbcurnew(bdb);
  tcbdbcurfirstimpl(cur);
  const char *kbuf, *vbuf;
  int ksiz, vsiz;
  int cnt = 0;
  while(!err && cur->id > 0 && tcbdbcurrecimpl(cur, &kbuf, &ksiz, &vbuf, &vsiz)){
    if(!tcbdbputdup(tbdb, kbuf, ksiz, vbuf, vsiz)){
      tcbdbsetecode(bdb, tcbdbecode(tbdb), __FILE__, __LINE__, __func__);
      err = true;
    }
    tcbdbcurnextimpl(cur);
    if((++cnt % 0xf == 0) && !tcbdbcacheadjust(bdb)) err = true;
  }
  tcbdbcurdel(cur);
  if(!tcbdbclose(tbdb)){
    tcbdbsetecode(bdb, tcbdbecode(tbdb), __FILE__, __LINE__, __func__);
    err = true;
  }
  bdb->lcnum = lcnum;
  bdb->ncnum = ncnum;
  tcbdbdel(tbdb);
  if(unlink(path) == -1){
    tcbdbsetecode(bdb, TCEUNLINK, __FILE__, __LINE__, __func__);
    err = true;
  }
  if(rename(tpath, path) == -1){
    tcbdbsetecode(bdb, TCERENAME, __FILE__, __LINE__, __func__);
    err = true;
  }
  TCFREE(tpath);
  if(err) return false;
  tpath = tcstrdup(path);
  int omode = (tchdbomode(bdb->hdb) & ~BDBOCREAT) & ~BDBOTRUNC;
  if(!tcbdbcloseimpl(bdb)){
    TCFREE(tpath);
    return false;
  }
  bool rv = tcbdbopenimpl(bdb, tpath, omode);
  TCFREE(tpath);
  return rv;
}


/* Remove all records of a B+ tree database object.
   `bdb' specifies the B+ tree database object.
   If successful, the return value is true, else, it is false. */
static bool tcbdbvanishimpl(TCBDB *bdb){
  assert(bdb);
  char *path = tcstrdup(tchdbpath(bdb->hdb));
  int omode = tchdbomode(bdb->hdb);
  bool err = false;
  if(!tcbdbcloseimpl(bdb)) err = true;
  if(!tcbdbopenimpl(bdb, path, BDBOTRUNC | omode)) err = true;
  TCFREE(path);
  return !err;
}


/* Lock a method of the B+ tree database object.
   `bdb' specifies the B+ tree database object.
   `wr' specifies whether the lock is writer or not.
   If successful, the return value is true, else, it is false. */
static bool tcbdblockmethod(TCBDB *bdb, bool wr){
  assert(bdb);
  if(wr ? pthread_rwlock_wrlock(bdb->mmtx) != 0 : pthread_rwlock_rdlock(bdb->mmtx) != 0){
    tcbdbsetecode(bdb, TCETHREAD, __FILE__, __LINE__, __func__);
    return false;
  }
  TCTESTYIELD();
  return true;
}


/* Unlock a method of the B+ tree database object.
   `bdb' specifies the B+ tree database object.
   If successful, the return value is true, else, it is false. */
static bool tcbdbunlockmethod(TCBDB *bdb){
  assert(bdb);
  if(pthread_rwlock_unlock(bdb->mmtx) != 0){
    tcbdbsetecode(bdb, TCETHREAD, __FILE__, __LINE__, __func__);
    return false;
  }
  TCTESTYIELD();
  return true;
}


/* Lock the cache of the B+ tree database object.
   `bdb' specifies the B+ tree database object.
   If successful, the return value is true, else, it is false. */
static bool tcbdblockcache(TCBDB *bdb){
  assert(bdb);
  if(pthread_mutex_lock(bdb->cmtx) != 0){
    tcbdbsetecode(bdb, TCETHREAD, __FILE__, __LINE__, __func__);
    return false;
  }
  TCTESTYIELD();
  return true;
}


/* Unlock the cache of the B+ tree database object.
   `bdb' specifies the B+ tree database object.
   If successful, the return value is true, else, it is false. */
static bool tcbdbunlockcache(TCBDB *bdb){
  assert(bdb);
  if(pthread_mutex_unlock(bdb->cmtx) != 0){
    tcbdbsetecode(bdb, TCETHREAD, __FILE__, __LINE__, __func__);
    return false;
  }
  TCTESTYIELD();
  return true;
}


/* Move a cursor object to the first record.
   `cur' specifies the cursor object.
   If successful, the return value is true, else, it is false. */
static bool tcbdbcurfirstimpl(BDBCUR *cur){
  assert(cur);
  TCBDB *bdb = cur->bdb;
  cur->clock = bdb->clock;
  cur->id = bdb->first;
  cur->kidx = 0;
  cur->vidx = 0;
  return tcbdbcuradjust(cur, true);
}


/* Move a cursor object to the last record.
   `cur' specifies the cursor object.
   If successful, the return value is true, else, it is false. */
static bool tcbdbcurlastimpl(BDBCUR *cur){
  assert(cur);
  TCBDB *bdb = cur->bdb;
  cur->clock = bdb->clock;
  cur->id = bdb->last;
  cur->kidx = INT_MAX;
  cur->vidx = INT_MAX;
  return tcbdbcuradjust(cur, false);
}


/* Move a cursor object to around records corresponding a key.
   `cur' specifies the cursor object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   `forward' specifies whether the cursor is to be the front of records.
   If successful, the return value is true, else, it is false. */
static bool tcbdbcurjumpimpl(BDBCUR *cur, const char *kbuf, int ksiz, bool forward){
  assert(cur && kbuf && ksiz >= 0);
  TCBDB *bdb = cur->bdb;
  cur->clock = bdb->clock;
  uint64_t pid = tcbdbsearchleaf(bdb, kbuf, ksiz);
  if(pid < 1){
    cur->id = 0;
    cur->kidx = 0;
    cur->vidx = 0;
    return false;
  }
  BDBLEAF *leaf = tcbdbleafload(bdb, pid);
  if(!leaf){
    cur->id = 0;
    cur->kidx = 0;
    cur->vidx = 0;
    return false;
  }
  if(leaf->dead || TCPTRLISTNUM(leaf->recs) < 1){
    cur->id = pid;
    cur->kidx = 0;
    cur->vidx = 0;
    return forward ? tcbdbcurnextimpl(cur) : tcbdbcurprevimpl(cur);
  }
  int ri;
  BDBREC *rec = tcbdbsearchrec(bdb, leaf, kbuf, ksiz, &ri);
  if(rec){
    cur->id = pid;
    cur->kidx = ri;
    if(forward){
      cur->vidx = 0;
    } else {
      cur->vidx = rec->rest ? TCLISTNUM(rec->rest) : 0;
    }
    return true;
  }
  cur->id = leaf->id;
  if(ri > 0 && ri >= TCPTRLISTNUM(leaf->recs)) ri = TCPTRLISTNUM(leaf->recs) - 1;
  cur->kidx = ri;
  rec = TCPTRLISTVAL(leaf->recs, ri);
  char *dbuf = (char *)rec + sizeof(*rec);
  if(forward){
    int rv;
    if(bdb->cmp == tccmplexical){
      TCCMPLEXICAL(rv, kbuf, ksiz, dbuf, rec->ksiz);
    } else {
      rv = bdb->cmp(kbuf, ksiz, dbuf, rec->ksiz, bdb->cmpop);
    }
    if(rv < 0){
      cur->vidx = 0;
      return true;
    }
    cur->vidx = rec->rest ? TCLISTNUM(rec->rest) : 0;
    return tcbdbcurnextimpl(cur);
  }
  int rv;
  if(bdb->cmp == tccmplexical){
    TCCMPLEXICAL(rv, kbuf, ksiz, dbuf, rec->ksiz);
  } else {
    rv = bdb->cmp(kbuf, ksiz, dbuf, rec->ksiz, bdb->cmpop);
  }
  if(rv > 0){
    cur->vidx = rec->rest ? TCLISTNUM(rec->rest) : 0;
    return true;
  }
  cur->vidx = 0;
  return tcbdbcurprevimpl(cur);
}


/* Adjust a cursor object forward to the suitable record.
   `cur' specifies the cursor object.
   `forward' specifies the direction is forward or not.
   If successful, the return value is true, else, it is false. */
static bool tcbdbcuradjust(BDBCUR *cur, bool forward){
  assert(cur);
  TCBDB *bdb = cur->bdb;
  if(cur->clock != bdb->clock){
    if(!tcbdbleafcheck(bdb, cur->id)){
      tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
      cur->id = 0;
      cur->kidx = 0;
      cur->vidx = 0;
      return false;
    }
    cur->clock = bdb->clock;
  }
  while(true){
    if(cur->id < 1){
      tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
      cur->id = 0;
      cur->kidx = 0;
      cur->vidx = 0;
      return false;
    }
    BDBLEAF *leaf = tcbdbleafload(bdb, cur->id);
    if(!leaf) return false;
    TCPTRLIST *recs = leaf->recs;
    int knum = TCPTRLISTNUM(recs);
    if(leaf->dead){
      if(forward){
        cur->id = leaf->next;
        cur->kidx = 0;
        cur->vidx = 0;
      } else {
        cur->id = leaf->prev;
        cur->kidx = INT_MAX;
        cur->vidx = INT_MAX;
      }
    } else if(cur->kidx < 0){
      if(forward){
        cur->kidx = 0;
        cur->vidx = 0;
      } else {
        cur->id = leaf->prev;
        cur->kidx = INT_MAX;
        cur->vidx = INT_MAX;
      }
    } else if(cur->kidx >= knum){
      if(forward){
        cur->id = leaf->next;
        cur->kidx = 0;
        cur->vidx = 0;
      } else {
        cur->kidx = knum - 1;
        cur->vidx = INT_MAX;
      }
    } else {
      BDBREC *rec = TCPTRLISTVAL(recs, cur->kidx);
      int vnum = rec->rest ? TCLISTNUM(rec->rest) + 1 : 1;
      if(cur->vidx < 0){
        if(forward){
          cur->vidx = 0;
        } else {
          cur->kidx--;
          cur->vidx = INT_MAX;
        }
      } else if(cur->vidx >= vnum){
        if(forward){
          cur->kidx++;
          cur->vidx = 0;
          if(cur->kidx >= knum){
            cur->id = leaf->next;
            cur->kidx = 0;
            cur->vidx = 0;
          } else {
            break;
          }
        } else {
          cur->vidx = vnum - 1;
          if(cur->vidx >= 0) break;
        }
      } else {
        break;
      }
    }
  }
  return true;
}


/* Move a cursor object to the previous record.
   `cur' specifies the cursor object.
   If successful, the return value is true, else, it is false. */
static bool tcbdbcurprevimpl(BDBCUR *cur){
  assert(cur);
  cur->vidx--;
  return tcbdbcuradjust(cur, false);
}


/* Move a cursor object to the next record.
   `cur' specifies the cursor object.
   If successful, the return value is true, else, it is false. */
static bool tcbdbcurnextimpl(BDBCUR *cur){
  assert(cur);
  cur->vidx++;
  return tcbdbcuradjust(cur, true);
}


/* Insert a record around a cursor object.
   `cur' specifies the cursor object.
   `vbuf' specifies the pointer to the region of the value.
   `vsiz' specifies the size of the region of the value.
   `cpmode' specifies detail adjustment.
   If successful, the return value is true, else, it is false. */
static bool tcbdbcurputimpl(BDBCUR *cur, const char *vbuf, int vsiz, int cpmode){
  assert(cur && vbuf && vsiz >= 0);
  TCBDB *bdb = cur->bdb;
  if(cur->clock != bdb->clock){
    if(!tcbdbleafcheck(bdb, cur->id)){
      tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
      cur->id = 0;
      cur->kidx = 0;
      cur->vidx = 0;
      return false;
    }
    cur->clock = bdb->clock;
  }
  BDBLEAF *leaf = tcbdbleafload(bdb, cur->id);
  if(!leaf) return false;
  TCPTRLIST *recs = leaf->recs;
  if(cur->kidx >= TCPTRLISTNUM(recs)){
    tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
    return false;
  }
  BDBREC *rec = TCPTRLISTVAL(recs, cur->kidx);
  int vnum = rec->rest ? TCLISTNUM(rec->rest) + 1 : 1;
  if(cur->vidx >= vnum){
    tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
    return false;
  }
  char *dbuf = (char *)rec + sizeof(*rec);
  int psiz = TCALIGNPAD(rec->ksiz);
  BDBREC *orec = rec;
  switch(cpmode){
    case BDBCPCURRENT:
      if(cur->vidx < 1){
        leaf->size += vsiz - rec->vsiz;
        if(vsiz > rec->vsiz){
          TCREALLOC(rec, rec, sizeof(*rec) + rec->ksiz + psiz + vsiz + 1);
          if(rec != orec){
            tcptrlistover(recs, cur->kidx, rec);
            dbuf = (char *)rec + sizeof(*rec);
          }
        }
        memcpy(dbuf + rec->ksiz + psiz, vbuf, vsiz);
        dbuf[rec->ksiz+psiz+vsiz] = '\0';
        rec->vsiz = vsiz;
      } else {
        leaf->size += vsiz - TCLISTVALSIZ(rec->rest, cur->vidx - 1);
        tclistover(rec->rest, cur->vidx - 1, vbuf, vsiz);
      }
      break;
    case BDBCPBEFORE:
      leaf->size += vsiz;
      if(cur->vidx < 1){
        if(!rec->rest) rec->rest = tclistnew2(1);
        tclistunshift(rec->rest, dbuf + rec->ksiz + psiz, rec->vsiz);
        if(vsiz > rec->vsiz){
          TCREALLOC(rec, rec, sizeof(*rec) + rec->ksiz + psiz + vsiz + 1);
          if(rec != orec){
            tcptrlistover(recs, cur->kidx, rec);
            dbuf = (char *)rec + sizeof(*rec);
          }
        }
        memcpy(dbuf + rec->ksiz + psiz, vbuf, vsiz);
        dbuf[rec->ksiz+psiz+vsiz] = '\0';
        rec->vsiz = vsiz;
      } else {
        TCLISTINSERT(rec->rest, cur->vidx - 1, vbuf, vsiz);
      }
      bdb->rnum++;
      break;
    case BDBCPAFTER:
      leaf->size += vsiz;
      if(!rec->rest) rec->rest = tclistnew2(1);
      TCLISTINSERT(rec->rest, cur->vidx, vbuf, vsiz);
      cur->vidx++;
      bdb->rnum++;
      break;
  }
  leaf->dirty = true;
  return true;
}


/* Delete the record where a cursor object is.
   `cur' specifies the cursor object.
   If successful, the return value is true, else, it is false. */
static bool tcbdbcuroutimpl(BDBCUR *cur){
  assert(cur);
  TCBDB *bdb = cur->bdb;
  if(cur->clock != bdb->clock){
    if(!tcbdbleafcheck(bdb, cur->id)){
      tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
      cur->id = 0;
      cur->kidx = 0;
      cur->vidx = 0;
      return false;
    }
    cur->clock = bdb->clock;
  }
  BDBLEAF *leaf = tcbdbleafload(bdb, cur->id);
  if(!leaf) return false;
  TCPTRLIST *recs = leaf->recs;
  if(cur->kidx >= TCPTRLISTNUM(recs)){
    tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
    return false;
  }
  BDBREC *rec = TCPTRLISTVAL(recs, cur->kidx);
  char *dbuf = (char *)rec + sizeof(*rec);
  int vnum = rec->rest ? TCLISTNUM(rec->rest) + 1 : 1;
  if(cur->vidx >= vnum){
    tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
    return false;
  }
  if(rec->rest){
    if(cur->vidx < 1){
      leaf->size -= rec->vsiz;
      int vsiz;
      char *vbuf = tclistshift(rec->rest, &vsiz);
      int psiz = TCALIGNPAD(rec->ksiz);
      if(vsiz > rec->vsiz){
        BDBREC *orec = rec;
        TCREALLOC(rec, rec, sizeof(*rec) + rec->ksiz + psiz + vsiz + 1);
        if(rec != orec){
          tcptrlistover(leaf->recs, cur->kidx, rec);
          dbuf = (char *)rec + sizeof(*rec);
        }
      }
      memcpy(dbuf + rec->ksiz + psiz, vbuf, vsiz);
      dbuf[rec->ksiz+psiz+vsiz] = '\0';
      rec->vsiz = vsiz;
      TCFREE(vbuf);
    } else {
      int vsiz;
      char *vbuf = tclistremove(rec->rest, cur->vidx - 1, &vsiz);
      leaf->size -= vsiz;
      TCFREE(vbuf);
    }
    if(TCLISTNUM(rec->rest) < 1){
      tclistdel(rec->rest);
      rec->rest = NULL;
    }
  } else {
    leaf->size -= rec->ksiz + rec->vsiz;
    if(TCPTRLISTNUM(recs) < 2){
      uint64_t pid = tcbdbsearchleaf(bdb, dbuf, rec->ksiz);
      if(pid < 1) return false;
      if(bdb->hnum > 0){
        if(!(leaf = tcbdbleafload(bdb, pid))) return false;
        if(!tcbdbleafkill(bdb, leaf)) return false;
        if(leaf->next != 0){
          cur->id = leaf->next;
          cur->kidx = 0;
          cur->vidx = 0;
          cur->clock = bdb->clock;
        }
      }
    }
    TCFREE(tcptrlistremove(leaf->recs, cur->kidx));
  }
  bdb->rnum--;
  leaf->dirty = true;
  return tcbdbcuradjust(cur, true) || tchdbecode(bdb->hdb) == TCENOREC;
}


/* Get the key and the value of the current record of the cursor object.
   `cur' specifies the cursor object.
   `kbp' specifies the pointer to the variable into which the pointer to the region of the key is
   assgined.
   `ksp' specifies the pointer to the variable into which the size of the key region is assigned.
   `vbp' specifies the pointer to the variable into which the pointer to the region of the value
   is assgined.
   `vsp' specifies the pointer to the variable into which the size of the value region is
   assigned. */
static bool tcbdbcurrecimpl(BDBCUR *cur, const char **kbp, int *ksp, const char **vbp, int *vsp){
  assert(cur && kbp && ksp && vbp && vsp);
  TCBDB *bdb = cur->bdb;
  if(cur->clock != bdb->clock){
    if(!tcbdbleafcheck(bdb, cur->id)){
      tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
      cur->id = 0;
      cur->kidx = 0;
      cur->vidx = 0;
      return false;
    }
    cur->clock = bdb->clock;
  }
  BDBLEAF *leaf = tcbdbleafload(bdb, cur->id);
  if(!leaf) return false;
  TCPTRLIST *recs = leaf->recs;
  if(cur->kidx >= TCPTRLISTNUM(recs)){
    tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
    return false;
  }
  BDBREC *rec = TCPTRLISTVAL(recs, cur->kidx);
  char *dbuf = (char *)rec + sizeof(*rec);
  int vnum = rec->rest ? TCLISTNUM(rec->rest) + 1 : 1;
  if(cur->vidx >= vnum){
    tcbdbsetecode(bdb, TCENOREC, __FILE__, __LINE__, __func__);
    return false;
  }
  *kbp = dbuf;
  *ksp = rec->ksiz;
  if(cur->vidx > 0){
    *vbp = tclistval(rec->rest, cur->vidx - 1, vsp);
  } else {
    *vbp = dbuf + rec->ksiz + TCALIGNPAD(rec->ksiz);
    *vsp = rec->vsiz;
  }
  return true;
}


/* Process each record atomically of a B+ tree database object.
   `func' specifies the pointer to the iterator function called for each record.
   `op' specifies an arbitrary pointer to be given as a parameter of the iterator function.
   If successful, the return value is true, else, it is false. */
static bool tcbdbforeachimpl(TCBDB *bdb, TCITER iter, void *op){
  assert(bdb && iter);
  bool err = false;
  BDBCUR *cur = tcbdbcurnew(bdb);
  tcbdbcurfirstimpl(cur);
  const char *kbuf, *vbuf;
  int ksiz, vsiz;
  while(cur->id > 0){
    if(tcbdbcurrecimpl(cur, &kbuf, &ksiz, &vbuf, &vsiz)){
      if(!iter(kbuf, ksiz, vbuf, vsiz, op)) break;
      tcbdbcurnextimpl(cur);
      if(bdb->tran){
        if(cur->id > 0){
          BDBLEAF *leaf = tcbdbleafload(bdb, cur->id);
          if(!leaf){
            err = true;
            break;
          }
          if(!leaf->dirty && !tcbdbleafcacheout(bdb, leaf)){
            err = false;
            break;
          }
        }
      } else if(TCMAPRNUM(bdb->leafc) > bdb->lcnum && !tcbdbcacheadjust(bdb)){
        err = true;
        break;
      }
    } else {
      if(tchdbecode(bdb->hdb) != TCEINVALID && tchdbecode(bdb->hdb) != TCENOREC) err = true;
      break;
    }
  }
  tcbdbcurdel(cur);
  return !err;
}



/*************************************************************************************************
 * debugging functions
 *************************************************************************************************/


/* Print meta data of the header into the debugging output.
   `bdb' specifies the B+ tree database object. */
void tcbdbprintmeta(TCBDB *bdb){
  assert(bdb);
  int dbgfd = tchdbdbgfd(bdb->hdb);
  if(dbgfd < 0) return;
  if(dbgfd == UINT16_MAX) dbgfd = 1;
  char buf[BDBPAGEBUFSIZ];
  char *wp = buf;
  wp += sprintf(wp, "META:");
  wp += sprintf(wp, " mmtx=%p", (void *)bdb->mmtx);
  wp += sprintf(wp, " cmtx=%p", (void *)bdb->cmtx);
  wp += sprintf(wp, " hdb=%p", (void *)bdb->hdb);
  wp += sprintf(wp, " opaque=%p", (void *)bdb->opaque);
  wp += sprintf(wp, " open=%d", bdb->open);
  wp += sprintf(wp, " wmode=%d", bdb->wmode);
  wp += sprintf(wp, " lmemb=%u", bdb->lmemb);
  wp += sprintf(wp, " nmemb=%u", bdb->nmemb);
  wp += sprintf(wp, " opts=%u", bdb->opts);
  wp += sprintf(wp, " root=%llx", (unsigned long long)bdb->root);
  wp += sprintf(wp, " first=%llx", (unsigned long long)bdb->first);
  wp += sprintf(wp, " last=%llx", (unsigned long long)bdb->last);
  wp += sprintf(wp, " lnum=%llu", (unsigned long long)bdb->lnum);
  wp += sprintf(wp, " nnum=%llu", (unsigned long long)bdb->nnum);
  wp += sprintf(wp, " rnum=%llu", (unsigned long long)bdb->rnum);
  wp += sprintf(wp, " leafc=%p", (void *)bdb->leafc);
  wp += sprintf(wp, " nodec=%p", (void *)bdb->nodec);
  wp += sprintf(wp, " cmp=%p", (void *)(intptr_t)bdb->cmp);
  wp += sprintf(wp, " cmpop=%p", (void *)bdb->cmpop);
  wp += sprintf(wp, " lcnum=%u", bdb->lcnum);
  wp += sprintf(wp, " ncnum=%u", bdb->ncnum);
  wp += sprintf(wp, " lsmax=%u", bdb->lsmax);
  wp += sprintf(wp, " lschk=%u", bdb->lschk);
  wp += sprintf(wp, " capnum=%llu", (unsigned long long)bdb->capnum);
  wp += sprintf(wp, " hist=%p", (void *)bdb->hist);
  wp += sprintf(wp, " hnum=%d", bdb->hnum);
  wp += sprintf(wp, " hleaf=%llu", (unsigned long long)bdb->hleaf);
  wp += sprintf(wp, " lleaf=%llu", (unsigned long long)bdb->lleaf);
  wp += sprintf(wp, " tran=%d", bdb->tran);
  wp += sprintf(wp, " rbopaque=%p", (void *)bdb->rbopaque);
  wp += sprintf(wp, " clock=%llu", (unsigned long long)bdb->clock);
  wp += sprintf(wp, " cnt_saveleaf=%lld", (long long)bdb->cnt_saveleaf);
  wp += sprintf(wp, " cnt_loadleaf=%lld", (long long)bdb->cnt_loadleaf);
  wp += sprintf(wp, " cnt_killleaf=%lld", (long long)bdb->cnt_killleaf);
  wp += sprintf(wp, " cnt_adjleafc=%lld", (long long)bdb->cnt_adjleafc);
  wp += sprintf(wp, " cnt_savenode=%lld", (long long)bdb->cnt_savenode);
  wp += sprintf(wp, " cnt_loadnode=%lld", (long long)bdb->cnt_loadnode);
  wp += sprintf(wp, " cnt_adjnodec=%lld", (long long)bdb->cnt_adjnodec);
  *(wp++) = '\n';
  tcwrite(dbgfd, buf, wp - buf);
}


/* Print records of a leaf object into the debugging output.
   `bdb' specifies the B+ tree database object.
   `leaf' specifies the leaf object. */
void tcbdbprintleaf(TCBDB *bdb, BDBLEAF *leaf){
  assert(bdb && leaf);
  int dbgfd = tchdbdbgfd(bdb->hdb);
  TCPTRLIST *recs = leaf->recs;
  if(dbgfd < 0) return;
  if(dbgfd == UINT16_MAX) dbgfd = 1;
  char buf[BDBPAGEBUFSIZ];
  char *wp = buf;
  wp += sprintf(wp, "LEAF:");
  wp += sprintf(wp, " id:%llx", (unsigned long long)leaf->id);
  wp += sprintf(wp, " size:%u", leaf->size);
  wp += sprintf(wp, " prev:%llx", (unsigned long long)leaf->prev);
  wp += sprintf(wp, " next:%llx", (unsigned long long)leaf->next);
  wp += sprintf(wp, " dirty:%d", leaf->dirty);
  wp += sprintf(wp, " dead:%d", leaf->dead);
  wp += sprintf(wp, " rnum:%d", TCPTRLISTNUM(recs));
  *(wp++) = ' ';
  for(int i = 0; i < TCPTRLISTNUM(recs); i++){
    tcwrite(dbgfd, buf, wp - buf);
    wp = buf;
    BDBREC *rec = TCPTRLISTVAL(recs, i);
    char *dbuf = (char *)rec + sizeof(*rec);
    wp += sprintf(wp, " [%s:%s]", dbuf, dbuf + rec->ksiz + TCALIGNPAD(rec->ksiz));
    TCLIST *rest = rec->rest;
    if(rest){
      for(int j = 0; j < TCLISTNUM(rest); j++){
        wp += sprintf(wp, ":%s", (char *)TCLISTVALPTR(rest, j));
      }
    }
  }
  *(wp++) = '\n';
  tcwrite(dbgfd, buf, wp - buf);
}


/* Print indices of a node object into the debugging output.
   `bdb' specifies the B+ tree database object.
   `node' specifies the node object. */
void tcbdbprintnode(TCBDB *bdb, BDBNODE *node){
  assert(bdb && node);
  int dbgfd = tchdbdbgfd(bdb->hdb);
  TCPTRLIST *idxs = node->idxs;
  if(dbgfd < 0) return;
  if(dbgfd == UINT16_MAX) dbgfd = 1;
  char buf[BDBPAGEBUFSIZ];
  char *wp = buf;
  wp += sprintf(wp, "NODE:");
  wp += sprintf(wp, " id:%llx", (unsigned long long)node->id);
  wp += sprintf(wp, " heir:%llx", (unsigned long long)node->heir);
  wp += sprintf(wp, " dirty:%d", node->dirty);
  wp += sprintf(wp, " dead:%d", node->dead);
  wp += sprintf(wp, " rnum:%d", TCPTRLISTNUM(idxs));
  *(wp++) = ' ';
  for(int i = 0; i < TCPTRLISTNUM(idxs); i++){
    tcwrite(dbgfd, buf, wp - buf);
    wp = buf;
    BDBIDX *idx = TCPTRLISTVAL(idxs, i);
    char *ebuf = (char *)idx + sizeof(*idx);
    wp += sprintf(wp, " [%llx:%s]", (unsigned long long)idx->pid, ebuf);
  }
  *(wp++) = '\n';
  tcwrite(dbgfd, buf, wp - buf);
}



// END OF FILE
/*************************************************************************************************
 * The fixed-length database API of Tokyo Cabinet
 *                                                               Copyright (C) 2006-2012 FAL Labs
 * This file is part of Tokyo Cabinet.
 * Tokyo Cabinet 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 any later version.  Tokyo Cabinet 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 Tokyo
 * Cabinet; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307 USA.
 *************************************************************************************************/


#include "tcutil.h"
#include "tcfdb.h"
#include "myconf.h"

#define FDBFILEMODE    00644             // permission of created files
#define FDBIOBUFSIZ    8192              // size of an I/O buffer

#define FDBMAGICDATA   "ToKyO CaBiNeT"   // magic data for identification
#define FDBHEADSIZ     256               // size of the reagion of the header
#define FDBTYPEOFF     32                // offset of the region for the database type
#define FDBFLAGSOFF    33                // offset of the region for the additional flags
#define FDBRNUMOFF     48                // offset of the region for the record number
#define FDBFSIZOFF     56                // offset of the region for the file size
#define FDBWIDTHOFF    64                // offset of the region for the record width
#define FDBLIMSIZOFF   72                // offset of the region for the limit size
#define FDBMINOFF      80                // offset of the region for the minimum ID offset
#define FDBMAXOFF      88                // offset of the region for the maximum ID offset
#define FDBOPAQUEOFF   128               // offset of the region for the opaque field

#define FDBDEFWIDTH    255               // default value width
#define FDBDEFLIMSIZ   (256LL<<20)       // default limit size
#define FDBRMTXNUM     127               // number of record mutexes
#define FDBTRUNCALW    256               // number of record for truncate allowance
#define FDBIDARYUNIT   2048              // size of ID array allocation unit
#define FDBWALSUFFIX   "wal"             // suffix of write ahead logging file

enum {                                   // enumeration for duplication behavior
  FDBPDOVER,                             // overwrite an existing value
  FDBPDKEEP,                             // keep the existing value
  FDBPDCAT,                              // concatenate values
  FDBPDADDINT,                           // add an integer
  FDBPDADDDBL,                           // add a real number
  FDBPDPROC                              // process by a callback function
};

typedef struct {                         // type of structure for a duplication callback
  TCPDPROC proc;                         // function pointer
  void *op;                              // opaque pointer
} FDBPDPROCOP;


/* private macros */
#define FDBLOCKMETHOD(TC_fdb, TC_wr)                            \
  ((TC_fdb)->mmtx ? tcfdblockmethod((TC_fdb), (TC_wr)) : true)
#define FDBUNLOCKMETHOD(TC_fdb)                         \
  ((TC_fdb)->mmtx ? tcfdbunlockmethod(TC_fdb) : true)
#define FDBLOCKATTR(TC_fdb)                             \
  ((TC_fdb)->mmtx ? tcfdblockattr(TC_fdb) : true)
#define FDBUNLOCKATTR(TC_fdb)                           \
  ((TC_fdb)->mmtx ? tcfdbunlockattr(TC_fdb) : true)
#define FDBLOCKRECORD(TC_fdb, TC_wr, TC_id)                             \
  ((TC_fdb)->mmtx ? tcfdblockrecord((TC_fdb), (TC_wr), (TC_id)) : true)
#define FDBUNLOCKRECORD(TC_fdb, TC_id)                                  \
  ((TC_fdb)->mmtx ? tcfdbunlockrecord((TC_fdb), (TC_id)) : true)
#define FDBLOCKALLRECORDS(TC_fdb, TC_wr)                                \
  ((TC_fdb)->mmtx ? tcfdblockallrecords((TC_fdb), (TC_wr)) : true)
#define FDBUNLOCKALLRECORDS(TC_fdb)                             \
  ((TC_fdb)->mmtx ? tcfdbunlockallrecords(TC_fdb) : true)
#define FDBLOCKWAL(TC_fdb)                              \
  ((TC_fdb)->mmtx ? tcfdblockwal(TC_fdb) : true)
#define FDBUNLOCKWAL(TC_fdb)                            \
  ((TC_fdb)->mmtx ? tcfdbunlockwal(TC_fdb) : true)
#define FDBTHREADYIELD(TC_fdb)                          \
  do { if((TC_fdb)->mmtx) sched_yield(); } while(false)


/* private function prototypes */
static void tcfdbdumpmeta(TCFDB *fdb, char *hbuf);
static void tcfdbloadmeta(TCFDB *fdb, const char *hbuf);
static void tcfdbclear(TCFDB *fdb);
static void tcfdbsetflag(TCFDB *fdb, int flag, bool sign);
static bool tcfdbwalinit(TCFDB *fdb);
static bool tcfdbwalwrite(TCFDB *fdb, uint64_t off, int64_t size);
static int tcfdbwalrestore(TCFDB *fdb, const char *path);
static bool tcfdbwalremove(TCFDB *fdb, const char *path);
static bool tcfdbopenimpl(TCFDB *fdb, const char *path, int omode);
static bool tcfdbcloseimpl(TCFDB *fdb);
static int64_t tcfdbprevid(TCFDB *fdb, int64_t id);
static int64_t tcfdbnextid(TCFDB *fdb, int64_t id);
static bool tcfdbputimpl(TCFDB *fdb, int64_t id, const void *vbuf, int vsiz, int dmode);
static bool tcfdboutimpl(TCFDB *fdb, int64_t id);
static const void *tcfdbgetimpl(TCFDB *fdb, int64_t id, int *sp);
static bool tcfdbiterinitimpl(TCFDB *fdb);
static uint64_t tcfdbiternextimpl(TCFDB *fdb);
static uint64_t *tcfdbrangeimpl(TCFDB *fdb, int64_t lower, int64_t upper, int max, int *np);
static bool tcfdboptimizeimpl(TCFDB *fdb, int32_t width, int64_t limsiz);
static bool tcfdbvanishimpl(TCFDB *fdb);
static bool tcfdbcopyimpl(TCFDB *fdb, const char *path);
static bool tcfdbiterjumpimpl(TCFDB *fdb, int64_t id);
static bool tcfdbforeachimpl(TCFDB *fdb, TCITER iter, void *op);
static bool tcfdblockmethod(TCFDB *fdb, bool wr);
static bool tcfdbunlockmethod(TCFDB *fdb);
static bool tcfdblockattr(TCFDB *fdb);
static bool tcfdbunlockattr(TCFDB *fdb);
static bool tcfdblockrecord(TCFDB *fdb, bool wr, uint64_t id);
static bool tcfdbunlockrecord(TCFDB *fdb, uint64_t id);
static bool tcfdblockallrecords(TCFDB *fdb, bool wr);
static bool tcfdbunlockallrecords(TCFDB *fdb);
static bool tcfdblockwal(TCFDB *fdb);
static bool tcfdbunlockwal(TCFDB *fdb);


/* debugging function prototypes */
void tcfdbprintmeta(TCFDB *fdb);



/*************************************************************************************************
 * API
 *************************************************************************************************/


/* Get the message string corresponding to an error code. */
const char *tcfdberrmsg(int ecode){
  return tcerrmsg(ecode);
}


/* Create a fixed-length database object. */
TCFDB *tcfdbnew(void){
  TCFDB *fdb;
  TCMALLOC(fdb, sizeof(*fdb));
  tcfdbclear(fdb);
  return fdb;
}


/* Delete a fixed-length database object. */
void tcfdbdel(TCFDB *fdb){
  assert(fdb);
  if(fdb->fd >= 0) tcfdbclose(fdb);
  if(fdb->mmtx){
    pthread_key_delete(*(pthread_key_t *)fdb->eckey);
    pthread_mutex_destroy(fdb->wmtx);
    pthread_mutex_destroy(fdb->tmtx);
    for(int i = FDBRMTXNUM - 1; i >= 0; i--){
      pthread_rwlock_destroy((pthread_rwlock_t *)fdb->rmtxs + i);
    }
    pthread_mutex_destroy(fdb->amtx);
    pthread_rwlock_destroy(fdb->mmtx);
    TCFREE(fdb->eckey);
    TCFREE(fdb->wmtx);
    TCFREE(fdb->tmtx);
    TCFREE(fdb->rmtxs);
    TCFREE(fdb->amtx);
    TCFREE(fdb->mmtx);
  }
  TCFREE(fdb);
}


/* Get the last happened error code of a fixed-length database object. */
int tcfdbecode(TCFDB *fdb){
  assert(fdb);
  return fdb->mmtx ?
    (int)(intptr_t)pthread_getspecific(*(pthread_key_t *)fdb->eckey) : fdb->ecode;
}


/* Set mutual exclusion control of a fixed-length database object for threading. */
bool tcfdbsetmutex(TCFDB *fdb){
  assert(fdb);
  if(!TCUSEPTHREAD) return true;
  if(fdb->mmtx || fdb->fd >= 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  TCMALLOC(fdb->mmtx, sizeof(pthread_rwlock_t));
  TCMALLOC(fdb->amtx, sizeof(pthread_mutex_t));
  TCMALLOC(fdb->rmtxs, sizeof(pthread_rwlock_t) * FDBRMTXNUM);
  TCMALLOC(fdb->tmtx, sizeof(pthread_mutex_t));
  TCMALLOC(fdb->wmtx, sizeof(pthread_mutex_t));
  TCMALLOC(fdb->eckey, sizeof(pthread_key_t));
  bool err = false;
  if(pthread_rwlock_init(fdb->mmtx, NULL) != 0) err = true;
  if(pthread_mutex_init(fdb->amtx, NULL) != 0) err = true;
  for(int i = 0; i < FDBRMTXNUM; i++){
    if(pthread_rwlock_init((pthread_rwlock_t *)fdb->rmtxs + i, NULL) != 0) err = true;
  }
  if(pthread_mutex_init(fdb->tmtx, NULL) != 0) err = true;
  if(pthread_mutex_init(fdb->wmtx, NULL) != 0) err = true;
  if(pthread_key_create(fdb->eckey, NULL) != 0) err = true;
  if(err){
    TCFREE(fdb->eckey);
    TCFREE(fdb->wmtx);
    TCFREE(fdb->tmtx);
    TCFREE(fdb->rmtxs);
    TCFREE(fdb->amtx);
    TCFREE(fdb->mmtx);
    fdb->eckey = NULL;
    fdb->wmtx = NULL;
    fdb->tmtx = NULL;
    fdb->rmtxs = NULL;
    fdb->amtx = NULL;
    fdb->mmtx = NULL;
    return false;
  }
  return true;
}


/* Set the tuning parameters of a fixed-length database object. */
bool tcfdbtune(TCFDB *fdb, int32_t width, int64_t limsiz){
  assert(fdb);
  if(fdb->fd >= 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  fdb->width = (width > 0) ? width : FDBDEFWIDTH;
  fdb->limsiz = (limsiz > 0) ? limsiz : FDBDEFLIMSIZ;
  if(fdb->limsiz < FDBHEADSIZ + fdb->width + sizeof(uint32_t))
    fdb->limsiz = FDBHEADSIZ + fdb->width + sizeof(uint32_t);
  fdb->limsiz = tcpagealign(fdb->limsiz);
  return true;
}


/* Open a database file and connect a fixed-length database object. */
bool tcfdbopen(TCFDB *fdb, const char *path, int omode){
  assert(fdb && path);
  if(!FDBLOCKMETHOD(fdb, true)) return false;
  if(fdb->fd >= 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  char *rpath = tcrealpath(path);
  if(!rpath){
    int ecode = TCEOPEN;
    switch(errno){
      case EACCES: ecode = TCENOPERM; break;
      case ENOENT: ecode = TCENOFILE; break;
      case ENOTDIR: ecode = TCENOFILE; break;
    }
    tcfdbsetecode(fdb, ecode, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  if(!tcpathlock(rpath)){
    tcfdbsetecode(fdb, TCETHREAD, __FILE__, __LINE__, __func__);
    TCFREE(rpath);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  bool rv = tcfdbopenimpl(fdb, path, omode);
  if(rv){
    fdb->rpath = rpath;
  } else {
    tcpathunlock(rpath);
    TCFREE(rpath);
  }
  FDBUNLOCKMETHOD(fdb);
  return rv;
}


/* Close a fixed-length database object. */
bool tcfdbclose(TCFDB *fdb){
  assert(fdb);
  if(!FDBLOCKMETHOD(fdb, true)) return false;
  if(fdb->fd < 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  bool rv = tcfdbcloseimpl(fdb);
  tcpathunlock(fdb->rpath);
  TCFREE(fdb->rpath);
  fdb->rpath = NULL;
  FDBUNLOCKMETHOD(fdb);
  return rv;
}


/* Store a record into a fixed-length database object. */
bool tcfdbput(TCFDB *fdb, int64_t id, const void *vbuf, int vsiz){
  assert(fdb && vbuf && vsiz >= 0);
  if(!FDBLOCKMETHOD(fdb, id < 1)) return false;
  if(fdb->fd < 0 || !(fdb->omode & FDBOWRITER)){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  if(id == FDBIDMIN){
    id = fdb->min;
  } else if(id == FDBIDPREV){
    id = fdb->min - 1;
  } else if(id == FDBIDMAX){
    id = fdb->max;
  } else if(id == FDBIDNEXT){
    id = fdb->max + 1;
  }
  if(id < 1 || id > fdb->limid){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  if(!FDBLOCKRECORD(fdb, true, id)){
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  bool rv = tcfdbputimpl(fdb, id, vbuf, vsiz, FDBPDOVER);
  FDBUNLOCKRECORD(fdb, id);
  FDBUNLOCKMETHOD(fdb);
  return rv;
}


/* Store a record with a decimal key into a fixed-length database object. */
bool tcfdbput2(TCFDB *fdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(fdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  return tcfdbput(fdb, tcfdbkeytoid(kbuf, ksiz), vbuf, vsiz);
}


/* Store a string record with a decimal key into a fixed-length database object. */
bool tcfdbput3(TCFDB *fdb, const char *kstr, const void *vstr){
  assert(fdb && kstr && vstr);
  return tcfdbput(fdb, tcfdbkeytoid(kstr, strlen(kstr)), vstr, strlen(vstr));
}


/* Store a new record into a fixed-length database object. */
bool tcfdbputkeep(TCFDB *fdb, int64_t id, const void *vbuf, int vsiz){
  assert(fdb && vbuf && vsiz >= 0);
  if(!FDBLOCKMETHOD(fdb, id < 1)) return false;
  if(fdb->fd < 0 || !(fdb->omode & FDBOWRITER)){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  if(id == FDBIDMIN){
    id = fdb->min;
  } else if(id == FDBIDPREV){
    id = fdb->min - 1;
  } else if(id == FDBIDMAX){
    id = fdb->max;
  } else if(id == FDBIDNEXT){
    id = fdb->max + 1;
  }
  if(id < 1 || id > fdb->limid){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  if(!FDBLOCKRECORD(fdb, true, id)){
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  bool rv = tcfdbputimpl(fdb, id, vbuf, vsiz, FDBPDKEEP);
  FDBUNLOCKRECORD(fdb, id);
  FDBUNLOCKMETHOD(fdb);
  return rv;
}


/* Store a new record with a decimal key into a fixed-length database object. */
bool tcfdbputkeep2(TCFDB *fdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(fdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  return tcfdbputkeep(fdb, tcfdbkeytoid(kbuf, ksiz), vbuf, vsiz);
}


/* Store a new string record with a decimal key into a fixed-length database object. */
bool tcfdbputkeep3(TCFDB *fdb, const char *kstr, const void *vstr){
  assert(fdb && kstr && vstr);
  return tcfdbputkeep(fdb, tcfdbkeytoid(kstr, strlen(kstr)), vstr, strlen(vstr));
}


/* Concatenate a value at the end of the existing record in a fixed-length database object. */
bool tcfdbputcat(TCFDB *fdb, int64_t id, const void *vbuf, int vsiz){
  assert(fdb && vbuf && vsiz >= 0);
  if(!FDBLOCKMETHOD(fdb, id < 1)) return false;
  if(fdb->fd < 0 || !(fdb->omode & FDBOWRITER)){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  if(id == FDBIDMIN){
    id = fdb->min;
  } else if(id == FDBIDPREV){
    id = fdb->min - 1;
  } else if(id == FDBIDMAX){
    id = fdb->max;
  } else if(id == FDBIDNEXT){
    id = fdb->max + 1;
  }
  if(id < 1 || id > fdb->limid){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  if(!FDBLOCKRECORD(fdb, true, id)){
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  bool rv = tcfdbputimpl(fdb, id, vbuf, vsiz, FDBPDCAT);
  FDBUNLOCKRECORD(fdb, id);
  FDBUNLOCKMETHOD(fdb);
  return rv;
}


/* Concatenate a value with a decimal key in a fixed-length database object. */
bool tcfdbputcat2(TCFDB *fdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(fdb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  return tcfdbputcat(fdb, tcfdbkeytoid(kbuf, ksiz), vbuf, vsiz);
}


/* Concatenate a string value with a decimal key in a fixed-length database object. */
bool tcfdbputcat3(TCFDB *fdb, const char *kstr, const void *vstr){
  assert(fdb && kstr && vstr);
  return tcfdbputcat(fdb, tcfdbkeytoid(kstr, strlen(kstr)), vstr, strlen(vstr));
}


/* Remove a record of a fixed-length database object. */
bool tcfdbout(TCFDB *fdb, int64_t id){
  assert(fdb);
  if(!FDBLOCKMETHOD(fdb, true)) return false;
  if(fdb->fd < 0 || !(fdb->omode & FDBOWRITER)){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  if(id == FDBIDMIN){
    id = fdb->min;
  } else if(id == FDBIDMAX){
    id = fdb->max;
  }
  if(id < 1 || id > fdb->limid){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  if(!FDBLOCKRECORD(fdb, true, id)){
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  bool rv = tcfdboutimpl(fdb, id);
  FDBUNLOCKRECORD(fdb, id);
  FDBUNLOCKMETHOD(fdb);
  return rv;
}


/* Remove a record with a decimal key of a fixed-length database object. */
bool tcfdbout2(TCFDB *fdb, const void *kbuf, int ksiz){
  assert(fdb && kbuf && ksiz >= 0);
  return tcfdbout(fdb, tcfdbkeytoid(kbuf, ksiz));
}


/* Remove a string record with a decimal key of a fixed-length database object. */
bool tcfdbout3(TCFDB *fdb, const char *kstr){
  assert(fdb && kstr);
  return tcfdbout(fdb, tcfdbkeytoid(kstr, strlen(kstr)));
}


/* Retrieve a record in a fixed-length database object. */
void *tcfdbget(TCFDB *fdb, int64_t id, int *sp){
  assert(fdb && sp);
  if(!FDBLOCKMETHOD(fdb, false)) return false;
  if(fdb->fd < 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  if(id == FDBIDMIN){
    id = fdb->min;
  } else if(id == FDBIDMAX){
    id = fdb->max;
  }
  if(id < 1 || id > fdb->limid){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  if(!FDBLOCKRECORD(fdb, false, id)){
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  const void *vbuf = tcfdbgetimpl(fdb, id, sp);
  char *rv = vbuf ? tcmemdup(vbuf, *sp) : NULL;
  FDBUNLOCKRECORD(fdb, id);
  FDBUNLOCKMETHOD(fdb);
  return rv;
}


/* Retrieve a record with a decimal key in a fixed-length database object. */
void *tcfdbget2(TCFDB *fdb, const void *kbuf, int ksiz, int *sp){
  assert(fdb && kbuf && ksiz >= 0 && sp);
  return tcfdbget(fdb, tcfdbkeytoid(kbuf, ksiz), sp);
}


/* Retrieve a string record with a decimal key in a fixed-length database object. */
char *tcfdbget3(TCFDB *fdb, const char *kstr){
  assert(fdb && kstr);
  int vsiz;
  return tcfdbget(fdb, tcfdbkeytoid(kstr, strlen(kstr)), &vsiz);
}


/* Retrieve a record in a fixed-length database object and write the value into a buffer. */
int tcfdbget4(TCFDB *fdb, int64_t id, void *vbuf, int max){
  assert(fdb && vbuf && max >= 0);
  if(!FDBLOCKMETHOD(fdb, false)) return false;
  if(fdb->fd < 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  if(id == FDBIDMIN){
    id = fdb->min;
  } else if(id == FDBIDMAX){
    id = fdb->max;
  }
  if(id < 1 || id > fdb->limid){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  if(!FDBLOCKRECORD(fdb, false, id)){
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  int vsiz;
  const void *rbuf = tcfdbgetimpl(fdb, id, &vsiz);
  if(rbuf){
    if(vsiz > max) vsiz = max;
    memcpy(vbuf, rbuf, vsiz);
  } else {
    vsiz = -1;
  }
  FDBUNLOCKRECORD(fdb, id);
  FDBUNLOCKMETHOD(fdb);
  return vsiz;
}


/* Get the size of the value of a record in a fixed-length database object. */
int tcfdbvsiz(TCFDB *fdb, int64_t id){
  assert(fdb);
  if(!FDBLOCKMETHOD(fdb, false)) return false;
  if(fdb->fd < 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  if(id == FDBIDMIN){
    id = fdb->min;
  } else if(id == FDBIDMAX){
    id = fdb->max;
  }
  if(id < 1 || id > fdb->limid){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  if(!FDBLOCKRECORD(fdb, false, id)){
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  int vsiz;
  const void *vbuf = tcfdbgetimpl(fdb, id, &vsiz);
  if(!vbuf) vsiz = -1;
  FDBUNLOCKRECORD(fdb, id);
  FDBUNLOCKMETHOD(fdb);
  return vsiz;
}


/* Get the size of the value with a decimal key in a fixed-length database object. */
int tcfdbvsiz2(TCFDB *fdb, const void *kbuf, int ksiz){
  assert(fdb && kbuf && ksiz >= 0);
  return tcfdbvsiz(fdb, tcfdbkeytoid(kbuf, ksiz));
}


/* Get the size of the string value with a decimal key in a fixed-length database object. */
int tcfdbvsiz3(TCFDB *fdb, const char *kstr){
  assert(fdb && kstr);
  return tcfdbvsiz(fdb, tcfdbkeytoid(kstr, strlen(kstr)));
}


/* Initialize the iterator of a fixed-length database object. */
bool tcfdbiterinit(TCFDB *fdb){
  assert(fdb);
  if(!FDBLOCKMETHOD(fdb, true)) return false;
  if(fdb->fd < 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  bool rv = tcfdbiterinitimpl(fdb);
  FDBUNLOCKMETHOD(fdb);
  return rv;
}


/* Get the next ID number of the iterator of a fixed-length database object. */
uint64_t tcfdbiternext(TCFDB *fdb){
  assert(fdb);
  if(!FDBLOCKMETHOD(fdb, true)) return false;
  if(fdb->fd < 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  uint64_t rv = tcfdbiternextimpl(fdb);
  FDBUNLOCKMETHOD(fdb);
  return rv;
}


/* Get the next decimay key of the iterator of a fixed-length database object. */
void *tcfdbiternext2(TCFDB *fdb, int *sp){
  assert(fdb && sp);
  uint64_t id = tcfdbiternextimpl(fdb);
  if(id < 1) return NULL;
  char kbuf[TCNUMBUFSIZ];
  int ksiz = sprintf(kbuf, "%llu", (unsigned long long)id);
  *sp = ksiz;
  return tcmemdup(kbuf, ksiz);
}


/* Get the next decimay key string of the iterator of a fixed-length database object. */
char *tcfdbiternext3(TCFDB *fdb){
  assert(fdb);
  int ksiz;
  return tcfdbiternext2(fdb, &ksiz);
}


/* Get range matching decimal keys in a fixed-length database object. */
uint64_t *tcfdbrange(TCFDB *fdb, int64_t lower, int64_t upper, int max, int *np){
  assert(fdb && np);
  if(!FDBLOCKMETHOD(fdb, true)) return false;
  if(fdb->fd < 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    *np = 0;
    return tcmalloc(1);
  }
  if(lower == FDBIDMIN) lower = fdb->min;
  if(upper == FDBIDMAX) upper = fdb->max;
  if(lower < 1 || lower > fdb->limid || upper < 1 || upper > fdb->limid){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    *np = 0;
    return tcmalloc(1);
  }
  uint64_t *rv = tcfdbrangeimpl(fdb, lower, upper, max, np);
  FDBUNLOCKMETHOD(fdb);
  return rv;
}


/* Get range matching decimal keys in a fixed-length database object. */
TCLIST *tcfdbrange2(TCFDB *fdb, const void *lbuf, int lsiz, const void *ubuf, int usiz, int max){
  assert(fdb && lbuf && lsiz >= 0 && ubuf && usiz >= 0);
  int num;
  uint64_t *ids = tcfdbrange(fdb, tcfdbkeytoid(lbuf, lsiz), tcfdbkeytoid(ubuf, usiz), max, &num);
  TCLIST *keys = tclistnew2(num);
  for(int i = 0; i < num; i++){
    char kbuf[TCNUMBUFSIZ];
    int ksiz = sprintf(kbuf, "%llu", (unsigned long long)ids[i]);
    TCLISTPUSH(keys, kbuf, ksiz);
  }
  TCFREE(ids);
  return keys;
}


/* Get range matching decimal keys with strings in a fixed-length database object. */
TCLIST *tcfdbrange3(TCFDB *fdb, const char *lstr, const char *ustr, int max){
  assert(fdb && lstr && ustr);
  return tcfdbrange2(fdb, lstr, strlen(lstr), ustr, strlen(ustr), max);
}


/* Get keys with an interval notation in a fixed-length database object. */
TCLIST *tcfdbrange4(TCFDB *fdb, const void *ibuf, int isiz, int max){
  assert(fdb && ibuf && isiz >= 0);
  char *expr;
  TCMEMDUP(expr, ibuf, isiz);
  char *pv = expr;
  while(*pv > '\0' && *pv <= ' '){
    pv++;
  }
  bool linc = false;
  if(*pv == '['){
    linc = true;
  } else if(*pv != '('){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TCFREE(expr);
    return tclistnew();
  }
  pv++;
  char *sep = strchr(pv, ',');
  if(!sep){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TCFREE(expr);
    return tclistnew();
  }
  *sep = '\0';
  tcstrtrim(pv);
  int64_t lower = tcfdbkeytoid(pv, strlen(pv));
  pv = sep + 1;
  bool uinc = false;
  if((sep = strchr(pv, ']')) != NULL){
    uinc = true;
    *sep = '\0';
  } else if((sep = strchr(pv, ')')) != NULL){
    *sep = '\0';
  } else {
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TCFREE(expr);
    return tclistnew();
  }
  tcstrtrim(pv);
  int64_t upper = tcfdbkeytoid(pv, strlen(pv));
  if(lower == FDBIDMIN){
    lower = fdb->min;
  } else if(lower == FDBIDPREV){
    lower = fdb->min - 1;
  } else if(lower == FDBIDMAX){
    lower = fdb->max;
  } else if(lower == FDBIDNEXT){
    lower = fdb->max + 1;
  }
  if(!linc) lower++;
  if(upper == FDBIDMIN){
    upper = fdb->min;
  } else if(upper == FDBIDPREV){
    upper = fdb->min - 1;
  } else if(upper == FDBIDMAX){
    upper = fdb->max;
  } else if(upper == FDBIDNEXT){
    upper = fdb->max + 1;
  }
  if(!uinc) upper--;
  TCFREE(expr);
  int num;
  uint64_t *ids = tcfdbrange(fdb, lower, upper, max, &num);
  TCLIST *keys = tclistnew2(num);
  for(int i = 0; i < num; i++){
    char kbuf[TCNUMBUFSIZ];
    int ksiz = sprintf(kbuf, "%llu", (unsigned long long)ids[i]);
    TCLISTPUSH(keys, kbuf, ksiz);
  }
  TCFREE(ids);
  return keys;
}


/* Get keys with an interval notation string in a fixed-length database object. */
TCLIST *tcfdbrange5(TCFDB *fdb, const void *istr, int max){
  assert(fdb && istr);
  return tcfdbrange4(fdb, istr, strlen(istr), max);
}


/* Add an integer to a record in a fixed-length database object. */
int tcfdbaddint(TCFDB *fdb, int64_t id, int num){
  assert(fdb);
  if(!FDBLOCKMETHOD(fdb, id < 1)) return INT_MIN;
  if(fdb->fd < 0 || !(fdb->omode & FDBOWRITER)){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return INT_MIN;
  }
  if(id == FDBIDMIN){
    id = fdb->min;
  } else if(id == FDBIDPREV){
    id = fdb->min - 1;
  } else if(id == FDBIDMAX){
    id = fdb->max;
  } else if(id == FDBIDNEXT){
    id = fdb->max + 1;
  }
  if(id < 1 || id > fdb->limid){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return INT_MIN;
  }
  if(!FDBLOCKRECORD(fdb, true, id)){
    FDBUNLOCKMETHOD(fdb);
    return INT_MIN;
  }
  bool rv = tcfdbputimpl(fdb, id, (char *)&num, sizeof(num), FDBPDADDINT);
  FDBUNLOCKRECORD(fdb, id);
  FDBUNLOCKMETHOD(fdb);
  return rv ? num : INT_MIN;
}


/* Add a real number to a record in a fixed-length database object. */
double tcfdbadddouble(TCFDB *fdb, int64_t id, double num){
  assert(fdb);
  if(!FDBLOCKMETHOD(fdb, id < 1)) return nan("");
  if(fdb->fd < 0 || !(fdb->omode & FDBOWRITER)){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return nan("");
  }
  if(id == FDBIDMIN){
    id = fdb->min;
  } else if(id == FDBIDPREV){
    id = fdb->min - 1;
  } else if(id == FDBIDMAX){
    id = fdb->max;
  } else if(id == FDBIDNEXT){
    id = fdb->max + 1;
  }
  if(id < 1 || id > fdb->limid){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return nan("");
  }
  if(!FDBLOCKRECORD(fdb, true, id)){
    FDBUNLOCKMETHOD(fdb);
    return nan("");
  }
  bool rv = tcfdbputimpl(fdb, id, (char *)&num, sizeof(num), FDBPDADDDBL);
  FDBUNLOCKRECORD(fdb, id);
  FDBUNLOCKMETHOD(fdb);
  return rv ? num : nan("");
}


/* Synchronize updated contents of a fixed-length database object with the file and the device. */
bool tcfdbsync(TCFDB *fdb){
  assert(fdb);
  if(!FDBLOCKMETHOD(fdb, true)) return false;
  if(fdb->fd < 0 || !(fdb->omode & FDBOWRITER) || fdb->tran){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  bool rv = tcfdbmemsync(fdb, true);
  FDBUNLOCKMETHOD(fdb);
  return rv;
}


/* Optimize the file of a fixed-length database object. */
bool tcfdboptimize(TCFDB *fdb, int32_t width, int64_t limsiz){
  assert(fdb);
  if(!FDBLOCKMETHOD(fdb, true)) return false;
  if(fdb->fd < 0 || !(fdb->omode & FDBOWRITER) || fdb->tran){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  FDBTHREADYIELD(fdb);
  bool rv = tcfdboptimizeimpl(fdb, width, limsiz);
  FDBUNLOCKMETHOD(fdb);
  return rv;
}


/* Remove all records of a fixed-length database object. */
bool tcfdbvanish(TCFDB *fdb){
  assert(fdb);
  if(!FDBLOCKMETHOD(fdb, true)) return false;
  if(fdb->fd < 0 || !(fdb->omode & FDBOWRITER) || fdb->tran){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  FDBTHREADYIELD(fdb);
  bool rv = tcfdbvanishimpl(fdb);
  FDBUNLOCKMETHOD(fdb);
  return rv;
}


/* Copy the database file of a fixed-length database object. */
bool tcfdbcopy(TCFDB *fdb, const char *path){
  assert(fdb && path);
  if(!FDBLOCKMETHOD(fdb, false)) return false;
  if(fdb->fd < 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  if(!FDBLOCKALLRECORDS(fdb, false)){
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  FDBTHREADYIELD(fdb);
  bool rv = tcfdbcopyimpl(fdb, path);
  FDBUNLOCKALLRECORDS(fdb);
  FDBUNLOCKMETHOD(fdb);
  return rv;
}


/* Begin the transaction of a fixed-length database object. */
bool tcfdbtranbegin(TCFDB *fdb){
  assert(fdb);
  for(double wsec = 1.0 / sysconf(_SC_CLK_TCK); true; wsec *= 2){
    if(!FDBLOCKMETHOD(fdb, true)) return false;
    if(fdb->fd < 0 || !(fdb->omode & FDBOWRITER) || fdb->fatal){
      tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
      FDBUNLOCKMETHOD(fdb);
      return false;
    }
    if(!fdb->tran) break;
    FDBUNLOCKMETHOD(fdb);
    if(wsec > 1.0) wsec = 1.0;
    tcsleep(wsec);
  }
  if(!tcfdbmemsync(fdb, false)){
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  if((fdb->omode & FDBOTSYNC) && fsync(fdb->fd) == -1){
    tcfdbsetecode(fdb, TCESYNC, __FILE__, __LINE__, __func__);
    return false;
  }
  if(fdb->walfd < 0){
    char *tpath = tcsprintf("%s%c%s", fdb->path, MYEXTCHR, FDBWALSUFFIX);
    int walfd = open(tpath, O_RDWR | O_CREAT | O_TRUNC, FDBFILEMODE);
    TCFREE(tpath);
    if(walfd < 0){
      int ecode = TCEOPEN;
      switch(errno){
        case EACCES: ecode = TCENOPERM; break;
        case ENOENT: ecode = TCENOFILE; break;
        case ENOTDIR: ecode = TCENOFILE; break;
      }
      tcfdbsetecode(fdb, ecode, __FILE__, __LINE__, __func__);
      FDBUNLOCKMETHOD(fdb);
      return false;
    }
    fdb->walfd = walfd;
  }
  tcfdbsetflag(fdb, FDBFOPEN, false);
  if(!tcfdbwalinit(fdb)){
    tcfdbsetflag(fdb, FDBFOPEN, true);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  tcfdbsetflag(fdb, FDBFOPEN, true);
  fdb->tran = true;
  FDBUNLOCKMETHOD(fdb);
  return true;
}


/* Commit the transaction of a fixed-length database object. */
bool tcfdbtrancommit(TCFDB *fdb){
  assert(fdb);
  if(!FDBLOCKMETHOD(fdb, true)) return false;
  if(fdb->fd < 0 || !(fdb->omode & FDBOWRITER) || fdb->fatal || !fdb->tran){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  bool err = false;
  if(!tcfdbmemsync(fdb, fdb->omode & FDBOTSYNC)) err = true;
  if(!err && ftruncate(fdb->walfd, 0) == -1){
    tcfdbsetecode(fdb, TCETRUNC, __FILE__, __LINE__, __func__);
    err = true;
  }
  fdb->tran = false;
  FDBUNLOCKMETHOD(fdb);
  return !err;
}


/* Abort the transaction of a fixed-length database object. */
bool tcfdbtranabort(TCFDB *fdb){
  assert(fdb);
  if(!FDBLOCKMETHOD(fdb, true)) return false;
  if(fdb->fd < 0 || !(fdb->omode & FDBOWRITER) || !fdb->tran){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  bool err = false;
  if(!tcfdbmemsync(fdb, false)) err = true;
  if(!tcfdbwalrestore(fdb, fdb->path)) err = true;
  char hbuf[FDBHEADSIZ];
  if(lseek(fdb->fd, 0, SEEK_SET) == -1){
    tcfdbsetecode(fdb, TCESEEK, __FILE__, __LINE__, __func__);
    err = false;
  } else if(!tcread(fdb->fd, hbuf, FDBHEADSIZ)){
    tcfdbsetecode(fdb, TCEREAD, __FILE__, __LINE__, __func__);
    err = false;
  } else {
    tcfdbloadmeta(fdb, hbuf);
  }
  fdb->tran = false;
  FDBUNLOCKMETHOD(fdb);
  return !err;
}


/* Get the file path of a fixed-length database object. */
const char *tcfdbpath(TCFDB *fdb){
  assert(fdb);
  if(!FDBLOCKMETHOD(fdb, false)) return NULL;
  if(fdb->fd < 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return NULL;
  }
  const char *rv = fdb->path;
  FDBUNLOCKMETHOD(fdb);
  return rv;
}


/* Get the number of records of a fixed-length database object. */
uint64_t tcfdbrnum(TCFDB *fdb){
  assert(fdb);
  if(!FDBLOCKMETHOD(fdb, false)) return 0;
  if(fdb->fd < 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return 0;
  }
  uint64_t rv = fdb->rnum;
  FDBUNLOCKMETHOD(fdb);
  return rv;
}


/* Get the size of the database file of a fixed-length database object. */
uint64_t tcfdbfsiz(TCFDB *fdb){
  assert(fdb);
  if(!FDBLOCKMETHOD(fdb, false)) return 0;
  if(fdb->fd < 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return 0;
  }
  uint64_t rv = fdb->fsiz;
  FDBUNLOCKMETHOD(fdb);
  return rv;
}



/*************************************************************************************************
 * features for experts
 *************************************************************************************************/


/* Set the error code of a fixed-length database object. */
void tcfdbsetecode(TCFDB *fdb, int ecode, const char *filename, int line, const char *func){
  assert(fdb && filename && line >= 1 && func);
  int myerrno = errno;
  if(!fdb->fatal){
    fdb->ecode = ecode;
    if(fdb->mmtx) pthread_setspecific(*(pthread_key_t *)fdb->eckey, (void *)(intptr_t)ecode);
  }
  if(ecode != TCEINVALID && ecode != TCEKEEP && ecode != TCENOREC){
    fdb->fatal = true;
    if(fdb->fd >= 0 && (fdb->omode & FDBOWRITER)) tcfdbsetflag(fdb, FDBFFATAL, true);
  }
  if(fdb->dbgfd >= 0 && (fdb->dbgfd != UINT16_MAX || fdb->fatal)){
    int dbgfd = (fdb->dbgfd == UINT16_MAX) ? 1 : fdb->dbgfd;
    char obuf[FDBIOBUFSIZ];
    int osiz = sprintf(obuf, "ERROR:%s:%d:%s:%s:%d:%s:%d:%s\n", filename, line, func,
                       fdb->path ? fdb->path : "-", ecode, tcfdberrmsg(ecode),
                       myerrno, strerror(myerrno));
    tcwrite(dbgfd, obuf, osiz);
  }
}


/* Set the file descriptor for debugging output. */
void tcfdbsetdbgfd(TCFDB *fdb, int fd){
  assert(fdb && fd >= 0);
  fdb->dbgfd = fd;
}


/* Get the file descriptor for debugging output. */
int tcfdbdbgfd(TCFDB *fdb){
  assert(fdb);
  return fdb->dbgfd;
}


/* Check whether mutual exclusion control is set to a fixed-length database object. */
bool tcfdbhasmutex(TCFDB *fdb){
  assert(fdb);
  return fdb->mmtx != NULL;
}


/* Synchronize updating contents on memory of a fixed-length database object. */
bool tcfdbmemsync(TCFDB *fdb, bool phys){
  assert(fdb);
  if(fdb->fd < 0 || !(fdb->omode & FDBOWRITER)){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  bool err = false;
  char hbuf[FDBHEADSIZ];
  tcfdbdumpmeta(fdb, hbuf);
  memcpy(fdb->map, hbuf, FDBOPAQUEOFF);
  if(phys){
    if(msync(fdb->map, fdb->limsiz, MS_SYNC) == -1){
      tcfdbsetecode(fdb, TCEMMAP, __FILE__, __LINE__, __func__);
      err = true;
    }
    if(fsync(fdb->fd) == -1){
      tcfdbsetecode(fdb, TCESYNC, __FILE__, __LINE__, __func__);
      err = true;
    }
  }
  return !err;
}


/* Get the minimum ID number of records of a fixed-length database object. */
uint64_t tcfdbmin(TCFDB *fdb){
  assert(fdb);
  if(fdb->fd < 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return fdb->min;
}


/* Get the maximum ID number of records of a fixed-length database object. */
uint64_t tcfdbmax(TCFDB *fdb){
  assert(fdb);
  if(fdb->fd < 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return fdb->max;
}


/* Get the width of the value of each record of a fixed-length database object. */
uint32_t tcfdbwidth(TCFDB *fdb){
  assert(fdb);
  if(fdb->fd < 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return fdb->width;
}


/* Get the limit file size of a fixed-length database object. */
uint64_t tcfdblimsiz(TCFDB *fdb){
  assert(fdb);
  if(fdb->fd < 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return fdb->limsiz;
}


/* Get the limit ID number of a fixed-length database object. */
uint64_t tcfdblimid(TCFDB *fdb){
  assert(fdb);
  if(fdb->fd < 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return fdb->limid;
}


/* Get the inode number of the database file of a fixed-length database object. */
uint64_t tcfdbinode(TCFDB *fdb){
  assert(fdb);
  if(fdb->fd < 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return fdb->inode;
}


/* Get the modification time of the database file of a fixed-length database object. */
time_t tcfdbmtime(TCFDB *fdb){
  assert(fdb);
  if(fdb->fd < 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return fdb->mtime;
}


/* Get the connection mode of a fixed-length database object. */
int tcfdbomode(TCFDB *fdb){
  assert(fdb);
  if(fdb->fd < 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return fdb->omode;
}


/* Get the database type of a fixed-length database object. */
uint8_t tcfdbtype(TCFDB *fdb){
  assert(fdb);
  if(fdb->fd < 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return fdb->type;
}


/* Get the additional flags of a fixed-length database object. */
uint8_t tcfdbflags(TCFDB *fdb){
  assert(fdb);
  if(fdb->fd < 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return fdb->flags;
}


/* Get the pointer to the opaque field of a fixed-length database object. */
char *tcfdbopaque(TCFDB *fdb){
  assert(fdb);
  if(fdb->fd < 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return NULL;
  }
  return fdb->map + FDBOPAQUEOFF;
}


/* Store a record into a fixed-length database object with a duplication handler. */
bool tcfdbputproc(TCFDB *fdb, int64_t id, const void *vbuf, int vsiz, TCPDPROC proc, void *op){
  assert(fdb && proc);
  if(!FDBLOCKMETHOD(fdb, id < 1)) return false;
  if(fdb->fd < 0 || !(fdb->omode & FDBOWRITER)){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  if(id == FDBIDMIN){
    id = fdb->min;
  } else if(id == FDBIDPREV){
    id = fdb->min - 1;
  } else if(id == FDBIDMAX){
    id = fdb->max;
  } else if(id == FDBIDNEXT){
    id = fdb->max + 1;
  }
  if(id < 1 || id > fdb->limid){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  if(!FDBLOCKRECORD(fdb, true, id)){
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  FDBPDPROCOP procop;
  procop.proc = proc;
  procop.op = op;
  FDBPDPROCOP *procptr = &procop;
  tcgeneric_t stack[(FDBDEFWIDTH+TCNUMBUFSIZ)/sizeof(tcgeneric_t)+1];
  char *rbuf;
  if(vbuf){
    if(vsiz <= sizeof(stack) - sizeof(procptr)){
      rbuf = (char *)stack;
    } else {
      TCMALLOC(rbuf, vsiz + sizeof(procptr));
    }
    char *wp = rbuf;
    memcpy(wp, &procptr, sizeof(procptr));
    wp += sizeof(procptr);
    memcpy(wp, vbuf, vsiz);
    vbuf = rbuf + sizeof(procptr);
  } else {
    rbuf = (char *)stack;
    memcpy(rbuf, &procptr, sizeof(procptr));
    vbuf = rbuf + sizeof(procptr);
    vsiz = -1;
  }
  bool rv = tcfdbputimpl(fdb, id, vbuf, vsiz, FDBPDPROC);
  if(rbuf != (char *)stack) TCFREE(rbuf);
  FDBUNLOCKRECORD(fdb, id);
  FDBUNLOCKMETHOD(fdb);
  return rv;
}


/* Move the iterator to the record corresponding a key of a fixed-length database object. */
bool tcfdbiterinit2(TCFDB *fdb, int64_t id){
  assert(fdb);
  if(!FDBLOCKMETHOD(fdb, true)) return false;
  if(fdb->fd < 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  if(id == FDBIDMIN){
    id = fdb->min;
  } else if(id == FDBIDMAX){
    id = fdb->max;
  }
  if(id < 1 || id > fdb->limid){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  bool rv = tcfdbiterjumpimpl(fdb, id);
  FDBUNLOCKMETHOD(fdb);
  return rv;
}


/* Move the iterator to the decimal record of a fixed-length database object. */
bool tcfdbiterinit3(TCFDB *fdb, const void *kbuf, int ksiz){
  assert(fdb && kbuf && ksiz >= 0);
  return tcfdbiterinit2(fdb, tcfdbkeytoid(kbuf, ksiz));
}


/* Move the iterator to the decimal string record of a fixed-length database object. */
bool tcfdbiterinit4(TCFDB *fdb, const char *kstr){
  assert(fdb && kstr);
  return tcfdbiterinit2(fdb, tcfdbkeytoid(kstr, strlen(kstr)));
}


/* Process each record atomically of a fixed-length database object. */
bool tcfdbforeach(TCFDB *fdb, TCITER iter, void *op){
  assert(fdb && iter);
  if(!FDBLOCKMETHOD(fdb, false)) return false;
  if(fdb->fd < 0){
    tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  if(!FDBLOCKALLRECORDS(fdb, false)){
    FDBUNLOCKMETHOD(fdb);
    return false;
  }
  FDBTHREADYIELD(fdb);
  bool rv = tcfdbforeachimpl(fdb, iter, op);
  FDBUNLOCKALLRECORDS(fdb);
  FDBUNLOCKMETHOD(fdb);
  return rv;
}


/* Generate the ID number from arbitrary binary data. */
int64_t tcfdbkeytoid(const char *kbuf, int ksiz){
  assert(kbuf && ksiz >= 0);
  if(ksiz == 3 && !memcmp(kbuf, "min", 3)){
    return FDBIDMIN;
  } else if(ksiz == 4 && !memcmp(kbuf, "prev", 4)){
    return FDBIDPREV;
  } else if(ksiz == 3 && !memcmp(kbuf, "max", 3)){
    return FDBIDMAX;
  } else if(ksiz == 4 && !memcmp(kbuf, "next", 4)){
    return FDBIDNEXT;
  }
  int64_t id = 0;
  const char *end = kbuf + ksiz;
  while(kbuf < end){
    int c = *(unsigned char *)(kbuf++);
    if(c >= '0' && c <= '9') id = id * 10 + c - '0';
  }
  return id;
}



/*************************************************************************************************
 * private features
 *************************************************************************************************/


/* Serialize meta data into a buffer.
   `fdb' specifies the fixed-length database object.
   `hbuf' specifies the buffer. */
static void tcfdbdumpmeta(TCFDB *fdb, char *hbuf){
  memset(hbuf, 0, FDBHEADSIZ);
  sprintf(hbuf, "%s\n%s:%d\n", FDBMAGICDATA, _TC_FORMATVER, _TC_LIBVER);
  memcpy(hbuf + FDBTYPEOFF, &(fdb->type), sizeof(fdb->type));
  memcpy(hbuf + FDBFLAGSOFF, &(fdb->flags), sizeof(fdb->flags));
  uint64_t llnum;
  llnum = fdb->rnum;
  llnum = TCHTOILL(llnum);
  memcpy(hbuf + FDBRNUMOFF, &llnum, sizeof(llnum));
  llnum = fdb->fsiz;
  llnum = TCHTOILL(llnum);
  memcpy(hbuf + FDBFSIZOFF, &llnum, sizeof(llnum));
  llnum = fdb->width;
  llnum = TCHTOILL(llnum);
  memcpy(hbuf + FDBWIDTHOFF, &llnum, sizeof(llnum));
  llnum = fdb->limsiz;
  llnum = TCHTOILL(llnum);
  memcpy(hbuf + FDBLIMSIZOFF, &llnum, sizeof(llnum));
  llnum = fdb->min;
  llnum = TCHTOILL(llnum);
  memcpy(hbuf + FDBMINOFF, &llnum, sizeof(llnum));
  llnum = fdb->max;
  llnum = TCHTOILL(llnum);
  memcpy(hbuf + FDBMAXOFF, &llnum, sizeof(llnum));
}


/* Deserialize meta data from a buffer.
   `fdb' specifies the fixed-length database object.
   `hbuf' specifies the buffer. */
static void tcfdbloadmeta(TCFDB *fdb, const char *hbuf){
  memcpy(&(fdb->type), hbuf + FDBTYPEOFF, sizeof(fdb->type));
  memcpy(&(fdb->flags), hbuf + FDBFLAGSOFF, sizeof(fdb->flags));
  uint64_t llnum;
  memcpy(&llnum, hbuf + FDBRNUMOFF, sizeof(llnum));
  fdb->rnum = TCITOHLL(llnum);
  memcpy(&llnum, hbuf + FDBFSIZOFF, sizeof(llnum));
  fdb->fsiz = TCITOHLL(llnum);
  memcpy(&llnum, hbuf + FDBWIDTHOFF, sizeof(llnum));
  fdb->width = TCITOHLL(llnum);
  memcpy(&llnum, hbuf + FDBLIMSIZOFF, sizeof(llnum));
  fdb->limsiz = TCITOHLL(llnum);
  memcpy(&llnum, hbuf + FDBMINOFF, sizeof(llnum));
  fdb->min = TCITOHLL(llnum);
  memcpy(&llnum, hbuf + FDBMAXOFF, sizeof(llnum));
  fdb->max = TCITOHLL(llnum);
}


/* Clear all members.
   `fdb' specifies the fixed-length database object. */
static void tcfdbclear(TCFDB *fdb){
  assert(fdb);
  fdb->mmtx = NULL;
  fdb->amtx = NULL;
  fdb->rmtxs = NULL;
  fdb->tmtx = NULL;
  fdb->wmtx = NULL;
  fdb->eckey = NULL;
  fdb->rpath = NULL;
  fdb->type = TCDBTFIXED;
  fdb->flags = 0;
  fdb->width = FDBDEFWIDTH;
  fdb->limsiz = FDBDEFLIMSIZ;
  fdb->wsiz = 0;
  fdb->rsiz = 0;
  fdb->limid = 0;
  fdb->path = NULL;
  fdb->fd = -1;
  fdb->omode = 0;
  fdb->rnum = 0;
  fdb->fsiz = 0;
  fdb->min = 0;
  fdb->max = 0;
  fdb->iter = 0;
  fdb->map = NULL;
  fdb->array = NULL;
  fdb->ecode = TCESUCCESS;
  fdb->fatal = false;
  fdb->inode = 0;
  fdb->mtime = 0;
  fdb->tran = false;
  fdb->walfd = -1;
  fdb->walend = 0;
  fdb->dbgfd = -1;
  fdb->cnt_writerec = -1;
  fdb->cnt_readrec = -1;
  fdb->cnt_truncfile = -1;
  TCDODEBUG(fdb->cnt_writerec = 0);
  TCDODEBUG(fdb->cnt_readrec = 0);
  TCDODEBUG(fdb->cnt_truncfile = 0);
}


/* Set the open flag.
   `fdb' specifies the fixed-length database object.
   `flag' specifies the flag value.
   `sign' specifies the sign. */
static void tcfdbsetflag(TCFDB *fdb, int flag, bool sign){
  assert(fdb);
  char *fp = (char *)fdb->map + FDBFLAGSOFF;
  if(sign){
    *fp |= (uint8_t)flag;
  } else {
    *fp &= ~(uint8_t)flag;
  }
  fdb->flags = *fp;
}


/* Initialize the write ahead logging file.
   `fdb' specifies the fixed-length database object.
   If successful, the return value is true, else, it is false. */
static bool tcfdbwalinit(TCFDB *fdb){
  assert(fdb);
  if(lseek(fdb->walfd, 0, SEEK_SET) == -1){
    tcfdbsetecode(fdb, TCESEEK, __FILE__, __LINE__, __func__);
    return false;
  }
  if(ftruncate(fdb->walfd, 0) == -1){
    tcfdbsetecode(fdb, TCETRUNC, __FILE__, __LINE__, __func__);
    return false;
  }
  uint64_t llnum = fdb->fsiz;
  llnum = TCHTOILL(llnum);
  if(!tcwrite(fdb->walfd, &llnum, sizeof(llnum))){
    tcfdbsetecode(fdb, TCEWRITE, __FILE__, __LINE__, __func__);
    return false;
  }
  fdb->walend = fdb->fsiz;
  if(!tcfdbwalwrite(fdb, 0, FDBHEADSIZ)) return false;
  return true;
}


/* Write an event into the write ahead logging file.
   `fdb' specifies the fixed-length database object.
   `off' specifies the offset of the region to be updated.
   `size' specifies the size of the region.
   If successful, the return value is true, else, it is false. */
static bool tcfdbwalwrite(TCFDB *fdb, uint64_t off, int64_t size){
  assert(fdb && off >= 0 && size >= 0);
  if(off + size > fdb->walend) size = fdb->walend - off;
  if(size < 1) return true;
  char stack[FDBIOBUFSIZ];
  char *buf;
  if(size + sizeof(off) + sizeof(size) <= FDBIOBUFSIZ){
    buf = stack;
  } else {
    TCMALLOC(buf, size + sizeof(off) + sizeof(size));
  }
  char *wp = buf;
  uint64_t llnum = TCHTOILL(off);
  memcpy(wp, &llnum, sizeof(llnum));
  wp += sizeof(llnum);
  uint32_t lnum = TCHTOIL(size);
  memcpy(wp, &lnum, sizeof(lnum));
  wp += sizeof(lnum);
  memcpy(wp, fdb->map + off, size);
  wp += size;
  if(!FDBLOCKWAL(fdb)) return false;
  if(!tcwrite(fdb->walfd, buf, wp - buf)){
    tcfdbsetecode(fdb, TCEWRITE, __FILE__, __LINE__, __func__);
    if(buf != stack) TCFREE(buf);
    FDBUNLOCKWAL(fdb);
    return false;
  }
  if(buf != stack) TCFREE(buf);
  if((fdb->omode & FDBOTSYNC) && fsync(fdb->walfd) == -1){
    tcfdbsetecode(fdb, TCESYNC, __FILE__, __LINE__, __func__);
    FDBUNLOCKWAL(fdb);
    return false;
  }
  FDBUNLOCKWAL(fdb);
  return true;
}


/* Restore the database from the write ahead logging file.
   `fdb' specifies the fixed-length database object.
   `path' specifies the path of the database file.
   If successful, the return value is true, else, it is false. */
static int tcfdbwalrestore(TCFDB *fdb, const char *path){
  assert(fdb && path);
  char *tpath = tcsprintf("%s%c%s", path, MYEXTCHR, FDBWALSUFFIX);
  int walfd = open(tpath, O_RDONLY, FDBFILEMODE);
  TCFREE(tpath);
  if(walfd < 0) return false;
  bool err = false;
  uint64_t walsiz = 0;
  struct stat sbuf;
  if(fstat(walfd, &sbuf) == 0){
    walsiz = sbuf.st_size;
  } else {
    tcfdbsetecode(fdb, TCESTAT, __FILE__, __LINE__, __func__);
    err = true;
  }
  if(walsiz >= sizeof(walsiz) + FDBHEADSIZ){
    int dbfd = fdb->fd;
    int tfd = -1;
    if(!(fdb->omode & FDBOWRITER)){
      tfd = open(path, O_WRONLY, FDBFILEMODE);
      if(tfd >= 0){
        dbfd = tfd;
      } else {
        int ecode = TCEOPEN;
        switch(errno){
          case EACCES: ecode = TCENOPERM; break;
          case ENOENT: ecode = TCENOFILE; break;
          case ENOTDIR: ecode = TCENOFILE; break;
        }
        tcfdbsetecode(fdb, ecode, __FILE__, __LINE__, __func__);
        err = true;
      }
    }
    uint64_t fsiz = 0;
    if(tcread(walfd, &fsiz, sizeof(fsiz))){
      fsiz = TCITOHLL(fsiz);
    } else {
      tcfdbsetecode(fdb, TCEREAD, __FILE__, __LINE__, __func__);
      err = true;
    }
    TCLIST *list = tclistnew();
    uint64_t waloff = sizeof(fsiz);
    char stack[FDBIOBUFSIZ];
    while(waloff < walsiz){
      uint64_t off;
      uint32_t size;
      if(!tcread(walfd, stack, sizeof(off) + sizeof(size))){
        tcfdbsetecode(fdb, TCEREAD, __FILE__, __LINE__, __func__);
        err = true;
        break;
      }
      memcpy(&off, stack, sizeof(off));
      off = TCITOHLL(off);
      memcpy(&size, stack + sizeof(off), sizeof(size));
      size = TCITOHL(size);
      char *buf;
      if(sizeof(off) + size <= FDBIOBUFSIZ){
        buf = stack;
      } else {
        TCMALLOC(buf, sizeof(off) + size);
      }
      *(uint64_t *)buf = off;
      if(!tcread(walfd, buf + sizeof(off), size)){
        tcfdbsetecode(fdb, TCEREAD, __FILE__, __LINE__, __func__);
        err = true;
        if(buf != stack) TCFREE(buf);
        break;
      }
      TCLISTPUSH(list, buf, sizeof(off) + size);
      if(buf != stack) TCFREE(buf);
      waloff += sizeof(off) + sizeof(size) + size;
    }
    for(int i = TCLISTNUM(list) - 1; i >= 0; i--){
      const char *rec;
      int size;
      TCLISTVAL(rec, list, i, size);
      uint64_t off = *(uint64_t *)rec;
      rec += sizeof(off);
      size -= sizeof(off);
      if(lseek(dbfd, off, SEEK_SET) == -1){
        tcfdbsetecode(fdb, TCESEEK, __FILE__, __LINE__, __func__);
        err = true;
        break;
      }
      if(!tcwrite(dbfd, rec, size)){
        tcfdbsetecode(fdb, TCEWRITE, __FILE__, __LINE__, __func__);
        err = true;
        break;
      }
    }
    tclistdel(list);
    if(ftruncate(dbfd, fsiz) == -1){
      tcfdbsetecode(fdb, TCETRUNC, __FILE__, __LINE__, __func__);
      err = true;
    }
    if((fdb->omode & FDBOTSYNC) && fsync(dbfd) == -1){
      tcfdbsetecode(fdb, TCESYNC, __FILE__, __LINE__, __func__);
      err = true;
    }
    if(tfd >= 0 && close(tfd) == -1){
      tcfdbsetecode(fdb, TCECLOSE, __FILE__, __LINE__, __func__);
      err = true;
    }
  } else {
    err = true;
  }
  if(close(walfd) == -1){
    tcfdbsetecode(fdb, TCECLOSE, __FILE__, __LINE__, __func__);
    err = true;
  }
  return !err;
}


/* Remove the write ahead logging file.
   `fdb' specifies the fixed-length database object.
   `path' specifies the path of the database file.
   If successful, the return value is true, else, it is false. */
static bool tcfdbwalremove(TCFDB *fdb, const char *path){
  assert(fdb && path);
  char *tpath = tcsprintf("%s%c%s", path, MYEXTCHR, FDBWALSUFFIX);
  bool err = false;
  if(unlink(tpath) == -1 && errno != ENOENT){
    tcfdbsetecode(fdb, TCEUNLINK, __FILE__, __LINE__, __func__);
    err = true;
  }
  TCFREE(tpath);
  return !err;
}


/* Open a database file and connect a fixed-length database object.
   `fdb' specifies the fixed-length database object.
   `path' specifies the path of the database file.
   `omode' specifies the connection mode.
   If successful, the return value is true, else, it is false. */
static bool tcfdbopenimpl(TCFDB *fdb, const char *path, int omode){
  assert(fdb && path);
  int mode = O_RDONLY;
  if(omode & FDBOWRITER){
    mode = O_RDWR;
    if(omode & FDBOCREAT) mode |= O_CREAT;
  }
  int fd = open(path, mode, FDBFILEMODE);
  if(fd < 0){
    int ecode = TCEOPEN;
    switch(errno){
      case EACCES: ecode = TCENOPERM; break;
      case ENOENT: ecode = TCENOFILE; break;
      case ENOTDIR: ecode = TCENOFILE; break;
    }
    tcfdbsetecode(fdb, ecode, __FILE__, __LINE__, __func__);
    return false;
  }
  if(!(omode & FDBONOLCK)){
    if(!tclock(fd, omode & FDBOWRITER, omode & FDBOLCKNB)){
      tcfdbsetecode(fdb, TCELOCK, __FILE__, __LINE__, __func__);
      close(fd);
      return false;
    }
  }
  if((omode & FDBOWRITER) && (omode & FDBOTRUNC)){
    if(ftruncate(fd, 0) == -1){
      tcfdbsetecode(fdb, TCETRUNC, __FILE__, __LINE__, __func__);
      close(fd);
      return false;
    }
    if(!tcfdbwalremove(fdb, path)){
      close(fd);
      return false;
    }
  }
  struct stat sbuf;
  if(fstat(fd, &sbuf) == -1 || !S_ISREG(sbuf.st_mode)){
    tcfdbsetecode(fdb, TCESTAT, __FILE__, __LINE__, __func__);
    close(fd);
    return false;
  }
  char hbuf[FDBHEADSIZ];
  if((omode & FDBOWRITER) && sbuf.st_size < 1){
    fdb->flags = 0;
    fdb->rnum = 0;
    fdb->fsiz = FDBHEADSIZ;
    fdb->min = 0;
    fdb->max = 0;
    tcfdbdumpmeta(fdb, hbuf);
    if(!tcwrite(fd, hbuf, FDBHEADSIZ)){
      tcfdbsetecode(fdb, TCEWRITE, __FILE__, __LINE__, __func__);
      close(fd);
      return false;
    }
    sbuf.st_size = fdb->fsiz;
  }
  if(lseek(fd, 0, SEEK_SET) == -1){
    tcfdbsetecode(fdb, TCESEEK, __FILE__, __LINE__, __func__);
    close(fd);
    return false;
  }
  if(!tcread(fd, hbuf, FDBHEADSIZ)){
    tcfdbsetecode(fdb, TCEREAD, __FILE__, __LINE__, __func__);
    close(fd);
    return false;
  }
  int type = fdb->type;
  tcfdbloadmeta(fdb, hbuf);
  if((fdb->flags & FDBFOPEN) && tcfdbwalrestore(fdb, path)){
    if(lseek(fd, 0, SEEK_SET) == -1){
      tcfdbsetecode(fdb, TCESEEK, __FILE__, __LINE__, __func__);
      close(fd);
      return false;
    }
    if(!tcread(fd, hbuf, FDBHEADSIZ)){
      tcfdbsetecode(fdb, TCEREAD, __FILE__, __LINE__, __func__);
      close(fd);
      return false;
    }
    tcfdbloadmeta(fdb, hbuf);
    if(!tcfdbwalremove(fdb, path)){
      close(fd);
      return false;
    }
  }
  if(!(omode & FDBONOLCK)){
    if(memcmp(hbuf, FDBMAGICDATA, strlen(FDBMAGICDATA)) || fdb->type != type ||
       fdb->width < 1 || sbuf.st_size < fdb->fsiz || fdb->limsiz < FDBHEADSIZ ||
       fdb->fsiz > fdb->limsiz){
      tcfdbsetecode(fdb, TCEMETA, __FILE__, __LINE__, __func__);
      close(fd);
      return false;
    }
    if(sbuf.st_size > fdb->fsiz) fdb->fsiz = sbuf.st_size;
  }
  void *map = mmap(0, fdb->limsiz, PROT_READ | ((omode & FDBOWRITER) ? PROT_WRITE : 0),
                   MAP_SHARED, fd, 0);
  if(map == MAP_FAILED){
    tcfdbsetecode(fdb, TCEMMAP, __FILE__, __LINE__, __func__);
    close(fd);
    return false;
  }
  if(fdb->width <= UINT8_MAX){
    fdb->wsiz = sizeof(uint8_t);
  } else if(fdb->width <= UINT16_MAX){
    fdb->wsiz = sizeof(uint16_t);
  } else {
    fdb->wsiz = sizeof(uint32_t);
  }
  fdb->rsiz = fdb->width + fdb->wsiz;
  fdb->limid = (fdb->limsiz - FDBHEADSIZ) / fdb->rsiz;
  fdb->path = tcstrdup(path);
  fdb->fd = fd;
  fdb->omode = omode;
  fdb->iter = 0;
  fdb->map = map;
  fdb->array = (unsigned char *)map + FDBHEADSIZ;
  fdb->ecode = TCESUCCESS;
  fdb->fatal = false;
  fdb->inode = (uint64_t)sbuf.st_ino;
  fdb->mtime = sbuf.st_mtime;
  fdb->tran = false;
  fdb->walfd = -1;
  fdb->walend = 0;
  if(fdb->omode & FDBOWRITER) tcfdbsetflag(fdb, FDBFOPEN, true);
  return true;
}


/* Close a fixed-length database object.
   `fdb' specifies the fixed-length database object.
   If successful, the return value is true, else, it is false. */
static bool tcfdbcloseimpl(TCFDB *fdb){
  assert(fdb);
  bool err = false;
  if(fdb->omode & FDBOWRITER) tcfdbsetflag(fdb, FDBFOPEN, false);
  if((fdb->omode & FDBOWRITER) && !tcfdbmemsync(fdb, false)) err = true;
  if(munmap(fdb->map, fdb->limsiz) == -1){
    tcfdbsetecode(fdb, TCEMMAP, __FILE__, __LINE__, __func__);
    err = true;
  }
  if(fdb->tran){
    if(!tcfdbwalrestore(fdb, fdb->path)) err = true;
    fdb->tran = false;
  }
  if(fdb->walfd >= 0){
    if(close(fdb->walfd) == -1){
      tcfdbsetecode(fdb, TCECLOSE, __FILE__, __LINE__, __func__);
      err = true;
    }
    if(!fdb->fatal && !tcfdbwalremove(fdb, fdb->path)) err = true;
  }
  if(close(fdb->fd) == -1){
    tcfdbsetecode(fdb, TCECLOSE, __FILE__, __LINE__, __func__);
    err = true;
  }
  TCFREE(fdb->path);
  fdb->path = NULL;
  fdb->fd = -1;
  return !err;
}


/* Get the previous record of a record.
   `fdb' specifies the fixed-length database object.
   `id' specifies the ID number.
   The return value is the ID number of the previous record or 0 if no record corresponds. */
static int64_t tcfdbprevid(TCFDB *fdb, int64_t id){
  assert(fdb && id >= 0);
  id--;
  while(id >= fdb->min){
    TCDODEBUG(fdb->cnt_readrec++);
    unsigned char *rec = fdb->array + (id - 1) * (fdb->rsiz);
    unsigned char *rp = rec;
    uint32_t osiz;
    uint16_t snum;
    uint32_t lnum;
    switch(fdb->wsiz){
      case 1:
        osiz = *(rp++);
        break;
      case 2:
        memcpy(&snum, rp, sizeof(snum));
        osiz = TCITOHS(snum);
        rp += sizeof(snum);
        break;
      default:
        memcpy(&lnum, rp, sizeof(lnum));
        osiz = TCITOHL(lnum);
        rp += sizeof(lnum);
        break;
    }
    if(osiz > 0 || *rp != 0) return id;
    id--;
  }
  return 0;
}


/* Get the next record of a record.
   `fdb' specifies the fixed-length database object.
   `id' specifies the ID number.
   The return value is the ID number of the next record or 0 if no record corresponds. */
static int64_t tcfdbnextid(TCFDB *fdb, int64_t id){
  assert(fdb && id >= 0);
  id++;
  while(id <= fdb->max){
    TCDODEBUG(fdb->cnt_readrec++);
    unsigned char *rec = fdb->array + (id - 1) * (fdb->rsiz);
    unsigned char *rp = rec;
    uint32_t osiz;
    uint16_t snum;
    uint32_t lnum;
    switch(fdb->wsiz){
      case 1:
        osiz = *(rp++);
        break;
      case 2:
        memcpy(&snum, rp, sizeof(snum));
        osiz = TCITOHS(snum);
        rp += sizeof(snum);
        break;
      default:
        memcpy(&lnum, rp, sizeof(lnum));
        osiz = TCITOHL(lnum);
        rp += sizeof(lnum);
        break;
    }
    if(osiz > 0 || *rp != 0) return id;
    id++;
  }
  return 0;
}


/* Store a record.
   `fdb' specifies the fixed-length database object.
   `id' specifies the ID number.
   `vbuf' specifies the pointer to the region of the value.
   `vsiz' specifies the size of the region of the value.
   `dmode' specifies behavior when the key overlaps.
   If successful, the return value is true, else, it is false. */
static bool tcfdbputimpl(TCFDB *fdb, int64_t id, const void *vbuf, int vsiz, int dmode){
  assert(fdb && id > 0);
  if(vsiz > (int64_t)fdb->width) vsiz = fdb->width;
  TCDODEBUG(fdb->cnt_readrec++);
  unsigned char *rec = fdb->array + (id - 1) * (fdb->rsiz);
  uint64_t nsiz = FDBHEADSIZ + id * fdb->rsiz;
  if(nsiz > fdb->fsiz){
    if(nsiz > fdb->limsiz){
      tcfdbsetecode(fdb, TCEINVALID, __FILE__, __LINE__, __func__);
      return false;
    }
    if(!FDBLOCKATTR(fdb)) return false;
    if(nsiz > fdb->fsiz){
      if(vsiz < 0){
        tcfdbsetecode(fdb, TCENOREC, __FILE__, __LINE__, __func__);
        FDBUNLOCKATTR(fdb);
        return false;
      }
      if(nsiz + fdb->rsiz * FDBTRUNCALW < fdb->limsiz) nsiz += fdb->rsiz * FDBTRUNCALW;
      if(ftruncate(fdb->fd, nsiz) == -1){
        tcfdbsetecode(fdb, TCETRUNC, __FILE__, __LINE__, __func__);
        FDBUNLOCKATTR(fdb);
        return false;
      }
      TCDODEBUG(fdb->cnt_truncfile++);
      fdb->fsiz = nsiz;
      unsigned char *wp = rec;
      uint16_t snum;
      uint32_t lnum;
      switch(fdb->wsiz){
        case 1:
          *(wp++) = vsiz;
          break;
        case 2:
          snum = TCHTOIS(vsiz);
          memcpy(wp, &snum, sizeof(snum));
          wp += sizeof(snum);
          break;
        default:
          lnum = TCHTOIL(vsiz);
          memcpy(wp, &lnum, sizeof(lnum));
          wp += sizeof(lnum);
          break;
      }
      if(vsiz > 0){
        memcpy(wp, vbuf, vsiz);
      } else {
        *wp = 1;
      }
      TCDODEBUG(fdb->cnt_writerec++);
      fdb->rnum++;
      if(fdb->min < 1 || id < fdb->min) fdb->min = id;
      if(fdb->max < 1 || id > fdb->max) fdb->max = id;
      FDBUNLOCKATTR(fdb);
      return true;
    }
    FDBUNLOCKATTR(fdb);
  }
  unsigned char *rp = rec;
  uint32_t osiz;
  uint16_t snum;
  uint32_t lnum;
  switch(fdb->wsiz){
    case 1:
      osiz = *(rp++);
      break;
    case 2:
      memcpy(&snum, rp, sizeof(snum));
      osiz = TCITOHS(snum);
      rp += sizeof(snum);
      break;
    default:
      memcpy(&lnum, rp, sizeof(lnum));
      osiz = TCITOHL(lnum);
      rp += sizeof(lnum);
      break;
  }
  bool miss = osiz == 0 && *rp == 0;
  if(dmode != FDBPDOVER && !miss){
    if(dmode == FDBPDKEEP){
      tcfdbsetecode(fdb, TCEKEEP, __FILE__, __LINE__, __func__);
      return false;
    }
    if(dmode == FDBPDCAT){
      if(fdb->tran && !tcfdbwalwrite(fdb, (char *)rec - fdb->map, fdb->width)) return false;
      vsiz = tclmin(vsiz, fdb->width - osiz);
      unsigned char *wp = rec;
      int usiz = osiz + vsiz;
      switch(fdb->wsiz){
        case 1:
          *(wp++) = usiz;
          break;
        case 2:
          snum = TCHTOIS(usiz);
          memcpy(wp, &snum, sizeof(snum));
          wp += sizeof(snum);
          break;
        default:
          lnum = TCHTOIL(usiz);
          memcpy(wp, &lnum, sizeof(lnum));
          wp += sizeof(lnum);
          break;
      }
      if(usiz > 0){
        memcpy(wp + osiz, vbuf, vsiz);
      } else {
        *wp = 1;
      }
      TCDODEBUG(fdb->cnt_writerec++);
      return true;
    }
    if(dmode == FDBPDADDINT){
      if(osiz != sizeof(int)){
        tcfdbsetecode(fdb, TCEKEEP, __FILE__, __LINE__, __func__);
        return false;
      }
      int lnum;
      memcpy(&lnum, rp, sizeof(lnum));
      if(*(int *)vbuf == 0){
        *(int *)vbuf = lnum;
        return true;
      }
      if(fdb->tran && !tcfdbwalwrite(fdb, (char *)rec - fdb->map, fdb->width)) return false;
      lnum += *(int *)vbuf;
      *(int *)vbuf = lnum;
      memcpy(rp, &lnum, sizeof(lnum));
      TCDODEBUG(fdb->cnt_writerec++);
      return true;
    }
    if(dmode == FDBPDADDDBL){
      if(osiz != sizeof(double)){
        tcfdbsetecode(fdb, TCEKEEP, __FILE__, __LINE__, __func__);
        return false;
      }
      double dnum;
      memcpy(&dnum, rp, sizeof(dnum));
      if(*(double *)vbuf == 0.0){
        *(double *)vbuf = dnum;
        return true;
      }
      if(fdb->tran && !tcfdbwalwrite(fdb, (char *)rec - fdb->map, fdb->width)) return false;
      dnum += *(double *)vbuf;
      *(double *)vbuf = dnum;
      memcpy(rp, &dnum, sizeof(dnum));
      TCDODEBUG(fdb->cnt_writerec++);
      return true;
    }
    if(dmode == FDBPDPROC){
      FDBPDPROCOP *procptr = *(FDBPDPROCOP **)((char *)vbuf - sizeof(procptr));
      int nvsiz;
      char *nvbuf = procptr->proc(rp, osiz, &nvsiz, procptr->op);
      if(nvbuf == (void *)-1){
        if(fdb->tran && !tcfdbwalwrite(fdb, (char *)rec - fdb->map, fdb->width)) return false;
        memset(rec, 0, fdb->wsiz + 1);
        TCDODEBUG(fdb->cnt_writerec++);
        if(!FDBLOCKATTR(fdb)) return false;
        fdb->rnum--;
        if(fdb->rnum < 1){
          fdb->min = 0;
          fdb->max = 0;
        } else if(fdb->rnum < 2){
          if(fdb->min == id){
            fdb->min = fdb->max;
          } else if(fdb->max == id){
            fdb->max = fdb->min;
          }
        } else {
          if(id == fdb->min) fdb->min = tcfdbnextid(fdb, id);
          if(id == fdb->max) fdb->max = tcfdbprevid(fdb, id);
        }
        FDBUNLOCKATTR(fdb);
        return true;
      }
      if(!nvbuf){
        tcfdbsetecode(fdb, TCEKEEP, __FILE__, __LINE__, __func__);
        return false;
      }
      if(fdb->tran && !tcfdbwalwrite(fdb, (char *)rec - fdb->map, fdb->width)) return false;
      if(nvsiz > fdb->width) nvsiz = fdb->width;
      unsigned char *wp = rec;
      switch(fdb->wsiz){
        case 1:
          *(wp++) = nvsiz;
          break;
        case 2:
          snum = TCHTOIS(nvsiz);
          memcpy(wp, &snum, sizeof(snum));
          wp += sizeof(snum);
          break;
        default:
          lnum = TCHTOIL(nvsiz);
          memcpy(wp, &lnum, sizeof(lnum));
          wp += sizeof(lnum);
          break;
      }
      if(nvsiz > 0){
        memcpy(wp, nvbuf, nvsiz);
      } else {
        *wp = 1;
      }
      TCFREE(nvbuf);
      TCDODEBUG(fdb->cnt_writerec++);
      return true;
    }
  }
  if(vsiz < 0){
    tcfdbsetecode(fdb, TCENOREC, __FILE__, __LINE__, __func__);
    return false;
  }
  if(fdb->tran && !tcfdbwalwrite(fdb, (char *)rec - fdb->map, fdb->width)) return false;
  unsigned char *wp = rec;
  switch(fdb->wsiz){
    case 1:
      *(wp++) = vsiz;
      break;
    case 2:
      snum = TCHTOIS(vsiz);
      memcpy(wp, &snum, sizeof(snum));
      wp += sizeof(snum);
      break;
    default:
      lnum = TCHTOIL(vsiz);
      memcpy(wp, &lnum, sizeof(lnum));
      wp += sizeof(lnum);
      break;
  }
  if(vsiz > 0){
    memcpy(wp, vbuf, vsiz);
  } else {
    *wp = 1;
  }
  TCDODEBUG(fdb->cnt_writerec++);
  if(miss){
    if(!FDBLOCKATTR(fdb)) return false;
    fdb->rnum++;
    if(fdb->min < 1 || id < fdb->min) fdb->min = id;
    if(fdb->max < 1 || id > fdb->max) fdb->max = id;
    FDBUNLOCKATTR(fdb);
  }
  return true;
}


/* Remove a record of a fixed-length database object.
   `fdb' specifies the fixed-length database object.
   `id' specifies the ID number.
   If successful, the return value is true, else, it is false. */
static bool tcfdboutimpl(TCFDB *fdb, int64_t id){
  assert(fdb && id >= 0);
  TCDODEBUG(fdb->cnt_readrec++);
  unsigned char *rec = fdb->array + (id - 1) * (fdb->rsiz);
  uint64_t nsiz = FDBHEADSIZ + id * fdb->rsiz;
  if(nsiz > fdb->fsiz){
    tcfdbsetecode(fdb, TCENOREC, __FILE__, __LINE__, __func__);
    return false;
  }
  unsigned char *rp = rec;
  uint32_t osiz;
  uint16_t snum;
  uint32_t lnum;
  switch(fdb->wsiz){
    case 1:
      osiz = *(rp++);
      break;
    case 2:
      memcpy(&snum, rp, sizeof(snum));
      osiz = TCITOHS(snum);
      rp += sizeof(snum);
      break;
    default:
      memcpy(&lnum, rp, sizeof(lnum));
      osiz = TCITOHL(lnum);
      rp += sizeof(lnum);
      break;
  }
  if(osiz == 0 && *rp == 0){
    tcfdbsetecode(fdb, TCENOREC, __FILE__, __LINE__, __func__);
    return false;
  }
  if(fdb->tran && !tcfdbwalwrite(fdb, (char *)rec - fdb->map, fdb->width)) return false;
  memset(rec, 0, fdb->wsiz + 1);
  TCDODEBUG(fdb->cnt_writerec++);
  if(!FDBLOCKATTR(fdb)) return false;
  fdb->rnum--;
  if(fdb->rnum < 1){
    fdb->min = 0;
    fdb->max = 0;
  } else if(fdb->rnum < 2){
    if(fdb->min == id){
      fdb->min = fdb->max;
    } else if(fdb->max == id){
      fdb->max = fdb->min;
    }
  } else {
    if(id == fdb->min) fdb->min = tcfdbnextid(fdb, id);
    if(id == fdb->max) fdb->max = tcfdbprevid(fdb, id);
  }
  FDBUNLOCKATTR(fdb);
  return true;
}


/* Retrieve a record.
   `fdb' specifies the fixed-length database object.
   `id' specifies the ID number.
   `sp' specifies the pointer to the variable into which the size of the region of the return
   value is assigned.
   If successful, the return value is the pointer to the region of the value of the corresponding
   record. */
static const void *tcfdbgetimpl(TCFDB *fdb, int64_t id, int *sp){
  assert(fdb && id >= 0 && sp);
  TCDODEBUG(fdb->cnt_readrec++);
  unsigned char *rec = fdb->array + (id - 1) * (fdb->rsiz);
  uint64_t nsiz = FDBHEADSIZ + id * fdb->rsiz;
  if(nsiz > fdb->fsiz){
    tcfdbsetecode(fdb, TCENOREC, __FILE__, __LINE__, __func__);
    return false;
  }
  unsigned char *rp = rec;
  uint32_t osiz;
  uint16_t snum;
  uint32_t lnum;
  switch(fdb->wsiz){
    case 1:
      osiz = *(rp++);
      break;
    case 2:
      memcpy(&snum, rp, sizeof(snum));
      osiz = TCITOHS(snum);
      rp += sizeof(snum);
      break;
    default:
      memcpy(&lnum, rp, sizeof(lnum));
      osiz = TCITOHL(lnum);
      rp += sizeof(lnum);
      break;
  }
  if(osiz == 0 && *rp == 0){
    tcfdbsetecode(fdb, TCENOREC, __FILE__, __LINE__, __func__);
    return false;
  }
  *sp = osiz;
  return rp;
}


/* Initialize the iterator of a fixed-length database object.
   `fdb' specifies the fixed-length database object.
   If successful, the return value is true, else, it is false. */
static bool tcfdbiterinitimpl(TCFDB *fdb){
  assert(fdb);
  fdb->iter = fdb->min;
  return true;
}


/* Get the next key of the iterator of a fixed-length database object.
   `fdb' specifies the fixed-length database object.
   If successful, the return value is the next ID number of the iterator, else, it is 0. */
static uint64_t tcfdbiternextimpl(TCFDB *fdb){
  assert(fdb);
  if(fdb->iter < 1){
    tcfdbsetecode(fdb, TCENOREC, __FILE__, __LINE__, __func__);
    return 0;
  }
  uint64_t cur = fdb->iter;
  fdb->iter = tcfdbnextid(fdb, fdb->iter);
  return cur;
}


/* Get range matching ID numbers in a fixed-length database object.
   `fdb' specifies the fixed-length database object.
   `lower' specifies the lower limit of the range.
   `upper' specifies the upper limit of the range.
   `max' specifies the maximum number of keys to be fetched.
   `np' specifies the pointer to the variable into which the number of elements of the return
   value is assigned.
   If successful, the return value is the pointer to an array of ID numbers of the corresponding
   records. */
static uint64_t *tcfdbrangeimpl(TCFDB *fdb, int64_t lower, int64_t upper, int max, int *np){
  assert(fdb && lower > 0 && upper > 0 && np);
  if(lower < fdb->min) lower = fdb->min;
  if(upper > fdb->max) upper = fdb->max;
  if(max < 0) max = INT_MAX;
  int anum = FDBIDARYUNIT;
  uint64_t *ids;
  TCMALLOC(ids, anum * sizeof(*ids));
  int num = 0;
  for(int64_t i = lower; i <= upper && num < max; i++){
    int vsiz;
    const void *vbuf = tcfdbgetimpl(fdb, i, &vsiz);
    if(vbuf){
      if(num >= anum){
        anum *= 2;
        TCREALLOC(ids, ids, anum * sizeof(*ids));
      }
      ids[num++] = i;
    }
  }
  *np = num;
  return ids;
}


/* Optimize the file of a fixed-length database object.
   `fdb' specifies the fixed-length database object.
   `width' specifies the width of the value of each record.
   `limsiz' specifies the limit size of the database file.
   If successful, the return value is true, else, it is false. */
static bool tcfdboptimizeimpl(TCFDB *fdb, int32_t width, int64_t limsiz){
  assert(fdb);
  char *tpath = tcsprintf("%s%ctmp%c%llu", fdb->path, MYEXTCHR, MYEXTCHR, fdb->inode);
  TCFDB *tfdb = tcfdbnew();
  tfdb->dbgfd = fdb->dbgfd;
  if(width < 1) width = fdb->width;
  if(limsiz < 1) limsiz = fdb->limsiz;
  tcfdbtune(tfdb, width, limsiz);
  if(!tcfdbopen(tfdb, tpath, FDBOWRITER | FDBOCREAT | FDBOTRUNC)){
    tcfdbsetecode(fdb, tfdb->ecode, __FILE__, __LINE__, __func__);
    tcfdbdel(tfdb);
    TCFREE(tpath);
    return false;
  }
  bool err = false;
  int64_t max = fdb->max;
  for(int i = fdb->min; !err && i <= max; i++){
    int vsiz;
    const void *vbuf = tcfdbgetimpl(fdb, i, &vsiz);
    if(vbuf && !tcfdbput(tfdb, i, vbuf, vsiz)){
      tcfdbsetecode(fdb, tfdb->ecode, __FILE__, __LINE__, __func__);
      err = true;
    }
  }
  if(!tcfdbclose(tfdb)){
    tcfdbsetecode(fdb, tfdb->ecode, __FILE__, __LINE__, __func__);
    err = true;
  }
  tcfdbdel(tfdb);
  if(unlink(fdb->path) == -1){
    tcfdbsetecode(fdb, TCEUNLINK, __FILE__, __LINE__, __func__);
    err = true;
  }
  if(rename(tpath, fdb->path) == -1){
    tcfdbsetecode(fdb, TCERENAME, __FILE__, __LINE__, __func__);
    err = true;
  }
  TCFREE(tpath);
  if(err) return false;
  tpath = tcstrdup(fdb->path);
  int omode = (fdb->omode & ~FDBOCREAT) & ~FDBOTRUNC;
  if(!tcfdbcloseimpl(fdb)){
    TCFREE(tpath);
    return false;
  }
  bool rv = tcfdbopenimpl(fdb, tpath, omode);
  TCFREE(tpath);
  return rv;
}


/* Remove all records of a fixed-length database object.
   `fdb' specifies the fixed-length database object.
   If successful, the return value is true, else, it is false. */
static bool tcfdbvanishimpl(TCFDB *fdb){
  assert(fdb);
  char *path = tcstrdup(fdb->path);
  int omode = fdb->omode;
  bool err = false;
  if(!tcfdbcloseimpl(fdb)) err = true;
  if(!tcfdbopenimpl(fdb, path, FDBOTRUNC | omode)){
    tcpathunlock(fdb->rpath);
    TCFREE(fdb->rpath);
    err = true;
  }
  TCFREE(path);
  return !err;
}


/* Copy the database file of a fixed-length database object.
   `fdb' specifies the fixed-length database object.
   `path' specifies the path of the destination file.
   If successful, the return value is true, else, it is false. */
static bool tcfdbcopyimpl(TCFDB *fdb, const char *path){
  assert(fdb && path);
  bool err = false;
  if(fdb->omode & FDBOWRITER){
    if(!tcfdbmemsync(fdb, false)) err = true;
    tcfdbsetflag(fdb, FDBFOPEN, false);
  }
  if(*path == '@'){
    char tsbuf[TCNUMBUFSIZ];
    sprintf(tsbuf, "%llu", (unsigned long long)(tctime() * 1000000));
    const char *args[3];
    args[0] = path + 1;
    args[1] = fdb->path;
    args[2] = tsbuf;
    if(tcsystem(args, sizeof(args) / sizeof(*args)) != 0) err = true;
  } else {
    if(!tccopyfile(fdb->path, path)){
      tcfdbsetecode(fdb, TCEMISC, __FILE__, __LINE__, __func__);
      err = true;
    }
  }
  if(fdb->omode & FDBOWRITER) tcfdbsetflag(fdb, FDBFOPEN, true);
  return !err;
}


/* Move the iterator to the record corresponding a key of a fixed-length database object.
   `fdb' specifies the fixed-length database object.
   `id' specifies the ID number.
   If successful, the return value is true, else, it is false. */
static bool tcfdbiterjumpimpl(TCFDB *fdb, int64_t id){
  assert(fdb && id >= 0);
  if(id <= fdb->min){
    fdb->iter = fdb->min;
  } else {
    int vsiz;
    if(tcfdbgetimpl(fdb, id, &vsiz)){
      fdb->iter = id;
    } else {
      uint64_t iter = tcfdbnextid(fdb, id);
      if(iter > 0){
        fdb->iter = iter;
      } else {
        return false;
      }
    }
  }
  return true;
}


/* Process each record atomically of a fixed-length database object.
   `fdb' specifies the fixed-length database object.
   `iter' specifies the pointer to the iterator function called for each record.
   `op' specifies an arbitrary pointer to be given as a parameter of the iterator function.
   If successful, the return value is true, else, it is false. */
static bool tcfdbforeachimpl(TCFDB *fdb, TCITER iter, void *op){
  bool err = false;
  uint64_t id = fdb->min;
  while(id > 0){
    int vsiz;
    const void *vbuf = tcfdbgetimpl(fdb, id, &vsiz);
    if(vbuf){
      char kbuf[TCNUMBUFSIZ];
      int ksiz = sprintf(kbuf, "%llu", (unsigned long long)id);
      if(!iter(kbuf, ksiz, vbuf, vsiz, op)) break;
    } else {
      tcfdbsetecode(fdb, TCEMISC, __FILE__, __LINE__, __func__);
      err = true;
    }
    id = tcfdbnextid(fdb, id);
  }
  return !err;
}


/* Lock a method of the fixed-length database object.
   `fdb' specifies the fixed-length database object.
   `wr' specifies whether the lock is writer or not.
   If successful, the return value is true, else, it is false. */
static bool tcfdblockmethod(TCFDB *fdb, bool wr){
  assert(fdb);
  if(wr ? pthread_rwlock_wrlock(fdb->mmtx) != 0 : pthread_rwlock_rdlock(fdb->mmtx) != 0){
    tcfdbsetecode(fdb, TCETHREAD, __FILE__, __LINE__, __func__);
    return false;
  }
  TCTESTYIELD();
  return true;
}


/* Unlock a method of the fixed-length database object.
   `fdb' specifies the fixed-length database object.
   If successful, the return value is true, else, it is false. */
static bool tcfdbunlockmethod(TCFDB *fdb){
  assert(fdb);
  if(pthread_rwlock_unlock(fdb->mmtx) != 0){
    tcfdbsetecode(fdb, TCETHREAD, __FILE__, __LINE__, __func__);
    return false;
  }
  TCTESTYIELD();
  return true;
}


/* Lock the attributes of the fixed-length database object.
   `fdb' specifies the fixed-length database object.
   If successful, the return value is true, else, it is false. */
static bool tcfdblockattr(TCFDB *fdb){
  assert(fdb);
  if(pthread_mutex_lock(fdb->amtx) != 0){
    tcfdbsetecode(fdb, TCETHREAD, __FILE__, __LINE__, __func__);
    return false;
  }
  TCTESTYIELD();
  return true;
}


/* Unlock the attributes of the fixed-length database object.
   `fdb' specifies the fixed-length database object.
   If successful, the return value is true, else, it is false. */
static bool tcfdbunlockattr(TCFDB *fdb){
  assert(fdb);
  if(pthread_mutex_unlock(fdb->amtx) != 0){
    tcfdbsetecode(fdb, TCETHREAD, __FILE__, __LINE__, __func__);
    return false;
  }
  TCTESTYIELD();
  return true;
}


/* Lock a record of the fixed-length database object.
   `fdb' specifies the fixed-length database object.
   `wr' specifies whether the lock is writer or not.
   If successful, the return value is true, else, it is false. */
static bool tcfdblockrecord(TCFDB *fdb, bool wr, uint64_t id){
  assert(fdb && id > 0);
  if(wr ? pthread_rwlock_wrlock((pthread_rwlock_t *)fdb->rmtxs + id % FDBRMTXNUM) != 0 :
     pthread_rwlock_rdlock((pthread_rwlock_t *)fdb->rmtxs + id % FDBRMTXNUM) != 0){
    tcfdbsetecode(fdb, TCETHREAD, __FILE__, __LINE__, __func__);
    return false;
  }
  TCTESTYIELD();
  return true;
}


/* Unlock a record of the fixed-length database object.
   `fdb' specifies the fixed-length database object.
   If successful, the return value is true, else, it is false. */
static bool tcfdbunlockrecord(TCFDB *fdb, uint64_t id){
  assert(fdb);
  if(pthread_rwlock_unlock((pthread_rwlock_t *)fdb->rmtxs + id % FDBRMTXNUM) != 0){
    tcfdbsetecode(fdb, TCETHREAD, __FILE__, __LINE__, __func__);
    return false;
  }
  TCTESTYIELD();
  return true;
}


/* Lock all records of the fixed-length database object.
   `fdb' specifies the fixed-length database object.
   `wr' specifies whether the lock is writer or not.
   If successful, the return value is true, else, it is false. */
static bool tcfdblockallrecords(TCFDB *fdb, bool wr){
  assert(fdb);
  for(int i = 0; i < FDBRMTXNUM; i++){
    if(wr ? pthread_rwlock_wrlock((pthread_rwlock_t *)fdb->rmtxs + i) != 0 :
       pthread_rwlock_rdlock((pthread_rwlock_t *)fdb->rmtxs + i) != 0){
      tcfdbsetecode(fdb, TCETHREAD, __FILE__, __LINE__, __func__);
      while(--i >= 0){
        pthread_rwlock_unlock((pthread_rwlock_t *)fdb->rmtxs + i);
      }
      return false;
    }
  }
  TCTESTYIELD();
  return true;
}


/* Unlock all records of the fixed-length database object.
   `fdb' specifies the fixed-length database object.
   If successful, the return value is true, else, it is false. */
static bool tcfdbunlockallrecords(TCFDB *fdb){
  assert(fdb);
  bool err = false;
  for(int i = FDBRMTXNUM - 1; i >= 0; i--){
    if(pthread_rwlock_unlock((pthread_rwlock_t *)fdb->rmtxs + i)) err = true;
  }
  TCTESTYIELD();
  if(err){
    tcfdbsetecode(fdb, TCETHREAD, __FILE__, __LINE__, __func__);
    return false;
  }
  return true;
}


/* Lock the write ahead logging file of the fixed-length database object.
   `fdb' specifies the fixed-length database object.
   If successful, the return value is true, else, it is false. */
static bool tcfdblockwal(TCFDB *fdb){
  assert(fdb);
  if(pthread_mutex_lock(fdb->wmtx) != 0){
    tcfdbsetecode(fdb, TCETHREAD, __FILE__, __LINE__, __func__);
    return false;
  }
  TCTESTYIELD();
  return true;
}


/* Unlock the write ahead logging file of the fixed-length database object.
   `fdb' specifies the fixed-length database object.
   If successful, the return value is true, else, it is false. */
static bool tcfdbunlockwal(TCFDB *fdb){
  assert(fdb);
  if(pthread_mutex_unlock(fdb->wmtx) != 0){
    tcfdbsetecode(fdb, TCETHREAD, __FILE__, __LINE__, __func__);
    return false;
  }
  TCTESTYIELD();
  return true;
}



/*************************************************************************************************
 * debugging functions
 *************************************************************************************************/


/* Print meta data of the header into the debugging output.
   `fdb' specifies the fixed-length database object. */
void tcfdbprintmeta(TCFDB *fdb){
  assert(fdb);
  if(fdb->dbgfd < 0) return;
  int dbgfd = (fdb->dbgfd == UINT16_MAX) ? 1 : fdb->dbgfd;
  char buf[FDBIOBUFSIZ];
  char *wp = buf;
  wp += sprintf(wp, "META:");
  wp += sprintf(wp, " mmtx=%p", (void *)fdb->mmtx);
  wp += sprintf(wp, " amtx=%p", (void *)fdb->amtx);
  wp += sprintf(wp, " rmtxs=%p", (void *)fdb->rmtxs);
  wp += sprintf(wp, " tmtx=%p", (void *)fdb->tmtx);
  wp += sprintf(wp, " wmtx=%p", (void *)fdb->wmtx);
  wp += sprintf(wp, " eckey=%p", (void *)fdb->eckey);
  wp += sprintf(wp, " rpath=%s", fdb->rpath ? fdb->rpath : "-");
  wp += sprintf(wp, " type=%02X", fdb->type);
  wp += sprintf(wp, " flags=%02X", fdb->flags);
  wp += sprintf(wp, " width=%u", fdb->width);
  wp += sprintf(wp, " limsiz=%llu", (unsigned long long)fdb->limsiz);
  wp += sprintf(wp, " wsiz=%u", fdb->wsiz);
  wp += sprintf(wp, " rsiz=%u", fdb->rsiz);
  wp += sprintf(wp, " limid=%llu", (unsigned long long)fdb->limid);
  wp += sprintf(wp, " path=%s", fdb->path ? fdb->path : "-");
  wp += sprintf(wp, " fd=%d", fdb->fd);
  wp += sprintf(wp, " omode=%u", fdb->omode);
  wp += sprintf(wp, " rnum=%llu", (unsigned long long)fdb->rnum);
  wp += sprintf(wp, " fsiz=%llu", (unsigned long long)fdb->fsiz);
  wp += sprintf(wp, " min=%llu", (unsigned long long)fdb->min);
  wp += sprintf(wp, " max=%llu", (unsigned long long)fdb->max);
  wp += sprintf(wp, " iter=%llu", (unsigned long long)fdb->iter);
  wp += sprintf(wp, " map=%p", (void *)fdb->map);
  wp += sprintf(wp, " array=%p", (void *)fdb->array);
  wp += sprintf(wp, " ecode=%d", fdb->ecode);
  wp += sprintf(wp, " fatal=%u", fdb->fatal);
  wp += sprintf(wp, " inode=%llu", (unsigned long long)fdb->inode);
  wp += sprintf(wp, " mtime=%llu", (unsigned long long)fdb->mtime);
  wp += sprintf(wp, " tran=%d", fdb->tran);
  wp += sprintf(wp, " walfd=%d", fdb->walfd);
  wp += sprintf(wp, " walend=%llu", (unsigned long long)fdb->walend);
  wp += sprintf(wp, " dbgfd=%d", fdb->dbgfd);
  wp += sprintf(wp, " cnt_writerec=%lld", (long long)fdb->cnt_writerec);
  wp += sprintf(wp, " cnt_readrec=%lld", (long long)fdb->cnt_readrec);
  wp += sprintf(wp, " cnt_truncfile=%lld", (long long)fdb->cnt_truncfile);
  *(wp++) = '\n';
  tcwrite(dbgfd, buf, wp - buf);
}



// END OF FILE
/*************************************************************************************************
 * The table database API of Tokyo Cabinet
 *                                                               Copyright (C) 2006-2012 FAL Labs
 * This file is part of Tokyo Cabinet.
 * Tokyo Cabinet 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 any later version.  Tokyo Cabinet 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 Tokyo
 * Cabinet; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307 USA.
 *************************************************************************************************/


#include "tcutil.h"
#include "tchdb.h"
#include "tcbdb.h"
#include "tctdb.h"
#include "myconf.h"

#define TDBOPAQUESIZ   64                // size of using opaque field
#define TDBLEFTOPQSIZ  64                // size of left opaque field
#define TDBPAGEBUFSIZ  32768             // size of a buffer to read each page

#define TDBDEFBNUM     131071            // default bucket number
#define TDBDEFAPOW     4                 // default alignment power
#define TDBDEFFPOW     10                // default free block pool power
#define TDBDEFLCNUM    4096              // default number of leaf cache
#define TDBDEFNCNUM    512               // default number of node cache
#define TDBDEFXMSIZ    (64LL<<20)        // default size of the extra mapped memory
#define TDBIDXSUFFIX   "idx"             // suffix of column index file
#define TDBIDXLMEMB    64                // number of members in each leaf of the index
#define TDBIDXNMEMB    256               // number of members in each node of the index
#define TDBIDXLSMAX    4096              // maximum size of each leaf of the index
#define TDBIDXICCBNUM  262139            // bucket number of the index cache
#define TDBIDXICCMAX   (64LL<<20)        // maximum size of the index cache
#define TDBIDXICCSYNC  0.01              // ratio of cache synchronization
#define TDBIDXQGUNIT   3                 // unit number of the q-gram index
#define TDBFTSUNITMAX  32                // maximum number of full-text search units
#define TDBFTSOCRUNIT  8192              // maximum number of full-text search units
#define TDBFTSBMNUM    524287            // number of elements of full-text search bitmap
#define TDBNUMCNTCOL   "_num"            // column name of number counting
#define TDBCOLBUFSIZ   1024              // size of a buffer for a column value
#define TDBNUMCOLMAX   16                // maximum number of columns of the long double
#define TDBHINTUSIZ    256               // unit size of the hint string
#define TDBORDRATIO    0.2               // ratio of records to use the order index

enum {                                   // enumeration for duplication behavior
  TDBPDOVER,                             // overwrite an existing value
  TDBPDKEEP,                             // keep the existing value
  TDBPDCAT                               // concatenate values
};

typedef struct {                         // type of structure for a sort record
  const char *kbuf;                      // pointer to the primary key
  int ksiz;                              // size of the primary key
  char *vbuf;                            // pointer to the value
  int vsiz;                              // size of the value
} TDBSORTREC;

typedef struct {                         // type of structure for a full-text search unit
  TCLIST *tokens;                        // q-gram tokens
  bool sign;                             // positive sign
} TDBFTSUNIT;

typedef struct {                         // type of structure for a full-text string occurrence
  const char *pkbuf;                     // primary key string
  int32_t pksiz;                         // size of the primary key
  int32_t off;                           // offset of the token
  uint16_t seq;                          // sequence number
  uint16_t hash;                         // hash value for counting sort
} TDBFTSSTROCR;

typedef struct {                         // type of structure for a full-text number occurrence
  int64_t pkid;                          // primery key number
  int32_t off;                           // offset of the token
  uint16_t seq;                          // sequence number
  uint16_t hash;                         // hash value for counting sort
} TDBFTSNUMOCR;


/* private macros */
#define TDBLOCKMETHOD(TC_tdb, TC_wr)                            \
  ((TC_tdb)->mmtx ? tctdblockmethod((TC_tdb), (TC_wr)) : true)
#define TDBUNLOCKMETHOD(TC_tdb)                         \
  ((TC_tdb)->mmtx ? tctdbunlockmethod(TC_tdb) : true)
#define TDBTHREADYIELD(TC_tdb)                          \
  do { if((TC_tdb)->mmtx) sched_yield(); } while(false)


/* private function prototypes */
static void tctdbclear(TCTDB *tdb);
static bool tctdbopenimpl(TCTDB *tdb, const char *path, int omode);
static bool tctdbcloseimpl(TCTDB *tdb);
static bool tctdbputimpl(TCTDB *tdb, const void *pkbuf, int pksiz, TCMAP *cols, int dmode);
static bool tctdboutimpl(TCTDB *tdb, const char *pkbuf, int pksiz);
static TCMAP *tctdbgetimpl(TCTDB *tdb, const void *pkbuf, int pksiz);
static char *tctdbgetonecol(TCTDB *tdb, const void *pkbuf, int pksiz,
                            const void *nbuf, int nsiz, int *sp);
static double tctdbaddnumber(TCTDB *tdb, const void *pkbuf, int pksiz, double num);
static bool tctdboptimizeimpl(TCTDB *tdb, int64_t bnum, int8_t apow, int8_t fpow, uint8_t opts);
static bool tctdbvanishimpl(TCTDB *tdb);
static bool tctdbcopyimpl(TCTDB *tdb, const char *path);
static bool tctdbtranbeginimpl(TCTDB *tdb);
static bool tctdbtrancommitimpl(TCTDB *tdb);
static bool tctdbtranabortimpl(TCTDB *tdb);
static bool tctdbsetindeximpl(TCTDB *tdb, const char *name, int type);
static int64_t tctdbgenuidimpl(TCTDB *tdb, int64_t inc);
static TCLIST *tctdbqrysearchimpl(TDBQRY *qry);
static TCMAP *tctdbqryidxfetch(TDBQRY *qry, TDBCOND *cond, TDBIDX *idx);
static long double tctdbatof(const char *str);
static bool tctdbqryidxcurjumpnum(BDBCUR *cur, const char *kbuf, int ksiz, bool first);
static bool tctdbqryonecondmatch(TDBQRY *qry, TDBCOND *cond, const char *pkbuf, int pksiz);
static bool tctdbqryallcondmatch(TDBQRY *qry, const char *pkbuf, int pksiz);
static bool tctdbqrycondmatch(TDBCOND *cond, const char *vbuf, int vsiz);
static bool tctdbqrycondcheckstrand(const char *tval, const char *oval);
static bool tctdbqrycondcheckstror(const char *tval, const char *oval);
static bool tctdbqrycondcheckstroreq(const char *vbuf, const char *expr);
static bool tctdbqrycondchecknumbt(const char *vbuf, const char *expr);
static bool tctdbqrycondchecknumoreq(const char *vbuf, const char *expr);
static bool tctdbqrycondcheckfts(const char *vbuf, int vsiz, TDBCOND *cond);
static int tdbcmppkeynumasc(const TCLISTDATUM *a, const TCLISTDATUM *b);
static int tdbcmppkeynumdesc(const TCLISTDATUM *a, const TCLISTDATUM *b);
static int tdbcmpsortrecstrasc(const TDBSORTREC *a, const TDBSORTREC *b);
static int tdbcmpsortrecstrdesc(const TDBSORTREC *a, const TDBSORTREC *b);
static int tdbcmpsortrecnumasc(const TDBSORTREC *a, const TDBSORTREC *b);
static int tdbcmpsortrecnumdesc(const TDBSORTREC *a, const TDBSORTREC *b);
static uint16_t tctdbidxhash(const char *pkbuf, int pksiz);
static bool tctdbidxput(TCTDB *tdb, const void *pkbuf, int pksiz, TCMAP *cols);
static bool tctdbidxputone(TCTDB *tdb, TDBIDX *idx, const char *pkbuf, int pksiz, uint16_t hash,
                           const char *vbuf, int vsiz);
static bool tctdbidxputtoken(TCTDB *tdb, TDBIDX *idx, const char *pkbuf, int pksiz,
                             const char *vbuf, int vsiz);
static bool tctdbidxputqgram(TCTDB *tdb, TDBIDX *idx, const char *pkbuf, int pksiz,
                             const char *vbuf, int vsiz);
static bool tctdbidxout(TCTDB *tdb, const void *pkbuf, int pksiz, TCMAP *cols);
static bool tctdbidxoutone(TCTDB *tdb, TDBIDX *idx, const char *pkbuf, int pksiz, uint16_t hash,
                           const char *vbuf, int vsiz);
static bool tctdbidxouttoken(TCTDB *tdb, TDBIDX *idx, const char *pkbuf, int pksiz,
                             const char *vbuf, int vsiz);
static bool tctdbidxoutqgram(TCTDB *tdb, TDBIDX *idx, const char *pkbuf, int pksiz,
                             const char *vbuf, int vsiz);
static bool tctdbidxsyncicc(TCTDB *tdb, TDBIDX *idx, bool all);
static int tctdbidxcmpkey(const char **a, const char **b);
static TCMAP *tctdbidxgetbytokens(TCTDB *tdb, TDBIDX *idx, const TCLIST *tokens, int op,
                                  TCXSTR *hint);
static TCMAP *tctdbidxgetbyfts(TCTDB *tdb, TDBIDX *idx, TDBCOND *cond, TCXSTR *hint);
static void tctdbidxgetbyftsunion(TDBIDX *idx, const TCLIST *tokens, bool sign,
                                  TCMAP *ores, TCMAP *nres, TCXSTR *hint);
static int tctdbidxftscmpstrocr(TDBFTSSTROCR *a, TDBFTSSTROCR *b);
static int tctdbidxftscmpnumocr(TDBFTSNUMOCR *a, TDBFTSNUMOCR *b);
static TDBFTSUNIT *tctdbftsparseexpr(const char *expr, int esiz, int op, int *np);
static bool tctdbdefragimpl(TCTDB *tdb, int64_t step);
static bool tctdbcacheclearimpl(TCTDB *tdb);
static bool tctdbforeachimpl(TCTDB *tdb, TCITER iter, void *op);
static int tctdbqryprocoutcb(const void *pkbuf, int pksiz, TCMAP *cols, void *op);
static bool tctdblockmethod(TCTDB *tdb, bool wr);
static bool tctdbunlockmethod(TCTDB *tdb);


/* debugging function prototypes */
void tctdbprintmeta(TCTDB *tdb);



/*************************************************************************************************
 * API
 *************************************************************************************************/


/* Get the message string corresponding to an error code. */
const char *tctdberrmsg(int ecode){
  return tcerrmsg(ecode);
}


/* Create a table database object. */
TCTDB *tctdbnew(void){
  TCTDB *tdb;
  TCMALLOC(tdb, sizeof(*tdb));
  tctdbclear(tdb);
  tdb->hdb = tchdbnew();
  tchdbtune(tdb->hdb, TDBDEFBNUM, TDBDEFAPOW, TDBDEFFPOW, 0);
  tchdbsetxmsiz(tdb->hdb, TDBDEFXMSIZ);
  return tdb;
}


/* Delete a table database object. */
void tctdbdel(TCTDB *tdb){
  assert(tdb);
  if(tdb->open) tctdbclose(tdb);
  tchdbdel(tdb->hdb);
  if(tdb->mmtx){
    pthread_rwlock_destroy(tdb->mmtx);
    TCFREE(tdb->mmtx);
  }
  TCFREE(tdb);
}


/* Get the last happened error code of a table database object. */
int tctdbecode(TCTDB *tdb){
  assert(tdb);
  return tchdbecode(tdb->hdb);
}


/* Set mutual exclusion control of a table database object for threading. */
bool tctdbsetmutex(TCTDB *tdb){
  assert(tdb);
  if(!TCUSEPTHREAD) return true;
  if(tdb->mmtx || tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  TCMALLOC(tdb->mmtx, sizeof(pthread_rwlock_t));
  bool err = false;
  if(pthread_rwlock_init(tdb->mmtx, NULL) != 0) err = true;
  if(err){
    TCFREE(tdb->mmtx);
    tdb->mmtx = NULL;
    return false;
  }
  return tchdbsetmutex(tdb->hdb);
}


/* Set the tuning parameters of a table database object. */
bool tctdbtune(TCTDB *tdb, int64_t bnum, int8_t apow, int8_t fpow, uint8_t opts){
  assert(tdb);
  if(tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  tdb->opts = opts;
  uint8_t hopts = 0;
  if(opts & TDBTLARGE) hopts |= HDBTLARGE;
  if(opts & TDBTDEFLATE) hopts |= HDBTDEFLATE;
  if(opts & TDBTBZIP) hopts |= HDBTBZIP;
  if(opts & TDBTTCBS) hopts |= HDBTTCBS;
  if(opts & TDBTEXCODEC) hopts |= HDBTEXCODEC;
  bnum = (bnum > 0) ? bnum : TDBDEFBNUM;
  apow = (apow >= 0) ? apow : TDBDEFAPOW;
  fpow = (fpow >= 0) ? fpow : TDBDEFFPOW;
  return tchdbtune(tdb->hdb, bnum, apow, fpow, hopts);
}


/* Set the caching parameters of a table database object. */
bool tctdbsetcache(TCTDB *tdb, int32_t rcnum, int32_t lcnum, int32_t ncnum){
  assert(tdb);
  if(tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  if(lcnum > 0) tdb->lcnum = lcnum;
  if(ncnum > 0) tdb->ncnum = ncnum;
  return tchdbsetcache(tdb->hdb, rcnum);
}


/* Set the size of the extra mapped memory of a table database object. */
bool tctdbsetxmsiz(TCTDB *tdb, int64_t xmsiz){
  assert(tdb);
  if(tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  return tchdbsetxmsiz(tdb->hdb, xmsiz);
}


/* Set the unit step number of auto defragmentation of a table database object. */
bool tctdbsetdfunit(TCTDB *tdb, int32_t dfunit){
  assert(tdb);
  if(tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  return tchdbsetdfunit(tdb->hdb, dfunit);
}


/* Open a database file and connect a table database object. */
bool tctdbopen(TCTDB *tdb, const char *path, int omode){
  assert(tdb && path);
  if(!TDBLOCKMETHOD(tdb, true)) return false;
  if(tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return false;
  }
  bool rv = tctdbopenimpl(tdb, path, omode);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Close a table database object. */
bool tctdbclose(TCTDB *tdb){
  assert(tdb);
  if(!TDBLOCKMETHOD(tdb, true)) return false;
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return false;
  }
  bool rv = tctdbcloseimpl(tdb);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Store a record into a table database object. */
bool tctdbput(TCTDB *tdb, const void *pkbuf, int pksiz, TCMAP *cols){
  assert(tdb && pkbuf && pksiz >= 0 && cols);
  int vsiz;
  if(tcmapget(cols, "", 0, &vsiz)){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  if(!TDBLOCKMETHOD(tdb, true)) return false;
  if(!tdb->open || !tdb->wmode){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return false;
  }
  bool rv = tctdbputimpl(tdb, pkbuf, pksiz, cols, TDBPDOVER);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Store a string record into a table database object with a zero separated column string. */
bool tctdbput2(TCTDB *tdb, const void *pkbuf, int pksiz, const void *cbuf, int csiz){
  assert(tdb && pkbuf && pksiz >= 0 && cbuf && csiz >= 0);
  TCMAP *cols = tcstrsplit4(cbuf, csiz);
  bool rv = tctdbput(tdb, pkbuf, pksiz, cols);
  tcmapdel(cols);
  return rv;
}


/* Store a string record into a table database object with a tab separated column string. */
bool tctdbput3(TCTDB *tdb, const char *pkstr, const char *cstr){
  assert(tdb && pkstr && cstr);
  TCMAP *cols = tcstrsplit3(cstr, "\t");
  bool rv = tctdbput(tdb, pkstr, strlen(pkstr), cols);
  tcmapdel(cols);
  return rv;
}


/* Store a new record into a table database object. */
bool tctdbputkeep(TCTDB *tdb, const void *pkbuf, int pksiz, TCMAP *cols){
  assert(tdb && pkbuf && pksiz >= 0 && cols);
  int vsiz;
  if(tcmapget(cols, "", 0, &vsiz)){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  if(!TDBLOCKMETHOD(tdb, true)) return false;
  if(!tdb->open || !tdb->wmode){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return false;
  }
  bool rv = tctdbputimpl(tdb, pkbuf, pksiz, cols, TDBPDKEEP);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Store a new string record into a table database object with a zero separated column string. */
bool tctdbputkeep2(TCTDB *tdb, const void *pkbuf, int pksiz, const void *cbuf, int csiz){
  assert(tdb && pkbuf && pksiz >= 0 && cbuf && csiz >= 0);
  TCMAP *cols = tcstrsplit4(cbuf, csiz);
  bool rv = tctdbputkeep(tdb, pkbuf, pksiz, cols);
  tcmapdel(cols);
  return rv;
}


/* Store a new string record into a table database object with a tab separated column string. */
bool tctdbputkeep3(TCTDB *tdb, const char *pkstr, const char *cstr){
  assert(tdb && pkstr && cstr);
  TCMAP *cols = tcstrsplit3(cstr, "\t");
  bool rv = tctdbputkeep(tdb, pkstr, strlen(pkstr), cols);
  tcmapdel(cols);
  return rv;
}


/* Concatenate columns of the existing record in a table database object. */
bool tctdbputcat(TCTDB *tdb, const void *pkbuf, int pksiz, TCMAP *cols){
  assert(tdb && pkbuf && pksiz >= 0 && cols);
  int vsiz;
  if(tcmapget(cols, "", 0, &vsiz)){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  if(!TDBLOCKMETHOD(tdb, true)) return false;
  if(!tdb->open || !tdb->wmode){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return false;
  }
  bool rv = tctdbputimpl(tdb, pkbuf, pksiz, cols, TDBPDCAT);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Concatenate columns in a table database object with a zero separated column string. */
bool tctdbputcat2(TCTDB *tdb, const void *pkbuf, int pksiz, const void *cbuf, int csiz){
  assert(tdb && pkbuf && pksiz >= 0 && cbuf && csiz >= 0);
  TCMAP *cols = tcstrsplit4(cbuf, csiz);
  bool rv = tctdbputcat(tdb, pkbuf, pksiz, cols);
  tcmapdel(cols);
  return rv;
}


/* Concatenate columns in a table database object with with a tab separated column string. */
bool tctdbputcat3(TCTDB *tdb, const char *pkstr, const char *cstr){
  assert(tdb && pkstr && cstr);
  TCMAP *cols = tcstrsplit3(cstr, "\t");
  bool rv = tctdbputcat(tdb, pkstr, strlen(pkstr), cols);
  tcmapdel(cols);
  return rv;
}


/* Remove a record of a table database object. */
bool tctdbout(TCTDB *tdb, const void *pkbuf, int pksiz){
  assert(tdb && pkbuf && pksiz >= 0);
  if(!TDBLOCKMETHOD(tdb, true)) return false;
  if(!tdb->open || !tdb->wmode){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return false;
  }
  bool rv = tctdboutimpl(tdb, pkbuf, pksiz);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Remove a string record of a table database object. */
bool tctdbout2(TCTDB *tdb, const char *pkstr){
  assert(tdb && pkstr);
  return tctdbout(tdb, pkstr, strlen(pkstr));
}


/* Retrieve a record in a table database object. */
TCMAP *tctdbget(TCTDB *tdb, const void *pkbuf, int pksiz){
  assert(tdb && pkbuf && pksiz >= 0);
  if(!TDBLOCKMETHOD(tdb, false)) return NULL;
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return NULL;
  }
  TCMAP *rv = tctdbgetimpl(tdb, pkbuf, pksiz);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Retrieve a record in a table database object as a zero separated column string. */
char *tctdbget2(TCTDB *tdb, const void *pkbuf, int pksiz, int *sp){
  assert(tdb && pkbuf && pksiz >= 0 && sp);
  TCMAP *cols = tctdbget(tdb, pkbuf, pksiz);
  if(!cols) return NULL;
  char *cbuf = tcstrjoin4(cols, sp);
  tcmapdel(cols);
  return cbuf;
}


/* Retrieve a string record in a table database object as a tab separated column string. */
char *tctdbget3(TCTDB *tdb, const char *pkstr){
  assert(tdb && pkstr);
  TCMAP *cols = tctdbget(tdb, pkstr, strlen(pkstr));
  if(!cols) return NULL;
  char *cstr = tcstrjoin3(cols, '\t');
  tcmapdel(cols);
  return cstr;
}


/* Get the size of the value of a record in a table database object. */
int tctdbvsiz(TCTDB *tdb, const void *pkbuf, int pksiz){
  assert(tdb && pkbuf && pksiz >= 0);
  if(!TDBLOCKMETHOD(tdb, false)) return -1;
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return -1;
  }
  int rv = tchdbvsiz(tdb->hdb, pkbuf, pksiz);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Get the size of the value of a string record in a table database object. */
int tctdbvsiz2(TCTDB *tdb, const char *pkstr){
  assert(tdb && pkstr);
  return tctdbvsiz(tdb, pkstr, strlen(pkstr));
}


/* Initialize the iterator of a table database object. */
bool tctdbiterinit(TCTDB *tdb){
  assert(tdb);
  if(!TDBLOCKMETHOD(tdb, true)) return false;
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return false;
  }
  bool rv = tchdbiterinit(tdb->hdb);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Get the next primary key of the iterator of a table database object. */
void *tctdbiternext(TCTDB *tdb, int *sp){
  assert(tdb && sp);
  if(!TDBLOCKMETHOD(tdb, true)) return NULL;
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return NULL;
  }
  char *rv = tchdbiternext(tdb->hdb, sp);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Get the next primary key string of the iterator of a table database object. */
char *tctdbiternext2(TCTDB *tdb){
  assert(tdb);
  int pksiz;
  return tctdbiternext(tdb, &pksiz);
}


/* Get the columns of the next record of the iterator of a table database object. */
TCMAP *tctdbiternext3(TCTDB *tdb){
  assert(tdb);
  TCXSTR *kstr = tcxstrnew();
  TCXSTR *vstr = tcxstrnew();
  TCMAP *cols = NULL;
  if(tchdbiternext3(tdb->hdb, kstr, vstr)){
    cols = tcmapload(TCXSTRPTR(vstr), TCXSTRSIZE(vstr));
    tcmapput(cols, "", 0, TCXSTRPTR(kstr), TCXSTRSIZE(kstr));
  }
  tcxstrdel(vstr);
  tcxstrdel(kstr);
  return cols;
}


/* Get forward matching primary keys in a table database object. */
TCLIST *tctdbfwmkeys(TCTDB *tdb, const void *pbuf, int psiz, int max){
  assert(tdb && pbuf && psiz >= 0);
  if(!TDBLOCKMETHOD(tdb, true)) return tclistnew();
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return tclistnew();
  }
  TCLIST *rv = tchdbfwmkeys(tdb->hdb, pbuf, psiz, max);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Get forward matching string primary keys in a table database object. */
TCLIST *tctdbfwmkeys2(TCTDB *tdb, const char *pstr, int max){
  assert(tdb && pstr);
  return tctdbfwmkeys(tdb, pstr, strlen(pstr), max);
}


/* Add an integer to a column of a record in a table database object. */
int tctdbaddint(TCTDB *tdb, const void *pkbuf, int pksiz, int num){
  assert(tdb && pkbuf && pksiz >= 0);
  if(!TDBLOCKMETHOD(tdb, true)) return INT_MIN;
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return INT_MIN;
  }
  double rv = tctdbaddnumber(tdb, pkbuf, pksiz, num);
  TDBUNLOCKMETHOD(tdb);
  return isnan(rv) ? INT_MIN : (int)rv;
}


/* Add a real number to a column of a record in a table database object. */
double tctdbadddouble(TCTDB *tdb, const void *pkbuf, int pksiz, double num){
  assert(tdb && pkbuf && pksiz >= 0);
  if(!TDBLOCKMETHOD(tdb, true)) return INT_MIN;
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return INT_MIN;
  }
  double rv = tctdbaddnumber(tdb, pkbuf, pksiz, num);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Synchronize updated contents of a table database object with the file and the device. */
bool tctdbsync(TCTDB *tdb){
  assert(tdb);
  if(!TDBLOCKMETHOD(tdb, true)) return false;
  if(!tdb->open || !tdb->wmode || tdb->tran){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return false;
  }
  bool rv = tctdbmemsync(tdb, true);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Optimize the file of a table database object. */
bool tctdboptimize(TCTDB *tdb, int64_t bnum, int8_t apow, int8_t fpow, uint8_t opts){
  assert(tdb);
  if(!TDBLOCKMETHOD(tdb, true)) return false;
  if(!tdb->open || !tdb->wmode || tdb->tran){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return false;
  }
  TDBTHREADYIELD(tdb);
  bool rv = tctdboptimizeimpl(tdb, bnum, apow, fpow, opts);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Remove all records of a table database object. */
bool tctdbvanish(TCTDB *tdb){
  assert(tdb);
  if(!TDBLOCKMETHOD(tdb, true)) return false;
  if(!tdb->open || !tdb->wmode || tdb->tran){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return false;
  }
  TDBTHREADYIELD(tdb);
  bool rv = tctdbvanishimpl(tdb);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Copy the database file of a table database object. */
bool tctdbcopy(TCTDB *tdb, const char *path){
  assert(tdb && path);
  if(!TDBLOCKMETHOD(tdb, false)) return false;
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return false;
  }
  TDBTHREADYIELD(tdb);
  bool rv = tctdbcopyimpl(tdb, path);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Begin the transaction of a table database object. */
bool tctdbtranbegin(TCTDB *tdb){
  assert(tdb);
  for(double wsec = 1.0 / sysconf(_SC_CLK_TCK); true; wsec *= 2){
    if(!TDBLOCKMETHOD(tdb, true)) return false;
    if(!tdb->open || !tdb->wmode){
      tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
      TDBUNLOCKMETHOD(tdb);
      return false;
    }
    if(!tdb->tran) break;
    TDBUNLOCKMETHOD(tdb);
    if(wsec > 1.0) wsec = 1.0;
    tcsleep(wsec);
  }
  if(!tctdbtranbeginimpl(tdb)){
    TDBUNLOCKMETHOD(tdb);
    return false;
  }
  tdb->tran = true;
  TDBUNLOCKMETHOD(tdb);
  return true;
}


/* Commit the transaction of a table database object. */
bool tctdbtrancommit(TCTDB *tdb){
  assert(tdb);
  if(!TDBLOCKMETHOD(tdb, true)) return false;
  if(!tdb->open || !tdb->wmode || !tdb->tran){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return false;
  }
  tdb->tran = false;
  bool err = false;
  if(!tctdbtrancommitimpl(tdb)) err = true;
  TDBUNLOCKMETHOD(tdb);
  return !err;
}


/* Abort the transaction of a table database object. */
bool tctdbtranabort(TCTDB *tdb){
  assert(tdb);
  if(!TDBLOCKMETHOD(tdb, true)) return false;
  if(!tdb->open || !tdb->wmode || !tdb->tran){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return false;
  }
  tdb->tran = false;
  bool err = false;
  if(!tctdbtranabortimpl(tdb)) err = true;
  TDBUNLOCKMETHOD(tdb);
  return !err;
}


/* Get the file path of a table database object. */
const char *tctdbpath(TCTDB *tdb){
  assert(tdb);
  if(!TDBLOCKMETHOD(tdb, false)) return NULL;
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return NULL;
  }
  const char *rv = tchdbpath(tdb->hdb);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Get the number of records of a table database object. */
uint64_t tctdbrnum(TCTDB *tdb){
  assert(tdb);
  if(!TDBLOCKMETHOD(tdb, false)) return 0;
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return 0;
  }
  uint64_t rv = tchdbrnum(tdb->hdb);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Get the size of the database file of a table database object. */
uint64_t tctdbfsiz(TCTDB *tdb){
  assert(tdb);
  if(!TDBLOCKMETHOD(tdb, false)) return 0;
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return 0;
  }
  uint64_t rv = tchdbfsiz(tdb->hdb);
  TDBIDX *idxs = tdb->idxs;
  int inum = tdb->inum;
  for(int i = 0; i < inum; i++){
    TDBIDX *idx = idxs + i;
    switch(idx->type){
      case TDBITLEXICAL:
      case TDBITDECIMAL:
      case TDBITTOKEN:
      case TDBITQGRAM:
        rv += tcbdbfsiz(idx->db);
        break;
    }
  }
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Set a column index to a table database object. */
bool tctdbsetindex(TCTDB *tdb, const char *name, int type){
  assert(tdb && name);
  if(!TDBLOCKMETHOD(tdb, true)) return false;
  if(!tdb->open || !tdb->wmode || tdb->tran){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return false;
  }
  bool err = false;
  double iccsync = tdb->iccsync;
  tdb->iccsync = 1.0;
  if(!tctdbsetindeximpl(tdb, name, type)) err = true;
  if(!tctdbmemsync(tdb, false)) err = true;
  tdb->iccsync = iccsync;
  TDBUNLOCKMETHOD(tdb);
  return !err;
}


/* Generate a unique ID number of a table database object. */
int64_t tctdbgenuid(TCTDB *tdb){
  assert(tdb);
  if(!TDBLOCKMETHOD(tdb, true)) return -1;
  if(!tdb->open || !tdb->wmode){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return -1;
  }
  int64_t rv = tctdbgenuidimpl(tdb, 1);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Create a query object. */
TDBQRY *tctdbqrynew(TCTDB *tdb){
  assert(tdb);
  TDBQRY *qry;
  TCMALLOC(qry, sizeof(*qry));
  qry->tdb = tdb;
  TCMALLOC(qry->conds, sizeof(qry->conds[0]) * 2);
  qry->cnum = 0;
  qry->oname = NULL;
  qry->otype = TDBQOSTRASC;
  qry->max = INT_MAX;
  qry->skip = 0;
  qry->hint = tcxstrnew3(TDBHINTUSIZ);
  qry->count = 0;
  return qry;
}


/* Delete a query object. */
void tctdbqrydel(TDBQRY *qry){
  assert(qry);
  tcxstrdel(qry->hint);
  TCFREE(qry->oname);
  TDBCOND *conds = qry->conds;
  int cnum = qry->cnum;
  for(int i = 0; i < cnum; i++){
    TDBCOND *cond = conds + i;
    if(cond->ftsunits){
      TDBFTSUNIT *ftsunits = cond->ftsunits;
      int ftsnum = cond->ftsnum;
      for(int j = 0; j < ftsnum; j++){
        TDBFTSUNIT *ftsunit = ftsunits + j;
        tclistdel(ftsunit->tokens);
      }
      TCFREE(ftsunits);
    }
    if(cond->regex){
      regfree(cond->regex);
      TCFREE(cond->regex);
    }
    TCFREE(cond->expr);
    TCFREE(cond->name);
  }
  TCFREE(conds);
  TCFREE(qry);
}


/* Add a narrowing condition to a query object. */
void tctdbqryaddcond(TDBQRY *qry, const char *name, int op, const char *expr){
  assert(qry && name && expr);
  int cnum = qry->cnum;
  TCREALLOC(qry->conds, qry->conds, sizeof(qry->conds[0]) * (cnum + 1));
  TDBCOND *cond = qry->conds + cnum;
  int nsiz = strlen(name);
  int esiz = strlen(expr);
  TCMEMDUP(cond->name, name, nsiz);
  cond->nsiz = nsiz;
  bool sign = true;
  if(op & TDBQCNEGATE){
    op &= ~TDBQCNEGATE;
    sign = false;
  }
  bool noidx = false;
  if(op & TDBQCNOIDX){
    op &= ~TDBQCNOIDX;
    noidx = true;
  }
  cond->op = op;
  cond->sign = sign;
  cond->noidx = noidx;
  TCMEMDUP(cond->expr, expr, esiz);
  cond->esiz = esiz;
  cond->regex = NULL;
  if(op == TDBQCSTRRX){
    const char *rxstr = expr;
    int rxopt = REG_EXTENDED | REG_NOSUB;
    if(*rxstr == '*'){
      rxopt |= REG_ICASE;
      rxstr++;
    }
    regex_t rxbuf;
    if(regcomp(&rxbuf, rxstr, rxopt) == 0){
      TCMALLOC(cond->regex, sizeof(rxbuf));
      memcpy(cond->regex, &rxbuf, sizeof(rxbuf));
    }
  }
  cond->ftsunits = NULL;
  cond->ftsnum = 0;
  if(op >= TDBQCFTSPH && op <= TDBQCFTSEX){
    cond->op = TDBQCFTSPH;
    cond->ftsunits = tctdbftsparseexpr(expr, esiz, op, &(cond->ftsnum));
  }
  qry->cnum++;
}


/* Set the order of a query object. */
void tctdbqrysetorder(TDBQRY *qry, const char *name, int type){
  assert(qry && name);
  if(qry->oname) TCFREE(qry->oname);
  qry->oname = tcstrdup(name);
  qry->otype = type;
}


/* Set the limit number of records of the result of a query object. */
void tctdbqrysetlimit(TDBQRY *qry, int max, int skip){
  assert(qry);
  qry->max = (max >= 0) ? max : INT_MAX;
  qry->skip = (skip > 0) ? skip : 0;
}


/* Execute the search of a query object. */
TCLIST *tctdbqrysearch(TDBQRY *qry){
  assert(qry);
  TCTDB *tdb = qry->tdb;
  if(!TDBLOCKMETHOD(tdb, false)) return tclistnew();
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return tclistnew();
  }
  TCLIST *rv = tctdbqrysearchimpl(qry);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Remove each record corresponding to a query object. */
bool tctdbqrysearchout(TDBQRY *qry){
  assert(qry);
  return tctdbqryproc(qry, tctdbqryprocoutcb, NULL);
}


/* Process each record corresponding to a query object. */
bool tctdbqryproc(TDBQRY *qry, TDBQRYPROC proc, void *op){
  assert(qry && proc);
  TCTDB *tdb = qry->tdb;
  if(!TDBLOCKMETHOD(tdb, true)) return false;
  if(!tdb->open || !tdb->wmode){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return false;
  }
  bool err = false;
  int64_t getnum = 0;
  int64_t putnum = 0;
  int64_t outnum = 0;
  TCLIST *res = tctdbqrysearchimpl(qry);
  int rnum = TCLISTNUM(res);
  for(int i = 0; i < rnum; i++){
    const char *pkbuf;
    int pksiz;
    TCLISTVAL(pkbuf, res, i, pksiz);
    TCMAP *cols = tctdbgetimpl(tdb, pkbuf, pksiz);
    if(!cols){
      err = true;
      continue;
    }
    getnum++;
    int flags = proc(pkbuf, pksiz, cols, op);
    if(flags & TDBQPPUT){
      if(tctdbputimpl(tdb, pkbuf, pksiz, cols, TDBPDOVER)){
        putnum++;
      } else {
        err = true;
      }
    } else if(flags & TDBQPOUT){
      if(tctdboutimpl(tdb, pkbuf, pksiz)){
        outnum++;
      } else {
        err = true;
      }
    }
    tcmapdel(cols);
    if(flags & TDBQPSTOP) break;
  }
  tclistdel(res);
  tcxstrprintf(qry->hint, "post treatment: get=%lld, put=%lld, out=%lld\n",
               (long long)getnum, (long long)putnum, (long long)outnum);
  TDBUNLOCKMETHOD(tdb);
  return !err;
}


/* Get the hint string of a query object. */
const char *tctdbqryhint(TDBQRY *qry){
  assert(qry);
  return tcxstrptr(qry->hint);
}


/* Retrieve records with multiple query objects and get the set of the result. */
TCLIST *tctdbmetasearch(TDBQRY **qrys, int num, int type){
  assert(qrys && num >= 0);
  if(num < 1) return tclistnew2(1);
  if(num < 2) return tctdbqrysearch(qrys[0]);
  const char *oname = qrys[0]->oname;
  int onsiz = oname ? strlen(oname) : 0;
  int otype = qrys[0]->otype;
  TCMAP *uset = tcmapnew();
  if(type == TDBMSUNION){
    for(int i = 0; i < num; i++){
      TDBQRY *qry = qrys[i];
      TCTDB *tdb = qry->tdb;
      int omax = qry->max;
      int oskip = qry->skip;
      if(qry->max < INT_MAX - qry->skip) qry->max += qry->skip;
      qry->skip = 0;
      TCLIST *res = tctdbqrysearch(qry);
      qry->max = omax;
      qry->skip = oskip;
      int rnum = TCLISTNUM(res);
      for(int j = 0; j < rnum; j++){
        const char *pkbuf;
        int pksiz;
        TCLISTVAL(pkbuf, res, j, pksiz);
        if(oname){
          int csiz;
          char *cbuf = tctdbget4(tdb, pkbuf, pksiz, oname, onsiz, &csiz);
          if(cbuf){
            tcmapput4(uset, pkbuf, pksiz, "=", 1, cbuf, csiz);
            TCFREE(cbuf);
          } else {
            tcmapput(uset, pkbuf, pksiz, "*", 1);
          }
        } else {
          tcmapputkeep(uset, pkbuf, pksiz, "", 0);
        }
      }
      tclistdel(res);
    }
  } else if(type == TDBMSISECT){
    int omax = qrys[0]->max;
    int oskip = qrys[0]->skip;
    qrys[0]->max = INT_MAX;
    qrys[0]->skip = 0;
    TCLIST *res = tctdbqrysearch(qrys[0]);
    qrys[0]->max = omax;
    qrys[0]->skip = oskip;
    int rnum = TCLISTNUM(res);
    for(int i = 0; i < rnum; i++){
      const char *pkbuf;
      int pksiz;
      TCLISTVAL(pkbuf, res, i, pksiz);
      tcmapputkeep(uset, pkbuf, pksiz, "", 0);
    }
    tclistdel(res);
    for(int i = 1; i < num; i++){
      TDBQRY *qry = qrys[i];
      if(TCMAPRNUM(uset) < 1){
        tcxstrclear(qry->hint);
        tcxstrprintf(qry->hint, "omitted\n");
        continue;
      }
      omax = qry->max;
      oskip = qry->skip;
      qry->max = INT_MAX;
      qry->skip = 0;
      res = tctdbqrysearch(qry);
      qry->max = omax;
      qry->skip = oskip;
      rnum = TCLISTNUM(res);
      TCMAP *nset = tcmapnew2(tclmin(TCMAPRNUM(uset), rnum) + 1);
      for(int j = 0; j < rnum; j++){
        const char *pkbuf;
        int pksiz;
        TCLISTVAL(pkbuf, res, j, pksiz);
        int vsiz;
        if(tcmapget(uset, pkbuf, pksiz, &vsiz)) tcmapputkeep(nset, pkbuf, pksiz, "", 0);
      }
      tcmapdel(uset);
      uset = nset;
      tclistdel(res);
    }
  } else if(type == TDBMSDIFF){
    int omax = qrys[0]->max;
    int oskip = qrys[0]->skip;
    qrys[0]->max = INT_MAX;
    qrys[0]->skip = 0;
    TCLIST *res = tctdbqrysearch(qrys[0]);
    qrys[0]->max = omax;
    qrys[0]->skip = oskip;
    int rnum = TCLISTNUM(res);
    for(int i = 0; i < rnum; i++){
      const char *pkbuf;
      int pksiz;
      TCLISTVAL(pkbuf, res, i, pksiz);
      tcmapputkeep(uset, pkbuf, pksiz, "", 0);
    }
    tclistdel(res);
    for(int i = 1; i < num; i++){
      TDBQRY *qry = qrys[i];
      if(TCMAPRNUM(uset) < 1){
        tcxstrclear(qry->hint);
        tcxstrprintf(qry->hint, "omitted\n");
        continue;
      }
      omax = qry->max;
      oskip = qry->skip;
      qry->max = INT_MAX;
      qry->skip = 0;
      res = tctdbqrysearch(qry);
      qry->max = omax;
      qry->skip = oskip;
      rnum = TCLISTNUM(res);
      for(int j = 0; j < rnum; j++){
        const char *pkbuf;
        int pksiz;
        TCLISTVAL(pkbuf, res, j, pksiz);
        tcmapout(uset, pkbuf, pksiz);
      }
      tclistdel(res);
    }
  }
  int max = qrys[0]->max;
  int skip = qrys[0]->skip;
  TCLIST *res;
  if(oname && type == TDBMSUNION){
    int rnum = TCMAPRNUM(uset);
    TDBSORTREC *keys;
    TCMALLOC(keys, sizeof(*keys) * rnum + 1);
    tcmapiterinit(uset);
    const char *pkbuf;
    int pksiz;
    TDBSORTREC *key = keys;
    while((pkbuf = tcmapiternext(uset, &pksiz)) != NULL){
      int vsiz;
      const char *vbuf = tcmapiterval(pkbuf, &vsiz);
      key->kbuf = pkbuf;
      key->ksiz = pksiz;
      if(*vbuf == '='){
        key->vbuf = (char *)vbuf + 1;
        key->vsiz = vsiz - 1;
      } else {
        key->vbuf = NULL;
        key->vsiz = 0;
      }
      key++;
    }
    int (*compar)(const TDBSORTREC *a, const TDBSORTREC *b) = NULL;
    switch(otype){
      case TDBQOSTRASC:
        compar = tdbcmpsortrecstrasc;
        break;
      case TDBQOSTRDESC:
        compar = tdbcmpsortrecstrdesc;
        break;
      case TDBQONUMASC:
        compar = tdbcmpsortrecnumasc;
        break;
      case TDBQONUMDESC:
        compar = tdbcmpsortrecnumdesc;
        break;
    }
    if(compar) qsort(keys, rnum, sizeof(*keys), (int (*)(const void *, const void *))compar);
    res = tclistnew2(tclmin(rnum, max));
    for(int i = skip; max > 0 && i < rnum; i++){
      key = keys + i;
      TCLISTPUSH(res, key->kbuf, key->ksiz);
      max--;
    }
    TCFREE(keys);
  } else {
    res = tclistnew2(tclmin(tcmaprnum(uset), max));
    tcmapiterinit(uset);
    const char *pkbuf;
    int pksiz;
    while(max > 0 && (pkbuf = tcmapiternext(uset, &pksiz)) != NULL){
      if(skip > 0){
        skip--;
      } else {
        TCLISTPUSH(res, pkbuf, pksiz);
        max--;
      }
    }
  }
  tcmapdel(uset);
  TCXSTR *hint = tcxstrnew();
  for(int i = 0; i < num; i++){
    TDBQRY *qry = qrys[i];
    TCLIST *lines = tcstrsplit(tctdbqryhint(qry), "\n");
    int lnum = TCLISTNUM(lines);
    for(int j = 0; j < lnum; j++){
      const char *line = TCLISTVALPTR(lines, j);
      if(*line != 0) tcxstrprintf(hint, "[%d] %s\n", i, line);
    }
    tclistdel(lines);
    tcxstrclear(qry->hint);
    qry->count = 0;
  }
  TCXSTRCAT(qrys[0]->hint, TCXSTRPTR(hint), TCXSTRSIZE(hint));
  qrys[0]->count = TCLISTNUM(res);
  tcxstrdel(hint);
  return res;
}



/*************************************************************************************************
 * features for experts
 *************************************************************************************************/


/* Set the error code of a table database object. */
void tctdbsetecode(TCTDB *tdb, int ecode, const char *filename, int line, const char *func){
  assert(tdb && filename && line >= 1 && func);
  tchdbsetecode(tdb->hdb, ecode, filename, line, func);
}


/* Set the file descriptor for debugging output. */
void tctdbsetdbgfd(TCTDB *tdb, int fd){
  assert(tdb && fd >= 0);
  tchdbsetdbgfd(tdb->hdb, fd);
}


/* Get the file descriptor for debugging output. */
int tctdbdbgfd(TCTDB *tdb){
  assert(tdb);
  return tchdbdbgfd(tdb->hdb);
}


/* Check whether mutual exclusion control is set to a table database object. */
bool tctdbhasmutex(TCTDB *tdb){
  assert(tdb);
  return tdb->mmtx != NULL;
}


/* Synchronize updating contents on memory of a table database object. */
bool tctdbmemsync(TCTDB *tdb, bool phys){
  assert(tdb);
  if(!tdb->open || !tdb->wmode){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  bool err = false;
  if(!tchdbmemsync(tdb->hdb, phys)) err = true;
  TDBIDX *idxs = tdb->idxs;
  int inum = tdb->inum;
  for(int i = 0; i < inum; i++){
    TDBIDX *idx = idxs + i;
    switch(idx->type){
      case TDBITTOKEN:
      case TDBITQGRAM:
        if(!tctdbidxsyncicc(tdb, idx, true)) err = true;
        break;
    }
  }
  for(int i = 0; i < inum; i++){
    TDBIDX *idx = idxs + i;
    switch(idx->type){
      case TDBITLEXICAL:
      case TDBITDECIMAL:
      case TDBITTOKEN:
      case TDBITQGRAM:
        if(!tcbdbmemsync(idx->db, phys)){
          tctdbsetecode(tdb, tcbdbecode(idx->db), __FILE__, __LINE__, __func__);
          err = true;
        }
        break;
    }
  }
  return !err;
}


/* Get the number of elements of the bucket array of a table database object. */
uint64_t tctdbbnum(TCTDB *tdb){
  assert(tdb);
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return tchdbbnum(tdb->hdb);
}


/* Get the record alignment of a table database object. */
uint32_t tctdbalign(TCTDB *tdb){
  assert(tdb);
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return tchdbalign(tdb->hdb);
}


/* Get the maximum number of the free block pool of a table database object. */
uint32_t tctdbfbpmax(TCTDB *tdb){
  assert(tdb);
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return tchdbfbpmax(tdb->hdb);
}


/* Get the inode number of the database file of a table database object. */
uint64_t tctdbinode(TCTDB *tdb){
  assert(tdb);
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return tchdbinode(tdb->hdb);
}


/* Get the modification time of the database file of a table database object. */
time_t tctdbmtime(TCTDB *tdb){
  assert(tdb);
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return tchdbmtime(tdb->hdb);
}


/* Get the additional flags of a table database object. */
uint8_t tctdbflags(TCTDB *tdb){
  assert(tdb);
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return tchdbflags(tdb->hdb);
}


/* Get the options of a table database object. */
uint8_t tctdbopts(TCTDB *tdb){
  assert(tdb);
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return tdb->opts;
}


/* Get the pointer to the opaque field of a table database object. */
char *tctdbopaque(TCTDB *tdb){
  assert(tdb);
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return NULL;
  }
  return tchdbopaque(tdb->hdb) + TDBOPAQUESIZ;
}


/* Get the number of used elements of the bucket array of a table database object. */
uint64_t tctdbbnumused(TCTDB *tdb){
  assert(tdb);
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return tchdbbnumused(tdb->hdb);
}


/* Get the number of column indices of a table database object. */
int tctdbinum(TCTDB *tdb){
  assert(tdb);
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return 0;
  }
  return tdb->inum;
}


/* Get the seed of unique ID unumbers of a table database object. */
int64_t tctdbuidseed(TCTDB *tdb){
  assert(tdb);
  if(!TDBLOCKMETHOD(tdb, false)) return -1;
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return -1;
  }
  int64_t rv = tctdbgenuidimpl(tdb, 0);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Set the parameters of the inverted cache of a table database object. */
bool tctdbsetinvcache(TCTDB *tdb, int64_t iccmax, double iccsync){
  assert(tdb);
  if(tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    return false;
  }
  tdb->iccmax = (iccmax > 0) ? iccmax : TDBIDXICCMAX;
  tdb->iccsync = (iccsync > 0) ? iccsync : TDBIDXICCSYNC;
  return true;
}


/* Set the seed of unique ID unumbers of a table database object. */
bool tctdbsetuidseed(TCTDB *tdb, int64_t seed){
  assert(tdb && seed >= 0);
  if(!TDBLOCKMETHOD(tdb, true)) return -1;
  if(!tdb->open || !tdb->wmode){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return false;
  }
  tctdbgenuidimpl(tdb, -seed - 1);
  TDBUNLOCKMETHOD(tdb);
  return true;
}


/* Set the custom codec functions of a table database object. */
bool tctdbsetcodecfunc(TCTDB *tdb, TCCODEC enc, void *encop, TCCODEC dec, void *decop){
  assert(tdb && enc && dec);
  if(!TDBLOCKMETHOD(tdb, true)) return false;
  if(tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return false;
  }
  bool rv = tchdbsetcodecfunc(tdb->hdb, enc, encop, dec, decop);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Get the unit step number of auto defragmentation of a table database object. */
uint32_t tctdbdfunit(TCTDB *tdb){
  assert(tdb);
  return tchdbdfunit(tdb->hdb);
}


/* Perform dynamic defragmentation of a table database object. */
bool tctdbdefrag(TCTDB *tdb, int64_t step){
  assert(tdb);
  if(!TDBLOCKMETHOD(tdb, false)) return false;
  if(!tdb->open || !tdb->wmode){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return false;
  }
  bool rv = tctdbdefragimpl(tdb, step);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Clear the cache of a table tree database object. */
bool tctdbcacheclear(TCTDB *tdb){
  assert(tdb);
  if(!TDBLOCKMETHOD(tdb, true)) return false;
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return false;
  }
  bool rv = tctdbcacheclearimpl(tdb);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Store a record into a table database object with a duplication handler. */
bool tctdbputproc(TCTDB *tdb, const void *pkbuf, int pksiz, const void *cbuf, int csiz,
                  TCPDPROC proc, void *op){
  assert(tdb && pkbuf && pksiz >= 0 && proc);
  if(!TDBLOCKMETHOD(tdb, true)) return false;
  if(!tdb->open || !tdb->wmode){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return false;
  }
  bool err = false;
  TCMAP *cols = tctdbgetimpl(tdb, pkbuf, pksiz);
  if(cols){
    int zsiz;
    char *zbuf = tcstrjoin4(cols, &zsiz);
    int ncsiz;
    void *ncbuf = proc(zbuf, zsiz, &ncsiz, op);
    if(ncbuf == (void *)-1){
      if(!tctdboutimpl(tdb, pkbuf, pksiz)) err = true;
    } else if(ncbuf){
      TCMAP *ncols = tcstrsplit4(ncbuf, ncsiz);
      if(!tctdbputimpl(tdb, pkbuf, pksiz, ncols, TDBPDOVER)) err = true;
      tcmapdel(ncols);
      TCFREE(ncbuf);
    } else {
      tctdbsetecode(tdb, TCEKEEP, __FILE__, __LINE__, __func__);
      err = true;
    }
    TCFREE(zbuf);
    tcmapdel(cols);
  } else {
    if(cbuf){
      cols = tcstrsplit4(cbuf, csiz);
      if(!tctdbputimpl(tdb, pkbuf, pksiz, cols, TDBPDOVER)) err = true;
      tcmapdel(cols);
    } else {
      tctdbsetecode(tdb, TCENOREC, __FILE__, __LINE__, __func__);
      err = true;
    }
  }
  TDBUNLOCKMETHOD(tdb);
  return !err;
}


/* Retrieve the value of a column of a record in a table database object. */
char *tctdbget4(TCTDB *tdb, const void *pkbuf, int pksiz, const void *nbuf, int nsiz, int *sp){
  assert(tdb && pkbuf && pksiz >= 0 && nbuf && nsiz >= 0 && sp);
  if(!TDBLOCKMETHOD(tdb, false)) return NULL;
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return NULL;
  }
  char *rv = tctdbgetonecol(tdb, pkbuf, pksiz, nbuf, nsiz, sp);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Move the iterator to the record corresponding a key of a table database object. */
bool tctdbiterinit2(TCTDB *tdb, const void *pkbuf, int pksiz){
  assert(tdb && pkbuf && pksiz >= 0);
  if(!TDBLOCKMETHOD(tdb, true)) return false;
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return false;
  }
  bool rv = tchdbiterinit2(tdb->hdb, pkbuf, pksiz);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Move the iterator to the record corresponding a key string of a table database object. */
bool tctdbiterinit3(TCTDB *tdb, const char *kstr){
  assert(tdb && kstr);
  return tctdbiterinit2(tdb, kstr, strlen(kstr));
}


/* Process each record atomically of a table database object. */
bool tctdbforeach(TCTDB *tdb, TCITER iter, void *op){
  assert(tdb && iter);
  if(!TDBLOCKMETHOD(tdb, false)) return false;
  if(!tdb->open){
    tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
    TDBUNLOCKMETHOD(tdb);
    return false;
  }
  TDBTHREADYIELD(tdb);
  bool rv = tctdbforeachimpl(tdb, iter, op);
  TDBUNLOCKMETHOD(tdb);
  return rv;
}


/* Process each record corresponding to a query object with non-atomic fashion. */
bool tctdbqryproc2(TDBQRY *qry, TDBQRYPROC proc, void *op){
  assert(qry && proc);
  TCTDB *tdb = qry->tdb;
  TDBCOND *conds = qry->conds;
  int cnum = qry->cnum;
  bool err = false;
  int64_t getnum = 0;
  int64_t putnum = 0;
  int64_t outnum = 0;
  TCLIST *res = tctdbqrysearch(qry);
  int rnum = TCLISTNUM(res);
  for(int i = 0; i < rnum; i++){
    if(!TDBLOCKMETHOD(tdb, true)){
      err = true;
      break;
    }
    if(!tdb->open || !tdb->wmode){
      tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
      TDBUNLOCKMETHOD(tdb);
      err = true;
      break;
    }
    int pksiz;
    const char *pkbuf;
    TCLISTVAL(pkbuf, res, i, pksiz);
    TCMAP *cols = tctdbgetimpl(tdb, pkbuf, pksiz);
    if(cols){
      getnum++;
      bool ok = true;
      for(int j = 0; j < cnum; j++){
        TDBCOND *cond = conds + j;
        if(cond->nsiz < 1){
          if(tctdbqrycondmatch(cond, pkbuf, pksiz) != cond->sign){
            ok = false;
            break;
          }
        } else {
          int vsiz;
          const char *vbuf = tcmapget(cols, cond->name, cond->nsiz, &vsiz);
          if(vbuf){
            if(tctdbqrycondmatch(cond, vbuf, vsiz) != cond->sign){
              ok = false;
              break;
            }
          } else {
            if(cond->sign){
              ok = false;
              break;
            }
          }
        }
      }
      if(ok){
        int flags = proc(pkbuf, pksiz, cols, op);
        if(flags & TDBQPPUT){
          if(tctdbputimpl(tdb, pkbuf, pksiz, cols, TDBPDOVER)){
            putnum++;
          } else {
            err = true;
          }
        } else if(flags & TDBQPOUT){
          if(tctdboutimpl(tdb, pkbuf, pksiz)){
            outnum++;
          } else {
            err = true;
          }
        }
        if(flags & TDBQPSTOP) i = rnum;
      }
      tcmapdel(cols);
    }
    TDBUNLOCKMETHOD(tdb);
  }
  tclistdel(res);
  tcxstrprintf(qry->hint, "post treatment: get=%lld, put=%lld, out=%lld\n",
               (long long)getnum, (long long)putnum, (long long)outnum);
  return !err;
}


/* Remove each record corresponding to a query object with non-atomic fashion. */
bool tctdbqrysearchout2(TDBQRY *qry){
  assert(qry);
  return tctdbqryproc2(qry, tctdbqryprocoutcb, NULL);
}


/* Convert a string into the index type number. */
int tctdbstrtoindextype(const char *str){
  assert(str);
  int type = -1;
  int flags = 0;
  if(*str == '+'){
    flags |= TDBITKEEP;
    str++;
  }
  if(!tcstricmp(str, "LEX") || !tcstricmp(str, "LEXICAL") || !tcstricmp(str, "STR")){
    type = TDBITLEXICAL;
  } else if(!tcstricmp(str, "DEC") || !tcstricmp(str, "DECIMAL") || !tcstricmp(str, "NUM")){
    type = TDBITDECIMAL;
  } else if(!tcstricmp(str, "TOK") || !tcstricmp(str, "TOKEN")){
    type = TDBITTOKEN;
  } else if(!tcstricmp(str, "QGR") || !tcstricmp(str, "QGRAM") || !tcstricmp(str, "FTS")){
    type = TDBITQGRAM;
  } else if(!tcstricmp(str, "OPT") || !tcstricmp(str, "OPTIMIZE")){
    type = TDBITOPT;
  } else if(!tcstricmp(str, "VOID") || !tcstricmp(str, "NULL")){
    type = TDBITVOID;
  } else if(tcstrisnum(str)){
    type = tcatoi(str);
  }
  return type | flags;
}


/* Convert a string into the meta search type number. */
int tctdbstrtometasearcytype(const char *str){
  assert(str);
  int type = -1;
  if(!tcstricmp(str, "UNION") || !tcstricmp(str, "OR")){
    type = TDBMSUNION;
  } else if(!tcstricmp(str, "ISECT") || !tcstricmp(str, "INTERSECTION") ||
            !tcstricmp(str, "AND")){
    type = TDBMSISECT;
  } else if(!tcstricmp(str, "DIFF") || !tcstricmp(str, "DIFFERENCE") ||
            !tcstricmp(str, "ANDNOT") || !tcstricmp(str, "NOT")){
    type = TDBMSDIFF;
  } else if(tcstrisnum(str)){
    type = tcatoi(str);
  }
  return type;
}


/* Get the count of corresponding records of a query object. */
int tctdbqrycount(TDBQRY *qry){
  assert(qry);
  return qry->count;
}


/* Generate keyword-in-context strings from a query object. */
TCLIST *tctdbqrykwic(TDBQRY *qry, TCMAP *cols, const char *name, int width, int opts){
  assert(qry && cols && width >= 0);
  TDBCOND *conds = qry->conds;
  int cnum = qry->cnum;
  TDBCOND *cond = NULL;
  if(name){
    for(int i = 0; i < cnum; i++){
      if(!strcmp(conds[i].name, name)){
        cond = conds + i;
        break;
      }
    }
  } else if(cnum > 0){
    cond = conds;
    name = cond->name;
  }
  if(!cond) return tclistnew2(1);
  const char *str = tcmapget2(cols, name);
  if(!str) return tclistnew2(1);
  TCLIST *words;
  if(cond->op == TDBQCSTRAND || cond->op == TDBQCSTROR ||
     cond->op == TDBQCSTROREQ || cond->op == TDBQCNUMOREQ){
    words = tcstrsplit(cond->expr, " ,");
  } else if(cond->op == TDBQCFTSPH){
    TDBFTSUNIT *ftsunits = cond->ftsunits;
    int ftsnum = cond->ftsnum;
    if(ftsnum > 0){
      words = tclistnew2(ftsnum * 2 + 1);
      for(int i = 0; i < ftsnum; i++){
        if(!ftsunits[i].sign) continue;
        TCLIST *tokens = ftsunits[i].tokens;
        int tnum = TCLISTNUM(tokens);
        for(int j = 0; j < tnum; j++){
          const char *token;
          int tsiz;
          TCLISTVAL(token, tokens, j, tsiz);
          TCLISTPUSH(words, token, tsiz);
        }
      }
    } else {
      words = tclistnew2(1);
    }
  } else {
    words = tclistnew3(cond->expr, NULL);
  }
  TCLIST *texts = tcstrkwic(str, words, width, opts);
  tclistdel(words);
  return texts;
}


/* Convert a string into the query operation number. */
int tctdbqrystrtocondop(const char *str){
  assert(str);
  int op = -1;
  int flags = 0;
  if(*str == '~' || *str == '!'){
    flags |= TDBQCNEGATE;
    str++;
  }
  if(*str == '+'){
    flags |= TDBQCNOIDX;
    str++;
  }
  if(!tcstricmp(str, "STREQ") || !tcstricmp(str, "STR") || !tcstricmp(str, "EQ")){
    op = TDBQCSTREQ;
  } else if(!tcstricmp(str, "STRINC") || !tcstricmp(str, "INC")){
    op = TDBQCSTRINC;
  } else if(!tcstricmp(str, "STRBW") || !tcstricmp(str, "BW")){
    op = TDBQCSTRBW;
  } else if(!tcstricmp(str, "STREW") || !tcstricmp(str, "EW")){
    op = TDBQCSTREW;
  } else if(!tcstricmp(str, "STRAND") || !tcstricmp(str, "AND")){
    op = TDBQCSTRAND;
  } else if(!tcstricmp(str, "STROR") || !tcstricmp(str, "OR")){
    op = TDBQCSTROR;
  } else if(!tcstricmp(str, "STROREQ") || !tcstricmp(str, "OREQ")){
    op = TDBQCSTROREQ;
  } else if(!tcstricmp(str, "STRRX") || !tcstricmp(str, "RX")){
    op = TDBQCSTRRX;
  } else if(!tcstricmp(str, "NUMEQ") || !tcstricmp(str, "NUM") ||
            !tcstricmp(str, "=") || !tcstricmp(str, "==")){
    op = TDBQCNUMEQ;
  } else if(!tcstricmp(str, "NUMGT") || !tcstricmp(str, ">")){
    op = TDBQCNUMGT;
  } else if(!tcstricmp(str, "NUMGE") || !tcstricmp(str, ">=")){
    op = TDBQCNUMGE;
  } else if(!tcstricmp(str, "NUMLT") || !tcstricmp(str, "<")){
    op = TDBQCNUMLT;
  } else if(!tcstricmp(str, "NUMLE") || !tcstricmp(str, "<=")){
    op = TDBQCNUMLE;
  } else if(!tcstricmp(str, "NUMBT")){
    op = TDBQCNUMBT;
  } else if(!tcstricmp(str, "NUMOREQ")){
    op = TDBQCNUMOREQ;
  } else if(!tcstricmp(str, "FTSPH") || !tcstricmp(str, "FTS")){
    op = TDBQCFTSPH;
  } else if(!tcstricmp(str, "FTSAND")){
    op = TDBQCFTSAND;
  } else if(!tcstricmp(str, "FTSOR")){
    op = TDBQCFTSOR;
  } else if(!tcstricmp(str, "FTSEX")){
    op = TDBQCFTSEX;
  } else if(tcstrisnum(str)){
    op = tcatoi(str);
  }
  return op | flags;
}


/* Convert a string into the query order type number. */
int tctdbqrystrtoordertype(const char *str){
  assert(str);
  int type = -1;
  if(!tcstricmp(str, "STRASC") || !tcstricmp(str, "STR") || !tcstricmp(str, "ASC")){
    type = TDBQOSTRASC;
  } else if(!tcstricmp(str, "STRDESC") || !tcstricmp(str, "DESC")){
    type = TDBQOSTRDESC;
  } else if(!tcstricmp(str, "NUMASC") || !tcstricmp(str, "NUM")){
    type = TDBQONUMASC;
  } else if(!tcstricmp(str, "NUMDESC")){
    type = TDBQONUMDESC;
  } else if(tcstrisnum(str)){
    type = tcatoi(str);
  }
  return type;
}


/* Convert a string into the set operation type number. */
int tctdbmetastrtosettype(const char *str){
  assert(str);
  int type = -1;
  if(!tcstricmp(str, "UNION") || !tcstricmp(str, "CUP") || !tcstricmp(str, "+")){
    type = TDBMSUNION;
  } else if(!tcstricmp(str, "ISECT") || !tcstricmp(str, "INTERSECTION") ||
            !tcstricmp(str, "CAP") || !tcstricmp(str, "*")){
    type = TDBMSISECT;
  } else if(!tcstricmp(str, "DIFF") || !tcstricmp(str, "DIFFERENCE") ||
            !tcstricmp(str, "MINUS") || !tcstricmp(str, "-")){
    type = TDBMSDIFF;
  } else if(tcstrisnum(str)){
    type = tcatoi(str);
  }
  return type;
}



/*************************************************************************************************
 * private features
 *************************************************************************************************/


/* Clear all members.
   `tdb' specifies the table database object. */
static void tctdbclear(TCTDB *tdb){
  assert(tdb);
  tdb->mmtx = NULL;
  tdb->hdb = NULL;
  tdb->open = false;
  tdb->wmode = false;
  tdb->opts = 0;
  tdb->lcnum = TDBDEFLCNUM;
  tdb->ncnum = TDBDEFNCNUM;
  tdb->iccmax = TDBIDXICCMAX;
  tdb->iccsync = TDBIDXICCSYNC;
  tdb->idxs = NULL;
  tdb->inum = 0;
  tdb->tran = false;
}


/* Open a database file and connect a table database object.
   `tdb' specifies the table database object.
   `path' specifies the path of the internal database file.
   `omode' specifies the connection mode.
   If successful, the return value is true, else, it is false. */
static bool tctdbopenimpl(TCTDB *tdb, const char *path, int omode){
  assert(tdb && path);
  int dbgfd = tchdbdbgfd(tdb->hdb);
  TCCODEC enc, dec;
  void *encop, *decop;
  tchdbcodecfunc(tdb->hdb, &enc, &encop, &dec, &decop);
  int homode = HDBOREADER;
  int bomode = BDBOREADER;
  if(omode & TDBOWRITER){
    homode = HDBOWRITER;
    bomode = BDBOWRITER;
    if(omode & TDBOCREAT){
      homode |= HDBOCREAT;
      bomode |= BDBOCREAT;
    }
    if(omode & TDBOTRUNC){
      homode |= HDBOTRUNC;
      bomode |= BDBOTRUNC;
    }
    tdb->wmode = true;
  } else {
    tdb->wmode = false;
  }
  if(omode & TDBONOLCK){
    homode |= HDBONOLCK;
    bomode |= BDBONOLCK;
  }
  if(omode & TDBOLCKNB){
    homode |= HDBOLCKNB;
    bomode |= BDBOLCKNB;
  }
  if(omode & TDBOTSYNC){
    homode |= HDBOTSYNC;
    bomode |= BDBOTSYNC;
  }
  tchdbsettype(tdb->hdb, TCDBTTABLE);
  if(!tchdbopen(tdb->hdb, path, homode)) return false;
  char *tpath = tcsprintf("%s%c%s%c*", path, MYEXTCHR, TDBIDXSUFFIX, MYEXTCHR);
  if((omode & TDBOWRITER) && (omode & TDBOTRUNC)){
    TCLIST *paths = tcglobpat(tpath);
    int pnum = TCLISTNUM(paths);
    for(int i = 0; i < pnum; i++){
      unlink(TCLISTVALPTR(paths, i));
    }
    tclistdel(paths);
  }
  TCLIST *paths = tcglobpat(tpath);
  int pnum = TCLISTNUM(paths);
  TCMALLOC(tdb->idxs, sizeof(tdb->idxs[0]) * pnum + 1);
  TDBIDX *idxs = tdb->idxs;
  int inum = 0;
  for(int i = 0; i < pnum; i++){
    const char *ipath = TCLISTVALPTR(paths, i);
    if(!tcstrfwm(ipath, path)) continue;
    const char *rp = ipath + strlen(path);
    if(*rp != MYEXTCHR) continue;
    rp++;
    if(!tcstrfwm(rp, TDBIDXSUFFIX)) continue;
    rp += strlen(TDBIDXSUFFIX);
    if(*rp != MYEXTCHR) continue;
    rp++;
    char *stem = tcstrdup(rp);
    char *ep = strrchr(stem, MYEXTCHR);
    if(!ep) continue;
    *(ep++) = '\0';
    int nsiz;
    char *name = tcurldecode(stem, &nsiz);
    if(!strcmp(ep, "lex") || !strcmp(ep, "dec") || !strcmp(ep, "tok") || !strcmp(ep, "qgr")){
      TCBDB *bdb = tcbdbnew();
      if(dbgfd >= 0) tcbdbsetdbgfd(bdb, dbgfd);
      if(tdb->mmtx) tcbdbsetmutex(bdb);
      if(enc && dec) tcbdbsetcodecfunc(bdb, enc, encop, dec, decop);
      tcbdbsetcache(bdb, tdb->lcnum, tdb->ncnum);
      tcbdbsetxmsiz(bdb, tchdbxmsiz(tdb->hdb));
      tcbdbsetdfunit(bdb, tchdbdfunit(tdb->hdb));
      tcbdbsetlsmax(bdb, TDBIDXLSMAX);
      if(tcbdbopen(bdb, ipath, bomode)){
        idxs[inum].name = tcstrdup(name);
        idxs[inum].type = TDBITLEXICAL;
        if(!strcmp(ep, "dec")){
          idxs[inum].type = TDBITDECIMAL;
        } else if(!strcmp(ep, "tok")){
          idxs[inum].type = TDBITTOKEN;
        } else if(!strcmp(ep, "qgr")){
          idxs[inum].type = TDBITQGRAM;
        }
        idxs[inum].db = bdb;
        idxs[inum].cc = NULL;
        if(idxs[inum].type == TDBITTOKEN){
          idxs[inum].cc = tcmapnew2(TDBIDXICCBNUM);
        } else if(idxs[inum].type == TDBITQGRAM){
          idxs[inum].cc = tcmapnew2(TDBIDXICCBNUM);
        }
        inum++;
      } else {
        tcbdbdel(bdb);
      }
    }
    TCFREE(name);
    TCFREE(stem);
  }
  tclistdel(paths);
  TCFREE(tpath);
  tdb->inum = inum;
  tdb->open = true;
  uint8_t hopts = tchdbopts(tdb->hdb);
  uint8_t opts = 0;
  if(hopts & HDBTLARGE) opts |= TDBTLARGE;
  if(hopts & HDBTDEFLATE) opts |= TDBTDEFLATE;
  if(hopts & HDBTBZIP) opts |= TDBTBZIP;
  if(hopts & HDBTTCBS) opts |= TDBTTCBS;
  if(hopts & HDBTEXCODEC) opts |= TDBTEXCODEC;
  tdb->opts = opts;
  tdb->tran = false;
  return true;
}


/* Close a table database object.
   `tdb' specifies the table database object.
   If successful, the return value is true, else, it is false. */
static bool tctdbcloseimpl(TCTDB *tdb){
  assert(tdb);
  bool err = false;
  if(tdb->tran && !tctdbtranabortimpl(tdb)) err = true;
  TDBIDX *idxs = tdb->idxs;
  int inum = tdb->inum;
  for(int i = 0; i < inum; i++){
    TDBIDX *idx = idxs + i;
    switch(idx->type){
      case TDBITTOKEN:
      case TDBITQGRAM:
        if(!tctdbidxsyncicc(tdb, idx, true)) err = true;
        tcmapdel(idx->cc);
        break;
    }
  }
  for(int i = 0; i < inum; i++){
    TDBIDX *idx = idxs + i;
    switch(idx->type){
      case TDBITLEXICAL:
      case TDBITDECIMAL:
      case TDBITTOKEN:
      case TDBITQGRAM:
        if(!tcbdbclose(idx->db)){
          tctdbsetecode(tdb, tcbdbecode(idx->db), __FILE__, __LINE__, __func__);
          err = true;
        }
        tcbdbdel(idx->db);
        break;
    }
    TCFREE(idx->name);
  }
  TCFREE(idxs);
  if(!tchdbclose(tdb->hdb)) err = true;
  tdb->open = false;
  return !err;
}


/* Store a record into a table database object.
   `tdb' specifies the table database object.
   `pkbuf' specifies the pointer to the region of the primary key.
   `pksiz' specifies the size of the region of the primary key.
   `cols' specifies a map object containing columns.
   `dmode' specifies behavior when the key overlaps.
   If successful, the return value is true, else, it is false. */
static bool tctdbputimpl(TCTDB *tdb, const void *pkbuf, int pksiz, TCMAP *cols, int dmode){
  assert(tdb && pkbuf && pksiz >= 0 && cols);
  bool err = false;
  int osiz;
  char *obuf = tchdbget(tdb->hdb, pkbuf, pksiz, &osiz);
  if(obuf){
    if(dmode == TDBPDKEEP){
      tctdbsetecode(tdb, TCEKEEP, __FILE__, __LINE__, __func__);
      TCFREE(obuf);
      return false;
    }
    TCMAP *ocols = tcmapload(obuf, osiz);
    if(dmode == TDBPDCAT){
      TCMAP *ncols = tcmapnew2(TCMAPRNUM(cols) + 1);
      tcmapiterinit(cols);
      const char *kbuf;
      int ksiz;
      while((kbuf = tcmapiternext(cols, &ksiz)) != NULL){
        int vsiz;
        const char *vbuf = tcmapiterval(kbuf, &vsiz);
        if(tcmapputkeep(ocols, kbuf, ksiz, vbuf, vsiz)) tcmapput(ncols, kbuf, ksiz, vbuf, vsiz);
      }
      if(!tctdbidxput(tdb, pkbuf, pksiz, ncols)) err = true;
      tcmapdel(ncols);
      int csiz;
      char *cbuf = tcmapdump(ocols, &csiz);
      if(!tchdbput(tdb->hdb, pkbuf, pksiz, cbuf, csiz)) err = true;
      TCFREE(cbuf);
    } else {
      TCMAP *ncols = tcmapnew2(TCMAPRNUM(cols) + 1);
      tcmapiterinit(cols);
      const char *kbuf;
      int ksiz;
      while((kbuf = tcmapiternext(cols, &ksiz)) != NULL){
        int vsiz;
        const char *vbuf = tcmapiterval(kbuf, &vsiz);
        int osiz;
        const char *obuf = tcmapget(ocols, kbuf, ksiz, &osiz);
        if(obuf && osiz == vsiz && !memcmp(obuf, vbuf, osiz)){
          tcmapout(ocols, kbuf, ksiz);
        } else {
          tcmapput(ncols, kbuf, ksiz, vbuf, vsiz);
        }
      }
      if(!tctdbidxout(tdb, pkbuf, pksiz, ocols)) err = true;
      if(!tctdbidxput(tdb, pkbuf, pksiz, ncols)) err = true;
      tcmapdel(ncols);
      int csiz;
      char *cbuf = tcmapdump(cols, &csiz);
      if(!tchdbput(tdb->hdb, pkbuf, pksiz, cbuf, csiz)) err = true;
      TCFREE(cbuf);
    }
    tcmapdel(ocols);
    TCFREE(obuf);
  } else {
    if(!tctdbidxput(tdb, pkbuf, pksiz, cols)) err = true;
    int csiz;
    char *cbuf = tcmapdump(cols, &csiz);
    if(!tchdbput(tdb->hdb, pkbuf, pksiz, cbuf, csiz)) err = true;
    TCFREE(cbuf);
  }
  return !err;
}


/* Remove a record of a table database object.
   `tdb' specifies the table database object.
   `pkbuf' specifies the pointer to the region of the primary key.
   `pksiz' specifies the size of the region of the primary key.
   If successful, the return value is true, else, it is false. */
static bool tctdboutimpl(TCTDB *tdb, const char *pkbuf, int pksiz){
  assert(tdb && pkbuf && pksiz >= 0);
  int csiz;
  char *cbuf = tchdbget(tdb->hdb, pkbuf, pksiz, &csiz);
  if(!cbuf) return false;
  bool err = false;
  TCMAP *cols = tcmapload(cbuf, csiz);
  if(!tctdbidxout(tdb, pkbuf, pksiz, cols)) err = true;
  if(!tchdbout(tdb->hdb, pkbuf, pksiz)) err = true;
  tcmapdel(cols);
  TCFREE(cbuf);
  return !err;
}


/* Retrieve a record in a table database object.
   `tdb' specifies the table database object.
   `pkbuf' specifies the pointer to the region of the primary key.
   `pksiz' specifies the size of the region of the primary key.
   If successful, the return value is a map object of the columns of the corresponding record. */
static TCMAP *tctdbgetimpl(TCTDB *tdb, const void *pkbuf, int pksiz){
  assert(tdb && pkbuf && pksiz >= 0);
  int csiz;
  char *cbuf = tchdbget(tdb->hdb, pkbuf, pksiz, &csiz);
  if(!cbuf) return NULL;
  TCMAP *cols = tcmapload(cbuf, csiz);
  TCFREE(cbuf);
  return cols;
}


/* Retrieve the value of a column of a record in a table database object.
   `tdb' specifies the table database object.
   `pkbuf' specifies the pointer to the region of the primary key.
   `pksiz' specifies the size of the region of the primary key.
   `nbuf' specifies the pointer to the region of the column name.
   `nsiz' specifies the size of the region of the column name.
   `sp' specifies the pointer to the variable into which the size of the region of the return
   value is assigned.
   If successful, the return value is the pointer to the region of the value of the column of the
   corresponding record. */
static char *tctdbgetonecol(TCTDB *tdb, const void *pkbuf, int pksiz,
                            const void *nbuf, int nsiz, int *sp){
  assert(tdb && pkbuf && pksiz >= 0 && nbuf && nsiz >= 0 && sp);
  int csiz;
  char *cbuf = tchdbget(tdb->hdb, pkbuf, pksiz, &csiz);
  if(!cbuf) return NULL;
  void *rv = tcmaploadone(cbuf, csiz, nbuf, nsiz, sp);
  TCFREE(cbuf);
  return rv;
}


/* Add a real number to a column of a record in a table database object.
   `tdb' specifies the table database object.
   `pkbuf' specifies the pointer to the region of the primary key.
   `pksiz' specifies the size of the region of the primary key.
   `num' specifies the additional value.
   If successful, the return value is the summation value, else, it is Not-a-Number. */
static double tctdbaddnumber(TCTDB *tdb, const void *pkbuf, int pksiz, double num){
  assert(tdb && pkbuf && pksiz >= 0);
  int csiz;
  char *cbuf = tchdbget(tdb->hdb, pkbuf, pksiz, &csiz);
  TCMAP *cols = cbuf ? tcmapload(cbuf, csiz) : tcmapnew2(1);
  if(cbuf){
    const char *vbuf = tcmapget2(cols, TDBNUMCNTCOL);
    if(vbuf) num += tctdbatof(vbuf);
    TCFREE(cbuf);
  }
  char numbuf[TDBCOLBUFSIZ];
  int len = snprintf(numbuf, TDBCOLBUFSIZ - 1, "%f", num);
  if(len > TDBCOLBUFSIZ - 1){
    tctdbsetecode(tdb, TCEMISC, __FILE__, __LINE__, __func__);
    num = nan("");
  } else {
    while(--len > 0){
      if(numbuf[len] != '0') break;
      numbuf[len] = '\0';
    }
    if(numbuf[len] == '.') numbuf[len] = '\0';
    tcmapput2(cols, TDBNUMCNTCOL, numbuf);
    if(!tctdbputimpl(tdb, pkbuf, pksiz, cols, TDBPDOVER)) num = nan("");
  }
  tcmapdel(cols);

  return num;
}


/* Optimize the file of a table database object.
   `tdb' specifies the table database object.
   `bnum' specifies the number of elements of the bucket array.
   `apow' specifies the size of record alignment by power of 2.
   `fpow' specifies the maximum number of elements of the free block pool by power of 2.
   `opts' specifies options by bitwise-or.
   If successful, the return value is true, else, it is false. */
static bool tctdboptimizeimpl(TCTDB *tdb, int64_t bnum, int8_t apow, int8_t fpow, uint8_t opts){
  assert(tdb);
  bool err = false;
  TCHDB *hdb = tdb->hdb;
  TDBIDX *idxs = tdb->idxs;
  int inum = tdb->inum;
  for(int i = 0; i < inum; i++){
    TDBIDX *idx = idxs + i;
    switch(idx->type){
      case TDBITTOKEN:
      case TDBITQGRAM:
        tcmapclear(idx->cc);
        break;
    }
  }
  for(int i = 0; i < inum; i++){
    TDBIDX *idx = idxs + i;
    switch(idx->type){
      case TDBITLEXICAL:
      case TDBITDECIMAL:
      case TDBITTOKEN:
      case TDBITQGRAM:
        if(!tcbdbvanish(idx->db)){
          tctdbsetecode(tdb, tcbdbecode(idx->db), __FILE__, __LINE__, __func__);
          err = true;
        }
        break;
    }
  }
  const char *path = tchdbpath(tdb->hdb);
  char *tpath = tcsprintf("%s%ctmp%c%llu", path, MYEXTCHR, MYEXTCHR, tchdbinode(tdb->hdb));
  TCHDB *thdb = tchdbnew();
  tchdbsettype(thdb, TCDBTTABLE);
  int dbgfd = tchdbdbgfd(tdb->hdb);
  if(dbgfd >= 0) tchdbsetdbgfd(thdb, dbgfd);
  TCCODEC enc, dec;
  void *encop, *decop;
  tchdbcodecfunc(hdb, &enc, &encop, &dec, &decop);
  if(enc && dec) tchdbsetcodecfunc(thdb, enc, encop, dec, decop);
  if(bnum < 1) bnum = tchdbrnum(hdb) * 2 + 1;
  if(apow < 0) apow = tclog2l(tchdbalign(hdb));
  if(fpow < 0) fpow = tclog2l(tchdbfbpmax(hdb));
  if(opts == UINT8_MAX) opts = tdb->opts;
  uint8_t hopts = 0;
  if(opts & TDBTLARGE) hopts |= HDBTLARGE;
  if(opts & TDBTDEFLATE) hopts |= HDBTDEFLATE;
  if(opts & TDBTBZIP) hopts |= HDBTBZIP;
  if(opts & TDBTTCBS) hopts |= HDBTTCBS;
  if(opts & TDBTEXCODEC) hopts |= HDBTEXCODEC;
  tchdbtune(thdb, bnum, apow, fpow, hopts);
  if(tchdbopen(thdb, tpath, HDBOWRITER | HDBOCREAT | HDBOTRUNC)){
    memcpy(tchdbopaque(thdb), tchdbopaque(hdb), TDBOPAQUESIZ + TDBLEFTOPQSIZ);
    if(!tchdbiterinit(hdb)) err = true;
    TCXSTR *kxstr = tcxstrnew();
    TCXSTR *vxstr = tcxstrnew();
    while(tchdbiternext3(hdb, kxstr, vxstr)){
      TCMAP *cols = tcmapload(TCXSTRPTR(vxstr), TCXSTRSIZE(vxstr));
      if(!tctdbidxput(tdb, TCXSTRPTR(kxstr), TCXSTRSIZE(kxstr), cols)) err = true;
      tcmapdel(cols);
      if(!tchdbput(thdb, TCXSTRPTR(kxstr), TCXSTRSIZE(kxstr),
                   TCXSTRPTR(vxstr), TCXSTRSIZE(vxstr))){
        tctdbsetecode(tdb, tchdbecode(thdb), __FILE__, __LINE__, __func__);
        err = true;
      }
    }
    tcxstrdel(vxstr);
    tcxstrdel(kxstr);
    if(!tchdbclose(thdb)){
      tctdbsetecode(tdb, tchdbecode(thdb), __FILE__, __LINE__, __func__);
      err = true;
    }
    if(!err){
      if(unlink(path) == -1){
        tctdbsetecode(tdb, TCEUNLINK, __FILE__, __LINE__, __func__);
        err = true;
      }
      if(rename(tpath, path) == -1){
        tctdbsetecode(tdb, TCERENAME, __FILE__, __LINE__, __func__);
        err = true;
      }
      char *npath = tcstrdup(path);
      int omode = (tchdbomode(hdb) & ~HDBOCREAT) & ~HDBOTRUNC;
      if(!tchdbclose(hdb)) err = true;
      if(!tchdbopen(hdb, npath, omode)) err = true;
      TCFREE(npath);
    }
  } else {
    tctdbsetecode(tdb, tchdbecode(thdb), __FILE__, __LINE__, __func__);
    err = true;
  }
  tchdbdel(thdb);
  TCFREE(tpath);
  for(int i = 0; i < inum; i++){
    TDBIDX *idx = idxs + i;
    switch(idx->type){
      case TDBITTOKEN:
      case TDBITQGRAM:
        if(!tctdbidxsyncicc(tdb, idx, true)) err = true;
        break;
    }
  }
  for(int i = 0; i < inum; i++){
    TDBIDX *idx = idxs + i;
    switch(idx->type){
      case TDBITLEXICAL:
      case TDBITDECIMAL:
      case TDBITTOKEN:
      case TDBITQGRAM:
        if(!tcbdboptimize(idx->db, -1, -1, -1, -1, -1, UINT8_MAX)){
          tctdbsetecode(tdb, tcbdbecode(idx->db), __FILE__, __LINE__, __func__);
          err = true;
        }
        break;
    }
  }
  return !err;
}


/* Remove all records of a table database object.
   `tdb' specifies the table database object.
   If successful, the return value is true, else, it is false. */
static bool tctdbvanishimpl(TCTDB *tdb){
  assert(tdb);
  bool err = false;
  if(!tchdbvanish(tdb->hdb)) err = true;
  TDBIDX *idxs = tdb->idxs;
  int inum = tdb->inum;
  for(int i = 0; i < inum; i++){
    TDBIDX *idx = idxs + i;
    switch(idx->type){
      case TDBITTOKEN:
      case TDBITQGRAM:
        tcmapclear(idx->cc);
        break;
    }
  }
  for(int i = 0; i < inum; i++){
    TDBIDX *idx = idxs + i;
    switch(idx->type){
      case TDBITLEXICAL:
      case TDBITDECIMAL:
      case TDBITTOKEN:
      case TDBITQGRAM:
        if(!tcbdbvanish(idx->db)){
          tctdbsetecode(tdb, tcbdbecode(idx->db), __FILE__, __LINE__, __func__);
          err = true;
        }
        break;
    }
  }
  return !err;
}


/* Copy the database file of a table database object.
   `tdb' specifies the table database object.
   `path' specifies the path of the destination file.
   If successful, the return value is true, else, it is false. */
static bool tctdbcopyimpl(TCTDB *tdb, const char *path){
  assert(tdb);
  bool err = false;
  if(!tchdbcopy(tdb->hdb, path)) err = true;
  const char *opath = tchdbpath(tdb->hdb);
  TDBIDX *idxs = tdb->idxs;
  int inum = tdb->inum;
  for(int i = 0; i < inum; i++){
    TDBIDX *idx = idxs + i;
    switch(idx->type){
      case TDBITTOKEN:
      case TDBITQGRAM:
        if(!tctdbidxsyncicc(tdb, idx, true)) err = true;
        break;
    }
  }
  for(int i = 0; i < inum; i++){
    TDBIDX *idx = idxs + i;
    const char *ipath;
    switch(idx->type){
      case TDBITLEXICAL:
      case TDBITDECIMAL:
      case TDBITTOKEN:
      case TDBITQGRAM:
        if(*path == '@'){
          if(!tcbdbcopy(idx->db, path)){
            tctdbsetecode(tdb, tcbdbecode(idx->db), __FILE__, __LINE__, __func__);
            err = true;
          }
        } else {
          ipath = tcbdbpath(idx->db);
          if(tcstrfwm(ipath, opath)){
            char *tpath = tcsprintf("%s%s", path, ipath + strlen(opath));
            if(!tcbdbcopy(idx->db, tpath)){
              tctdbsetecode(tdb, tcbdbecode(idx->db), __FILE__, __LINE__, __func__);
              err = true;
            }
            TCFREE(tpath);
          } else {
            tctdbsetecode(tdb, TCEMISC, __FILE__, __LINE__, __func__);
            err = true;
          }
        }
        break;
    }
  }
  return !err;
}


/* Begin the transaction of a table database object.
   `tdb' specifies the table database object.
   If successful, the return value is true, else, it is false. */
static bool tctdbtranbeginimpl(TCTDB *tdb){
  assert(tdb);
  if(!tctdbmemsync(tdb, false)) return false;
  if(!tchdbtranbegin(tdb->hdb)) return false;
  bool err = false;
  TDBIDX *idxs = tdb->idxs;
  int inum = tdb->inum;
  for(int i = 0; i < inum; i++){
    TDBIDX *idx = idxs + i;
    switch(idx->type){
      case TDBITTOKEN:
      case TDBITQGRAM:
        if(!tctdbidxsyncicc(tdb, idx, true)) err = true;
        break;
    }
  }
  for(int i = 0; i < inum; i++){
    TDBIDX *idx = idxs + i;
    switch(idx->type){
      case TDBITLEXICAL:
      case TDBITDECIMAL:
      case TDBITTOKEN:
      case TDBITQGRAM:
        if(!tcbdbtranbegin(idx->db)){
          tctdbsetecode(tdb, tcbdbecode(idx->db), __FILE__, __LINE__, __func__);
          err = true;
        }
        break;
    }
  }
  return !err;
}


/* Commit the transaction of a table database object.
   `tdb' specifies the table database object.
   If successful, the return value is true, else, it is false. */
static bool tctdbtrancommitimpl(TCTDB *tdb){
  assert(tdb);
  bool err = false;
  if(!tctdbmemsync(tdb, false)) err = true;
  if(!tchdbtrancommit(tdb->hdb)) err = true;
  TDBIDX *idxs = tdb->idxs;
  int inum = tdb->inum;
  for(int i = 0; i < inum; i++){
    TDBIDX *idx = idxs + i;
    switch(idx->type){
      case TDBITTOKEN:
      case TDBITQGRAM:
        if(!tctdbidxsyncicc(tdb, idx, true)) err = true;
        break;
    }
  }
  for(int i = 0; i < inum; i++){
    TDBIDX *idx = idxs + i;
    switch(idx->type){
      case TDBITLEXICAL:
      case TDBITDECIMAL:
      case TDBITTOKEN:
      case TDBITQGRAM:
        if(!tcbdbtrancommit(idx->db)){
          tctdbsetecode(tdb, tcbdbecode(idx->db), __FILE__, __LINE__, __func__);
          err = true;
        }
        break;
    }
  }
  return !err;
}


/* Abort the transaction of a table database object.
   `tdb' specifies the table database object.
   If successful, the return value is true, else, it is false. */
static bool tctdbtranabortimpl(TCTDB *tdb){
  assert(tdb);
  bool err = false;
  if(!tchdbtranabort(tdb->hdb)) err = true;
  TDBIDX *idxs = tdb->idxs;
  int inum = tdb->inum;
  for(int i = 0; i < inum; i++){
    TDBIDX *idx = idxs + i;
    switch(idx->type){
      case TDBITTOKEN:
      case TDBITQGRAM:
        tcmapclear(idx->cc);
        break;
    }
  }
  for(int i = 0; i < inum; i++){
    TDBIDX *idx = idxs + i;
    switch(idx->type){
      case TDBITLEXICAL:
      case TDBITDECIMAL:
      case TDBITTOKEN:
      case TDBITQGRAM:
        if(!tcbdbtranabort(idx->db)){
          tctdbsetecode(tdb, tcbdbecode(idx->db), __FILE__, __LINE__, __func__);
          err = true;
        }
        break;
    }
  }
  return !err;
}


/* Set a column index to a table database object.
   `tdb' specifies the table database object. connected as a writer.
   `name' specifies the name of a column.
   `type' specifies the index type.
   If successful, the return value is true, else, it is false. */
static bool tctdbsetindeximpl(TCTDB *tdb, const char *name, int type){
  assert(tdb && name);
  bool err = false;
  bool keep = false;
  if(type & TDBITKEEP){
    type &= ~TDBITKEEP;
    keep = true;
  }
  bool done = false;
  TDBIDX *idxs = tdb->idxs;
  int inum = tdb->inum;
  for(int i = 0; i < inum; i++){
    TDBIDX *idx = idxs + i;
    const char *path;
    if(!strcmp(idx->name, name)){
      if(keep){
        tctdbsetecode(tdb, TCEKEEP, __FILE__, __LINE__, __func__);
        return false;
      }
      if(type == TDBITOPT){
        switch(idx->type){
          case TDBITTOKEN:
          case TDBITQGRAM:
            if(!tctdbidxsyncicc(tdb, idx, true)) err = true;
            break;
        }
        switch(idx->type){
          case TDBITLEXICAL:
          case TDBITDECIMAL:
          case TDBITTOKEN:
          case TDBITQGRAM:
            if(!tcbdboptimize(idx->db, -1, -1, -1, -1, -1, UINT8_MAX)){
              tctdbsetecode(tdb, tcbdbecode(idx->db), __FILE__, __LINE__, __func__);
              err = true;
            }
            break;
        }
        done = true;
        break;
      }
      switch(idx->type){
        case TDBITTOKEN:
        case TDBITQGRAM:
          tcmapdel(idx->cc);
          break;
      }
      switch(idx->type){
        case TDBITLEXICAL:
        case TDBITDECIMAL:
        case TDBITTOKEN:
        case TDBITQGRAM:
          path = tcbdbpath(idx->db);
          if(path && unlink(path)){
            tctdbsetecode(tdb, TCEUNLINK, __FILE__, __LINE__, __func__);
            err = true;
          }
          tcbdbdel(idx->db);
          break;
      }
      TCFREE(idx->name);
      tdb->inum--;
      inum = tdb->inum;
      memmove(idxs + i, idxs + i + 1, sizeof(*idxs) * (inum - i));
      done = true;
      break;
    }
  }
  if(type == TDBITOPT || type == TDBITVOID){
    if(!done){
      tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
      err = true;
    }
    return !err;
  }
  TCXSTR *pbuf = tcxstrnew();
  tcxstrprintf(pbuf, "%s%c%s%c%?", tchdbpath(tdb->hdb), MYEXTCHR, TDBIDXSUFFIX, MYEXTCHR, name);
  TCREALLOC(tdb->idxs, tdb->idxs, sizeof(tdb->idxs[0]) * (inum + 1));
  TDBIDX *idx = tdb->idxs + inum;
  int homode = tchdbomode(tdb->hdb);
  int bomode = BDBOWRITER | BDBOCREAT | BDBOTRUNC;
  if(homode & HDBONOLCK) bomode |= BDBONOLCK;
  if(homode & HDBOLCKNB) bomode |= BDBOLCKNB;
  if(homode & HDBOTSYNC) bomode |= BDBOTSYNC;
  int dbgfd = tchdbdbgfd(tdb->hdb);
  TCCODEC enc, dec;
  void *encop, *decop;
  tchdbcodecfunc(tdb->hdb, &enc, &encop, &dec, &decop);
  int64_t bbnum = (tchdbbnum(tdb->hdb) / TDBIDXLMEMB) * 4 + TDBIDXLMEMB;
  int64_t bxmsiz = tchdbxmsiz(tdb->hdb);
  uint8_t opts = tdb->opts;
  uint8_t bopts = 0;
  if(opts & TDBTLARGE) bopts |= BDBTLARGE;
  if(opts & TDBTDEFLATE) bopts |= BDBTDEFLATE;
  if(opts & TDBTBZIP) bopts |= BDBTBZIP;
  if(opts & TDBTTCBS) bopts |= BDBTTCBS;
  if(opts & TDBTEXCODEC) bopts |= BDBTEXCODEC;
  switch(type){
    case TDBITLEXICAL:
      idx->db = tcbdbnew();
      idx->name = tcstrdup(name);
      tcxstrprintf(pbuf, "%clex", MYEXTCHR);
      if(dbgfd >= 0) tcbdbsetdbgfd(idx->db, dbgfd);
      if(tdb->mmtx) tcbdbsetmutex(idx->db);
      if(enc && dec) tcbdbsetcodecfunc(idx->db, enc, encop, dec, decop);
      tcbdbtune(idx->db, TDBIDXLMEMB, TDBIDXNMEMB, bbnum, -1, -1, bopts);
      tcbdbsetcache(idx->db, tdb->lcnum, tdb->ncnum);
      tcbdbsetxmsiz(idx->db, bxmsiz);
      tcbdbsetdfunit(idx->db, tchdbdfunit(tdb->hdb));
      tcbdbsetlsmax(idx->db, TDBIDXLSMAX);
      if(!tcbdbopen(idx->db, TCXSTRPTR(pbuf), bomode)){
        tctdbsetecode(tdb, tcbdbecode(idx->db), __FILE__, __LINE__, __func__);
        err = true;
      }
      tdb->inum++;
      break;
    case TDBITDECIMAL:
      idx->db = tcbdbnew();
      idx->name = tcstrdup(name);
      tcxstrprintf(pbuf, "%cdec", MYEXTCHR);
      if(dbgfd >= 0) tcbdbsetdbgfd(idx->db, dbgfd);
      if(tdb->mmtx) tcbdbsetmutex(idx->db);
      tcbdbsetcmpfunc(idx->db, tccmpdecimal, NULL);
      if(enc && dec) tcbdbsetcodecfunc(idx->db, enc, encop, dec, decop);
      tcbdbtune(idx->db, TDBIDXLMEMB, TDBIDXNMEMB, bbnum, -1, -1, bopts);
      tcbdbsetcache(idx->db, tdb->lcnum, tdb->ncnum);
      tcbdbsetxmsiz(idx->db, bxmsiz);
      tcbdbsetdfunit(idx->db, tchdbdfunit(tdb->hdb));
      tcbdbsetlsmax(idx->db, TDBIDXLSMAX);
      if(!tcbdbopen(idx->db, TCXSTRPTR(pbuf), bomode)){
        tctdbsetecode(tdb, tcbdbecode(idx->db), __FILE__, __LINE__, __func__);
        err = true;
      }
      tdb->inum++;
      break;
    case TDBITTOKEN:
      idx->db = tcbdbnew();
      idx->cc = tcmapnew2(TDBIDXICCBNUM);
      idx->name = tcstrdup(name);
      tcxstrprintf(pbuf, "%ctok", MYEXTCHR);
      if(dbgfd >= 0) tcbdbsetdbgfd(idx->db, dbgfd);
      if(tdb->mmtx) tcbdbsetmutex(idx->db);
      if(enc && dec) tcbdbsetcodecfunc(idx->db, enc, encop, dec, decop);
      tcbdbtune(idx->db, TDBIDXLMEMB, TDBIDXNMEMB, bbnum, -1, -1, bopts);
      tcbdbsetcache(idx->db, tdb->lcnum, tdb->ncnum);
      tcbdbsetxmsiz(idx->db, bxmsiz);
      tcbdbsetdfunit(idx->db, tchdbdfunit(tdb->hdb));
      tcbdbsetlsmax(idx->db, TDBIDXLSMAX);
      if(!tcbdbopen(idx->db, TCXSTRPTR(pbuf), bomode)){
        tctdbsetecode(tdb, tcbdbecode(idx->db), __FILE__, __LINE__, __func__);
        err = true;
      }
      tdb->inum++;
      break;
    case TDBITQGRAM:
      idx->db = tcbdbnew();
      idx->cc = tcmapnew2(TDBIDXICCBNUM);
      idx->name = tcstrdup(name);
      tcxstrprintf(pbuf, "%cqgr", MYEXTCHR);
      if(dbgfd >= 0) tcbdbsetdbgfd(idx->db, dbgfd);
      if(tdb->mmtx) tcbdbsetmutex(idx->db);
      if(enc && dec) tcbdbsetcodecfunc(idx->db, enc, encop, dec, decop);
      tcbdbtune(idx->db, TDBIDXLMEMB, TDBIDXNMEMB, bbnum, -1, -1, bopts);
      tcbdbsetcache(idx->db, tdb->lcnum, tdb->ncnum);
      tcbdbsetxmsiz(idx->db, bxmsiz);
      tcbdbsetdfunit(idx->db, tchdbdfunit(tdb->hdb));
      tcbdbsetlsmax(idx->db, TDBIDXLSMAX);
      if(!tcbdbopen(idx->db, TCXSTRPTR(pbuf), bomode)){
        tctdbsetecode(tdb, tcbdbecode(idx->db), __FILE__, __LINE__, __func__);
        err = true;
      }
      tdb->inum++;
      break;
    default:
      tctdbsetecode(tdb, TCEINVALID, __FILE__, __LINE__, __func__);
      err = true;
      break;
  }
  idx->type = type;
  if(!err){
    TCHDB *hdb = tdb->hdb;
    if(!tchdbiterinit(hdb)) err = true;
    void *db = idx->db;
    TCXSTR *kxstr = tcxstrnew();
    TCXSTR *vxstr = tcxstrnew();
    int nsiz = strlen(name);
    while(tchdbiternext3(hdb, kxstr, vxstr)){
      if(nsiz < 1){
        const char *pkbuf = TCXSTRPTR(kxstr);
        int pksiz = TCXSTRSIZE(kxstr);
        switch(type){
          case TDBITLEXICAL:
          case TDBITDECIMAL:
            if(!tcbdbput(db, pkbuf, pksiz, pkbuf, pksiz)){
              tctdbsetecode(tdb, tcbdbecode(db), __FILE__, __LINE__, __func__);
              err = true;
            }
            break;
          case TDBITTOKEN:
            if(!tctdbidxputtoken(tdb, idx, pkbuf, pksiz, pkbuf, pksiz)) err = true;
            break;
          case TDBITQGRAM:
            if(!tctdbidxputqgram(tdb, idx, pkbuf, pksiz, pkbuf, pksiz)) err = true;
            break;
        }
      } else {
        const char *pkbuf = TCXSTRPTR(kxstr);
        int pksiz = TCXSTRSIZE(kxstr);
        uint16_t hash = tctdbidxhash(pkbuf, pksiz);
        int vsiz;
        char *vbuf = tcmaploadone(TCXSTRPTR(vxstr), TCXSTRSIZE(vxstr), name, nsiz, &vsiz);
        if(vbuf){
          switch(type){
            case TDBITLEXICAL:
            case TDBITDECIMAL:
              if(!tctdbidxputone(tdb, idx, pkbuf, pksiz, hash, vbuf, vsiz)) err = true;
              break;
            case TDBITTOKEN:
              if(!tctdbidxputtoken(tdb, idx, pkbuf, pksiz, vbuf, vsiz)) err = true;
              break;
            case TDBITQGRAM:
              if(!tctdbidxputqgram(tdb, idx, pkbuf, pksiz, vbuf, vsiz)) err = true;
              break;
          }
          TCFREE(vbuf);
        }
      }
    }
    tcxstrdel(vxstr);
    tcxstrdel(kxstr);
  }
  tcxstrdel(pbuf);
  return !err;
}


/* Generate a unique ID number.
   `tdb' specifies the table database object.
   `inc' specifies the increment of the seed.
   The return value is the new unique ID number or -1 on failure. */
static int64_t tctdbgenuidimpl(TCTDB *tdb, int64_t inc){
  assert(tdb);
  void *opq = tchdbopaque(tdb->hdb);
  uint64_t llnum, uid;
  if(inc < 0){
    uid = -inc - 1;
  } else {
    memcpy(&llnum, opq, sizeof(llnum));
    if(inc == 0) return TCITOHLL(llnum);
    uid = TCITOHLL(llnum) + inc;
  }
  llnum = TCITOHLL(uid);
  memcpy(opq, &llnum, sizeof(llnum));
  return uid;
}


/* Execute the search of a query object.
   `qry' specifies the query object.
   The return value is a list object of the primary keys of the corresponding records. */
static TCLIST *tctdbqrysearchimpl(TDBQRY *qry){
  assert(qry);
  TCTDB *tdb = qry->tdb;
  TCHDB *hdb = tdb->hdb;
  TDBIDX *idxs = tdb->idxs;
  int inum = tdb->inum;
  TDBCOND *conds = qry->conds;
  int cnum = qry->cnum;
  int acnum = cnum;
  int max = qry->max;
  if(max < INT_MAX - qry->skip) max += qry->skip;
  const char *oname = qry->oname;
  int otype = qry->otype;
  TCXSTR *hint = qry->hint;
  TCLIST *res = NULL;
  for(int i = 0; i < cnum; i++){
    TDBCOND *cond = conds + i;
    cond->alive = true;
  }
  tcxstrclear(hint);
  bool isord = oname != NULL;
  TDBCOND *mcond = NULL;
  TDBIDX *midx = NULL;
  TDBCOND *ncond = NULL;
  TDBIDX *nidx = NULL;
  TDBCOND *scond = NULL;
  TDBIDX *sidx = NULL;
  for(int i = 0; i < cnum; i++){
    TDBCOND *cond = conds + i;
    if(!cond->sign || cond->noidx) continue;
    for(int j = 0; j < inum; j++){
      TDBIDX *idx = idxs + j;
      if(!strcmp(cond->name, idx->name)){
        switch(idx->type){
          case TDBITLEXICAL:
            switch(cond->op){
              case TDBQCSTREQ:
              case TDBQCSTRBW:
              case TDBQCSTROREQ:
                if(!mcond){
                  mcond = cond;
                  midx = idx;
                } else if(!ncond){
                  ncond = cond;
                  nidx = idx;
                }
                break;
              default:
                if(!scond){
                  scond = cond;
                  sidx = idx;
                }
                break;
            }
            break;
          case TDBITDECIMAL:
            switch(cond->op){
              case TDBQCNUMEQ:
              case TDBQCNUMGT:
              case TDBQCNUMGE:
              case TDBQCNUMLT:
              case TDBQCNUMLE:
              case TDBQCNUMBT:
              case TDBQCNUMOREQ:
                if(!mcond){
                  mcond = cond;
                  midx = idx;
                } else if(!ncond){
                  ncond = cond;
                  nidx = idx;
                }
                break;
              default:
                if(!scond){
                  scond = cond;
                  sidx = idx;
                }
                break;
            }
            break;
          case TDBITTOKEN:
            switch(cond->op){
              case TDBQCSTRAND:
              case TDBQCSTROR:
                if(!mcond){
                  mcond = cond;
                  midx = idx;
                } else if(!ncond){
                  ncond = cond;
                  nidx = idx;
                }
                break;
            }
            break;
          case TDBITQGRAM:
            switch(cond->op){
              case TDBQCFTSPH:
                if(!mcond){
                  mcond = cond;
                  midx = idx;
                } else if(!ncond){
                  ncond = cond;
                  nidx = idx;
                }
                break;
            }
            break;
        }
      }
    }
  }
  if(mcond){
    res = tclistnew();
    mcond->alive = false;
    acnum--;
    TCMAP *nmap = NULL;
    if(ncond){
      ncond->alive = false;
      acnum--;
      nmap = tctdbqryidxfetch(qry, ncond, nidx);
      max = tclmin(max, TCMAPRNUM(nmap));
    }
    const char *expr = mcond->expr;
    int esiz = mcond->esiz;
    TDBCOND *ucond = NULL;
    for(int i = 0; i < cnum; i++){
      TDBCOND *cond = conds + i;
      if(!cond->alive) continue;
      if(ucond){
        ucond = NULL;
        break;
      }
      ucond = cond;
    }
    bool trim = *midx->name != '\0';
    if(mcond->op == TDBQCSTREQ){
      tcxstrprintf(hint, "using an index: \"%s\" asc (STREQ)\n", mcond->name);
      BDBCUR *cur = tcbdbcurnew(midx->db);
      tcbdbcurjump(cur, expr, esiz + trim);
      if(oname && !strcmp(oname, mcond->name)) oname = NULL;
      bool all = oname != NULL;
      if(!all && max < INT_MAX) tcxstrprintf(hint, "limited matching: %d\n", max);
      trim = *midx->name != '\0';
      const char *kbuf;
      int ksiz;
      while((all || TCLISTNUM(res) < max) && (kbuf = tcbdbcurkey3(cur, &ksiz)) != NULL){
        if(trim) ksiz -= 3;
        if(ksiz == esiz && !memcmp(kbuf, expr, esiz)){
          int vsiz;
          const char *vbuf = tcbdbcurval3(cur, &vsiz);
          int nsiz;
          if(!nmap || tcmapget(nmap, vbuf, vsiz, &nsiz)){
            if(acnum < 1){
              TCLISTPUSH(res, vbuf, vsiz);
            } else if(ucond){
              if(tctdbqryonecondmatch(qry, ucond, vbuf, vsiz)) TCLISTPUSH(res, vbuf, vsiz);
            } else if(tctdbqryallcondmatch(qry, vbuf, vsiz)){
              TCLISTPUSH(res, vbuf, vsiz);
            }
          }
        } else {
          break;
        }
        tcbdbcurnext(cur);
      }
      tcbdbcurdel(cur);
    } else if(mcond->op == TDBQCSTRBW){
      tcxstrprintf(hint, "using an index: \"%s\" asc (STRBW)\n", mcond->name);
      BDBCUR *cur = tcbdbcurnew(midx->db);
      tcbdbcurjump(cur, expr, esiz + trim);
      bool all = oname && (strcmp(oname, mcond->name) || otype != TDBQOSTRASC);
      if(!all && max < INT_MAX) tcxstrprintf(hint, "limited matching: %d\n", max);
      const char *kbuf;
      int ksiz;
      while((all || TCLISTNUM(res) < max) && (kbuf = tcbdbcurkey3(cur, &ksiz)) != NULL){
        if(trim) ksiz -= 3;
        if(ksiz >= esiz && !memcmp(kbuf, expr, esiz)){
          int vsiz;
          const char *vbuf = tcbdbcurval3(cur, &vsiz);
          int nsiz;
          if(!nmap || tcmapget(nmap, vbuf, vsiz, &nsiz)){
            if(acnum < 1){
              TCLISTPUSH(res, vbuf, vsiz);
            } else if(ucond){
              if(tctdbqryonecondmatch(qry, ucond, vbuf, vsiz)) TCLISTPUSH(res, vbuf, vsiz);
            } else if(tctdbqryallcondmatch(qry, vbuf, vsiz)){
              TCLISTPUSH(res, vbuf, vsiz);
            }
          }
        } else {
          break;
        }
        tcbdbcurnext(cur);
      }
      tcbdbcurdel(cur);
      if(oname && !strcmp(oname, mcond->name)){
        if(otype == TDBQOSTRASC){
          oname = NULL;
        } else if(otype == TDBQOSTRDESC){
          tclistinvert(res);
          oname = NULL;
        }
      }
    } else if(mcond->op == TDBQCSTROREQ){
      tcxstrprintf(hint, "using an index: \"%s\" skip (STROREQ)\n", mcond->name);
      BDBCUR *cur = tcbdbcurnew(midx->db);
      TCLIST *tokens = tcstrsplit(expr, "\t\n\r ,");
      tclistsort(tokens);
      for(int i = 1; i < TCLISTNUM(tokens); i++){
        if(!strcmp(TCLISTVALPTR(tokens, i), TCLISTVALPTR(tokens, i - 1))){
          TCFREE(tclistremove2(tokens, i));
          i--;
        }
      }
      if(oname && !strcmp(oname, mcond->name)){
        if(otype == TDBQOSTRASC){
          oname = NULL;
        } else if(otype == TDBQOSTRDESC){
          tclistinvert(tokens);
          oname = NULL;
        }
      }
      int tnum = TCLISTNUM(tokens);
      bool all = oname != NULL;
      if(!all && max < INT_MAX) tcxstrprintf(hint, "limited matching: %d\n", max);
      for(int i = 0; (all || TCLISTNUM(res) < max) && i < tnum; i++){
        const char *token;
        int tsiz;
        TCLISTVAL(token, tokens, i, tsiz);
        if(tsiz < 1) continue;
        tcbdbcurjump(cur, token, tsiz + trim);
        const char *kbuf;
        int ksiz;
        while((all || TCLISTNUM(res) < max) && (kbuf = tcbdbcurkey3(cur, &ksiz)) != NULL){
          if(trim) ksiz -= 3;
          if(ksiz == tsiz && !memcmp(kbuf, token, tsiz)){
            int vsiz;
            const char *vbuf = tcbdbcurval3(cur, &vsiz);
            int nsiz;
            if(!nmap || tcmapget(nmap, vbuf, vsiz, &nsiz)){
              if(acnum < 1){
                TCLISTPUSH(res, vbuf, vsiz);
              } else if(ucond){
                if(tctdbqryonecondmatch(qry, ucond, vbuf, vsiz)) TCLISTPUSH(res, vbuf, vsiz);
              } else if(tctdbqryallcondmatch(qry, vbuf, vsiz)){
                TCLISTPUSH(res, vbuf, vsiz);
              }
            }
          } else {
            break;
          }
          tcbdbcurnext(cur);
        }
      }
      tclistdel(tokens);
      tcbdbcurdel(cur);
    } else if(mcond->op == TDBQCNUMEQ){
      tcxstrprintf(hint, "using an index: \"%s\" asc (NUMEQ)\n", mcond->name);
      BDBCUR *cur = tcbdbcurnew(midx->db);
      if(oname && !strcmp(oname, mcond->name)) oname = NULL;
      long double xnum = tctdbatof(expr);
      tctdbqryidxcurjumpnum(cur, expr, esiz, true);
      bool all = oname != NULL;
      if(!all && max < INT_MAX) tcxstrprintf(hint, "limited matching: %d\n", max);
      const char *kbuf;
      int ksiz;
      while((all || TCLISTNUM(res) < max) && (kbuf = tcbdbcurkey3(cur, &ksiz)) != NULL){
        if(tctdbatof(kbuf) == xnum){
          int vsiz;
          const char *vbuf = tcbdbcurval3(cur, &vsiz);
          int nsiz;
          if(!nmap || tcmapget(nmap, vbuf, vsiz, &nsiz)){
            if(acnum < 1){
              TCLISTPUSH(res, vbuf, vsiz);
            } else if(ucond){
              if(tctdbqryonecondmatch(qry, ucond, vbuf, vsiz)) TCLISTPUSH(res, vbuf, vsiz);
            } else if(tctdbqryallcondmatch(qry, vbuf, vsiz)){
              TCLISTPUSH(res, vbuf, vsiz);
            }
          }
        } else {
          break;
        }
        tcbdbcurnext(cur);
      }
      tcbdbcurdel(cur);
    } else if(mcond->op == TDBQCNUMGT || mcond->op == TDBQCNUMGE){
      if(oname && !strcmp(oname, mcond->name) && otype == TDBQONUMDESC){
        tcxstrprintf(hint, "using an index: \"%s\" desc (NUMGT/NUMGE)\n", mcond->name);
        long double xnum = tctdbatof(expr);
        BDBCUR *cur = tcbdbcurnew(midx->db);
        tcbdbcurlast(cur);
        if(max < INT_MAX) tcxstrprintf(hint, "limited matching: %d\n", max);
        const char *kbuf;
        int ksiz;
        while(TCLISTNUM(res) < max && (kbuf = tcbdbcurkey3(cur, &ksiz)) != NULL){
          long double knum = tctdbatof(kbuf);
          if(knum < xnum) break;
          if(knum > xnum || (knum >= xnum && mcond->op == TDBQCNUMGE)){
            int vsiz;
            const char *vbuf = tcbdbcurval3(cur, &vsiz);
            int nsiz;
            if(!nmap || tcmapget(nmap, vbuf, vsiz, &nsiz)){
              if(acnum < 1){
                TCLISTPUSH(res, vbuf, vsiz);
              } else if(ucond){
                if(tctdbqryonecondmatch(qry, ucond, vbuf, vsiz)) TCLISTPUSH(res, vbuf, vsiz);
              } else if(tctdbqryallcondmatch(qry, vbuf, vsiz)){
                TCLISTPUSH(res, vbuf, vsiz);
              }
            }
          }
          tcbdbcurprev(cur);
        }
        tcbdbcurdel(cur);
        oname = NULL;
      } else {
        tcxstrprintf(hint, "using an index: \"%s\" asc (NUMGT/NUMGE)\n", mcond->name);
        long double xnum = tctdbatof(expr);
        BDBCUR *cur = tcbdbcurnew(midx->db);
        tctdbqryidxcurjumpnum(cur, expr, esiz, true);
        bool all = oname && (strcmp(oname, mcond->name) || otype != TDBQONUMASC);
        if(!all && max < INT_MAX) tcxstrprintf(hint, "limited matching: %d\n", max);
        const char *kbuf;
        int ksiz;
        while((all || TCLISTNUM(res) < max) && (kbuf = tcbdbcurkey3(cur, &ksiz)) != NULL){
          long double knum = tctdbatof(kbuf);
          if(knum > xnum || (knum >= xnum && mcond->op == TDBQCNUMGE)){
            int vsiz;
            const char *vbuf = tcbdbcurval3(cur, &vsiz);
            int nsiz;
            if(!nmap || tcmapget(nmap, vbuf, vsiz, &nsiz)){
              if(acnum < 1){
                TCLISTPUSH(res, vbuf, vsiz);
              } else if(ucond){
                if(tctdbqryonecondmatch(qry, ucond, vbuf, vsiz)) TCLISTPUSH(res, vbuf, vsiz);
              } else if(tctdbqryallcondmatch(qry, vbuf, vsiz)){
                TCLISTPUSH(res, vbuf, vsiz);
              }
            }
          }
          tcbdbcurnext(cur);
        }
        tcbdbcurdel(cur);
        if(!all) oname = NULL;
      }
    } else if(mcond->op == TDBQCNUMLT || mcond->op == TDBQCNUMLE){
      if(oname && !strcmp(oname, mcond->name) && otype == TDBQONUMASC){
        tcxstrprintf(hint, "using an index: \"%s\" asc (NUMLT/NUMLE)\n", mcond->name);
        long double xnum = tctdbatof(expr);
        BDBCUR *cur = tcbdbcurnew(midx->db);
        tcbdbcurfirst(cur);
        if(max < INT_MAX) tcxstrprintf(hint, "limited matching: %d\n", max);
        const char *kbuf;
        int ksiz;
        while(TCLISTNUM(res) < max && (kbuf = tcbdbcurkey3(cur, &ksiz)) != NULL){
          long double knum = tctdbatof(kbuf);
          if(knum > xnum) break;
          if(knum < xnum || (knum <= xnum && mcond->op == TDBQCNUMLE)){
            int vsiz;
            const char *vbuf = tcbdbcurval3(cur, &vsiz);
            int nsiz;
            if(!nmap || tcmapget(nmap, vbuf, vsiz, &nsiz)){
              if(acnum < 1){
                TCLISTPUSH(res, vbuf, vsiz);
              } else if(ucond){
                if(tctdbqryonecondmatch(qry, ucond, vbuf, vsiz)) TCLISTPUSH(res, vbuf, vsiz);
              } else if(tctdbqryallcondmatch(qry, vbuf, vsiz)){
                TCLISTPUSH(res, vbuf, vsiz);
              }
            }
          }
          tcbdbcurnext(cur);
        }
        tcbdbcurdel(cur);
        oname = NULL;
      } else {
        tcxstrprintf(hint, "using an index: \"%s\" desc (NUMLT/NUMLE)\n", mcond->name);
        long double xnum = tctdbatof(expr);
        BDBCUR *cur = tcbdbcurnew(midx->db);
        tctdbqryidxcurjumpnum(cur, expr, esiz, false);
        bool all = oname && (strcmp(oname, mcond->name) || otype != TDBQONUMDESC);
        if(!all && max < INT_MAX) tcxstrprintf(hint, "limited matching: %d\n", max);
        const char *kbuf;
        int ksiz;
        while((all || TCLISTNUM(res) < max) && (kbuf = tcbdbcurkey3(cur, &ksiz)) != NULL){
          long double knum = tctdbatof(kbuf);
          if(knum < xnum || (knum <= xnum && mcond->op == TDBQCNUMLE)){
            int vsiz;
            const char *vbuf = tcbdbcurval3(cur, &vsiz);
            int nsiz;
            if(!nmap || tcmapget(nmap, vbuf, vsiz, &nsiz)){
              if(acnum < 1){
                TCLISTPUSH(res, vbuf, vsiz);
              } else if(ucond){
                if(tctdbqryonecondmatch(qry, ucond, vbuf, vsiz)) TCLISTPUSH(res, vbuf, vsiz);
              } else if(tctdbqryallcondmatch(qry, vbuf, vsiz)){
                TCLISTPUSH(res, vbuf, vsiz);
              }
            }
          }
          tcbdbcurprev(cur);
        }
        tcbdbcurdel(cur);
        if(!all) oname = NULL;
      }
    } else if(mcond->op == TDBQCNUMBT){
      tcxstrprintf(hint, "using an index: \"%s\" asc (NUMBT)\n", mcond->name);
      while(*expr == ' ' || *expr == ','){
        expr++;
      }
      const char *pv = expr;
      while(*pv != '\0' && *pv != ' ' && *pv != ','){
        pv++;
      }
      esiz = pv - expr;
      if(*pv != ' ' && *pv != ',') pv = " ";
      pv++;
      while(*pv == ' ' || *pv == ','){
        pv++;
      }
      long double lower = tctdbatof(expr);
      long double upper = tctdbatof(pv);
      if(lower > upper){
        expr = pv;
        esiz = strlen(expr);
        long double swap = lower;
        lower = upper;
        upper = swap;
      }
      BDBCUR *cur = tcbdbcurnew(midx->db);
      tctdbqryidxcurjumpnum(cur, expr, esiz, true);
      bool all = oname && (strcmp(oname, mcond->name) || otype != TDBQONUMASC);
      if(!all && max < INT_MAX) tcxstrprintf(hint, "limited matching: %d\n", max);
      const char *kbuf;
      int ksiz;
      while((all || TCLISTNUM(res) < max) && (kbuf = tcbdbcurkey3(cur, &ksiz)) != NULL){
        if(tctdbatof(kbuf) > upper) break;
        int vsiz;
        const char *vbuf = tcbdbcurval3(cur, &vsiz);
        int nsiz;
        if(!nmap || tcmapget(nmap, vbuf, vsiz, &nsiz)){
          if(acnum < 1){
            TCLISTPUSH(res, vbuf, vsiz);
          } else if(ucond){
            if(tctdbqryonecondmatch(qry, ucond, vbuf, vsiz)) TCLISTPUSH(res, vbuf, vsiz);
          } else if(tctdbqryallcondmatch(qry, vbuf, vsiz)){
            TCLISTPUSH(res, vbuf, vsiz);
          }
        }
        tcbdbcurnext(cur);
      }
      tcbdbcurdel(cur);
      if(oname && !strcmp(oname, mcond->name)){
        if(otype == TDBQONUMASC){
          oname = NULL;
        } else if(otype == TDBQONUMDESC){
          tclistinvert(res);
          oname = NULL;
        }
      }
    } else if(mcond->op == TDBQCNUMOREQ){
      tcxstrprintf(hint, "using an index: \"%s\" skip (NUMOREQ)\n", mcond->name);
      BDBCUR *cur = tcbdbcurnew(midx->db);
      TCLIST *tokens = tcstrsplit(expr, "\t\n\r ,");
      tclistsortex(tokens, tdbcmppkeynumasc);
      for(int i = 1; i < TCLISTNUM(tokens); i++){
        if(tctdbatof(TCLISTVALPTR(tokens, i)) == tctdbatof(TCLISTVALPTR(tokens, i - 1))){
          TCFREE(tclistremove2(tokens, i));
          i--;
        }
      }
      if(oname && !strcmp(oname, mcond->name)){
        if(otype == TDBQONUMASC){
          oname = NULL;
        } else if(otype == TDBQONUMDESC){
          tclistinvert(tokens);
          oname = NULL;
        }
      }
      int tnum = TCLISTNUM(tokens);
      bool all = oname != NULL;
      if(!all && max < INT_MAX) tcxstrprintf(hint, "limited matching: %d\n", max);
      for(int i = 0; (all || TCLISTNUM(res) < max) && i < tnum; i++){
        const char *token;
        int tsiz;
        TCLISTVAL(token, tokens, i, tsiz);
        if(tsiz < 1) continue;
        long double xnum = tctdbatof(token);
        tctdbqryidxcurjumpnum(cur, token, tsiz, true);
        const char *kbuf;
        int ksiz;
        while((all || TCLISTNUM(res) < max) && (kbuf = tcbdbcurkey3(cur, &ksiz)) != NULL){
          if(tctdbatof(kbuf) == xnum){
            int vsiz;
            const char *vbuf = tcbdbcurval3(cur, &vsiz);
            int nsiz;
            if(!nmap || tcmapget(nmap, vbuf, vsiz, &nsiz)){
              if(acnum < 1){
                TCLISTPUSH(res, vbuf, vsiz);
              } else if(ucond){
                if(tctdbqryonecondmatch(qry, ucond, vbuf, vsiz)) TCLISTPUSH(res, vbuf, vsiz);
              } else if(tctdbqryallcondmatch(qry, vbuf, vsiz)){
                TCLISTPUSH(res, vbuf, vsiz);
              }
            }
          } else {
            break;
          }
          tcbdbcurnext(cur);
        }
      }
      tclistdel(tokens);
      tcbdbcurdel(cur);
    } else if(mcond->op == TDBQCSTRAND || mcond->op == TDBQCSTROR){
      tcxstrprintf(hint, "using an index: \"%s\" inverted (%s)\n",
                   mcond->name, mcond->op == TDBQCSTRAND ? "STRAND" : "STROR");
      bool all = oname != NULL;
      if(!all && max < INT_MAX) tcxstrprintf(hint, "limited matching: %d\n", max);
      TCLIST *tokens = tcstrsplit(expr, "\t\n\r ,");
      tclistsort(tokens);
      for(int i = 1; i < TCLISTNUM(tokens); i++){
        if(!strcmp(TCLISTVALPTR(tokens, i), TCLISTVALPTR(tokens, i - 1))){
          TCFREE(tclistremove2(tokens, i));
          i--;
        }
      }
      TCMAP *tres = tctdbidxgetbytokens(tdb, midx, tokens, mcond->op, hint);
      tcmapiterinit(tres);
      const char *kbuf;
      int ksiz;
      while((all || TCLISTNUM(res) < max) && (kbuf = tcmapiternext(tres, &ksiz)) != NULL){
        int nsiz;
        if(!nmap || tcmapget(nmap, kbuf, ksiz, &nsiz)){
          if(acnum < 1){
            TCLISTPUSH(res, kbuf, ksiz);
          } else if(ucond){
            if(tctdbqryonecondmatch(qry, ucond, kbuf, ksiz)) TCLISTPUSH(res, kbuf, ksiz);
          } else if(tctdbqryallcondmatch(qry, kbuf, ksiz)){
            TCLISTPUSH(res, kbuf, ksiz);
          }
        }
      }
      tcmapdel(tres);
      tclistdel(tokens);
    } else if(mcond->op == TDBQCFTSPH){
      tcxstrprintf(hint, "using an index: \"%s\" inverted (FTS)\n", mcond->name);
      TCMAP *tres = tctdbidxgetbyfts(tdb, midx, mcond, hint);
      bool all = oname != NULL;
      if(!all && max < INT_MAX) tcxstrprintf(hint, "limited matching: %d\n", max);
      tcmapiterinit(tres);
      const char *kbuf;
      int ksiz;
      while((all || TCLISTNUM(res) < max) && (kbuf = tcmapiternext(tres, &ksiz)) != NULL){
        int nsiz;
        if(!nmap || tcmapget(nmap, kbuf, ksiz, &nsiz)){
          if(acnum < 1){
            TCLISTPUSH(res, kbuf, ksiz);
          } else if(ucond){
            if(tctdbqryonecondmatch(qry, ucond, kbuf, ksiz)) TCLISTPUSH(res, kbuf, ksiz);
          } else if(tctdbqryallcondmatch(qry, kbuf, ksiz)){
            TCLISTPUSH(res, kbuf, ksiz);
          }
        }
      }
      tcmapdel(tres);
    }
    if(nmap) tcmapdel(nmap);
  }
  if(!res && scond){
    res = tclistnew();
    scond->alive = false;
    acnum--;
    TDBCOND *ucond = NULL;
    for(int i = 0; i < cnum; i++){
      TDBCOND *cond = conds + i;
      if(!cond->alive) continue;
      if(ucond){
        ucond = NULL;
        break;
      }
      ucond = cond;
    }
    bool trim = *sidx->name != '\0';
    bool asc = true;
    bool all = true;
    if(!oname){
      all = false;
    } else if(!strcmp(oname, scond->name)){
      switch(sidx->type){
        case TDBITLEXICAL:
          switch(otype){
            case TDBQOSTRASC:
              asc = true;
              all = false;
              break;
            case TDBQOSTRDESC:
              asc = false;
              all = false;
              break;
          }
          break;
        case TDBITDECIMAL:
          switch(otype){
            case TDBQONUMASC:
              asc = true;
              all = false;
              break;
            case TDBQONUMDESC:
              asc = false;
              all = false;
              break;
          }
          break;
      }
    }
    if(asc){
      tcxstrprintf(hint, "using an index: \"%s\" asc (all)\n", scond->name);
      BDBCUR *cur = tcbdbcurnew(sidx->db);
      tcbdbcurfirst(cur);
      if(!all && max < INT_MAX) tcxstrprintf(hint, "limited matching: %d\n", max);
      const char *kbuf;
      int ksiz;
      while((all || TCLISTNUM(res) < max) && (kbuf = tcbdbcurkey3(cur, &ksiz)) != NULL){
        if(trim) ksiz -= 3;
        if(ksiz < 0) break;
        if(tctdbqrycondmatch(scond, kbuf, ksiz)){
          int vsiz;
          const char *vbuf = tcbdbcurval3(cur, &vsiz);
          if(acnum < 1){
            TCLISTPUSH(res, vbuf, vsiz);
          } else if(ucond){
            if(tctdbqryonecondmatch(qry, ucond, vbuf, vsiz)) TCLISTPUSH(res, vbuf, vsiz);
          } else if(tctdbqryallcondmatch(qry, vbuf, vsiz)){
            TCLISTPUSH(res, vbuf, vsiz);
          }
        }
        tcbdbcurnext(cur);
      }
      tcbdbcurdel(cur);
    } else {
      tcxstrprintf(hint, "using an index: \"%s\" desc (all)\n", scond->name);
      BDBCUR *cur = tcbdbcurnew(sidx->db);
      tcbdbcurlast(cur);
      if(!all && max < INT_MAX) tcxstrprintf(hint, "limited matching: %d\n", max);
      const char *kbuf;
      int ksiz;
      while((all || TCLISTNUM(res) < max) && (kbuf = tcbdbcurkey3(cur, &ksiz)) != NULL){
        if(trim) ksiz -= 3;
        if(ksiz < 0) break;
        if(tctdbqrycondmatch(scond, kbuf, ksiz)){
          int vsiz;
          const char *vbuf = tcbdbcurval3(cur, &vsiz);
          if(acnum < 1){
            TCLISTPUSH(res, vbuf, vsiz);
          } else if(ucond){
            if(tctdbqryonecondmatch(qry, ucond, vbuf, vsiz)) TCLISTPUSH(res, vbuf, vsiz);
          } else if(tctdbqryallcondmatch(qry, vbuf, vsiz)){
            TCLISTPUSH(res, vbuf, vsiz);
          }
        }
        tcbdbcurprev(cur);
      }
      tcbdbcurdel(cur);
    }
    if(!all) oname = NULL;
  }
  if(!res && oname && max < tchdbrnum(hdb) * TDBORDRATIO){
    TDBIDX *oidx = NULL;
    bool asc = true;
    for(int i = 0; !oidx && i < inum; i++){
      TDBIDX *idx = idxs + i;
      if(strcmp(idx->name, oname)) continue;
      switch(idx->type){
        case TDBITLEXICAL:
          switch(otype){
            case TDBQOSTRASC:
              oidx = idx;
              asc = true;
              break;
            case TDBQOSTRDESC:
              oidx = idx;
              asc = false;
              break;
          }
          break;
        case TDBITDECIMAL:
          switch(otype){
            case TDBQONUMASC:
              oidx = idx;
              asc = true;
              break;
            case TDBQONUMDESC:
              oidx = idx;
              asc = false;
              break;
          }
          break;
      }
    }
    if(oidx){
      res = tclistnew();
      TDBCOND *ucond = NULL;
      for(int i = 0; i < cnum; i++){
        TDBCOND *cond = conds + i;
        if(!cond->alive) continue;
        if(ucond){
          ucond = NULL;
          break;
        }
        ucond = cond;
      }
      bool trim = *oidx->name != '\0';
      if(asc){
        tcxstrprintf(hint, "using an index: \"%s\" asc (order)\n", oname);
        BDBCUR *cur = tcbdbcurnew(oidx->db);
        tcbdbcurfirst(cur);
        tcxstrprintf(hint, "limited matching: %d\n", max);
        const char *kbuf;
        int ksiz;
        while(TCLISTNUM(res) < max && (kbuf = tcbdbcurkey3(cur, &ksiz)) != NULL){
          if(trim) ksiz -= 3;
          if(ksiz < 0) break;
          int vsiz;
          const char *vbuf = tcbdbcurval3(cur, &vsiz);
          if(acnum < 1){
            TCLISTPUSH(res, vbuf, vsiz);
          } else if(ucond){
            if(tctdbqryonecondmatch(qry, ucond, vbuf, vsiz)) TCLISTPUSH(res, vbuf, vsiz);
          } else if(tctdbqryallcondmatch(qry, vbuf, vsiz)){
            TCLISTPUSH(res, vbuf, vsiz);
          }
          tcbdbcurnext(cur);
        }
        tcbdbcurdel(cur);
      } else {
        tcxstrprintf(hint, "using an index: \"%s\" desc (order)\n", oname);
        BDBCUR *cur = tcbdbcurnew(oidx->db);
        tcbdbcurlast(cur);
        tcxstrprintf(hint, "limited matching: %d\n", max);
        const char *kbuf;
        int ksiz;
        while(TCLISTNUM(res) < max && (kbuf = tcbdbcurkey3(cur, &ksiz)) != NULL){
          if(trim) ksiz -= 3;
          if(ksiz < 0) break;
          int vsiz;
          const char *vbuf = tcbdbcurval3(cur, &vsiz);
          if(acnum < 1){
            TCLISTPUSH(res, vbuf, vsiz);
          } else if(ucond){
            if(tctdbqryonecondmatch(qry, ucond, vbuf, vsiz)) TCLISTPUSH(res, vbuf, vsiz);
          } else if(tctdbqryallcondmatch(qry, vbuf, vsiz)){
            TCLISTPUSH(res, vbuf, vsiz);
          }
          tcbdbcurprev(cur);
        }
        tcbdbcurdel(cur);
      }
      int rnum = TCLISTNUM(res);
      if(rnum >= max || tcbdbrnum(oidx->db) >= tchdbrnum(hdb)){
        oname = NULL;
      } else {
        tcxstrprintf(hint, "abort the result: %d\n", rnum);
        tclistdel(res);
        res = NULL;
      }
    }
  }
  if(!res){
    tcxstrprintf(hint, "scanning the whole table\n");
    res = tclistnew();
    TDBCOND *ucond = NULL;
    for(int i = 0; i < cnum; i++){
      TDBCOND *cond = conds + i;
      if(!cond->alive) continue;
      if(ucond){
        ucond = NULL;
        break;
      }
      ucond = cond;
    }
    char *lkbuf = NULL;
    int lksiz = 0;
    char *pkbuf;
    int pksiz;
    const char *cbuf;
    int csiz;
    bool all = oname != NULL;
    if(!all && max < INT_MAX) tcxstrprintf(hint, "limited matching: %d\n", max);
    while((all || TCLISTNUM(res) < max) &&
          (pkbuf = tchdbgetnext3(hdb, lkbuf, lksiz, &pksiz, &cbuf, &csiz)) != NULL){
      if(ucond){
        if(ucond->nsiz < 1){
          char *tkbuf;
          TCMEMDUP(tkbuf, pkbuf, pksiz);
          if(tctdbqrycondmatch(ucond, tkbuf, pksiz) == ucond->sign)
            TCLISTPUSH(res, pkbuf, pksiz);
          TCFREE(tkbuf);
        } else {
          int vsiz;
          char *vbuf = tcmaploadone(cbuf, csiz, ucond->name, ucond->nsiz, &vsiz);
          if(vbuf){
            if(tctdbqrycondmatch(ucond, vbuf, vsiz) == ucond->sign)
              TCLISTPUSH(res, pkbuf, pksiz);
            TCFREE(vbuf);
          } else {
            if(!ucond->sign) TCLISTPUSH(res, pkbuf, pksiz);
          }
        }
      } else {
        TCMAP *cols = tcmapload(cbuf, csiz);
        bool ok = true;
        for(int i = 0; i < cnum; i++){
          TDBCOND *cond = conds + i;
          if(cond->nsiz < 1){
            char *tkbuf;
            TCMEMDUP(tkbuf, pkbuf, pksiz);
            if(tctdbqrycondmatch(cond, tkbuf, pksiz) != cond->sign){
              TCFREE(tkbuf);
              ok = false;
              break;
            }
            TCFREE(tkbuf);
          } else {
            int vsiz;
            const char *vbuf = tcmapget(cols, cond->name, cond->nsiz, &vsiz);
            if(vbuf){
              if(tctdbqrycondmatch(cond, vbuf, vsiz) != cond->sign){
                ok = false;
                break;
              }
            } else {
              if(cond->sign){
                ok = false;
                break;
              }
            }
          }
        }
        if(ok) TCLISTPUSH(res, pkbuf, pksiz);
        tcmapdel(cols);
      }
      TCFREE(lkbuf);
      lkbuf = pkbuf;
      lksiz = pksiz;
    }
    TCFREE(lkbuf);
  }
  int rnum = TCLISTNUM(res);
  tcxstrprintf(hint, "result set size: %d\n", rnum);
  if(oname){
    if(*oname == '\0'){
      tcxstrprintf(hint, "sorting the result set: \"%s\"\n", oname);
      switch(otype){
        case TDBQOSTRASC:
          tclistsort(res);
          break;
        case TDBQOSTRDESC:
          tclistsort(res);
          tclistinvert(res);
          break;
        case TDBQONUMASC:
          tclistsortex(res, tdbcmppkeynumasc);
          break;
        case TDBQONUMDESC:
          tclistsortex(res, tdbcmppkeynumdesc);
          break;
      }
    } else {
      tcxstrprintf(hint, "sorting the result set: \"%s\"\n", oname);
      TDBSORTREC *keys;
      TCMALLOC(keys, sizeof(*keys) * rnum + 1);
      int onsiz = strlen(oname);
      for(int i = 0; i < rnum; i++){
        TDBSORTREC *key = keys + i;
        const char *kbuf;
        int ksiz;
        TCLISTVAL(kbuf, res, i, ksiz);
        char *vbuf = NULL;
        int vsiz = 0;
        int csiz;
        char *cbuf = tchdbget(hdb, kbuf, ksiz, &csiz);
        if(cbuf){
          vbuf = tcmaploadone(cbuf, csiz, oname, onsiz, &vsiz);
          TCFREE(cbuf);
        }
        key->kbuf = kbuf;
        key->ksiz = ksiz;
        key->vbuf = vbuf;
        key->vsiz = vsiz;
      }
      int (*compar)(const TDBSORTREC *a, const TDBSORTREC *b) = NULL;
      switch(otype){
        case TDBQOSTRASC:
          compar = tdbcmpsortrecstrasc;
          break;
        case TDBQOSTRDESC:
          compar = tdbcmpsortrecstrdesc;
          break;
        case TDBQONUMASC:
          compar = tdbcmpsortrecnumasc;
          break;
        case TDBQONUMDESC:
          compar = tdbcmpsortrecnumdesc;
          break;
      }
      if(compar){
        if(max <= rnum / 16){
          tctopsort(keys, rnum, sizeof(*keys), max, (int (*)(const void *, const void *))compar);
        } else {
          qsort(keys, rnum, sizeof(*keys), (int (*)(const void *, const void *))compar);
        }
      }
      TCLIST *nres = tclistnew2(rnum);
      for(int i = 0; i < rnum; i++){
        TDBSORTREC *key = keys + i;
        TCLISTPUSH(nres, key->kbuf, key->ksiz);
        TCFREE(key->vbuf);
      }
      tclistdel(res);
      res = nres;
      TCFREE(keys);
    }
  } else if(isord){
    tcxstrprintf(hint, "leaving the index order\n");
  } else {
    tcxstrprintf(hint, "leaving the natural order\n");
  }
  if(qry->skip > 0){
    int left = tclmin(TCLISTNUM(res), qry->skip);
    while(left-- > 0){
      int rsiz;
      TCFREE(tclistshift(res, &rsiz));
    }
    max -= qry->skip;
  }
  if(TCLISTNUM(res) > max){
    int left = TCLISTNUM(res) - max;
    while(left-- > 0){
      int rsiz;
      TCFREE(tclistpop(res, &rsiz));
    }
  }
  qry->count = TCLISTNUM(res);
  return res;
}


/* Fetch record keys from an index matching to a condition.
   `qry' specifies the query object.
   `cond' specifies the condition object.
   `idx' specifies an index object.
   The return value is a map object containing primary keys of the corresponding records. */
static TCMAP *tctdbqryidxfetch(TDBQRY *qry, TDBCOND *cond, TDBIDX *idx){
  assert(qry && cond && idx);
  TCTDB *tdb = qry->tdb;
  TCHDB *hdb = tdb->hdb;
  TCXSTR *hint = qry->hint;
  const char *expr = cond->expr;
  int esiz = cond->esiz;
  bool trim = *idx->name != '\0';
  TCMAP *nmap = tcmapnew2(tclmin(TDBDEFBNUM, tchdbrnum(hdb)) / 4 + 1);
  if(cond->op == TDBQCSTREQ){
    tcxstrprintf(hint, "using an auxiliary index: \"%s\" one (STREQ)\n", cond->name);
    BDBCUR *cur = tcbdbcurnew(idx->db);
    tcbdbcurjump(cur, expr, esiz + trim);
    const char *kbuf;
    int ksiz;
    while((kbuf = tcbdbcurkey3(cur, &ksiz)) != NULL){
      if(trim) ksiz -= 3;
      if(ksiz == esiz && !memcmp(kbuf, expr, esiz)){
        int vsiz;
        const char *vbuf = tcbdbcurval3(cur, &vsiz);
        tcmapputkeep(nmap, vbuf, vsiz, "", 0);
      } else {
        break;
      }
      tcbdbcurnext(cur);
    }
    tcbdbcurdel(cur);
  } else if(cond->op == TDBQCSTRBW){
    tcxstrprintf(hint, "using an auxiliary index: \"%s\" asc (STRBW)\n", cond->name);
    BDBCUR *cur = tcbdbcurnew(idx->db);
    tcbdbcurjump(cur, expr, esiz + trim);
    const char *kbuf;
    int ksiz;
    while((kbuf = tcbdbcurkey3(cur, &ksiz)) != NULL){
      if(trim) ksiz -= 3;
      if(ksiz >= esiz && !memcmp(kbuf, expr, esiz)){
        int vsiz;
        const char *vbuf = tcbdbcurval3(cur, &vsiz);
        tcmapputkeep(nmap, vbuf, vsiz, "", 0);
      } else {
        break;
      }
      tcbdbcurnext(cur);
    }
    tcbdbcurdel(cur);
  } else if(cond->op == TDBQCSTROREQ){
    tcxstrprintf(hint, "using an auxiliary index: \"%s\" skip (STROREQ)\n", cond->name);
    TCLIST *tokens = tcstrsplit(expr, "\t\n\r ,");
    tclistsort(tokens);
    for(int i = 1; i < TCLISTNUM(tokens); i++){
      if(!strcmp(TCLISTVALPTR(tokens, i), TCLISTVALPTR(tokens, i - 1))){
        TCFREE(tclistremove2(tokens, i));
        i--;
      }
    }
    int tnum = TCLISTNUM(tokens);
    for(int i = 0; i < tnum; i++){
      const char *token;
      int tsiz;
      TCLISTVAL(token, tokens, i, tsiz);
      if(tsiz < 1) continue;
      BDBCUR *cur = tcbdbcurnew(idx->db);
      tcbdbcurjump(cur, token, tsiz + trim);
      const char *kbuf;
      int ksiz;
      while((kbuf = tcbdbcurkey3(cur, &ksiz)) != NULL){
        if(trim) ksiz -= 3;
        if(ksiz == tsiz && !memcmp(kbuf, token, tsiz)){
          int vsiz;
          const char *vbuf = tcbdbcurval3(cur, &vsiz);
          tcmapputkeep(nmap, vbuf, vsiz, "", 0);
        } else {
          break;
        }
        tcbdbcurnext(cur);
      }
      tcbdbcurdel(cur);
    }
    tclistdel(tokens);
  } else if(cond->op == TDBQCNUMEQ){
    tcxstrprintf(hint, "using an auxiliary index: \"%s\" asc (NUMEQ)\n", cond->name);
    long double xnum = tctdbatof(expr);
    BDBCUR *cur = tcbdbcurnew(idx->db);
    tctdbqryidxcurjumpnum(cur, expr, esiz, true);
    const char *kbuf;
    int ksiz;
    while((kbuf = tcbdbcurkey3(cur, &ksiz)) != NULL){
      if(tctdbatof(kbuf) == xnum){
        int vsiz;
        const char *vbuf = tcbdbcurval3(cur, &vsiz);
        tcmapputkeep(nmap, vbuf, vsiz, "", 0);
      } else {
        break;
      }
      tcbdbcurnext(cur);
    }
    tcbdbcurdel(cur);
  } else if(cond->op == TDBQCNUMGT || cond->op == TDBQCNUMGE){
    tcxstrprintf(hint, "using an auxiliary index: \"%s\" asc (NUMGT/NUMGE)\n", cond->name);
    long double xnum = tctdbatof(expr);
    BDBCUR *cur = tcbdbcurnew(idx->db);
    tctdbqryidxcurjumpnum(cur, expr, esiz, true);
    const char *kbuf;
    int ksiz;
    while((kbuf = tcbdbcurkey3(cur, &ksiz)) != NULL){
      long double knum = tctdbatof(kbuf);
      if(knum > xnum || (knum >= xnum && cond->op == TDBQCNUMGE)){
        int vsiz;
        const char *vbuf = tcbdbcurval3(cur, &vsiz);
        tcmapputkeep(nmap, vbuf, vsiz, "", 0);
      }
      tcbdbcurnext(cur);
    }
    tcbdbcurdel(cur);
  } else if(cond->op == TDBQCNUMLT || cond->op == TDBQCNUMLE){
    tcxstrprintf(hint, "using an auxiliary index: \"%s\" desc (NUMLT/NUMLE)\n", cond->name);
    long double xnum = tctdbatof(expr);
    BDBCUR *cur = tcbdbcurnew(idx->db);
    tctdbqryidxcurjumpnum(cur, expr, esiz, false);
    const char *kbuf;
    int ksiz;
    while((kbuf = tcbdbcurkey3(cur, &ksiz)) != NULL){
      long double knum = tctdbatof(kbuf);
      if(knum < xnum || (knum <= xnum && cond->op == TDBQCNUMLE)){
        int vsiz;
        const char *vbuf = tcbdbcurval3(cur, &vsiz);
        tcmapputkeep(nmap, vbuf, vsiz, "", 0);
      }
      tcbdbcurprev(cur);
    }
    tcbdbcurdel(cur);
  } else if(cond->op == TDBQCNUMBT){
    tcxstrprintf(hint, "using an auxiliary index: \"%s\" asc (NUMBT)\n", cond->name);
    while(*expr == ' ' || *expr == ','){
      expr++;
    }
    const char *pv = expr;
    while(*pv != '\0' && *pv != ' ' && *pv != ','){
      pv++;
    }
    esiz = pv - expr;
    if(*pv != ' ' && *pv != ',') pv = " ";
    pv++;
    while(*pv == ' ' || *pv == ','){
      pv++;
    }
    long double lower = tctdbatof(expr);
    long double upper = tctdbatof(pv);
    if(lower > upper){
      expr = pv;
      esiz = strlen(expr);
      long double swap = lower;
      lower = upper;
      upper = swap;
    }
    BDBCUR *cur = tcbdbcurnew(idx->db);
    tctdbqryidxcurjumpnum(cur, expr, esiz, true);
    const char *kbuf;
    int ksiz;
    while((kbuf = tcbdbcurkey3(cur, &ksiz)) != NULL){
      if(tctdbatof(kbuf) > upper) break;
      int vsiz;
      const char *vbuf = tcbdbcurval3(cur, &vsiz);
      tcmapputkeep(nmap, vbuf, vsiz, "", 0);
      tcbdbcurnext(cur);
    }
    tcbdbcurdel(cur);
  } else if(cond->op == TDBQCNUMOREQ){
    tcxstrprintf(hint, "using an auxiliary index: \"%s\" skip (NUMOREQ)\n", cond->name);
    BDBCUR *cur = tcbdbcurnew(idx->db);
    TCLIST *tokens = tcstrsplit(expr, "\t\n\r ,");
    tclistsortex(tokens, tdbcmppkeynumasc);
    for(int i = 1; i < TCLISTNUM(tokens); i++){
      if(tctdbatof(TCLISTVALPTR(tokens, i)) == tctdbatof(TCLISTVALPTR(tokens, i - 1))){
        TCFREE(tclistremove2(tokens, i));
        i--;
      }
    }
    int tnum = TCLISTNUM(tokens);
    for(int i = 0; i < tnum; i++){
      const char *token;
      int tsiz;
      TCLISTVAL(token, tokens, i, tsiz);
      if(tsiz < 1) continue;
      long double xnum = tctdbatof(token);
      tctdbqryidxcurjumpnum(cur, token, tsiz, true);
      const char *kbuf;
      int ksiz;
      while((kbuf = tcbdbcurkey3(cur, &ksiz)) != NULL){
        if(tctdbatof(kbuf) == xnum){
          int vsiz;
          const char *vbuf = tcbdbcurval3(cur, &vsiz);
          tcmapputkeep(nmap, vbuf, vsiz, "", 0);
        } else {
          break;
        }
        tcbdbcurnext(cur);
      }
    }
    tclistdel(tokens);
    tcbdbcurdel(cur);
  } else if(cond->op == TDBQCSTRAND || cond->op == TDBQCSTROR){
    tcxstrprintf(hint, "using an auxiliary index: \"%s\" inverted (%s)\n",
                 cond->name, cond->op == TDBQCSTRAND ? "STRAND" : "STROR");
    TCLIST *tokens = tcstrsplit(expr, "\t\n\r ,");
    tclistsort(tokens);
    for(int i = 1; i < TCLISTNUM(tokens); i++){
      if(!strcmp(TCLISTVALPTR(tokens, i), TCLISTVALPTR(tokens, i - 1))){
        TCFREE(tclistremove2(tokens, i));
        i--;
      }
    }
    tcmapdel(nmap);
    nmap = tctdbidxgetbytokens(tdb, idx, tokens, cond->op, hint);
    tclistdel(tokens);
  } else if(cond->op == TDBQCFTSPH){
    tcxstrprintf(hint, "using an auxiliary index: \"%s\" inverted (FTS)\n", cond->name);
    tcmapdel(nmap);
    nmap = tctdbidxgetbyfts(tdb, idx, cond, hint);
  }
  tcxstrprintf(hint, "auxiliary result set size: %lld\n", (long long)TCMAPRNUM(nmap));
  return nmap;
}


/* Convert a string to a real number.
   `str' specifies the string.
   The return value is the real number. */
static long double tctdbatof(const char *str){
  assert(str);
  while(*str > '\0' && *str <= ' '){
    str++;
  }
  int sign = 1;
  if(*str == '-'){
    str++;
    sign = -1;
  } else if(*str == '+'){
    str++;
  }
  if(tcstrifwm(str, "inf")) return HUGE_VALL * sign;
  if(tcstrifwm(str, "nan")) return nanl("");
  long double num = 0;
  int col = 0;
  while(*str != '\0'){
    if(*str < '0' || *str > '9') break;
    num = num * 10 + *str - '0';
    str++;
    if(num > 0) col++;
  }
  if(*str == '.'){
    str++;
    long double fract = 0.0;
    long double base = 10;
    while(col < TDBNUMCOLMAX && *str != '\0'){
      if(*str < '0' || *str > '9') break;
      fract += (*str - '0') / base;
      str++;
      col++;
      base *= 10;
    }
    num += fract;
  }
  return num * sign;
}


/* Jump a cursor to the record of a key.
   `cur' specifies the cursor object.
   `expr' specifies the expression.
   `esiz' specifies the size of the expression.
   `first' specifies whether to jump the first candidate.
   If successful, the return value is true, else, it is false. */
static bool tctdbqryidxcurjumpnum(BDBCUR *cur, const char *expr, int esiz, bool first){
  assert(cur && expr && esiz >= 0);
  char stack[TCNUMBUFSIZ], *rbuf;
  if(esiz < sizeof(stack)){
    rbuf = stack;
  } else {
    TCMALLOC(rbuf, esiz + 1);
  }
  rbuf[0] = first ? 0x00 : 0x7f;
  memcpy(rbuf + 1, expr, esiz);
  bool err = false;
  if(first){
    if(!tcbdbcurjump(cur, rbuf, esiz + 1)) err = true;
  } else {
    if(!tcbdbcurjumpback(cur, rbuf, esiz + 1)) err = true;
  }
  if(rbuf != stack) TCFREE(rbuf);
  return !err;
}


/* Check matching of one condition and a record.
   `qry' specifies the query object.
   `cond' specifies the condition object.
   `pkbuf' specifies the pointer to the region of the primary key.
   `pksiz' specifies the size of the region of the primary key.
   If they matches, the return value is true, else it is false. */
static bool tctdbqryonecondmatch(TDBQRY *qry, TDBCOND *cond, const char *pkbuf, int pksiz){
  assert(qry && cond && pkbuf && pksiz >= 0);
  if(cond->nsiz < 1) return tctdbqrycondmatch(cond, pkbuf, pksiz) == cond->sign;
  int csiz;
  char *cbuf = tchdbget(qry->tdb->hdb, pkbuf, pksiz, &csiz);
  if(!cbuf) return false;
  bool rv;
  int vsiz;
  char *vbuf = tcmaploadone(cbuf, csiz, cond->name, cond->nsiz, &vsiz);
  if(vbuf){
    rv = tctdbqrycondmatch(cond, vbuf, vsiz) == cond->sign;
    TCFREE(vbuf);
  } else {
    rv = !cond->sign;
  }
  TCFREE(cbuf);
  return rv;
}


/* Check matching of all conditions and a record.
   `qry' specifies the query object.
   `pkbuf' specifies the pointer to the region of the primary key.
   `pksiz' specifies the size of the region of the primary key.
   If they matches, the return value is true, else it is false. */
static bool tctdbqryallcondmatch(TDBQRY *qry, const char *pkbuf, int pksiz){
  assert(qry && pkbuf && pksiz >= 0);
  TCTDB *tdb = qry->tdb;
  TDBCOND *conds = qry->conds;
  int cnum = qry->cnum;
  int csiz;
  char *cbuf = tchdbget(tdb->hdb, pkbuf, pksiz, &csiz);
  if(!cbuf) return false;
  TCMAP *cols = tcmapload(cbuf, csiz);
  bool ok = true;
  for(int i = 0; i < cnum; i++){
    TDBCOND *cond = conds + i;
    if(!cond->alive) continue;
    if(cond->nsiz < 1){
      if(tctdbqrycondmatch(cond, pkbuf, pksiz) != cond->sign){
        ok = false;
        break;
      }
    } else {
      int vsiz;
      const char *vbuf = tcmapget(cols, cond->name, cond->nsiz, &vsiz);
      if(vbuf){
        if(tctdbqrycondmatch(cond, vbuf, vsiz) != cond->sign){
          ok = false;
          break;
        }
      } else {
        if(cond->sign){
          ok = false;
          break;
        }
      }
    }
  }
  tcmapdel(cols);
  TCFREE(cbuf);
  return ok;
}


/* Check matching of a operand expression and a column value.
   `cond' specifies the condition object.
   `vbuf' specifies the column value.
   `vsiz' specifies the size of the column value.
   If they matches, the return value is true, else it is false. */
static bool tctdbqrycondmatch(TDBCOND *cond, const char *vbuf, int vsiz){
  assert(cond && vbuf && vsiz >= 0);
  bool hit = false;
  switch(cond->op){
    case TDBQCSTREQ:
      hit = vsiz == cond->esiz && !memcmp(vbuf, cond->expr, cond->esiz);
      break;
    case TDBQCSTRINC:
      hit = strstr(vbuf, cond->expr) != NULL;
      break;
    case TDBQCSTRBW:
      hit = tcstrfwm(vbuf, cond->expr);
      break;
    case TDBQCSTREW:
      hit = tcstrbwm(vbuf, cond->expr);
      break;
    case TDBQCSTRAND:
      hit = tctdbqrycondcheckstrand(vbuf, cond->expr);
      break;
    case TDBQCSTROR:
      hit = tctdbqrycondcheckstror(vbuf, cond->expr);
      break;
    case TDBQCSTROREQ:
      hit = tctdbqrycondcheckstroreq(vbuf, cond->expr);
      break;
    case TDBQCSTRRX:
      hit = cond->regex && regexec(cond->regex, vbuf, 0, NULL, 0) == 0;
      break;
    case TDBQCNUMEQ:
      hit = tctdbatof(vbuf) == tctdbatof(cond->expr);
      break;
    case TDBQCNUMGT:
      hit = tctdbatof(vbuf) > tctdbatof(cond->expr);
      break;
    case TDBQCNUMGE:
      hit = tctdbatof(vbuf) >= tctdbatof(cond->expr);
      break;
    case TDBQCNUMLT:
      hit = tctdbatof(vbuf) < tctdbatof(cond->expr);
      break;
    case TDBQCNUMLE:
      hit = tctdbatof(vbuf) <= tctdbatof(cond->expr);
      break;
    case TDBQCNUMBT:
      hit = tctdbqrycondchecknumbt(vbuf, cond->expr);
      break;
    case TDBQCNUMOREQ:
      hit = tctdbqrycondchecknumoreq(vbuf, cond->expr);
      break;
    case TDBQCFTSPH:
      hit = tctdbqrycondcheckfts(vbuf, vsiz, cond);
      break;
  }
  return hit;
}


/* Check whether a string includes all tokens in another string.
   `vbuf' specifies the column value.
   `expr' specifies the operand expression.
   If they matches, the return value is true, else it is false. */
static bool tctdbqrycondcheckstrand(const char *vbuf, const char *expr){
  assert(vbuf && expr);
  const unsigned char *sp = (unsigned char *)expr;
  while(*sp != '\0'){
    while((*sp != '\0' && *sp <= ' ') || *sp == ','){
      sp++;
    }
    const unsigned char *ep = sp;
    while(*ep > ' ' && *ep != ','){
      ep++;
    }
    if(ep > sp){
      bool hit = false;
      const unsigned char *rp = (unsigned char *)vbuf;
      while(*rp != '\0'){
        const unsigned char *pp;
        for(pp = sp; pp < ep; pp++, rp++){
          if(*pp != *rp) break;
        }
        if(pp == ep && (*rp <= ' ' || *rp == ',')){
          hit = true;
          break;
        }
        while(*rp > ' ' && *rp != ','){
          rp++;
        }
        while((*rp != '\0' && *rp <= ' ') || *rp == ','){
          rp++;
        }
      }
      if(!hit) return false;
    }
    sp = ep;
  }
  return true;
}


/* Check whether a string includes at least one token in another string.
   `vbuf' specifies the target value.
   `expr' specifies the operation value.
   If they matches, the return value is true, else it is false. */
static bool tctdbqrycondcheckstror(const char *vbuf, const char *expr){
  assert(vbuf && expr);
  const unsigned char *sp = (unsigned char *)expr;
  while(*sp != '\0'){
    while((*sp != '\0' && *sp <= ' ') || *sp == ','){
      sp++;
    }
    const unsigned char *ep = sp;
    while(*ep > ' ' && *ep != ','){
      ep++;
    }
    if(ep > sp){
      bool hit = false;
      const unsigned char *rp = (unsigned char *)vbuf;
      while(*rp != '\0'){
        const unsigned char *pp;
        for(pp = sp; pp < ep; pp++, rp++){
          if(*pp != *rp) break;
        }
        if(pp == ep && (*rp <= ' ' || *rp == ',')){
          hit = true;
          break;
        }
        while(*rp > ' ' && *rp != ','){
          rp++;
        }
        while((*rp != '\0' && *rp <= ' ') || *rp == ','){
          rp++;
        }
      }
      if(hit) return true;
    }
    sp = ep;
  }
  return false;
}


/* Check whether a string is equal to at least one token in another string.
   `vbuf' specifies the target value.
   `expr' specifies the operation value.
   If they matches, the return value is true, else it is false. */
static bool tctdbqrycondcheckstroreq(const char *vbuf, const char *expr){
  assert(vbuf && expr);
  const unsigned char *sp = (unsigned char *)expr;
  while(*sp != '\0'){
    while((*sp != '\0' && *sp <= ' ') || *sp == ','){
      sp++;
    }
    const unsigned char *ep = sp;
    while(*ep > ' ' && *ep != ','){
      ep++;
    }
    if(ep > sp){
      const unsigned char *rp;
      for(rp = (unsigned char *)vbuf; *rp != '\0'; rp++){
        if(*sp != *rp || sp >= ep) break;
        sp++;
      }
      if(*rp == '\0' && sp == ep) return true;
    }
    sp = ep;
  }
  return false;
}


/* Check whether a decimal string is between two tokens in another string.
   `vbuf' specifies the target value.
   `expr' specifies the operation value.
   If they matches, the return value is true, else it is false. */
static bool tctdbqrycondchecknumbt(const char *vbuf, const char *expr){
  assert(vbuf && expr);
  while(*expr == ' ' || *expr == ','){
    expr++;
  }
  const char *pv = expr;
  while(*pv != '\0' && *pv != ' ' && *pv != ','){
    pv++;
  }
  if(*pv != ' ' && *pv != ',') pv = " ";
  pv++;
  while(*pv == ' ' || *pv == ','){
    pv++;
  }
  long double val = tctdbatof(vbuf);
  long double lower = tctdbatof(expr);
  long double upper = tctdbatof(pv);
  if(lower > upper){
    long double swap = lower;
    lower = upper;
    upper = swap;
  }
  return val >= lower && val <= upper;
}


/* Check whether a number is equal to at least one token in another string.
   `vbuf' specifies the target value.
   `expr' specifies the operation value.
   If they matches, the return value is true, else it is false. */
static bool tctdbqrycondchecknumoreq(const char *vbuf, const char *expr){
  assert(vbuf && expr);
  long double vnum = tctdbatof(vbuf);
  const char *sp = expr;
  while(*sp != '\0'){
    while(*sp == ' ' || *sp == ','){
      sp++;
    }
    const char *ep = sp;
    while(*ep != '\0' && *ep != ' ' && *ep != ','){
      ep++;
    }
    if(ep > sp && vnum == tctdbatof(sp)) return true;
    sp = ep;
  }
  return false;
}


/* Check whether a text matches a condition.
   `vbuf' specifies the target value.
   `vsiz' specifies the size of the target value.
   `cond' specifies the condition object.
   If they matches, the return value is true, else it is false. */
static bool tctdbqrycondcheckfts(const char *vbuf, int vsiz, TDBCOND *cond){
  assert(vbuf && cond);
  TDBFTSUNIT *ftsunits = cond->ftsunits;
  int ftsnum = cond->ftsnum;
  if(ftsnum < 1) return false;
  if(!ftsunits[0].sign) return false;
  char astack[TDBCOLBUFSIZ];
  uint16_t *ary;
  int asiz = sizeof(*ary) * (vsiz + 1);
  if(asiz < sizeof(astack)){
    ary = (uint16_t *)astack;
  } else {
    TCMALLOC(ary, asiz + 1);
  }
  int anum;
  tcstrutftoucs(vbuf, ary, &anum);
  anum = tcstrucsnorm(ary, anum, TCUNSPACE | TCUNLOWER | TCUNNOACC | TCUNWIDTH);
  char sstack[TDBCOLBUFSIZ], *str;
  int ssiz = anum * 3 + 1;
  if(ssiz < sizeof(sstack)){
    str = sstack;
  } else {
    TCMALLOC(str, ssiz + 1);
  }
  tcstrucstoutf(ary, anum, str);
  bool ok = true;
  for(int i = 0; i < ftsnum; i++){
    TDBFTSUNIT *ftsunit = ftsunits + i;
    TCLIST *tokens = ftsunit->tokens;
    int tnum = TCLISTNUM(tokens);
    bool hit = false;
    for(int j = 0; j < tnum; j++){
      if(strstr(str, TCLISTVALPTR(tokens, j))){
        hit = true;
        break;
      }
    }
    if(hit != ftsunit->sign) ok = false;
  }
  if(str != sstack) TCFREE(str);
  if(ary != (uint16_t *)astack) TCFREE(ary);
  return ok;
}


/* Compare two primary keys by number ascending.
   `a' specifies a key.
   `b' specifies of the other key.
   The return value is positive if the former is big, negative if the latter is big, 0 if both
   are equivalent. */
static int tdbcmppkeynumasc(const TCLISTDATUM *a, const TCLISTDATUM *b){
  assert(a && b);
  return tccmpdecimal(a->ptr, a->size, b->ptr, b->size, NULL);
}


/* Compare two primary keys by number descending.
   `a' specifies a key.
   `b' specifies of the other key.
   The return value is positive if the former is big, negative if the latter is big, 0 if both
   are equivalent. */
static int tdbcmppkeynumdesc(const TCLISTDATUM *a, const TCLISTDATUM *b){
  assert(a && b);
  return tccmpdecimal(b->ptr, b->size, a->ptr, a->size, NULL);
}


/* Compare two sort records by string ascending.
   `a' specifies a key.
   `b' specifies of the other key.
   The return value is positive if the former is big, negative if the latter is big, 0 if both
   are equivalent. */
static int tdbcmpsortrecstrasc(const TDBSORTREC *a, const TDBSORTREC *b){
  assert(a && b);
  if(!a->vbuf){
    if(!b->vbuf) return 0;
    return 1;
  }
  if(!b->vbuf){
    if(!a->vbuf) return 0;
    return -1;
  }
  int rv;
  TCCMPLEXICAL(rv, a->vbuf, a->vsiz, b->vbuf, b->vsiz);
  return rv;
}


/* Compare two sort records by string descending.
   `a' specifies a key.
   `b' specifies of the other key.
   The return value is positive if the former is big, negative if the latter is big, 0 if both
   are equivalent. */
static int tdbcmpsortrecstrdesc(const TDBSORTREC *a, const TDBSORTREC *b){
  assert(a && b);
  if(!a->vbuf){
    if(!b->vbuf) return 0;
    return 1;
  }
  if(!b->vbuf){
    if(!a->vbuf) return 0;
    return -1;
  }
  int rv;
  TCCMPLEXICAL(rv, a->vbuf, a->vsiz, b->vbuf, b->vsiz);
  return -rv;
}


/* Compare two sort records by number ascending.
   `a' specifies a key.
   `b' specifies of the other key.
   The return value is positive if the former is big, negative if the latter is big, 0 if both
   are equivalent. */
static int tdbcmpsortrecnumasc(const TDBSORTREC *a, const TDBSORTREC *b){
  assert(a && b);
  if(!a->vbuf){
    if(!b->vbuf) return 0;
    return 1;
  }
  if(!b->vbuf){
    if(!a->vbuf) return 0;
    return -1;
  }
  long double anum = tctdbatof(a->vbuf);
  long double bnum = tctdbatof(b->vbuf);
  if(anum < bnum) return -1;
  if(anum > bnum) return 1;
  return 0;
}


/* Compare two sort records by number descending.
   `a' specifies a key.
   `b' specifies of the other key.
   The return value is positive if the former is big, negative if the latter is big, 0 if both
   are equivalent. */
static int tdbcmpsortrecnumdesc(const TDBSORTREC *a, const TDBSORTREC *b){
  assert(a && b);
  if(!a->vbuf){
    if(!b->vbuf) return 0;
    return 1;
  }
  if(!b->vbuf){
    if(!a->vbuf) return 0;
    return -1;
  }
  long double anum = tctdbatof(a->vbuf);
  long double bnum = tctdbatof(b->vbuf);
  if(anum < bnum) return 1;
  if(anum > bnum) return -1;
  return 0;
}


/* Get the hash value of a record.
   `pkbuf' specifies the pointer to the region of the primary key.
   `pksiz' specifies the size of the region of the primary key.
   The return value is the hash value. */
static uint16_t tctdbidxhash(const char *pkbuf, int pksiz){
  assert(pkbuf && pksiz && pksiz >= 0);
  uint32_t hash = 19780211;
  while(pksiz--){
    hash = hash * 37 + *(uint8_t *)pkbuf++;
  }
  return hash;
}


/* Add a record into indices of a table database object.
   `tdb' specifies the table database object.
   `pkbuf' specifies the pointer to the region of the primary key.
   `pksiz' specifies the size of the region of the primary key.
   `cols' specifies a map object containing columns.
   If successful, the return value is true, else, it is false. */
static bool tctdbidxput(TCTDB *tdb, const void *pkbuf, int pksiz, TCMAP *cols){
  assert(tdb && pkbuf && pksiz >= 0 && cols);
  bool err = false;
  uint16_t hash = tctdbidxhash(pkbuf, pksiz);
  TDBIDX *idxs = tdb->idxs;
  int inum = tdb->inum;
  for(int i = 0; i < inum; i++){
    TDBIDX *idx = idxs + i;
    if(*(idx->name) != '\0') continue;
    char stack[TDBCOLBUFSIZ], *rbuf;
    if(pksiz < sizeof(stack)){
      rbuf = stack;
    } else {
      TCMALLOC(rbuf, pksiz + 1);
    }
    memcpy(rbuf, pkbuf, pksiz);
    rbuf[pksiz] = '\0';
    switch(idx->type){
      case TDBITLEXICAL:
      case TDBITDECIMAL:
        if(!tcbdbput(idx->db, pkbuf, pksiz, rbuf, pksiz)){
          tctdbsetecode(tdb, tcbdbecode(idx->db), __FILE__, __LINE__, __func__);
          err = true;
        }
        break;
      case TDBITTOKEN:
        if(!tctdbidxputtoken(tdb, idx, pkbuf, pksiz, pkbuf, pksiz)) err = true;
        break;
      case TDBITQGRAM:
        if(!tctdbidxputqgram(tdb, idx, pkbuf, pksiz, pkbuf, pksiz)) err = true;
        break;
    }
    if(rbuf != stack) TCFREE(rbuf);
  }
  tcmapiterinit(cols);
  const char *kbuf;
  int ksiz;
  while((kbuf = tcmapiternext(cols, &ksiz)) != NULL){
    int vsiz;
    const char *vbuf = tcmapiterval(kbuf, &vsiz);
    for(int i = 0; i < inum; i++){
      TDBIDX *idx = idxs + i;
      if(strcmp(idx->name, kbuf)) continue;
      switch(idx->type){
        case TDBITLEXICAL:
        case TDBITDECIMAL:
          if(!tctdbidxputone(tdb, idx, pkbuf, pksiz, hash, vbuf, vsiz)) err = true;
          break;
        case TDBITTOKEN:
          if(!tctdbidxputtoken(tdb, idx, pkbuf, pksiz, vbuf, vsiz)) err = true;
          break;
        case TDBITQGRAM:
          if(!tctdbidxputqgram(tdb, idx, pkbuf, pksiz, vbuf, vsiz)) err = true;
          break;
      }
    }
  }
  return !err;
}


/* Add a column of a record into an index of a table database object.
   `tdb' specifies the table database object.
   `idx' specifies the index object.
   `pkbuf' specifies the pointer to the region of the primary key.
   `pksiz' specifies the size of the region of the primary key.
   `hash' specifies the hash value of the primary key.
   `vbuf' specifies the pointer to the region of the column value.
   `vsiz' specifies the size of the region of the column value.
   If successful, the return value is true, else, it is false. */
static bool tctdbidxputone(TCTDB *tdb, TDBIDX *idx, const char *pkbuf, int pksiz, uint16_t hash,
                           const char *vbuf, int vsiz){
  assert(tdb && pkbuf && pksiz >= 0 && vbuf && vsiz);
  bool err = false;
  char stack[TDBCOLBUFSIZ], *rbuf;
  int rsiz = vsiz + 3;
  if(rsiz <= sizeof(stack)){
    rbuf = stack;
  } else {
    TCMALLOC(rbuf, rsiz);
  }
  memcpy(rbuf, vbuf, vsiz);
  rbuf[vsiz] = '\0';
  rbuf[vsiz+1] = hash >> 8;
  rbuf[vsiz+2] = hash & 0xff;
  if(!tcbdbputdup(idx->db, rbuf, rsiz, pkbuf, pksiz)){
    tctdbsetecode(tdb, tcbdbecode(idx->db), __FILE__, __LINE__, __func__);
    err = true;
  }
  if(rbuf != stack) TCFREE(rbuf);
  return !err;
}


/* Add a column of a record into an token inverted index of a table database object.
   `tdb' specifies the table database object.
   `idx' specifies the index object.
   `pkbuf' specifies the pointer to the region of the primary key.
   `pksiz' specifies the size of the region of the primary key.
   `vbuf' specifies the pointer to the region of the column value.
   `vsiz' specifies the size of the region of the column value.
   If successful, the return value is true, else, it is false. */
static bool tctdbidxputtoken(TCTDB *tdb, TDBIDX *idx, const char *pkbuf, int pksiz,
                             const char *vbuf, int vsiz){
  assert(tdb && idx && pkbuf && pksiz >= 0 && vbuf && vsiz >= 0);
  bool err = false;
  TCMAP *cc = idx->cc;
  char stack[TDBCOLBUFSIZ], *rbuf;
  int rsiz = pksiz + TCNUMBUFSIZ;
  if(rsiz < sizeof(stack)){
    rbuf = stack;
  } else {
    TCMALLOC(rbuf, rsiz);
  }
  uint64_t pkid = 0;
  for(int i = 0; i < pksiz; i++){
    int c = pkbuf[i];
    if(c >= '0' && c <= '9'){
      pkid = pkid * 10 + c - '0';
      if(pkid > INT64_MAX){
        pkid = 0;
        break;
      }
    } else {
      pkid = 0;
      break;
    }
  }
  if(pksiz > 0 && *pkbuf == '0') pkid = 0;
  if(pkid > 0){
    TCSETVNUMBUF64(rsiz, rbuf, pkid);
  } else {
    char *wp = rbuf;
    *(wp++) = '\0';
    TCSETVNUMBUF(rsiz, wp, pksiz);
    wp += rsiz;
    memcpy(wp, pkbuf, pksiz);
    wp += pksiz;
    rsiz = wp - rbuf;
  }
  const unsigned char *sp = (unsigned char *)vbuf;
  while(*sp != '\0'){
    while((*sp != '\0' && *sp <= ' ') || *sp == ','){
      sp++;
    }
    const unsigned char *ep = sp;
    while(*ep > ' ' && *ep != ','){
      ep++;
    }
    if(ep > sp) tcmapputcat3(cc, sp, ep - sp, rbuf, rsiz);
    sp = ep;
  }
  if(rbuf != stack) TCFREE(rbuf);
  if(tcmapmsiz(cc) > tdb->iccmax && !tctdbidxsyncicc(tdb, idx, false)) err = true;
  return !err;
}


/* Add a column of a record into an q-gram inverted index of a table database object.
   `tdb' specifies the table database object.
   `idx' specifies the index object.
   `pkbuf' specifies the pointer to the region of the primary key.
   `pksiz' specifies the size of the region of the primary key.
   `vbuf' specifies the pointer to the region of the column value.
   `vsiz' specifies the size of the region of the column value.
   If successful, the return value is true, else, it is false. */
static bool tctdbidxputqgram(TCTDB *tdb, TDBIDX *idx, const char *pkbuf, int pksiz,
                             const char *vbuf, int vsiz){
  assert(tdb && idx && pkbuf && pksiz >= 0 && vbuf && vsiz >= 0);
  bool err = false;
  TCMAP *cc = idx->cc;
  char stack[TDBCOLBUFSIZ], *rbuf;
  int rsiz = pksiz + TCNUMBUFSIZ * 2;
  if(rsiz < sizeof(stack)){
    rbuf = stack;
  } else {
    TCMALLOC(rbuf, rsiz);
  }
  uint64_t pkid = 0;
  for(int i = 0; i < pksiz; i++){
    int c = pkbuf[i];
    if(c >= '0' && c <= '9'){
      pkid = pkid * 10 + c - '0';
      if(pkid > INT64_MAX){
        pkid = 0;
        break;
      }
    } else {
      pkid = 0;
      break;
    }
  }
  if(pksiz > 0 && *pkbuf == '0') pkid = 0;
  if(pkid > 0){
    TCSETVNUMBUF64(rsiz, rbuf, pkid);
  } else {
    char *wp = rbuf;
    *(wp++) = '\0';
    TCSETVNUMBUF(rsiz, wp, pksiz);
    wp += rsiz;
    memcpy(wp, pkbuf, pksiz);
    wp += pksiz;
    rsiz = wp - rbuf;
  }
  uint16_t *ary;
  TCMALLOC(ary, sizeof(*ary) * (vsiz + TDBIDXQGUNIT));
  int anum;
  tcstrutftoucs(vbuf, ary, &anum);
  anum = tcstrucsnorm(ary, anum, TCUNSPACE | TCUNLOWER | TCUNNOACC | TCUNWIDTH);
  for(int i = 0; i < TDBIDXQGUNIT; i++){
    ary[anum+i] = 0;
  }
  char *wp = rbuf + rsiz;
  char token[TDBIDXQGUNIT*3+1];
  for(int i = 0; i < anum; i++){
    tcstrucstoutf(ary + i, TDBIDXQGUNIT, token);
    int step;
    TCSETVNUMBUF(step, wp, i);
    tcmapputcat3(cc, token, strlen(token), rbuf, rsiz + step);
  }
  TCFREE(ary);
  if(rbuf != stack) TCFREE(rbuf);
  if(tcmapmsiz(cc) > tdb->iccmax && !tctdbidxsyncicc(tdb, idx, false)) err = true;
  return !err;
}


/* Remove a record from indices of a table database object.
   `tdb' specifies the table database object.
   `pkbuf' specifies the pointer to the region of the primary key.
   `pksiz' specifies the size of the region of the primary key.
   `cols' specifies a map object containing columns.
   If successful, the return value is true, else, it is false. */
static bool tctdbidxout(TCTDB *tdb, const void *pkbuf, int pksiz, TCMAP *cols){
  assert(tdb && pkbuf && pksiz >= 0 && cols);
  bool err = false;
  uint16_t hash = tctdbidxhash(pkbuf, pksiz);
  TDBIDX *idxs = tdb->idxs;
  int inum = tdb->inum;
  for(int i = 0; i < inum; i++){
    TDBIDX *idx = idxs + i;
    if(*(idx->name) != '\0') continue;
    char stack[TDBCOLBUFSIZ], *rbuf;
    if(pksiz < sizeof(stack)){
      rbuf = stack;
    } else {
      TCMALLOC(rbuf, pksiz + 1);
    }
    memcpy(rbuf, pkbuf, pksiz);
    rbuf[pksiz] = '\0';
    switch(idx->type){
      case TDBITLEXICAL:
      case TDBITDECIMAL:
        if(!tcbdbout(idx->db, pkbuf, pksiz)){
          tctdbsetecode(tdb, tcbdbecode(idx->db), __FILE__, __LINE__, __func__);
          err = true;
        }
        break;
      case TDBITTOKEN:
        if(!tctdbidxouttoken(tdb, idx, pkbuf, pksiz, rbuf, pksiz)) err = true;
        break;
      case TDBITQGRAM:
        if(!tctdbidxoutqgram(tdb, idx, pkbuf, pksiz, rbuf, pksiz)) err = true;
        break;
    }
    if(rbuf != stack) TCFREE(rbuf);
  }
  tcmapiterinit(cols);
  const char *kbuf;
  int ksiz;
  while((kbuf = tcmapiternext(cols, &ksiz)) != NULL){
    int vsiz;
    const char *vbuf = tcmapiterval(kbuf, &vsiz);
    for(int i = 0; i < inum; i++){
      TDBIDX *idx = idxs + i;
      if(strcmp(idx->name, kbuf)) continue;
      switch(idx->type){
        case TDBITLEXICAL:
        case TDBITDECIMAL:
          if(!tctdbidxoutone(tdb, idx, pkbuf, pksiz, hash, vbuf, vsiz)) err = true;
          break;
        case TDBITTOKEN:
          if(!tctdbidxouttoken(tdb, idx, pkbuf, pksiz, vbuf, vsiz)) err = true;
          break;
        case TDBITQGRAM:
          if(!tctdbidxoutqgram(tdb, idx, pkbuf, pksiz, vbuf, vsiz)) err = true;
          break;
      }
    }
  }
  return !err;
}


/* Remove a column of a record from an index of a table database object.
   `tdb' specifies the table database object.
   `idx' specifies the index object.
   `pkbuf' specifies the pointer to the region of the primary key.
   `pksiz' specifies the size of the region of the primary key.
   `hash' specifies the hash value of the primary key.
   `vbuf' specifies the pointer to the region of the column value.
   `vsiz' specifies the size of the region of the column value.
   If successful, the return value is true, else, it is false. */
static bool tctdbidxoutone(TCTDB *tdb, TDBIDX *idx, const char *pkbuf, int pksiz, uint16_t hash,
                           const char *vbuf, int vsiz){
  assert(tdb && idx && pkbuf && pksiz >= 0 && vbuf && vsiz >= 0);
  bool err = false;
  char stack[TDBCOLBUFSIZ], *rbuf;
  int rsiz = vsiz + 3;
  if(rsiz <= sizeof(stack)){
    rbuf = stack;
  } else {
    TCMALLOC(rbuf, rsiz);
  }
  memcpy(rbuf, vbuf, vsiz);
  rbuf[vsiz] = '\0';
  rbuf[vsiz+1] = hash >> 8;
  rbuf[vsiz+2] = hash & 0xff;
  int ovsiz;
  const char *ovbuf = tcbdbget3(idx->db, rbuf, rsiz, &ovsiz);
  if(ovbuf && ovsiz == pksiz && !memcmp(ovbuf, pkbuf, ovsiz)){
    if(!tcbdbout(idx->db, rbuf, rsiz)){
      tctdbsetecode(tdb, tcbdbecode(idx->db), __FILE__, __LINE__, __func__);
      err = true;
    }
  } else {
    BDBCUR *cur = tcbdbcurnew(idx->db);
    if(tcbdbcurjump(cur, rbuf, rsiz)){
      int oksiz;
      const char *okbuf;
      while((okbuf = tcbdbcurkey3(cur, &oksiz)) != NULL){
        if(oksiz != rsiz || memcmp(okbuf, rbuf, oksiz)) break;
        ovbuf = tcbdbcurval3(cur, &ovsiz);
        if(ovsiz == pksiz && !memcmp(ovbuf, pkbuf, ovsiz)){
          if(!tcbdbcurout(cur)){
            tctdbsetecode(tdb, tcbdbecode(idx->db), __FILE__, __LINE__, __func__);
            err = true;
          }
          break;
        }
        tcbdbcurnext(cur);
      }
    } else {
      tctdbsetecode(tdb, tcbdbecode(idx->db), __FILE__, __LINE__, __func__);
      err = true;
    }
    tcbdbcurdel(cur);
  }
  if(rbuf != stack) TCFREE(rbuf);
  return !err;
}


/* Remove a column of a record from a token inverted index of a table database object.
   `tdb' specifies the table database object.
   `idx' specifies the index object.
   `pkbuf' specifies the pointer to the region of the primary key.
   `pksiz' specifies the size of the region of the primary key.
   `vbuf' specifies the pointer to the region of the column value.
   `vsiz' specifies the size of the region of the column value.
   If successful, the return value is true, else, it is false. */
static bool tctdbidxouttoken(TCTDB *tdb, TDBIDX *idx, const char *pkbuf, int pksiz,
                             const char *vbuf, int vsiz){
  assert(tdb && idx && pkbuf && pksiz >= 0 && vbuf && vsiz >= 0);
  bool err = false;
  TCBDB *db = idx->db;
  TCMAP *cc = idx->cc;
  uint64_t pkid = 0;
  for(int i = 0; i < pksiz; i++){
    int c = pkbuf[i];
    if(c >= '0' && c <= '9'){
      pkid = pkid * 10 + c - '0';
      if(pkid > INT64_MAX){
        pkid = 0;
        break;
      }
    } else {
      pkid = 0;
      break;
    }
  }
  TCXSTR *xstr = tcxstrnew();
  const unsigned char *sp = (unsigned char *)vbuf;
  while(*sp != '\0'){
    while((*sp != '\0' && *sp <= ' ') || *sp == ','){
      sp++;
    }
    const unsigned char *ep = sp;
    while(*ep > ' ' && *ep != ','){
      ep++;
    }
    if(ep > sp){
      tcxstrclear(xstr);
      int len = ep - sp;
      int csiz;
      const char *cbuf = tcmapget(cc, sp, len, &csiz);
      if(cbuf){
        while(csiz > 0){
          const char *pv = cbuf;
          bool ok = true;
          if(*cbuf == '\0'){
            cbuf++;
            csiz--;
            int tsiz, step;
            TCREADVNUMBUF(cbuf, tsiz, step);
            cbuf += step;
            csiz -= step;
            if(tsiz == pksiz && !memcmp(cbuf, pkbuf, tsiz)) ok = false;
            cbuf += tsiz;
            csiz -= tsiz;
          } else {
            int64_t tid;
            int step;
            TCREADVNUMBUF64(cbuf, tid, step);
            if(tid == pkid) ok = false;
            cbuf += step;
            csiz -= step;
          }
          if(ok) TCXSTRCAT(xstr, pv, cbuf - pv);
        }
        if(csiz != 0){
          tctdbsetecode(tdb, TCEMISC, __FILE__, __LINE__, __func__);
          err = true;
        }
      }
      cbuf = tcbdbget3(db, sp, len, &csiz);
      if(cbuf){
        while(csiz > 0){
          const char *pv = cbuf;
          bool ok = true;
          if(*cbuf == '\0'){
            cbuf++;
            csiz--;
            int tsiz, step;
            TCREADVNUMBUF(cbuf, tsiz, step);
            cbuf += step;
            csiz -= step;
            if(tsiz == pksiz && !memcmp(cbuf, pkbuf, tsiz)) ok = false;
            cbuf += tsiz;
            csiz -= tsiz;
          } else {
            int64_t tid;
            int step;
            TCREADVNUMBUF64(cbuf, tid, step);
            if(tid == pkid) ok = false;
            cbuf += step;
            csiz -= step;
          }
          if(ok) TCXSTRCAT(xstr, pv, cbuf - pv);
        }
        if(csiz != 0){
          tctdbsetecode(tdb, TCEMISC, __FILE__, __LINE__, __func__);
          err = true;
        }
        if(!tcbdbout(db, sp, len)){
          tctdbsetecode(tdb, tcbdbecode(db), __FILE__, __LINE__, __func__);
          err = true;
        }
      }
      tcmapput(cc, sp, len, TCXSTRPTR(xstr), TCXSTRSIZE(xstr));
    }
    sp = ep;
  }
  tcxstrdel(xstr);
  if(tcmapmsiz(cc) > tdb->iccmax && !tctdbidxsyncicc(tdb, idx, false)) err = true;
  return !err;
}


/* Remove a column of a record from a q-gram inverted index of a table database object.
   `tdb' specifies the table database object.
   `idx' specifies the index object.
   `pkbuf' specifies the pointer to the region of the primary key.
   `pksiz' specifies the size of the region of the primary key.
   `vbuf' specifies the pointer to the region of the column value.
   `vsiz' specifies the size of the region of the column value.
   If successful, the return value is true, else, it is false. */
static bool tctdbidxoutqgram(TCTDB *tdb, TDBIDX *idx, const char *pkbuf, int pksiz,
                             const char *vbuf, int vsiz){
  assert(tdb && idx && pkbuf && pksiz >= 0 && vbuf && vsiz >= 0);
  bool err = false;
  TCBDB *db = idx->db;
  TCMAP *cc = idx->cc;
  uint64_t pkid = 0;
  for(int i = 0; i < pksiz; i++){
    int c = pkbuf[i];
    if(c >= '0' && c <= '9'){
      pkid = pkid * 10 + c - '0';
      if(pkid > INT64_MAX){
        pkid = 0;
        break;
      }
    } else {
      pkid = 0;
      break;
    }
  }
  TCXSTR *xstr = tcxstrnew();
  uint16_t *ary;
  TCMALLOC(ary, sizeof(*ary) * (vsiz + TDBIDXQGUNIT));
  int anum;
  tcstrutftoucs(vbuf, ary, &anum);
  anum = tcstrucsnorm(ary, anum, TCUNSPACE | TCUNLOWER | TCUNNOACC | TCUNWIDTH);
  for(int i = 0; i < TDBIDXQGUNIT; i++){
    ary[anum+i] = 0;
  }
  char token[TDBIDXQGUNIT*3+1];
  for(int i = 0; i < anum; i++){
    tcstrucstoutf(ary + i, TDBIDXQGUNIT, token);
    int tsiz = strlen(token);
    tcxstrclear(xstr);
    int csiz;
    const char *cbuf = tcmapget(cc, token, tsiz, &csiz);
    if(cbuf){
      while(csiz > 0){
        const char *pv = cbuf;
        bool ok = true;
        if(*cbuf == '\0'){
          cbuf++;
          csiz--;
          int tsiz, step;
          TCREADVNUMBUF(cbuf, tsiz, step);
          cbuf += step;
          csiz -= step;
          if(tsiz == pksiz && !memcmp(cbuf, pkbuf, tsiz)) ok = false;
          cbuf += tsiz;
          csiz -= tsiz;
        } else {
          int64_t tid;
          int step;
          TCREADVNUMBUF64(cbuf, tid, step);
          if(tid == pkid) ok = false;
          cbuf += step;
          csiz -= step;
        }
        if(csiz > 0){
          int off, step;
          TCREADVNUMBUF(cbuf, off, step);
          cbuf += step;
          csiz -= step;
          if(ok) TCXSTRCAT(xstr, pv, cbuf - pv);
        }
      }
      if(csiz != 0){
        tctdbsetecode(tdb, TCEMISC, __FILE__, __LINE__, __func__);
        err = true;
      }
    }
    cbuf = tcbdbget3(db, token, tsiz, &csiz);
    if(cbuf){
      while(csiz > 0){
        const char *pv = cbuf;
        bool ok = true;
        if(*cbuf == '\0'){
          cbuf++;
          csiz--;
          int tsiz, step;
          TCREADVNUMBUF(cbuf, tsiz, step);
          cbuf += step;
          csiz -= step;
          if(tsiz == pksiz && !memcmp(cbuf, pkbuf, tsiz)) ok = false;
          cbuf += tsiz;
          csiz -= tsiz;
        } else {
          int64_t tid;
          int step;
          TCREADVNUMBUF64(cbuf, tid, step);
          if(tid == pkid) ok = false;
          cbuf += step;
          csiz -= step;
        }
        if(csiz > 0){
          int off, step;
          TCREADVNUMBUF(cbuf, off, step);
          cbuf += step;
          csiz -= step;
          if(ok) TCXSTRCAT(xstr, pv, cbuf - pv);
        }
      }
      if(csiz != 0){
        tctdbsetecode(tdb, TCEMISC, __FILE__, __LINE__, __func__);
        err = true;
      }
      if(!tcbdbout(db, token, tsiz)){
        tctdbsetecode(tdb, tcbdbecode(db), __FILE__, __LINE__, __func__);
        err = true;
      }
    }
    tcmapput(cc, token, tsiz, TCXSTRPTR(xstr), TCXSTRSIZE(xstr));
  }
  TCFREE(ary);
  tcxstrdel(xstr);
  if(tcmapmsiz(cc) > tdb->iccmax && !tctdbidxsyncicc(tdb, idx, false)) err = true;
  return !err;
}


/* Synchronize updated contents of an inverted cache of a table database object.
   `tdb' specifies the table database object.
   `idx' specifies the index object.
   `all' specifies whether to sync all tokens.
   If successful, the return value is true, else, it is false. */
static bool tctdbidxsyncicc(TCTDB *tdb, TDBIDX *idx, bool all){
  assert(tdb && idx);
  TCBDB *db = idx->db;
  TCMAP *cc = idx->cc;
  int rnum = TCMAPRNUM(cc);
  if(rnum < 1) return true;
  bool err = false;
  const char **keys;
  TCMALLOC(keys, sizeof(*keys) * rnum);
  int knum = 0;
  int64_t usiz = tcmapmsiz(cc) - sizeof(void *) * TDBIDXICCBNUM;
  int64_t max = all ? INT64_MAX : usiz * tdb->iccsync;
  int64_t sum = 0;
  const char *kbuf;
  int ksiz;
  tcmapiterinit(cc);
  while(sum < max && (kbuf = tcmapiternext(cc, &ksiz)) != NULL){
    int vsiz;
    tcmapiterval(kbuf, &vsiz);
    keys[knum++] = kbuf;
    sum += sizeof(TCMAPREC) + sizeof(void *) + ksiz + vsiz;
  }
  qsort(keys, knum, sizeof(*keys), (int(*)(const void *, const void *))tctdbidxcmpkey);
  for(int i = 0; i < knum; i++){
    const char *kbuf = keys[i];
    int ksiz = strlen(kbuf);
    int vsiz;
    const char *vbuf = tcmapget(cc, kbuf, ksiz, &vsiz);
    if(vsiz > 0 && !tcbdbputcat(db, kbuf, ksiz, vbuf, vsiz)){
      tctdbsetecode(tdb, tcbdbecode(db), __FILE__, __LINE__, __func__);
      err = true;
    }
    tcmapout(cc, kbuf, ksiz);
  }
  TCFREE(keys);
  return !err;
}


/* Compare two index search keys in lexical order.
   `a' specifies the pointer to one element.
   `b' specifies the pointer to the other element.
   The return value is positive if the former is big, negative if the latter is big, 0 if both
   are equivalent. */
static int tctdbidxcmpkey(const char **a, const char **b){
  assert(a && b);
  const unsigned char *ap = (unsigned char *)*a;
  const unsigned char *bp = (unsigned char *)*b;
  while(true){
    if(*ap == '\0') return *bp == '\0' ? 0 : -1;
    if(*bp == '\0') return *ap == '\0' ? 0 : 1;
    if(*ap != *bp) return *ap - *bp;
    ap++;
    bp++;
  }
  return 0;
}


/* Retrieve records by a token inverted index of a table database object.
   `tdb' specifies the table database object.
   `idx' specifies the index object.
   `token' specifies the list object of tokens.
   `op' specifies the operation type.
   `hint' specifies the hint object.
   The return value is a map object of the primary keys of the corresponding records. */
static TCMAP *tctdbidxgetbytokens(TCTDB *tdb, TDBIDX *idx, const TCLIST *tokens, int op,
                                  TCXSTR *hint){
  assert(tdb && idx && tokens && hint);
  TCBDB *db = idx->db;
  TCMAP *cc = idx->cc;
  int tnum = TCLISTNUM(tokens);
  TCMAP *res = tcmapnew();
  int cnt = 0;
  for(int i = 0; i < tnum; i++){
    const char *token;
    int tsiz;
    TCLISTVAL(token, tokens, i, tsiz);
    if(tsiz < 1) continue;
    int onum = 0;
    TCMAP *wring = (cnt > 0 && op == TDBQCSTRAND) ? tcmapnew() : NULL;
    int csiz;
    const char *cbuf = tcmapget(cc, token, tsiz, &csiz);
    if(cbuf){
      while(csiz > 0){
        if(*cbuf == '\0'){
          cbuf++;
          csiz--;
          int tsiz, step;
          TCREADVNUMBUF(cbuf, tsiz, step);
          cbuf += step;
          csiz -= step;
          if(cnt < 1){
            tcmapput(res, cbuf, tsiz, "", 0);
          } else if(wring){
            int rsiz;
            if(tcmapget(res, cbuf, tsiz, &rsiz)) tcmapput(wring, cbuf, tsiz, "", 0);
          } else {
            tcmapput(res, cbuf, tsiz, "", 0);
          }
          cbuf += tsiz;
          csiz -= tsiz;
        } else {
          int64_t tid;
          int step;
          TCREADVNUMBUF64(cbuf, tid, step);
          char pkbuf[TCNUMBUFSIZ];
          int pksiz = sprintf(pkbuf, "%lld", (long long)tid);
          if(cnt < 1){
            tcmapput(res, pkbuf, pksiz, "", 0);
          } else if(wring){
            int rsiz;
            if(tcmapget(res, pkbuf, pksiz, &rsiz)) tcmapput(wring, pkbuf, pksiz, "", 0);
          } else {
            tcmapput(res, pkbuf, pksiz, "", 0);
          }
          cbuf += step;
          csiz -= step;
        }
        onum++;
      }
    }
    cbuf = tcbdbget3(db, token, tsiz, &csiz);
    if(cbuf){
      while(csiz > 0){
        if(*cbuf == '\0'){
          cbuf++;
          csiz--;
          int tsiz, step;
          TCREADVNUMBUF(cbuf, tsiz, step);
          cbuf += step;
          csiz -= step;
          if(cnt < 1){
            tcmapput(res, cbuf, tsiz, "", 0);
          } else if(wring){
            int rsiz;
            if(tcmapget(res, cbuf, tsiz, &rsiz)) tcmapput(wring, cbuf, tsiz, "", 0);
          } else {
            tcmapput(res, cbuf, tsiz, "", 0);
          }
          cbuf += tsiz;
          csiz -= tsiz;
        } else {
          int64_t tid;
          int step;
          TCREADVNUMBUF64(cbuf, tid, step);
          char pkbuf[TCNUMBUFSIZ];
          int pksiz = sprintf(pkbuf, "%lld", (long long)tid);
          if(cnt < 1){
            tcmapput(res, pkbuf, pksiz, "", 0);
          } else if(wring){
            int rsiz;
            if(tcmapget(res, pkbuf, pksiz, &rsiz)) tcmapput(wring, pkbuf, pksiz, "", 0);
          } else {
            tcmapput(res, pkbuf, pksiz, "", 0);
          }
          cbuf += step;
          csiz -= step;
        }
        onum++;
      }
    }
    if(wring){
      tcmapdel(res);
      res = wring;
    }
    tcxstrprintf(hint, "token occurrence: \"%s\" %d\n", token, onum);
    cnt++;
  }
  return res;
}


/* Retrieve records by a token inverted index of a table database object.
   `tdb' specifies the table database object.
   `idx' specifies the index object.
   `cond' specifies the condition object.
   `hint' specifies the hint object.
   The return value is a map object of the primary keys of the corresponding records. */
static TCMAP *tctdbidxgetbyfts(TCTDB *tdb, TDBIDX *idx, TDBCOND *cond, TCXSTR *hint){
  assert(tdb && idx && cond && hint);
  TDBFTSUNIT *ftsunits = cond->ftsunits;
  int ftsnum = cond->ftsnum;
  if(ftsnum < 1) return tcmapnew2(1);
  if(!ftsunits[0].sign) return tcmapnew2(1);
  TCMAP *res = tcmapnew();
  tctdbidxgetbyftsunion(idx, ftsunits->tokens, true, NULL, res, hint);
  for(int i = 1; i < ftsnum; i++){
    TDBFTSUNIT *ftsunit = ftsunits + i;
    if(ftsunit->sign){
      TCMAP *nres = tcmapnew2(TCMAPRNUM(res) + 1);
      tctdbidxgetbyftsunion(idx, ftsunit->tokens, true, res, nres, hint);
      tcmapdel(res);
      res = nres;
    } else {
      tctdbidxgetbyftsunion(idx, ftsunit->tokens, false, res, NULL, hint);
    }
  }
  return res;
}


/* Retrieve union records by a token inverted index of a table database object.
   `idx' specifies the index object.
   `tokens' specifies a list object of the union tokens.
   `sign' specifies the logical sign.
   `ores' specifies a map object of old primary keys.
   `nres' specifies a map object of new primary keys.
   `hint' specifies the hint object. */
static void tctdbidxgetbyftsunion(TDBIDX *idx, const TCLIST *tokens, bool sign,
                                  TCMAP *ores, TCMAP *nres, TCXSTR *hint){
  assert(idx && tokens && hint);
  TCBDB *db = idx->db;
  TCMAP *cc = idx->cc;
  int tnum = TCLISTNUM(tokens);
  for(int i = 0; i < tnum; i++){
    const char *word;
    int wsiz;
    TCLISTVAL(word, tokens, i, wsiz);
    uint16_t *ary;
    TCMALLOC(ary, sizeof(*ary) * (wsiz + TDBIDXQGUNIT));
    int anum;
    tcstrutftoucs(word, ary, &anum);
    for(int j = 0; j < TDBIDXQGUNIT; j++){
      ary[anum+j] = 0;
    }
    if(anum >= TDBIDXQGUNIT){
      TDBFTSSTROCR *socrs;
      TCMALLOC(socrs, TDBFTSOCRUNIT * sizeof(*socrs));
      int sonum = 0;
      int soanum = TDBFTSOCRUNIT;
      int sobase = 0;
      TDBFTSNUMOCR *nocrs;
      TCMALLOC(nocrs, TDBFTSOCRUNIT * sizeof(*nocrs));
      int nonum = 0;
      int noanum = TDBFTSOCRUNIT;
      int nobase = 0;
      TCBITMAP *pkmap = TCBITMAPNEW(TDBFTSBMNUM);
      char token[TDBIDXQGUNIT*3+1];
      uint16_t seq = 0;
      for(int j = 0; j < anum; j += TDBIDXQGUNIT){
        sobase = sonum;
        nobase = nonum;
        int diff = anum - j - TDBIDXQGUNIT;
        if(diff < 0){
          j += diff;
          diff = -diff;
        } else {
          diff = 0;
        }
        tcstrucstoutf(ary + j, TDBIDXQGUNIT, token);
        int tsiz = strlen(token);
        int csiz;
        const char *cbuf = tcmapget(cc, token, tsiz, &csiz);
        if(cbuf){
          while(csiz > 0){
            const char *pkbuf = NULL;
            int32_t pksiz = 0;
            int64_t pkid = 0;
            if(*cbuf == '\0'){
              cbuf++;
              csiz--;
              int step;
              TCREADVNUMBUF(cbuf, pksiz, step);
              cbuf += step;
              csiz -= step;
              pkbuf = cbuf;
              cbuf += pksiz;
              csiz -= pksiz;
            } else {
              int step;
              TCREADVNUMBUF64(cbuf, pkid, step);
              cbuf += step;
              csiz -= step;
            }
            if(csiz > 0){
              int off, step;
              TCREADVNUMBUF(cbuf, off, step);
              cbuf += step;
              csiz -= step;
              off += diff;
              if(pkbuf){
                unsigned int hash = 19780211;
                for(int k = 0; k < pksiz; k++){
                  hash = hash * 37 + ((unsigned char *)pkbuf)[k];
                }
                hash = hash % TDBFTSBMNUM;
                if(j == 0 || TCBITMAPCHECK(pkmap, hash)){
                  if(sonum >= soanum){
                    soanum *= 2;
                    TCREALLOC(socrs, socrs, soanum * sizeof(*socrs));
                  }
                  TDBFTSSTROCR *ocr = socrs + sonum;
                  ocr->pkbuf = pkbuf;
                  ocr->pksiz = pksiz;
                  ocr->off = off;
                  ocr->seq = seq;
                  ocr->hash = hash;
                  sonum++;
                  if(j == 0) TCBITMAPON(pkmap, hash);
                }
              } else {
                unsigned int hash = pkid % TDBFTSBMNUM;
                if(j == 0 || TCBITMAPCHECK(pkmap, hash)){
                  if(nonum >= noanum){
                    noanum *= 2;
                    TCREALLOC(nocrs, nocrs, noanum * sizeof(*nocrs));
                  }
                  TDBFTSNUMOCR *ocr = nocrs + nonum;
                  ocr->pkid = pkid;
                  ocr->off = off;
                  ocr->seq = seq;
                  ocr->hash = hash;
                  nonum++;
                  if(j == 0) TCBITMAPON(pkmap, hash);
                }
              }
            }
          }
        }
        cbuf = tcbdbget3(db, token, tsiz, &csiz);
        if(cbuf){
          while(csiz > 0){
            const char *pkbuf = NULL;
            int32_t pksiz = 0;
            int64_t pkid = 0;
            if(*cbuf == '\0'){
              cbuf++;
              csiz--;
              int step;
              TCREADVNUMBUF(cbuf, pksiz, step);
              cbuf += step;
              csiz -= step;
              pkbuf = cbuf;
              cbuf += pksiz;
              csiz -= pksiz;
            } else {
              int step;
              TCREADVNUMBUF64(cbuf, pkid, step);
              cbuf += step;
              csiz -= step;
            }
            if(csiz > 0){
              int off, step;
              TCREADVNUMBUF(cbuf, off, step);
              cbuf += step;
              csiz -= step;
              off += diff;
              if(pkbuf){
                unsigned int hash = 19780211;
                for(int k = 0; k < pksiz; k++){
                  hash = hash * 37 + ((unsigned char *)pkbuf)[k];
                }
                hash = hash % TDBFTSBMNUM;
                if(j == 0 || TCBITMAPCHECK(pkmap, hash)){
                  if(sonum >= soanum){
                    soanum *= 2;
                    TCREALLOC(socrs, socrs, soanum * sizeof(*socrs));
                  }
                  TDBFTSSTROCR *ocr = socrs + sonum;
                  ocr->pkbuf = pkbuf;
                  ocr->pksiz = pksiz;
                  ocr->off = off;
                  ocr->seq = seq;
                  ocr->hash = hash;
                  sonum++;
                  if(j == 0) TCBITMAPON(pkmap, hash);
                }
              } else {
                unsigned int hash = pkid % TDBFTSBMNUM;
                if(j == 0 || TCBITMAPCHECK(pkmap, hash)){
                  if(nonum >= noanum){
                    noanum *= 2;
                    TCREALLOC(nocrs, nocrs, noanum * sizeof(*nocrs));
                  }
                  TDBFTSNUMOCR *ocr = nocrs + nonum;
                  ocr->pkid = pkid;
                  ocr->off = off;
                  ocr->seq = seq;
                  ocr->hash = hash;
                  nonum++;
                  if(j == 0) TCBITMAPON(pkmap, hash);
                }
              }
            }
          }
        }
        seq++;
        if(sonum <= sobase && nonum <= nobase){
          sonum = 0;
          nonum = 0;
          break;
        }
      }
      TCBITMAPDEL(pkmap);
      if(seq > 1){
        if(sonum > UINT16_MAX){
          int flnum = sonum * 16 + 1;
          TCBITMAP *flmap = TCBITMAPNEW(flnum);
          for(int j = sobase; j < sonum; j++){
            TDBFTSSTROCR *ocr = socrs + j;
            uint32_t hash = (((uint32_t)ocr->off << 16) | ocr->hash) % flnum;
            TCBITMAPON(flmap, hash);
          }
          int wi = 0;
          for(int j = 0; j < sobase; j++){
            TDBFTSSTROCR *ocr = socrs + j;
            int rem = (seq - ocr->seq - 1) * TDBIDXQGUNIT;
            uint32_t hash = (((uint32_t)(ocr->off + rem) << 16) | ocr->hash) % flnum;
            if(TCBITMAPCHECK(flmap, hash)) socrs[wi++] = *ocr;
          }
          for(int j = sobase; j < sonum; j++){
            socrs[wi++] = socrs[j];
          }
          sonum = wi;
          TCBITMAPDEL(flmap);
        }
        if(sonum > UINT16_MAX * 2){
          TDBFTSSTROCR *rocrs;
          TCMALLOC(rocrs, sizeof(*rocrs) * sonum);
          uint32_t *counts;
          TCCALLOC(counts, sizeof(*counts), (UINT16_MAX + 1));
          for(int j = 0; j < sonum; j++){
            counts[socrs[j].hash]++;
          }
          for(int j = 0; j < UINT16_MAX; j++){
            counts[j+1] += counts[j];
          }
          for(int j = sonum - 1; j >= 0; j--){
            rocrs[--counts[socrs[j].hash]] = socrs[j];
          }
          for(int j = 0; j < UINT16_MAX; j++){
            int num = counts[j+1] - counts[j];
            if(num > 1) qsort(rocrs + counts[j], num, sizeof(*rocrs),
                              (int (*)(const void *, const void *))tctdbidxftscmpstrocr);
          }
          int num = sonum - counts[UINT16_MAX];
          if(num > 1) qsort(rocrs + counts[UINT16_MAX], num, sizeof(*rocrs),
                            (int (*)(const void *, const void *))tctdbidxftscmpstrocr);
          TCFREE(counts);
          TCFREE(socrs);
          socrs = rocrs;
        } else if(sonum > 1){
          qsort(socrs, sonum, sizeof(*socrs),
                (int (*)(const void *, const void *))tctdbidxftscmpstrocr);
        }
        if(nonum > UINT16_MAX){
          int flnum = nonum * 16 + 1;
          TCBITMAP *flmap = TCBITMAPNEW(flnum);
          for(int j = nobase; j < nonum; j++){
            TDBFTSNUMOCR *ocr = nocrs + j;
            uint32_t hash = (((uint32_t)ocr->off << 16) | ocr->hash) % flnum;
            TCBITMAPON(flmap, hash);
          }
          int wi = 0;
          for(int j = 0; j < nobase; j++){
            TDBFTSNUMOCR *ocr = nocrs + j;
            int rem = (seq - ocr->seq - 1) * TDBIDXQGUNIT;
            uint32_t hash = (((uint32_t)(ocr->off + rem) << 16) | ocr->hash) % flnum;
            if(TCBITMAPCHECK(flmap, hash)) nocrs[wi++] = *ocr;
          }
          for(int j = nobase; j < nonum; j++){
            nocrs[wi++] = nocrs[j];
          }
          nonum = wi;
          TCBITMAPDEL(flmap);
        }
        if(nonum > UINT16_MAX * 2){
          TDBFTSNUMOCR *rocrs;
          TCMALLOC(rocrs, sizeof(*rocrs) * nonum);
          uint32_t *counts;
          TCCALLOC(counts, sizeof(*counts), (UINT16_MAX + 1));
          for(int j = 0; j < nonum; j++){
            counts[nocrs[j].hash]++;
          }
          for(int j = 0; j < UINT16_MAX; j++){
            counts[j+1] += counts[j];
          }
          for(int j = nonum - 1; j >= 0; j--){
            rocrs[--counts[nocrs[j].hash]] = nocrs[j];
          }
          for(int j = 0; j < UINT16_MAX; j++){
            int num = counts[j+1] - counts[j];
            if(num > 1) qsort(rocrs + counts[j], num, sizeof(*rocrs),
                              (int (*)(const void *, const void *))tctdbidxftscmpnumocr);
          }
          int num = nonum - counts[UINT16_MAX];
          if(num > 1) qsort(rocrs + counts[UINT16_MAX], num, sizeof(*rocrs),
                            (int (*)(const void *, const void *))tctdbidxftscmpnumocr);
          TCFREE(counts);
          TCFREE(nocrs);
          nocrs = rocrs;
        } else if(nonum > 1){
          qsort(nocrs, nonum, sizeof(*nocrs),
                (int (*)(const void *, const void *))tctdbidxftscmpnumocr);
        }
      }
      int rem = (seq - 1) * TDBIDXQGUNIT;
      int onum = 0;
      int ri = 0;
      while(ri < sonum){
        TDBFTSSTROCR *ocr = socrs + ri;
        ri++;
        if(ocr->seq > 0) continue;
        const char *pkbuf = ocr->pkbuf;
        int32_t pksiz = ocr->pksiz;
        int32_t off = ocr->off;
        uint16_t seq = 1;
        for(int j = ri; j < sonum; j++){
          TDBFTSSTROCR *tocr = socrs + j;
          if(!tocr->pkbuf || tocr->pksiz != pksiz || memcmp(tocr->pkbuf, pkbuf, pksiz) ||
             tocr->off > off + TDBIDXQGUNIT) break;
          if(tocr->seq == seq && tocr->off == off + TDBIDXQGUNIT){
            off = tocr->off;
            seq++;
          }
        }
        if(off == ocr->off + rem){
          onum++;
          if(ores){
            int rsiz;
            if(tcmapget(ores, pkbuf, pksiz, &rsiz)){
              if(sign){
                tcmapputkeep(nres, pkbuf, pksiz, "", 0);
              } else {
                tcmapout(ores, pkbuf, pksiz);
              }
            }
          } else {
            tcmapputkeep(nres, pkbuf, pksiz, "", 0);
          }
          while(ri < sonum){
            ocr = socrs + ri;
            if(!ocr->pkbuf || ocr->pksiz != pksiz || memcmp(ocr->pkbuf, pkbuf, pksiz)) break;
            ri++;
          }
        }
      }
      ri = 0;
      while(ri < nonum){
        TDBFTSNUMOCR *ocr = nocrs + ri;
        ri++;
        if(ocr->seq > 0) continue;
        int64_t pkid = ocr->pkid;
        int32_t off = ocr->off;
        uint16_t seq = 1;
        for(int j = ri; j < nonum; j++){
          TDBFTSNUMOCR *tocr = nocrs + j;
          if(tocr->pkid != pkid || tocr->off > off + TDBIDXQGUNIT) break;
          if(tocr->seq == seq && tocr->off == off + TDBIDXQGUNIT){
            off = tocr->off;
            seq++;
          }
        }
        if(off == ocr->off + rem){
          onum++;
          char pkbuf[TCNUMBUFSIZ];
          int pksiz = sprintf(pkbuf, "%lld", (long long)pkid);
          if(ores){
            int rsiz;
            if(tcmapget(ores, pkbuf, pksiz, &rsiz)){
              if(sign){
                tcmapputkeep(nres, pkbuf, pksiz, "", 0);
              } else {
                tcmapout(ores, pkbuf, pksiz);
              }
            }
          } else {
            tcmapputkeep(nres, pkbuf, pksiz, "", 0);
          }
          while(ri < nonum && nocrs[ri].pkid == pkid){
            ri++;
          }
        }
      }
      tcxstrprintf(hint, "token occurrence: \"%s\" %d\n", word, onum);
      TCFREE(nocrs);
      TCFREE(socrs);
    } else {
      int onum = 0;
      TCMAP *uniq = (i > 0 || ores) ? tcmapnew2(UINT16_MAX) : NULL;
      tcmapiterinit(cc);
      const char *kbuf;
      int ksiz;
      while((kbuf = tcmapiternext(cc, &ksiz)) != NULL){
        if(ksiz < wsiz || memcmp(kbuf, word, wsiz)) continue;
        int csiz;
        const char *cbuf = tcmapiterval(kbuf, &csiz);
        while(csiz > 0){
          const char *pkbuf = NULL;
          int32_t pksiz = 0;
          int64_t pkid = 0;
          if(*cbuf == '\0'){
            cbuf++;
            csiz--;
            int step;
            TCREADVNUMBUF(cbuf, pksiz, step);
            cbuf += step;
            csiz -= step;
            pkbuf = cbuf;
            cbuf += pksiz;
            csiz -= pksiz;
          } else {
            int step;
            TCREADVNUMBUF64(cbuf, pkid, step);
            cbuf += step;
            csiz -= step;
          }
          if(csiz > 0){
            int off, step;
            TCREADVNUMBUF(cbuf, off, step);
            cbuf += step;
            csiz -= step;
            if(pkbuf){
              if(ores){
                int rsiz;
                if(tcmapget(ores, pkbuf, pksiz, &rsiz)){
                  if(sign){
                    tcmapputkeep(nres, pkbuf, pksiz, "", 0);
                  } else {
                    tcmapout(ores, pkbuf, pksiz);
                  }
                }
              } else {
                if(tcmapputkeep(nres, pkbuf, pksiz, "", 0)) onum++;
              }
              if(uniq) tcmapputkeep(uniq, pkbuf, pksiz, "", 0);
            } else {
              char numbuf[TCNUMBUFSIZ];
              int pksiz = sprintf(numbuf, "%lld", (long long)pkid);
              if(ores){
                int rsiz;
                if(tcmapget(ores, numbuf, pksiz, &rsiz)){
                  if(sign){
                    tcmapputkeep(nres, numbuf, pksiz, "", 0);
                  } else {
                    tcmapout(ores, numbuf, pksiz);
                  }
                }
              } else {
                if(tcmapputkeep(nres, numbuf, pksiz, "", 0)) onum++;
              }
              if(uniq) tcmapputkeep(uniq, numbuf, pksiz, "", 0);
            }
          }
        }
      }
      BDBCUR *cur = tcbdbcurnew(db);
      tcbdbcurjump(cur, word, wsiz);
      TCXSTR *key = tcxstrnew();
      TCXSTR *val = tcxstrnew();
      while(tcbdbcurrec(cur, key, val)){
        const char *kbuf = TCXSTRPTR(key);
        int ksiz = TCXSTRSIZE(key);
        if(ksiz < wsiz || memcmp(kbuf, word, wsiz)) break;
        const char *cbuf = TCXSTRPTR(val);
        int csiz = TCXSTRSIZE(val);
        while(csiz > 0){
          const char *pkbuf = NULL;
          int32_t pksiz = 0;
          int64_t pkid = 0;
          if(*cbuf == '\0'){
            cbuf++;
            csiz--;
            int step;
            TCREADVNUMBUF(cbuf, pksiz, step);
            cbuf += step;
            csiz -= step;
            pkbuf = cbuf;
            cbuf += pksiz;
            csiz -= pksiz;
          } else {
            int step;
            TCREADVNUMBUF64(cbuf, pkid, step);
            cbuf += step;
            csiz -= step;
          }
          if(csiz > 0){
            int off, step;
            TCREADVNUMBUF(cbuf, off, step);
            cbuf += step;
            csiz -= step;
            if(pkbuf){
              if(ores){
                int rsiz;
                if(tcmapget(ores, pkbuf, pksiz, &rsiz)){
                  if(sign){
                    tcmapputkeep(nres, pkbuf, pksiz, "", 0);
                  } else {
                    tcmapout(ores, pkbuf, pksiz);
                  }
                }
              } else {
                if(tcmapputkeep(nres, pkbuf, pksiz, "", 0)) onum++;
              }
              if(uniq) tcmapputkeep(uniq, pkbuf, pksiz, "", 0);
            } else {
              char numbuf[TCNUMBUFSIZ];
              int pksiz = sprintf(numbuf, "%lld", (long long)pkid);
              if(ores){
                int rsiz;
                if(tcmapget(ores, numbuf, pksiz, &rsiz)){
                  if(sign){
                    tcmapputkeep(nres, numbuf, pksiz, "", 0);
                  } else {
                    tcmapout(ores, numbuf, pksiz);
                  }
                }
              } else {
                if(tcmapputkeep(nres, numbuf, pksiz, "", 0)) onum++;
              }
              if(uniq) tcmapputkeep(uniq, numbuf, pksiz, "", 0);
            }
          }
        }
        tcbdbcurnext(cur);
      }
      tcxstrdel(val);
      tcxstrdel(key);
      tcbdbcurdel(cur);
      tcxstrprintf(hint, "token occurrence: \"%s\" %d\n",
                   word, uniq ? (int)tcmaprnum(uniq) : onum);
      if(uniq) tcmapdel(uniq);
    }
    TCFREE(ary);
  }
}


/* Compare two string occurrences of full-text search in identical order.
   `a' specifies the pointer to one occurrence.
   `b' specifies the pointer to the other occurrence.
   The return value is positive if the former is big, negative if the latter is big, 0 if both
   are equivalent. */
static int tctdbidxftscmpstrocr(TDBFTSSTROCR *a, TDBFTSSTROCR *b){
  assert(a && b);
  if(a->pksiz > b->pksiz) return 1;
  if(a->pksiz < b->pksiz) return -1;
  int diff = memcmp(a->pkbuf, b->pkbuf, a->pksiz);
  if(diff != 0) return diff;
  return a->off - b->off;
}


/* Compare two number occurrences of full-text search in identical order.
   `a' specifies the pointer to one occurrence.
   `b' specifies the pointer to the other occurrence.
   The return value is positive if the former is big, negative if the latter is big, 0 if both
   are equivalent. */
static int tctdbidxftscmpnumocr(TDBFTSNUMOCR *a, TDBFTSNUMOCR *b){
  assert(a && b);
  if(a->pkid > b->pkid) return 1;
  if(a->pkid < b->pkid) return -1;
  return a->off - b->off;
}


/* Parse an expression of full-text search.
   `expr' specifies the expression.
   `esiz' specifies the size of the expression.
   `np' specifies the pointer to a variable into which the number of elements of the return value
   is assigned.
   `op' specifies the operation type.
   The return value is the pointer to the array of full-text search units. */
static TDBFTSUNIT *tctdbftsparseexpr(const char *expr, int esiz, int op, int *np){
  assert(expr && esiz >= 0 && np);
  TDBFTSUNIT *ftsunits;
  TCMALLOC(ftsunits, TDBFTSUNITMAX * sizeof(*ftsunits));
  int ftsnum = 0;
  uint16_t *ary;
  TCMALLOC(ary, sizeof(*ary) * esiz + 1);
  int anum;
  tcstrutftoucs(expr, ary, &anum);
  anum = tcstrucsnorm(ary, anum, TCUNSPACE | TCUNLOWER | TCUNNOACC | TCUNWIDTH);
  char *str;
  TCMALLOC(str, esiz + 1);
  tcstrucstoutf(ary, anum, str);
  if(op == TDBQCFTSPH){
    TCLIST *tokens = tclistnew2(1);
    tclistpush2(tokens, str);
    ftsunits[ftsnum].tokens = tokens;
    ftsunits[ftsnum].sign = true;
    ftsnum++;
  } else if(op == TDBQCFTSAND){
    TCLIST *tokens = tcstrsplit(expr, "\t\n\r ,");
    int tnum = TCLISTNUM(tokens);
    for(int i = 0; i < tnum && ftsnum < TDBFTSUNITMAX; i++){
      const char *token = TCLISTVALPTR(tokens, i);
      if(*token == '\0') continue;
      TCLIST *ttokens = tclistnew2(1);
      tclistpush2(ttokens, token);
      ftsunits[ftsnum].tokens = ttokens;
      ftsunits[ftsnum].sign = true;
      ftsnum++;
    }
    tclistdel(tokens);
  } else if(op == TDBQCFTSOR){
    TCLIST *tokens = tcstrsplit(expr, "\t\n\r ,");
    int tnum = TCLISTNUM(tokens);
    TCLIST *ttokens = tclistnew2(tnum);
    for(int i = 0; i < tnum; i++){
      const char *token = TCLISTVALPTR(tokens, i);
      if(*token == '\0') continue;
      tclistpush2(ttokens, token);
    }
    ftsunits[ftsnum].tokens = ttokens;
    ftsunits[ftsnum].sign = true;
    ftsnum++;
    tclistdel(tokens);
  } else if(op == TDBQCFTSEX){
    TCLIST *tokens = tcstrtokenize(str);
    int op = 0;
    for(int i = 0; i < tclistnum(tokens); i++){
      const char *token = TCLISTVALPTR(tokens, i);
      if(!strcmp(token, "&&")){
        op = 0;
      } else if(!strcmp(token, "||")){
        op = 1;
      } else if(!strcmp(token, "!!")){
        op = 2;
      } else {
        if(op == 0 || op == 2){
          if(ftsnum >= TDBFTSUNITMAX) break;
          TCLIST *ttokens = tclistnew2(2);
          tclistpush2(ttokens, token);
          ftsunits[ftsnum].tokens = ttokens;
          ftsunits[ftsnum].sign = op == 0;
          ftsnum++;
        } else if(op == 1){
          if(ftsnum < 1){
            ftsunits[ftsnum].tokens = tclistnew2(2);
            ftsunits[ftsnum].sign = op == 0;
            ftsnum++;
          }
          TCLIST *ttokens = ftsunits[ftsnum-1].tokens;
          tclistpush2(ttokens, token);
        }
        op = 0;
      }
    }
    tclistdel(tokens);
  }
  TCFREE(str);
  TCFREE(ary);
  *np = ftsnum;
  return ftsunits;
}


/* Perform dynamic defragmentation of a table database object.
   `tdb' specifies the table database object.
   `step' specifie the number of steps.
   If successful, the return value is true, else, it is false. */
static bool tctdbdefragimpl(TCTDB *tdb, int64_t step){
  assert(tdb);
  bool err = false;
  TCHDB *hdb = tdb->hdb;
  TDBIDX *idxs = tdb->idxs;
  int inum = tdb->inum;
  if(!tchdbdefrag(hdb, step)) err = true;
  for(int i = 0; i < inum; i++){
    TDBIDX *idx = idxs + i;
    switch(idx->type){
      case TDBITLEXICAL:
      case TDBITDECIMAL:
      case TDBITTOKEN:
      case TDBITQGRAM:
        if(!tcbdbdefrag(idx->db, step)){
          tctdbsetecode(tdb, tcbdbecode(idx->db), __FILE__, __LINE__, __func__);
          err = true;
        }
        break;
    }
  }
  return !err;
}


/* Clear the cache of a table tree database object.
   `tdb' specifies the table tree database object.
   If successful, the return value is true, else, it is false. */
static bool tctdbcacheclearimpl(TCTDB *tdb){
  assert(tdb);
  bool err = false;
  TCHDB *hdb = tdb->hdb;
  TDBIDX *idxs = tdb->idxs;
  int inum = tdb->inum;
  if(!tchdbcacheclear(hdb)) err = true;
  for(int i = 0; i < inum; i++){
    TDBIDX *idx = idxs + i;
    switch(idx->type){
      case TDBITLEXICAL:
      case TDBITDECIMAL:
      case TDBITTOKEN:
      case TDBITQGRAM:
        if(!tcbdbcacheclear(idx->db)){
          tctdbsetecode(tdb, tcbdbecode(idx->db), __FILE__, __LINE__, __func__);
          err = true;
        }
        break;
    }
  }
  return !err;
}


/* Process each record atomically of a table database object.
   `tdb' specifies the table database object.
   `func' specifies the pointer to the iterator function called for each record.
   `op' specifies an arbitrary pointer to be given as a parameter of the iterator function.
   If successful, the return value is true, else, it is false. */
static bool tctdbforeachimpl(TCTDB *tdb, TCITER iter, void *op){
  assert(tdb && iter);
  TCHDB *hdb = tdb->hdb;
  char *lkbuf = NULL;
  int lksiz = 0;
  char *pkbuf, stack[TDBPAGEBUFSIZ], *rbuf;
  int pksiz;
  const char *cbuf;
  int csiz;
  while((pkbuf = tchdbgetnext3(hdb, lkbuf, lksiz, &pksiz, &cbuf, &csiz)) != NULL){
    if(pksiz < TDBPAGEBUFSIZ){
      rbuf = stack;
    } else {
      TCMALLOC(rbuf, pksiz + 1);
    }
    memcpy(rbuf, pkbuf, pksiz);
    stack[pksiz] = '\0';
    TCMAP *cols = tcmapload(cbuf, csiz);
    int zsiz;
    char *zbuf = tcstrjoin4(cols, &zsiz);
    bool rv = iter(rbuf, pksiz, zbuf, zsiz, op);
    TCFREE(zbuf);
    if(rbuf != stack) TCFREE(rbuf);
    tcmapdel(cols);
    TCFREE(lkbuf);
    lkbuf = pkbuf;
    lksiz = pksiz;
    if(!rv) break;
  }
  TCFREE(lkbuf);
  return true;
}


/* Answer to remove for each record of a query.
   `pkbuf' is ignored.
   `pksiz' is ignored.
   `op' is ignored.
   The return value is always `TDBQPOUT'. */
static int tctdbqryprocoutcb(const void *pkbuf, int pksiz, TCMAP *cols, void *op){
  assert(pkbuf && pksiz >= 0 && cols);
  return TDBQPOUT;
}


/* Lock a method of the table database object.
   `tdb' specifies the table database object.
   `wr' specifies whether the lock is writer or not.
   If successful, the return value is true, else, it is false. */
static bool tctdblockmethod(TCTDB *tdb, bool wr){
  assert(tdb);
  if(wr ? pthread_rwlock_wrlock(tdb->mmtx) != 0 : pthread_rwlock_rdlock(tdb->mmtx) != 0){
    tctdbsetecode(tdb, TCETHREAD, __FILE__, __LINE__, __func__);
    return false;
  }
  TCTESTYIELD();
  return true;
}


/* Unlock a method of the table database object.
   `tdb' specifies the table database object.
   If successful, the return value is true, else, it is false. */
static bool tctdbunlockmethod(TCTDB *tdb){
  assert(tdb);
  if(pthread_rwlock_unlock(tdb->mmtx) != 0){
    tctdbsetecode(tdb, TCETHREAD, __FILE__, __LINE__, __func__);
    return false;
  }
  TCTESTYIELD();
  return true;
}



/*************************************************************************************************
 * debugging functions
 *************************************************************************************************/


/* Print meta data of the header into the debugging output.
   `tdb' specifies the table database object. */
void tctdbprintmeta(TCTDB *tdb){
  assert(tdb);
  int dbgfd = tchdbdbgfd(tdb->hdb);
  if(dbgfd < 0) return;
  if(dbgfd == UINT16_MAX) dbgfd = 1;
  char buf[TDBPAGEBUFSIZ];
  char *wp = buf;
  wp += sprintf(wp, "META:");
  wp += sprintf(wp, " mmtx=%p", (void *)tdb->mmtx);
  wp += sprintf(wp, " hdb=%p", (void *)tdb->hdb);
  wp += sprintf(wp, " open=%d", tdb->open);
  wp += sprintf(wp, " wmode=%d", tdb->wmode);
  wp += sprintf(wp, " opts=%u", tdb->opts);
  wp += sprintf(wp, " lcnum=%d", tdb->lcnum);
  wp += sprintf(wp, " ncnum=%d", tdb->ncnum);
  wp += sprintf(wp, " iccmax=%lld", (long long)tdb->iccmax);
  wp += sprintf(wp, " iccsync=%f", tdb->iccsync);
  wp += sprintf(wp, " idxs=%p", (void *)tdb->idxs);
  wp += sprintf(wp, " inum=%d", tdb->inum);
  wp += sprintf(wp, " tran=%d", tdb->tran);
  *(wp++) = '\n';
  tcwrite(dbgfd, buf, wp - buf);
}



// END OF FILE
/*************************************************************************************************
 * The abstract database API of Tokyo Cabinet
 *                                                               Copyright (C) 2006-2012 FAL Labs
 * This file is part of Tokyo Cabinet.
 * Tokyo Cabinet 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 any later version.  Tokyo Cabinet 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 Tokyo
 * Cabinet; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307 USA.
 *************************************************************************************************/


#include "tcutil.h"
#include "tchdb.h"
#include "tcbdb.h"
#include "tcfdb.h"
#include "tctdb.h"
#include "tcadb.h"
#include "myconf.h"

#define ADBDIRMODE     00755             // permission of created directories
#define ADBMULPREFIX   "adbmul-"         // prefix of multiple database files

typedef struct {                         // type of structure for multiple database
  TCADB **adbs;                          // inner database objects
  int num;                               // number of inner databases
  int iter;                              // index of the iterator
  char *path;                            // path of the base directory
} ADBMUL;

typedef struct {                         // type of structure for mapper to B+ tree database
  TCADB *adb;                            // source database object
  TCBDB *bdb;                            // destination database object
  TCLIST *recs;                          // cached records
  int64_t rsiz;                          // total size of cached records
  int64_t csiz;                          // capacity of cached records
  ADBMAPPROC proc;                       // mapping function
  void *op;                              // opaque object for the mapping function
} ADBMAPBDB;


/* private function prototypes */
static ADBMUL *tcadbmulnew(int num);
static void tcadbmuldel(ADBMUL *mul);
static bool tcadbmulopen(ADBMUL *mul, const char *name);
static bool tcadbmulclose(ADBMUL *mul);
static bool tcadbmulput(ADBMUL *mul, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
static bool tcadbmulputkeep(ADBMUL *mul, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
static bool tcadbmulputcat(ADBMUL *mul, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
static bool tcadbmulout(ADBMUL *mul, const void *kbuf, int ksiz);
static void *tcadbmulget(ADBMUL *mul, const void *kbuf, int ksiz, int *sp);
static int tcadbmulvsiz(ADBMUL *mul, const void *kbuf, int ksiz);
static bool tcadbmuliterinit(ADBMUL *mul);
static void *tcadbmuliternext(ADBMUL *mul, int *sp);
static TCLIST *tcadbmulfwmkeys(ADBMUL *mul, const void *pbuf, int psiz, int max);
static int tcadbmuladdint(ADBMUL *mul, const void *kbuf, int ksiz, int num);
static double tcadbmuladddouble(ADBMUL *mul, const void *kbuf, int ksiz, double num);
static bool tcadbmulsync(ADBMUL *mul);
static bool tcadbmuloptimize(ADBMUL *mul, const char *params);
static bool tcadbmulvanish(ADBMUL *mul);
static bool tcadbmulcopy(ADBMUL *mul, const char *path);
static bool tcadbmultranbegin(ADBMUL *mul);
static bool tcadbmultrancommit(ADBMUL *mul);
static bool tcadbmultranabort(ADBMUL *mul);
static const char *tcadbmulpath(ADBMUL *mul);
static uint64_t tcadbmulrnum(ADBMUL *mul);
static uint64_t tcadbmulsize(ADBMUL *mul);
static TCLIST *tcadbmulmisc(ADBMUL *mul, const char *name, const TCLIST *args);
static bool tcadbmulputproc(ADBMUL *mul, const void *kbuf, int ksiz, const void *vbuf, int vsiz,
                            TCPDPROC proc, void *op);
static bool tcadbmulforeach(ADBMUL *mul, TCITER iter, void *op);
static int tcadbmulidx(ADBMUL *mul, const void *kbuf, int ksiz);
static bool tcadbmapbdbiter(const void *kbuf, int ksiz, const void *vbuf, int vsiz, void *op);
static bool tcadbmapbdbdump(ADBMAPBDB *map);
static int tcadbmapreccmplexical(const TCLISTDATUM *a, const TCLISTDATUM *b);
static int tcadbmapreccmpdecimal(const TCLISTDATUM *a, const TCLISTDATUM *b);
static int tcadbmapreccmpint32(const TCLISTDATUM *a, const TCLISTDATUM *b);
static int tcadbmapreccmpint64(const TCLISTDATUM *a, const TCLISTDATUM *b);
static int tcadbtdbqrygetout(const void *pkbuf, int pksiz, TCMAP *cols, void *op);



/*************************************************************************************************
 * API
 *************************************************************************************************/


/* Create an abstract database object. */
TCADB *tcadbnew(void){
  TCADB *adb;
  TCMALLOC(adb, sizeof(*adb));
  adb->omode = ADBOVOID;
  adb->mdb = NULL;
  adb->ndb = NULL;
  adb->hdb = NULL;
  adb->bdb = NULL;
  adb->fdb = NULL;
  adb->tdb = NULL;
  adb->capnum = -1;
  adb->capsiz = -1;
  adb->capcnt = 0;
  adb->cur = NULL;
  adb->skel = NULL;
  return adb;
}


/* Delete an abstract database object. */
void tcadbdel(TCADB *adb){
  assert(adb);
  if(adb->omode != ADBOVOID) tcadbclose(adb);
  if(adb->skel){
    ADBSKEL *skel = adb->skel;
    if(skel->del) skel->del(skel->opq);
    TCFREE(skel);
  }
  TCFREE(adb);
}


/* Open an abstract database. */
bool tcadbopen(TCADB *adb, const char *name){
  assert(adb && name);
  if(adb->omode != ADBOVOID) return false;
  TCLIST *elems = tcstrsplit(name, "#");
  char *path = tclistshift2(elems);
  if(!path){
    tclistdel(elems);
    return false;
  }
  int dbgfd = -1;
  int64_t bnum = -1;
  int64_t capnum = -1;
  int64_t capsiz = -1;
  bool owmode = true;
  bool ocmode = true;
  bool otmode = false;
  bool onlmode = false;
  bool onbmode = false;
  int8_t apow = -1;
  int8_t fpow = -1;
  bool tlmode = false;
  bool tdmode = false;
  bool tbmode = false;
  bool ttmode = false;
  int32_t rcnum = -1;
  int64_t xmsiz = -1;
  int32_t dfunit = -1;
  int32_t lmemb = -1;
  int32_t nmemb = -1;
  int32_t lcnum = -1;
  int32_t ncnum = -1;
  int32_t width = -1;
  int64_t limsiz = -1;
  TCLIST *idxs = NULL;
  int ln = TCLISTNUM(elems);
  for(int i = 0; i < ln; i++){
    const char *elem = TCLISTVALPTR(elems, i);
    char *pv = strchr(elem, '=');
    if(!pv) continue;
    *(pv++) = '\0';
    if(!tcstricmp(elem, "dbgfd")){
      dbgfd = tcatoi(pv);
    } else if(!tcstricmp(elem, "bnum")){
      bnum = tcatoix(pv);
    } else if(!tcstricmp(elem, "capnum")){
      capnum = tcatoix(pv);
    } else if(!tcstricmp(elem, "capsiz")){
      capsiz = tcatoix(pv);
    } else if(!tcstricmp(elem, "mode")){
      owmode = strchr(pv, 'w') || strchr(pv, 'W');
      ocmode = strchr(pv, 'c') || strchr(pv, 'C');
      otmode = strchr(pv, 't') || strchr(pv, 'T');
      onlmode = strchr(pv, 'e') || strchr(pv, 'E');
      onbmode = strchr(pv, 'f') || strchr(pv, 'F');
    } else if(!tcstricmp(elem, "apow")){
      apow = tcatoix(pv);
    } else if(!tcstricmp(elem, "fpow")){
      fpow = tcatoix(pv);
    } else if(!tcstricmp(elem, "opts")){
      if(strchr(pv, 'l') || strchr(pv, 'L')) tlmode = true;
      if(strchr(pv, 'd') || strchr(pv, 'D')) tdmode = true;
      if(strchr(pv, 'b') || strchr(pv, 'B')) tbmode = true;
      if(strchr(pv, 't') || strchr(pv, 'T')) ttmode = true;
    } else if(!tcstricmp(elem, "rcnum")){
      rcnum = tcatoix(pv);
    } else if(!tcstricmp(elem, "xmsiz")){
      xmsiz = tcatoix(pv);
    } else if(!tcstricmp(elem, "dfunit")){
      dfunit = tcatoix(pv);
    } else if(!tcstricmp(elem, "lmemb")){
      lmemb = tcatoix(pv);
    } else if(!tcstricmp(elem, "nmemb")){
      nmemb = tcatoix(pv);
    } else if(!tcstricmp(elem, "lcnum")){
      lcnum = tcatoix(pv);
    } else if(!tcstricmp(elem, "ncnum")){
      ncnum = tcatoix(pv);
    } else if(!tcstricmp(elem, "width")){
      width = tcatoix(pv);
    } else if(!tcstricmp(elem, "limsiz")){
      limsiz = tcatoix(pv);
    } else if(!tcstricmp(elem, "idx")){
      if(!idxs) idxs = tclistnew();
      TCLISTPUSH(idxs, pv, strlen(pv));
    }
  }
  tclistdel(elems);
  adb->omode = ADBOVOID;
  if(adb->skel){
    ADBSKEL *skel = adb->skel;
    if(!skel->open || !skel->open(skel->opq, name)){
      if(idxs) tclistdel(idxs);
      TCFREE(path);
      return false;
    }
    adb->omode = ADBOSKEL;
  } else if(!tcstricmp(path, "*")){
    adb->mdb = bnum > 0 ? tcmdbnew2(bnum) : tcmdbnew();
    adb->capnum = capnum;
    adb->capsiz = capsiz;
    adb->capcnt = 0;
    adb->omode = ADBOMDB;
  } else if(!tcstricmp(path, "+")){
    adb->ndb = tcndbnew();
    adb->capnum = capnum;
    adb->capsiz = capsiz;
    adb->capcnt = 0;
    adb->omode = ADBONDB;
  } else if(tcstribwm(path, ".tch") || tcstribwm(path, ".hdb")){
    TCHDB *hdb = tchdbnew();
    if(dbgfd >= 0) tchdbsetdbgfd(hdb, dbgfd);
    tchdbsetmutex(hdb);
    int opts = 0;
    if(tlmode) opts |= HDBTLARGE;
    if(tdmode) opts |= HDBTDEFLATE;
    if(tbmode) opts |= HDBTBZIP;
    if(ttmode) opts |= HDBTTCBS;
    tchdbtune(hdb, bnum, apow, fpow, opts);
    tchdbsetcache(hdb, rcnum);
    if(xmsiz >= 0) tchdbsetxmsiz(hdb, xmsiz);
    if(dfunit >= 0) tchdbsetdfunit(hdb, dfunit);
    int omode = owmode ? HDBOWRITER : HDBOREADER;
    if(ocmode) omode |= HDBOCREAT;
    if(otmode) omode |= HDBOTRUNC;
    if(onlmode) omode |= HDBONOLCK;
    if(onbmode) omode |= HDBOLCKNB;
    if(!tchdbopen(hdb, path, omode)){
      tchdbdel(hdb);
      if(idxs) tclistdel(idxs);
      TCFREE(path);
      return false;
    }
    adb->hdb = hdb;
    adb->omode = ADBOHDB;
  } else if(tcstribwm(path, ".tcb") || tcstribwm(path, ".bdb")){
    TCBDB *bdb = tcbdbnew();
    if(dbgfd >= 0) tcbdbsetdbgfd(bdb, dbgfd);
    tcbdbsetmutex(bdb);
    int opts = 0;
    if(tlmode) opts |= BDBTLARGE;
    if(tdmode) opts |= BDBTDEFLATE;
    if(tbmode) opts |= BDBTBZIP;
    if(ttmode) opts |= BDBTTCBS;
    tcbdbtune(bdb, lmemb, nmemb, bnum, apow, fpow, opts);
    tcbdbsetcache(bdb, lcnum, ncnum);
    if(xmsiz >= 0) tcbdbsetxmsiz(bdb, xmsiz);
    if(dfunit >= 0) tcbdbsetdfunit(bdb, dfunit);
    if(capnum > 0) tcbdbsetcapnum(bdb, capnum);
    int omode = owmode ? BDBOWRITER : BDBOREADER;
    if(ocmode) omode |= BDBOCREAT;
    if(otmode) omode |= BDBOTRUNC;
    if(onlmode) omode |= BDBONOLCK;
    if(onbmode) omode |= BDBOLCKNB;
    if(!tcbdbopen(bdb, path, omode)){
      tcbdbdel(bdb);
      if(idxs) tclistdel(idxs);
      TCFREE(path);
      return false;
    }
    adb->bdb = bdb;
    adb->cur = tcbdbcurnew(bdb);
    adb->omode = ADBOBDB;
  } else if(tcstribwm(path, ".tcf") || tcstribwm(path, ".fdb")){
    TCFDB *fdb = tcfdbnew();
    if(dbgfd >= 0) tcfdbsetdbgfd(fdb, dbgfd);
    tcfdbsetmutex(fdb);
    tcfdbtune(fdb, width, limsiz);
    int omode = owmode ? FDBOWRITER : FDBOREADER;
    if(ocmode) omode |= FDBOCREAT;
    if(otmode) omode |= FDBOTRUNC;
    if(onlmode) omode |= FDBONOLCK;
    if(onbmode) omode |= FDBOLCKNB;
    if(!tcfdbopen(fdb, path, omode)){
      tcfdbdel(fdb);
      if(idxs) tclistdel(idxs);
      TCFREE(path);
      return false;
    }
    adb->fdb = fdb;
    adb->omode = ADBOFDB;
  } else if(tcstribwm(path, ".tct") || tcstribwm(path, ".tdb")){
    TCTDB *tdb = tctdbnew();
    if(dbgfd >= 0) tctdbsetdbgfd(tdb, dbgfd);
    tctdbsetmutex(tdb);
    int opts = 0;
    if(tlmode) opts |= TDBTLARGE;
    if(tdmode) opts |= TDBTDEFLATE;
    if(tbmode) opts |= TDBTBZIP;
    if(ttmode) opts |= TDBTTCBS;
    tctdbtune(tdb, bnum, apow, fpow, opts);
    tctdbsetcache(tdb, rcnum, lcnum, ncnum);
    if(xmsiz >= 0) tctdbsetxmsiz(tdb, xmsiz);
    if(dfunit >= 0) tctdbsetdfunit(tdb, dfunit);
    int omode = owmode ? TDBOWRITER : TDBOREADER;
    if(ocmode) omode |= TDBOCREAT;
    if(otmode) omode |= TDBOTRUNC;
    if(onlmode) omode |= TDBONOLCK;
    if(onbmode) omode |= TDBOLCKNB;
    if(!tctdbopen(tdb, path, omode)){
      tctdbdel(tdb);
      if(idxs) tclistdel(idxs);
      TCFREE(path);
      return false;
    }
    if(idxs){
      int xnum = TCLISTNUM(idxs);
      for(int i = 0; i < xnum; i++){
        const char *expr = TCLISTVALPTR(idxs, i);
        int type = TDBITLEXICAL;
        char *pv = strchr(expr, ':');
        if(pv){
          *(pv++) = '\0';
          type = tctdbstrtoindextype(pv);
        }
        if(type >= 0) tctdbsetindex(tdb, expr, type | TDBITKEEP);
      }
    }
    adb->tdb = tdb;
    adb->omode = ADBOTDB;
  }
  if(idxs) tclistdel(idxs);
  TCFREE(path);
  if(adb->omode == ADBOVOID) return false;
  return true;
}


/* Close an abstract database object. */
bool tcadbclose(TCADB *adb){
  assert(adb);
  int err = false;
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
      tcmdbdel(adb->mdb);
      adb->mdb = NULL;
      break;
    case ADBONDB:
      tcndbdel(adb->ndb);
      adb->ndb = NULL;
      break;
    case ADBOHDB:
      if(!tchdbclose(adb->hdb)) err = true;
      tchdbdel(adb->hdb);
      adb->hdb = NULL;
      break;
    case ADBOBDB:
      tcbdbcurdel(adb->cur);
      if(!tcbdbclose(adb->bdb)) err = true;
      tcbdbdel(adb->bdb);
      adb->bdb = NULL;
      break;
    case ADBOFDB:
      if(!tcfdbclose(adb->fdb)) err = true;
      tcfdbdel(adb->fdb);
      adb->fdb = NULL;
      break;
    case ADBOTDB:
      if(!tctdbclose(adb->tdb)) err = true;
      tctdbdel(adb->tdb);
      adb->tdb = NULL;
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->close){
        if(!skel->close(skel->opq)) err = true;
      } else {
        err = true;
      }
      break;
    default:
      err = true;
      break;
  }
  adb->omode = ADBOVOID;
  return !err;
}


/* Store a record into an abstract database object. */
bool tcadbput(TCADB *adb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(adb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  bool err = false;
  char numbuf[TCNUMBUFSIZ];
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
      if(adb->capnum > 0 || adb->capsiz > 0){
        tcmdbput3(adb->mdb, kbuf, ksiz, vbuf, vsiz);
        adb->capcnt++;
        if((adb->capcnt & 0xff) == 0){
          if(adb->capnum > 0 && tcmdbrnum(adb->mdb) > adb->capnum + 0x100)
            tcmdbcutfront(adb->mdb, 0x100);
          if(adb->capsiz > 0 && tcmdbmsiz(adb->mdb) > adb->capsiz)
            tcmdbcutfront(adb->mdb, 0x200);
        }
      } else {
        tcmdbput(adb->mdb, kbuf, ksiz, vbuf, vsiz);
      }
      break;
    case ADBONDB:
      tcndbput(adb->ndb, kbuf, ksiz, vbuf, vsiz);
      if(adb->capnum > 0 || adb->capsiz > 0){
        adb->capcnt++;
        if((adb->capcnt & 0xff) == 0){
          if(adb->capnum > 0 && tcndbrnum(adb->ndb) > adb->capnum + 0x100)
            tcndbcutfringe(adb->ndb, 0x100);
          if(adb->capsiz > 0 && tcndbmsiz(adb->ndb) > adb->capsiz)
            tcndbcutfringe(adb->ndb, 0x200);
        }
      }
      break;
    case ADBOHDB:
      if(!tchdbput(adb->hdb, kbuf, ksiz, vbuf, vsiz)) err = true;
      break;
    case ADBOBDB:
      if(!tcbdbput(adb->bdb, kbuf, ksiz, vbuf, vsiz)) err = true;
      break;
    case ADBOFDB:
      if(!tcfdbput2(adb->fdb, kbuf, ksiz, vbuf, vsiz)) err = true;
      break;
    case ADBOTDB:
      if(ksiz < 1){
        ksiz = sprintf(numbuf, "%lld", (long long)tctdbgenuid(adb->tdb));
        kbuf = numbuf;
      }
      if(!tctdbput2(adb->tdb, kbuf, ksiz, vbuf, vsiz)) err = true;
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->put){
        if(!skel->put(skel->opq, kbuf, ksiz, vbuf, vsiz)) err = true;
      } else {
        err = true;
      }
      break;
    default:
      err = true;
      break;
  }
  return !err;
}


/* Store a string record into an abstract object. */
bool tcadbput2(TCADB *adb, const char *kstr, const char *vstr){
  assert(adb && kstr && vstr);
  return tcadbput(adb, kstr, strlen(kstr), vstr, strlen(vstr));
}


/* Store a new record into an abstract database object. */
bool tcadbputkeep(TCADB *adb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(adb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  bool err = false;
  char numbuf[TCNUMBUFSIZ];
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
      if(tcmdbputkeep(adb->mdb, kbuf, ksiz, vbuf, vsiz)){
        if(adb->capnum > 0 || adb->capsiz > 0){
          adb->capcnt++;
          if((adb->capcnt & 0xff) == 0){
            if(adb->capnum > 0 && tcmdbrnum(adb->mdb) > adb->capnum + 0x100)
              tcmdbcutfront(adb->mdb, 0x100);
            if(adb->capsiz > 0 && tcmdbmsiz(adb->mdb) > adb->capsiz)
              tcmdbcutfront(adb->mdb, 0x200);
          }
        }
      } else {
        err = true;
      }
      break;
    case ADBONDB:
      if(tcndbputkeep(adb->ndb, kbuf, ksiz, vbuf, vsiz)){
        if(adb->capnum > 0 || adb->capsiz > 0){
          adb->capcnt++;
          if((adb->capcnt & 0xff) == 0){
            if(adb->capnum > 0 && tcndbrnum(adb->ndb) > adb->capnum + 0x100)
              tcndbcutfringe(adb->ndb, 0x100);
            if(adb->capsiz > 0 && tcndbmsiz(adb->ndb) > adb->capsiz)
              tcndbcutfringe(adb->ndb, 0x200);
          }
        }
      } else {
        err = true;
      }
      break;
    case ADBOHDB:
      if(!tchdbputkeep(adb->hdb, kbuf, ksiz, vbuf, vsiz)) err = true;
      break;
    case ADBOBDB:
      if(!tcbdbputkeep(adb->bdb, kbuf, ksiz, vbuf, vsiz)) err = true;
      break;
    case ADBOFDB:
      if(!tcfdbputkeep2(adb->fdb, kbuf, ksiz, vbuf, vsiz)) err = true;
      break;
    case ADBOTDB:
      if(ksiz < 1){
        ksiz = sprintf(numbuf, "%lld", (long long)tctdbgenuid(adb->tdb));
        kbuf = numbuf;
      }
      if(!tctdbputkeep2(adb->tdb, kbuf, ksiz, vbuf, vsiz)) err = true;
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->putkeep){
        if(!skel->putkeep(skel->opq, kbuf, ksiz, vbuf, vsiz)) err = true;
      } else {
        err = true;
      }
      break;
    default:
      err = true;
      break;
  }
  return !err;
}


/* Store a new string record into an abstract database object. */
bool tcadbputkeep2(TCADB *adb, const char *kstr, const char *vstr){
  assert(adb && kstr && vstr);
  return tcadbputkeep(adb, kstr, strlen(kstr), vstr, strlen(vstr));
}


/* Concatenate a value at the end of the existing record in an abstract database object. */
bool tcadbputcat(TCADB *adb, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(adb && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  bool err = false;
  char numbuf[TCNUMBUFSIZ];
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
      if(adb->capnum > 0 || adb->capsiz > 0){
        tcmdbputcat3(adb->mdb, kbuf, ksiz, vbuf, vsiz);
        adb->capcnt++;
        if((adb->capcnt & 0xff) == 0){
          if(adb->capnum > 0 && tcmdbrnum(adb->mdb) > adb->capnum + 0x100)
            tcmdbcutfront(adb->mdb, 0x100);
          if(adb->capsiz > 0 && tcmdbmsiz(adb->mdb) > adb->capsiz)
            tcmdbcutfront(adb->mdb, 0x200);
        }
      } else {
        tcmdbputcat(adb->mdb, kbuf, ksiz, vbuf, vsiz);
      }
      break;
    case ADBONDB:
      tcndbputcat(adb->ndb, kbuf, ksiz, vbuf, vsiz);
      if(adb->capnum > 0 || adb->capsiz > 0){
        adb->capcnt++;
        if((adb->capcnt & 0xff) == 0){
          if(adb->capnum > 0 && tcndbrnum(adb->ndb) > adb->capnum + 0x100)
            tcndbcutfringe(adb->ndb, 0x100);
          if(adb->capsiz > 0 && tcndbmsiz(adb->ndb) > adb->capsiz)
            tcndbcutfringe(adb->ndb, 0x200);
        }
      }
      break;
    case ADBOHDB:
      if(!tchdbputcat(adb->hdb, kbuf, ksiz, vbuf, vsiz)) err = true;
      break;
    case ADBOBDB:
      if(!tcbdbputcat(adb->bdb, kbuf, ksiz, vbuf, vsiz)) err = true;
      break;
    case ADBOFDB:
      if(!tcfdbputcat2(adb->fdb, kbuf, ksiz, vbuf, vsiz)) err = true;
      break;
    case ADBOTDB:
      if(ksiz < 1){
        ksiz = sprintf(numbuf, "%lld", (long long)tctdbgenuid(adb->tdb));
        kbuf = numbuf;
      }
      if(!tctdbputcat2(adb->tdb, kbuf, ksiz, vbuf, vsiz)) err = true;
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->putcat){
        if(!skel->putcat(skel->opq, kbuf, ksiz, vbuf, vsiz)) err = true;
      } else {
        err = true;
      }
      break;
    default:
      err = true;
      break;
  }
  return !err;
}


/* Concatenate a string value at the end of the existing record in an abstract database object. */
bool tcadbputcat2(TCADB *adb, const char *kstr, const char *vstr){
  assert(adb && kstr && vstr);
  return tcadbputcat(adb, kstr, strlen(kstr), vstr, strlen(vstr));
}


/* Remove a record of an abstract database object. */
bool tcadbout(TCADB *adb, const void *kbuf, int ksiz){
  assert(adb && kbuf && ksiz >= 0);
  bool err = false;
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
      if(!tcmdbout(adb->mdb, kbuf, ksiz)) err = true;
      break;
    case ADBONDB:
      if(!tcndbout(adb->ndb, kbuf, ksiz)) err = true;
      break;
    case ADBOHDB:
      if(!tchdbout(adb->hdb, kbuf, ksiz)) err = true;
      break;
    case ADBOBDB:
      if(!tcbdbout(adb->bdb, kbuf, ksiz)) err = true;
      break;
    case ADBOFDB:
      if(!tcfdbout2(adb->fdb, kbuf, ksiz)) err = true;
      break;
    case ADBOTDB:
      if(!tctdbout(adb->tdb, kbuf, ksiz)) err = true;
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->out){
        if(!skel->out(skel->opq, kbuf, ksiz)) err = true;
      } else {
        err = true;
      }
      break;
    default:
      err = true;
      break;
  }
  return !err;
}


/* Remove a string record of an abstract database object. */
bool tcadbout2(TCADB *adb, const char *kstr){
  assert(adb && kstr);
  return tcadbout(adb, kstr, strlen(kstr));
}


/* Retrieve a record in an abstract database object. */
void *tcadbget(TCADB *adb, const void *kbuf, int ksiz, int *sp){
  assert(adb && kbuf && ksiz >= 0 && sp);
  char *rv;
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
      rv = tcmdbget(adb->mdb, kbuf, ksiz, sp);
      break;
    case ADBONDB:
      rv = tcndbget(adb->ndb, kbuf, ksiz, sp);
      break;
    case ADBOHDB:
      rv = tchdbget(adb->hdb, kbuf, ksiz, sp);
      break;
    case ADBOBDB:
      rv = tcbdbget(adb->bdb, kbuf, ksiz, sp);
      break;
    case ADBOFDB:
      rv = tcfdbget2(adb->fdb, kbuf, ksiz, sp);
      break;
    case ADBOTDB:
      rv = tctdbget2(adb->tdb, kbuf, ksiz, sp);
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->get){
        rv = skel->get(skel->opq, kbuf, ksiz, sp);
      } else {
        rv = NULL;
      }
      break;
    default:
      rv = NULL;
      break;
  }
  return rv;
}


/* Retrieve a string record in an abstract database object. */
char *tcadbget2(TCADB *adb, const char *kstr){
  assert(adb && kstr);
  int vsiz;
  return tcadbget(adb, kstr, strlen(kstr), &vsiz);
}


/* Get the size of the value of a record in an abstract database object. */
int tcadbvsiz(TCADB *adb, const void *kbuf, int ksiz){
  assert(adb && kbuf && ksiz >= 0);
  int rv;
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
      rv = tcmdbvsiz(adb->mdb, kbuf, ksiz);
      break;
    case ADBONDB:
      rv = tcndbvsiz(adb->ndb, kbuf, ksiz);
      break;
    case ADBOHDB:
      rv = tchdbvsiz(adb->hdb, kbuf, ksiz);
      break;
    case ADBOBDB:
      rv = tcbdbvsiz(adb->bdb, kbuf, ksiz);
      break;
    case ADBOFDB:
      rv = tcfdbvsiz2(adb->fdb, kbuf, ksiz);
      break;
    case ADBOTDB:
      rv = tctdbvsiz(adb->tdb, kbuf, ksiz);
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->vsiz){
        rv = skel->vsiz(skel->opq, kbuf, ksiz);
      } else {
        rv = -1;
      }
      break;
    default:
      rv = -1;
      break;
  }
  return rv;
}


/* Get the size of the value of a string record in an abstract database object. */
int tcadbvsiz2(TCADB *adb, const char *kstr){
  assert(adb && kstr);
  return tcadbvsiz(adb, kstr, strlen(kstr));
}


/* Initialize the iterator of an abstract database object. */
bool tcadbiterinit(TCADB *adb){
  assert(adb);
  bool err = false;
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
      tcmdbiterinit(adb->mdb);
      break;
    case ADBONDB:
      tcndbiterinit(adb->ndb);
      break;
    case ADBOHDB:
      if(!tchdbiterinit(adb->hdb)) err = true;
      break;
    case ADBOBDB:
      if(!tcbdbcurfirst(adb->cur)){
        int ecode = tcbdbecode(adb->bdb);
        if(ecode != TCESUCCESS && ecode != TCEINVALID && ecode != TCEKEEP && ecode != TCENOREC)
          err = true;
      }
      break;
    case ADBOFDB:
      if(!tcfdbiterinit(adb->fdb)) err = true;
      break;
    case ADBOTDB:
      if(!tctdbiterinit(adb->tdb)) err = true;
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->iterinit){
        if(!skel->iterinit(skel->opq)) err = true;
      } else {
        err = true;
      }
      break;
    default:
      err = true;
      break;
  }
  return !err;
}


/* Get the next key of the iterator of an abstract database object. */
void *tcadbiternext(TCADB *adb, int *sp){
  assert(adb && sp);
  char *rv;
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
      rv = tcmdbiternext(adb->mdb, sp);
      break;
    case ADBONDB:
      rv = tcndbiternext(adb->ndb, sp);
      break;
    case ADBOHDB:
      rv = tchdbiternext(adb->hdb, sp);
      break;
    case ADBOBDB:
      rv = tcbdbcurkey(adb->cur, sp);
      tcbdbcurnext(adb->cur);
      break;
    case ADBOFDB:
      rv = tcfdbiternext2(adb->fdb, sp);
      break;
    case ADBOTDB:
      rv = tctdbiternext(adb->tdb, sp);
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->iternext){
        rv = skel->iternext(skel->opq, sp);
      } else {
        rv = NULL;
      }
      break;
    default:
      rv = NULL;
      break;
  }
  return rv;
}


/* Get the next key string of the iterator of an abstract database object. */
char *tcadbiternext2(TCADB *adb){
  assert(adb);
  int vsiz;
  return tcadbiternext(adb, &vsiz);
}


/* Get forward matching keys in an abstract database object. */
TCLIST *tcadbfwmkeys(TCADB *adb, const void *pbuf, int psiz, int max){
  assert(adb && pbuf && psiz >= 0);
  TCLIST *rv;
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
      rv = tcmdbfwmkeys(adb->mdb, pbuf, psiz, max);
      break;
    case ADBONDB:
      rv = tcndbfwmkeys(adb->ndb, pbuf, psiz, max);
      break;
    case ADBOHDB:
      rv = tchdbfwmkeys(adb->hdb, pbuf, psiz, max);
      break;
    case ADBOBDB:
      rv = tcbdbfwmkeys(adb->bdb, pbuf, psiz, max);
      break;
    case ADBOFDB:
      rv = tcfdbrange4(adb->fdb, pbuf, psiz, max);
      break;
    case ADBOTDB:
      rv = tctdbfwmkeys(adb->tdb, pbuf, psiz, max);
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->fwmkeys){
        rv = skel->fwmkeys(skel->opq, pbuf, psiz, max);
      } else {
        rv = NULL;
      }
      break;
    default:
      rv = tclistnew();
      break;
  }
  return rv;
}


/* Get forward matching string keys in an abstract database object. */
TCLIST *tcadbfwmkeys2(TCADB *adb, const char *pstr, int max){
  assert(adb && pstr);
  return tcadbfwmkeys(adb, pstr, strlen(pstr), max);
}


/* Add an integer to a record in an abstract database object. */
int tcadbaddint(TCADB *adb, const void *kbuf, int ksiz, int num){
  assert(adb && kbuf && ksiz >= 0);
  int rv;
  char numbuf[TCNUMBUFSIZ];
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
      rv = tcmdbaddint(adb->mdb, kbuf, ksiz, num);
      if(adb->capnum > 0 || adb->capsiz > 0){
        adb->capcnt++;
        if((adb->capcnt & 0xff) == 0){
          if(adb->capnum > 0 && tcmdbrnum(adb->mdb) > adb->capnum + 0x100)
            tcmdbcutfront(adb->mdb, 0x100);
          if(adb->capsiz > 0 && tcmdbmsiz(adb->mdb) > adb->capsiz)
            tcmdbcutfront(adb->mdb, 0x200);
        }
      }
      break;
    case ADBONDB:
      rv = tcndbaddint(adb->ndb, kbuf, ksiz, num);
      if(adb->capnum > 0 || adb->capsiz > 0){
        adb->capcnt++;
        if((adb->capcnt & 0xff) == 0){
          if(adb->capnum > 0 && tcndbrnum(adb->ndb) > adb->capnum + 0x100)
            tcndbcutfringe(adb->ndb, 0x100);
          if(adb->capsiz > 0 && tcndbmsiz(adb->ndb) > adb->capsiz)
            tcndbcutfringe(adb->ndb, 0x200);
        }
      }
      break;
    case ADBOHDB:
      rv = tchdbaddint(adb->hdb, kbuf, ksiz, num);
      break;
    case ADBOBDB:
      rv = tcbdbaddint(adb->bdb, kbuf, ksiz, num);
      break;
    case ADBOFDB:
      rv = tcfdbaddint(adb->fdb, tcfdbkeytoid(kbuf, ksiz), num);
      break;
    case ADBOTDB:
      if(ksiz < 1){
        ksiz = sprintf(numbuf, "%lld", (long long)tctdbgenuid(adb->tdb));
        kbuf = numbuf;
      }
      rv = tctdbaddint(adb->tdb, kbuf, ksiz, num);
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->addint){
        rv = skel->addint(skel->opq, kbuf, ksiz, num);
      } else {
        rv = INT_MIN;
      }
      break;
    default:
      rv = INT_MIN;
      break;
  }
  return rv;
}


/* Add a real number to a record in an abstract database object. */
double tcadbadddouble(TCADB *adb, const void *kbuf, int ksiz, double num){
  assert(adb && kbuf && ksiz >= 0);
  double rv;
  char numbuf[TCNUMBUFSIZ];
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
      rv = tcmdbadddouble(adb->mdb, kbuf, ksiz, num);
      if(adb->capnum > 0 || adb->capsiz > 0){
        adb->capcnt++;
        if((adb->capcnt & 0xff) == 0){
          if(adb->capnum > 0 && tcmdbrnum(adb->mdb) > adb->capnum + 0x100)
            tcmdbcutfront(adb->mdb, 0x100);
          if(adb->capsiz > 0 && tcmdbmsiz(adb->mdb) > adb->capsiz)
            tcmdbcutfront(adb->mdb, 0x200);
        }
      }
      break;
    case ADBONDB:
      rv = tcndbadddouble(adb->ndb, kbuf, ksiz, num);
      if(adb->capnum > 0 || adb->capsiz > 0){
        adb->capcnt++;
        if((adb->capcnt & 0xff) == 0){
          if(adb->capnum > 0 && tcndbrnum(adb->ndb) > adb->capnum + 0x100)
            tcndbcutfringe(adb->ndb, 0x100);
          if(adb->capsiz > 0 && tcndbmsiz(adb->ndb) > adb->capsiz)
            tcndbcutfringe(adb->ndb, 0x200);
        }
      }
      break;
    case ADBOHDB:
      rv = tchdbadddouble(adb->hdb, kbuf, ksiz, num);
      break;
    case ADBOBDB:
      rv = tcbdbadddouble(adb->bdb, kbuf, ksiz, num);
      break;
    case ADBOFDB:
      rv = tcfdbadddouble(adb->fdb, tcfdbkeytoid(kbuf, ksiz), num);
      break;
    case ADBOTDB:
      if(ksiz < 1){
        ksiz = sprintf(numbuf, "%lld", (long long)tctdbgenuid(adb->tdb));
        kbuf = numbuf;
      }
      rv = tctdbadddouble(adb->tdb, kbuf, ksiz, num);
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->adddouble){
        rv = skel->adddouble(skel->opq, kbuf, ksiz, num);
      } else {
        rv = nan("");
      }
      break;
    default:
      rv = nan("");
      break;
  }
  return rv;
}


/* Synchronize updated contents of an abstract database object with the file and the device. */
bool tcadbsync(TCADB *adb){
  assert(adb);
  bool err = false;
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
      if(adb->capnum > 0){
        while(tcmdbrnum(adb->mdb) > adb->capnum){
          tcmdbcutfront(adb->mdb, 1);
        }
      }
      if(adb->capsiz > 0){
        while(tcmdbmsiz(adb->mdb) > adb->capsiz && tcmdbrnum(adb->mdb) > 0){
          tcmdbcutfront(adb->mdb, 1);
        }
      }
      adb->capcnt = 0;
      break;
    case ADBONDB:
      if(adb->capnum > 0 && tcndbrnum(adb->ndb) > adb->capnum)
        tcndbcutfringe(adb->ndb, tcndbrnum(adb->ndb) - adb->capnum);
      if(adb->capsiz > 0){
        while(tcndbmsiz(adb->ndb) > adb->capsiz && tcndbrnum(adb->ndb) > 0){
          tcndbcutfringe(adb->ndb, 0x100);
        }
      }
      adb->capcnt = 0;
      break;
    case ADBOHDB:
      if(!tchdbsync(adb->hdb)) err = true;
      break;
    case ADBOBDB:
      if(!tcbdbsync(adb->bdb)) err = true;
      break;
    case ADBOFDB:
      if(!tcfdbsync(adb->fdb)) err = true;
      break;
    case ADBOTDB:
      if(!tctdbsync(adb->tdb)) err = true;
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->sync){
        if(!skel->sync(skel->opq)) err = true;
      } else {
        err = true;
      }
      break;
    default:
      err = true;
      break;
  }
  return !err;
}


/* Optimize the storage of an abstract database object. */
bool tcadboptimize(TCADB *adb, const char *params){
  assert(adb);
  TCLIST *elems = params ? tcstrsplit(params, "#") : tclistnew();
  int64_t bnum = -1;
  int64_t capnum = -1;
  int64_t capsiz = -1;
  int8_t apow = -1;
  int8_t fpow = -1;
  bool tdefault = true;
  bool tlmode = false;
  bool tdmode = false;
  bool tbmode = false;
  bool ttmode = false;
  int32_t lmemb = -1;
  int32_t nmemb = -1;
  int32_t width = -1;
  int64_t limsiz = -1;
  int ln = TCLISTNUM(elems);
  for(int i = 0; i < ln; i++){
    const char *elem = TCLISTVALPTR(elems, i);
    char *pv = strchr(elem, '=');
    if(!pv) continue;
    *(pv++) = '\0';
    if(!tcstricmp(elem, "bnum")){
      bnum = tcatoix(pv);
    } else if(!tcstricmp(elem, "capnum")){
      capnum = tcatoix(pv);
    } else if(!tcstricmp(elem, "capsiz")){
      capsiz = tcatoix(pv);
    } else if(!tcstricmp(elem, "apow")){
      apow = tcatoix(pv);
    } else if(!tcstricmp(elem, "fpow")){
      fpow = tcatoix(pv);
    } else if(!tcstricmp(elem, "opts")){
      tdefault = false;
      if(strchr(pv, 'l') || strchr(pv, 'L')) tlmode = true;
      if(strchr(pv, 'd') || strchr(pv, 'D')) tdmode = true;
      if(strchr(pv, 'b') || strchr(pv, 'B')) tbmode = true;
      if(strchr(pv, 't') || strchr(pv, 'T')) ttmode = true;
    } else if(!tcstricmp(elem, "lmemb")){
      lmemb = tcatoix(pv);
    } else if(!tcstricmp(elem, "nmemb")){
      nmemb = tcatoix(pv);
    } else if(!tcstricmp(elem, "width")){
      width = tcatoix(pv);
    } else if(!tcstricmp(elem, "limsiz")){
      limsiz = tcatoix(pv);
    }
  }
  tclistdel(elems);
  bool err = false;
  int opts;
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
      adb->capnum = capnum;
      adb->capsiz = capsiz;
      tcadbsync(adb);
      break;
    case ADBONDB:
      adb->capnum = capnum;
      adb->capsiz = capsiz;
      tcadbsync(adb);
      break;
    case ADBOHDB:
      opts = 0;
      if(tdefault){
        opts = UINT8_MAX;
      } else {
        if(tlmode) opts |= HDBTLARGE;
        if(tdmode) opts |= HDBTDEFLATE;
        if(tbmode) opts |= HDBTBZIP;
        if(ttmode) opts |= HDBTTCBS;
      }
      if(!tchdboptimize(adb->hdb, bnum, apow, fpow, opts)) err = true;
      break;
    case ADBOBDB:
      opts = 0;
      if(tdefault){
        opts = UINT8_MAX;
      } else {
        if(tlmode) opts |= BDBTLARGE;
        if(tdmode) opts |= BDBTDEFLATE;
        if(tbmode) opts |= BDBTBZIP;
        if(ttmode) opts |= BDBTTCBS;
      }
      if(!tcbdboptimize(adb->bdb, lmemb, nmemb, bnum, apow, fpow, opts)) err = true;
      break;
    case ADBOFDB:
      if(!tcfdboptimize(adb->fdb, width, limsiz)) err = true;
      break;
    case ADBOTDB:
      opts = 0;
      if(tdefault){
        opts = UINT8_MAX;
      } else {
        if(tlmode) opts |= TDBTLARGE;
        if(tdmode) opts |= TDBTDEFLATE;
        if(tbmode) opts |= TDBTBZIP;
        if(ttmode) opts |= TDBTTCBS;
      }
      if(!tctdboptimize(adb->tdb, bnum, apow, fpow, opts)) err = true;
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->optimize){
        if(!skel->optimize(skel->opq, params)) err = true;
      } else {
        err = true;
      }
      break;
    default:
      err = true;
      break;
  }
  return !err;
}


/* Remove all records of an abstract database object. */
bool tcadbvanish(TCADB *adb){
  assert(adb);
  bool err = false;
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
      tcmdbvanish(adb->mdb);
      break;
    case ADBONDB:
      tcndbvanish(adb->ndb);
      break;
    case ADBOHDB:
      if(!tchdbvanish(adb->hdb)) err = true;
      break;
    case ADBOBDB:
      if(!tcbdbvanish(adb->bdb)) err = true;
      break;
    case ADBOFDB:
      if(!tcfdbvanish(adb->fdb)) err = true;
      break;
    case ADBOTDB:
      if(!tctdbvanish(adb->tdb)) err = true;
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->vanish){
        if(!skel->vanish(skel->opq)) err = true;
      } else {
        err = true;
      }
      break;
    default:
      err = true;
      break;
  }
  return !err;
}


/* Copy the database file of an abstract database object. */
bool tcadbcopy(TCADB *adb, const char *path){
  assert(adb && path);
  bool err = false;
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
    case ADBONDB:
      if(*path == '@'){
        char tsbuf[TCNUMBUFSIZ];
        sprintf(tsbuf, "%llu", (unsigned long long)(tctime() * 1000000));
        const char *args[2];
        args[0] = path + 1;
        args[1] = tsbuf;
        if(tcsystem(args, sizeof(args) / sizeof(*args)) != 0) err = true;
      } else {
        TCADB *tadb = tcadbnew();
        if(tcadbopen(tadb, path)){
          tcadbiterinit(adb);
          char *kbuf;
          int ksiz;
          while((kbuf = tcadbiternext(adb, &ksiz)) != NULL){
            int vsiz;
            char *vbuf = tcadbget(adb, kbuf, ksiz, &vsiz);
            if(vbuf){
              if(!tcadbput(tadb, kbuf, ksiz, vbuf, vsiz)) err = true;
              TCFREE(vbuf);
            }
            TCFREE(kbuf);
          }
          if(!tcadbclose(tadb)) err = true;
        } else {
          err = true;
        }
        tcadbdel(tadb);
      }
      break;
    case ADBOHDB:
      if(!tchdbcopy(adb->hdb, path)) err = true;
      break;
    case ADBOBDB:
      if(!tcbdbcopy(adb->bdb, path)) err = true;
      break;
    case ADBOFDB:
      if(!tcfdbcopy(adb->fdb, path)) err = true;
      break;
    case ADBOTDB:
      if(!tctdbcopy(adb->tdb, path)) err = true;
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->copy){
        if(!skel->copy(skel->opq, path)) err = true;
      } else {
        err = true;
      }
      break;
    default:
      err = true;
      break;
  }
  return !err;
}


/* Begin the transaction of an abstract database object. */
bool tcadbtranbegin(TCADB *adb){
  assert(adb);
  bool err = false;
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
      err = true;
      break;
    case ADBONDB:
      err = true;
      break;
    case ADBOHDB:
      if(!tchdbtranbegin(adb->hdb)) err = true;
      break;
    case ADBOBDB:
      if(!tcbdbtranbegin(adb->bdb)) err = true;
      break;
    case ADBOFDB:
      if(!tcfdbtranbegin(adb->fdb)) err = true;
      break;
    case ADBOTDB:
      if(!tctdbtranbegin(adb->tdb)) err = true;
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->tranbegin){
        if(!skel->tranbegin(skel->opq)) err = true;
      } else {
        err = true;
      }
      break;
    default:
      err = true;
      break;
  }
  return !err;
}


/* Commit the transaction of an abstract database object. */
bool tcadbtrancommit(TCADB *adb){
  assert(adb);
  bool err = false;
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
      err = true;
      break;
    case ADBONDB:
      err = true;
      break;
    case ADBOHDB:
      if(!tchdbtrancommit(adb->hdb)) err = true;
      break;
    case ADBOBDB:
      if(!tcbdbtrancommit(adb->bdb)) err = true;
      break;
    case ADBOFDB:
      if(!tcfdbtrancommit(adb->fdb)) err = true;
      break;
    case ADBOTDB:
      if(!tctdbtrancommit(adb->tdb)) err = true;
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->trancommit){
        if(!skel->trancommit(skel->opq)) err = true;
      } else {
        err = true;
      }
      break;
    default:
      err = true;
      break;
  }
  return !err;
}


/* Abort the transaction of an abstract database object. */
bool tcadbtranabort(TCADB *adb){
  assert(adb);
  bool err = false;
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
      err = true;
      break;
    case ADBONDB:
      err = true;
      break;
    case ADBOHDB:
      if(!tchdbtranabort(adb->hdb)) err = true;
      break;
    case ADBOBDB:
      if(!tcbdbtranabort(adb->bdb)) err = true;
      break;
    case ADBOFDB:
      if(!tcfdbtranabort(adb->fdb)) err = true;
      break;
    case ADBOTDB:
      if(!tctdbtranabort(adb->tdb)) err = true;
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->tranabort){
        if(!skel->tranabort(skel->opq)) err = true;
      } else {
        err = true;
      }
      break;
    default:
      err = true;
      break;
  }
  return !err;
}


/* Get the file path of an abstract database object. */
const char *tcadbpath(TCADB *adb){
  assert(adb);
  const char *rv;
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
      rv = "*";
      break;
    case ADBONDB:
      rv = "+";
      break;
    case ADBOHDB:
      rv = tchdbpath(adb->hdb);
      break;
    case ADBOBDB:
      rv = tcbdbpath(adb->bdb);
      break;
    case ADBOFDB:
      rv = tcfdbpath(adb->fdb);
      break;
    case ADBOTDB:
      rv = tctdbpath(adb->tdb);
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->path){
        rv = skel->path(skel->opq);
      } else {
        rv = NULL;
      }
      break;
    default:
      rv = NULL;
      break;
  }
  return rv;
}


/* Get the number of records of an abstract database object. */
uint64_t tcadbrnum(TCADB *adb){
  assert(adb);
  uint64_t rv;
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
      rv = tcmdbrnum(adb->mdb);
      break;
    case ADBONDB:
      rv = tcndbrnum(adb->ndb);
      break;
    case ADBOHDB:
      rv = tchdbrnum(adb->hdb);
      break;
    case ADBOBDB:
      rv = tcbdbrnum(adb->bdb);
      break;
    case ADBOFDB:
      rv = tcfdbrnum(adb->fdb);
      break;
    case ADBOTDB:
      rv = tctdbrnum(adb->tdb);
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->rnum){
        rv = skel->rnum(skel->opq);
      } else {
        rv = 0;
      }
      break;
    default:
      rv = 0;
      break;
  }
  return rv;
}


/* Get the size of the database of an abstract database object. */
uint64_t tcadbsize(TCADB *adb){
  assert(adb);
  uint64_t rv;
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
      rv = tcmdbmsiz(adb->mdb);
      break;
    case ADBONDB:
      rv = tcndbmsiz(adb->ndb);
      break;
    case ADBOHDB:
      rv = tchdbfsiz(adb->hdb);
      break;
    case ADBOBDB:
      rv = tcbdbfsiz(adb->bdb);
      break;
    case ADBOFDB:
      rv = tcfdbfsiz(adb->fdb);
      break;
    case ADBOTDB:
      rv = tctdbfsiz(adb->tdb);
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->size){
        rv = skel->size(skel->opq);
      } else {
        rv = 0;
      }
      break;
    default:
      rv = 0;
      break;
  }
  return rv;
}


/* Call a versatile function for miscellaneous operations of an abstract database object. */
TCLIST *tcadbmisc(TCADB *adb, const char *name, const TCLIST *args){
  assert(adb && name && args);
  int argc = TCLISTNUM(args);
  TCLIST *rv;
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
      if(!strcmp(name, "put") || !strcmp(name, "putkeep") || !strcmp(name, "putcat")){
        if(argc > 1){
          rv = tclistnew2(1);
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          const char *vbuf;
          int vsiz;
          TCLISTVAL(vbuf, args, 1, vsiz);
          bool err = false;
          if(!strcmp(name, "put")){
            tcmdbput(adb->mdb, kbuf, ksiz, vbuf, vsiz);
          } else if(!strcmp(name, "putkeep")){
            if(!tcmdbputkeep(adb->mdb, kbuf, ksiz, vbuf, vsiz)) err = true;
          } else if(!strcmp(name, "putcat")){
            tcmdbputcat(adb->mdb, kbuf, ksiz, vbuf, vsiz);
          }
          if(err){
            tclistdel(rv);
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "out")){
        if(argc > 0){
          rv = tclistnew2(1);
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          if(!tcmdbout(adb->mdb, kbuf, ksiz)){
            tclistdel(rv);
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "get")){
        if(argc > 0){
          rv = tclistnew2(1);
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          int vsiz;
          char *vbuf = tcmdbget(adb->mdb, kbuf, ksiz, &vsiz);
          if(vbuf){
            TCLISTPUSH(rv, vbuf, vsiz);
            TCFREE(vbuf);
          } else {
            tclistdel(rv);
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "putlist")){
        rv = tclistnew2(1);
        argc--;
        for(int i = 0; i < argc; i += 2){
          const char *kbuf, *vbuf;
          int ksiz, vsiz;
          TCLISTVAL(kbuf, args, i, ksiz);
          TCLISTVAL(vbuf, args, i + 1, vsiz);
          tcmdbput(adb->mdb, kbuf, ksiz, vbuf, vsiz);
        }
      } else if(!strcmp(name, "outlist")){
        rv = tclistnew2(1);
        for(int i = 0; i < argc; i++){
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, i, ksiz);
          tcmdbout(adb->mdb, kbuf, ksiz);
        }
      } else if(!strcmp(name, "getlist")){
        rv = tclistnew2(argc * 2);
        for(int i = 0; i < argc; i++){
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, i, ksiz);
          int vsiz;
          char *vbuf = tcmdbget(adb->mdb, kbuf, ksiz, &vsiz);
          if(vbuf){
            TCLISTPUSH(rv, kbuf, ksiz);
            TCLISTPUSH(rv, vbuf, vsiz);
            TCFREE(vbuf);
          }
        }
      } else if(!strcmp(name, "getpart")){
        if(argc > 0){
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          int off = argc > 1 ? tcatoi(TCLISTVALPTR(args, 1)) : 0;
          if(off < 0) off = 0;
          if(off > INT_MAX / 2 - 1) off = INT_MAX - 1;
          int len = argc > 2 ? tcatoi(TCLISTVALPTR(args, 2)) : -1;
          if(len < 0 || len > INT_MAX / 2) len = INT_MAX / 2;
          int vsiz;
          char *vbuf = tcmdbget(adb->mdb, kbuf, ksiz, &vsiz);
          if(vbuf){
            if(off < vsiz){
              rv = tclistnew2(1);
              vsiz -= off;
              if(vsiz > len) vsiz = len;
              if(off > 0) memmove(vbuf, vbuf + off, vsiz);
              tclistpushmalloc(rv, vbuf, vsiz);
            } else {
              rv = NULL;
              TCFREE(vbuf);
            }
          } else {
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "iterinit")){
        rv = tclistnew2(1);
        if(argc > 0){
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          tcmdbiterinit2(adb->mdb, kbuf, ksiz);
        } else {
          tcmdbiterinit(adb->mdb);
        }
      } else if(!strcmp(name, "iternext")){
        rv = tclistnew2(1);
        int ksiz;
        char *kbuf = tcmdbiternext(adb->mdb, &ksiz);
        if(kbuf){
          TCLISTPUSH(rv, kbuf, ksiz);
          int vsiz;
          char *vbuf = tcmdbget(adb->mdb, kbuf, ksiz, &vsiz);
          if(vbuf){
            TCLISTPUSH(rv, vbuf, vsiz);
            TCFREE(vbuf);
          }
          TCFREE(kbuf);
        } else {
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "sync")){
        rv = tclistnew2(1);
        if(!tcadbsync(adb)){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "optimize")){
        rv = tclistnew2(1);
        const char *params = argc > 0 ? TCLISTVALPTR(args, 0) : NULL;
        if(!tcadboptimize(adb, params)){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "vanish")){
        rv = tclistnew2(1);
        if(!tcadbvanish(adb)){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "regex")){
        if(argc > 0){
          const char *regex = TCLISTVALPTR(args, 0);
          int options = REG_EXTENDED | REG_NOSUB;
          if(*regex == '*'){
            options |= REG_ICASE;
            regex++;
          }
          regex_t rbuf;
          if(regcomp(&rbuf, regex, options) == 0){
            rv = tclistnew();
            int max = argc > 1 ? tcatoi(TCLISTVALPTR(args, 1)) : 0;
            if(max < 1) max = INT_MAX;
            tcmdbiterinit(adb->mdb);
            char *kbuf;
            int ksiz;
            while(max > 0 && (kbuf = tcmdbiternext(adb->mdb, &ksiz))){
              if(regexec(&rbuf, kbuf, 0, NULL, 0) == 0){
                int vsiz;
                char *vbuf = tcmdbget(adb->mdb, kbuf, ksiz, &vsiz);
                if(vbuf){
                  TCLISTPUSH(rv, kbuf, ksiz);
                  TCLISTPUSH(rv, vbuf, vsiz);
                  TCFREE(vbuf);
                  max--;
                }
              }
              TCFREE(kbuf);
            }
            regfree(&rbuf);
          } else {
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else {
        rv = NULL;
      }
      break;
    case ADBONDB:
      if(!strcmp(name, "put") || !strcmp(name, "putkeep") || !strcmp(name, "putcat")){
        if(argc > 1){
          rv = tclistnew2(1);
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          const char *vbuf;
          int vsiz;
          TCLISTVAL(vbuf, args, 1, vsiz);
          bool err = false;
          if(!strcmp(name, "put")){
            tcndbput(adb->ndb, kbuf, ksiz, vbuf, vsiz);
          } else if(!strcmp(name, "putkeep")){
            if(!tcndbputkeep(adb->ndb, kbuf, ksiz, vbuf, vsiz)) err = true;
          } else if(!strcmp(name, "putcat")){
            tcndbputcat(adb->ndb, kbuf, ksiz, vbuf, vsiz);
          }
          if(err){
            tclistdel(rv);
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "out")){
        if(argc > 0){
          rv = tclistnew2(1);
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          if(!tcndbout(adb->ndb, kbuf, ksiz)){
            tclistdel(rv);
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "get")){
        if(argc > 0){
          rv = tclistnew2(1);
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          int vsiz;
          char *vbuf = tcndbget(adb->ndb, kbuf, ksiz, &vsiz);
          if(vbuf){
            TCLISTPUSH(rv, vbuf, vsiz);
            TCFREE(vbuf);
          } else {
            tclistdel(rv);
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "putlist")){
        rv = tclistnew2(1);
        argc--;
        for(int i = 0; i < argc; i += 2){
          const char *kbuf, *vbuf;
          int ksiz, vsiz;
          TCLISTVAL(kbuf, args, i, ksiz);
          TCLISTVAL(vbuf, args, i + 1, vsiz);
          tcndbput(adb->ndb, kbuf, ksiz, vbuf, vsiz);
        }
      } else if(!strcmp(name, "outlist")){
        rv = tclistnew2(1);
        for(int i = 0; i < argc; i++){
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, i, ksiz);
          tcndbout(adb->ndb, kbuf, ksiz);
        }
      } else if(!strcmp(name, "getlist")){
        rv = tclistnew2(argc * 2);
        for(int i = 0; i < argc; i++){
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, i, ksiz);
          int vsiz;
          char *vbuf = tcndbget(adb->ndb, kbuf, ksiz, &vsiz);
          if(vbuf){
            TCLISTPUSH(rv, kbuf, ksiz);
            TCLISTPUSH(rv, vbuf, vsiz);
            TCFREE(vbuf);
          }
        }
      } else if(!strcmp(name, "getpart")){
        if(argc > 0){
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          int off = argc > 1 ? tcatoi(TCLISTVALPTR(args, 1)) : 0;
          if(off < 0) off = 0;
          if(off > INT_MAX / 2 - 1) off = INT_MAX - 1;
          int len = argc > 2 ? tcatoi(TCLISTVALPTR(args, 2)) : -1;
          if(len < 0 || len > INT_MAX / 2) len = INT_MAX / 2;
          int vsiz;
          char *vbuf = tcndbget(adb->ndb, kbuf, ksiz, &vsiz);
          if(vbuf){
            if(off < vsiz){
              rv = tclistnew2(1);
              vsiz -= off;
              if(vsiz > len) vsiz = len;
              if(off > 0) memmove(vbuf, vbuf + off, vsiz);
              tclistpushmalloc(rv, vbuf, vsiz);
            } else {
              rv = NULL;
              TCFREE(vbuf);
            }
          } else {
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "iterinit")){
        rv = tclistnew2(1);
        if(argc > 0){
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          tcndbiterinit2(adb->ndb, kbuf, ksiz);
        } else {
          tcndbiterinit(adb->ndb);
        }
      } else if(!strcmp(name, "iternext")){
        rv = tclistnew2(1);
        int ksiz;
        char *kbuf = tcndbiternext(adb->ndb, &ksiz);
        if(kbuf){
          TCLISTPUSH(rv, kbuf, ksiz);
          int vsiz;
          char *vbuf = tcndbget(adb->ndb, kbuf, ksiz, &vsiz);
          if(vbuf){
            TCLISTPUSH(rv, vbuf, vsiz);
            TCFREE(vbuf);
          }
          TCFREE(kbuf);
        } else {
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "sync")){
        rv = tclistnew2(1);
        if(!tcadbsync(adb)){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "optimize")){
        rv = tclistnew2(1);
        const char *params = argc > 0 ? TCLISTVALPTR(args, 0) : NULL;
        if(!tcadboptimize(adb, params)){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "vanish")){
        rv = tclistnew2(1);
        if(!tcadbvanish(adb)){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "regex")){
        if(argc > 0){
          const char *regex = TCLISTVALPTR(args, 0);
          int options = REG_EXTENDED | REG_NOSUB;
          if(*regex == '*'){
            options |= REG_ICASE;
            regex++;
          }
          regex_t rbuf;
          if(regcomp(&rbuf, regex, options) == 0){
            rv = tclistnew();
            int max = argc > 1 ? tcatoi(TCLISTVALPTR(args, 1)) : 0;
            if(max < 1) max = INT_MAX;
            tcndbiterinit(adb->ndb);
            char *kbuf;
            int ksiz;
            while(max > 0 && (kbuf = tcndbiternext(adb->ndb, &ksiz))){
              if(regexec(&rbuf, kbuf, 0, NULL, 0) == 0){
                int vsiz;
                char *vbuf = tcndbget(adb->ndb, kbuf, ksiz, &vsiz);
                if(vbuf){
                  TCLISTPUSH(rv, kbuf, ksiz);
                  TCLISTPUSH(rv, vbuf, vsiz);
                  TCFREE(vbuf);
                  max--;
                }
              }
              TCFREE(kbuf);
            }
            regfree(&rbuf);
          } else {
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "range")){
        rv = tclistnew();
        int bksiz = 0;
        const char *bkbuf = NULL;
        if(argc > 0) TCLISTVAL(bkbuf, args, 0, bksiz);
        int max = argc > 1 ? tcatoi(TCLISTVALPTR(args, 1)) : 0;
        if(max < 1) max = INT_MAX;
        int eksiz = 0;
        const char *ekbuf = NULL;
        if(argc > 2) TCLISTVAL(ekbuf, args, 2, eksiz);
        if(bkbuf){
          tcndbiterinit2(adb->ndb, bkbuf, bksiz);
        } else {
          tcndbiterinit(adb->ndb);
        }
        char *kbuf;
        int ksiz;
        while(max > 0 && (kbuf = tcndbiternext(adb->ndb, &ksiz)) != NULL){
          if(ekbuf && tccmplexical(kbuf, ksiz, ekbuf, eksiz, NULL) >= 0){
            TCFREE(kbuf);
            break;
          }
          int vsiz;
          char *vbuf = tcndbget(adb->ndb, kbuf, ksiz, &vsiz);
          if(vbuf){
            TCLISTPUSH(rv, kbuf, ksiz);
            TCLISTPUSH(rv, vbuf, vsiz);
            TCFREE(vbuf);
            max--;
          }
          TCFREE(kbuf);
        }
      } else {
        rv = NULL;
      }
      break;
    case ADBOHDB:
      if(!strcmp(name, "put") || !strcmp(name, "putkeep") || !strcmp(name, "putcat")){
        if(argc > 1){
          rv = tclistnew2(1);
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          const char *vbuf;
          int vsiz;
          TCLISTVAL(vbuf, args, 1, vsiz);
          bool err = false;
          if(!strcmp(name, "put")){
            if(!tchdbput(adb->hdb, kbuf, ksiz, vbuf, vsiz)) err = true;
          } else if(!strcmp(name, "putkeep")){
            if(!tchdbputkeep(adb->hdb, kbuf, ksiz, vbuf, vsiz)) err = true;
          } else if(!strcmp(name, "putcat")){
            if(!tchdbputcat(adb->hdb, kbuf, ksiz, vbuf, vsiz)) err = true;
          }
          if(err){
            tclistdel(rv);
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "out")){
        if(argc > 0){
          rv = tclistnew2(1);
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          if(!tchdbout(adb->hdb, kbuf, ksiz)){
            tclistdel(rv);
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "get")){
        if(argc > 0){
          rv = tclistnew2(1);
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          int vsiz;
          char *vbuf = tchdbget(adb->hdb, kbuf, ksiz, &vsiz);
          if(vbuf){
            TCLISTPUSH(rv, vbuf, vsiz);
            TCFREE(vbuf);
          } else {
            tclistdel(rv);
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "putlist")){
        rv = tclistnew2(1);
        bool err = false;
        argc--;
        for(int i = 0; i < argc; i += 2){
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, i, ksiz);
          int vsiz;
          const char *vbuf = tclistval(args, i + 1, &vsiz);
          if(!tchdbput(adb->hdb, kbuf, ksiz, vbuf, vsiz)){
            err = true;
            break;
          }
        }
        if(err){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "outlist")){
        rv = tclistnew2(1);
        bool err = false;
        for(int i = 0; i < argc; i++){
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, i, ksiz);
          if(!tchdbout(adb->hdb, kbuf, ksiz) && tchdbecode(adb->hdb) != TCENOREC){
            err = true;
            break;
          }
        }
        if(err){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "getlist")){
        rv = tclistnew2(argc * 2);
        bool err = false;
        for(int i = 0; i < argc; i++){
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, i, ksiz);
          int vsiz;
          char *vbuf = tchdbget(adb->hdb, kbuf, ksiz, &vsiz);
          if(vbuf){
            TCLISTPUSH(rv, kbuf, ksiz);
            TCLISTPUSH(rv, vbuf, vsiz);
            TCFREE(vbuf);
          } else if(tchdbecode(adb->hdb) != TCENOREC){
            err = true;
          }
        }
        if(err){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "getpart")){
        if(argc > 0){
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          int off = argc > 1 ? tcatoi(TCLISTVALPTR(args, 1)) : 0;
          if(off < 0) off = 0;
          if(off > INT_MAX / 2 - 1) off = INT_MAX - 1;
          int len = argc > 2 ? tcatoi(TCLISTVALPTR(args, 2)) : -1;
          if(len < 0 || len > INT_MAX / 2) len = INT_MAX / 2;
          int vsiz;
          char *vbuf = tchdbget(adb->hdb, kbuf, ksiz, &vsiz);
          if(vbuf){
            if(off < vsiz){
              rv = tclistnew2(1);
              vsiz -= off;
              if(vsiz > len) vsiz = len;
              if(off > 0) memmove(vbuf, vbuf + off, vsiz);
              tclistpushmalloc(rv, vbuf, vsiz);
            } else {
              rv = NULL;
              TCFREE(vbuf);
            }
          } else {
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "iterinit")){
        rv = tclistnew2(1);
        bool err = false;
        if(argc > 0){
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          if(!tchdbiterinit2(adb->hdb, kbuf, ksiz)) err = true;
        } else {
          if(!tchdbiterinit(adb->hdb)) err = true;
        }
        if(err){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "iternext")){
        rv = tclistnew2(1);
        int ksiz;
        char *kbuf = tchdbiternext(adb->hdb, &ksiz);
        if(kbuf){
          TCLISTPUSH(rv, kbuf, ksiz);
          int vsiz;
          char *vbuf = tchdbget(adb->hdb, kbuf, ksiz, &vsiz);
          if(vbuf){
            TCLISTPUSH(rv, vbuf, vsiz);
            TCFREE(vbuf);
          }
          TCFREE(kbuf);
        } else {
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "sync")){
        rv = tclistnew2(1);
        if(!tcadbsync(adb)){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "optimize")){
        rv = tclistnew2(1);
        const char *params = argc > 0 ? TCLISTVALPTR(args, 0) : NULL;
        if(!tcadboptimize(adb, params)){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "vanish")){
        rv = tclistnew2(1);
        if(!tcadbvanish(adb)){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "error")){
        rv = tclistnew2(1);
        int ecode = tchdbecode(adb->hdb);
        tclistprintf(rv, "%d: %s", ecode, tchdberrmsg(ecode));
        uint8_t flags = tchdbflags(adb->hdb);
        if(flags & HDBFFATAL) tclistprintf(rv, "fatal");
      } else if(!strcmp(name, "defrag")){
        rv = tclistnew2(1);
        int64_t step = argc > 0 ? tcatoi(TCLISTVALPTR(args, 0)) : -1;
        if(!tchdbdefrag(adb->hdb, step)){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "cacheclear")){
        rv = tclistnew2(1);
        if(!tchdbcacheclear(adb->hdb)){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "regex")){
        if(argc > 0){
          const char *regex = TCLISTVALPTR(args, 0);
          int options = REG_EXTENDED | REG_NOSUB;
          if(*regex == '*'){
            options |= REG_ICASE;
            regex++;
          }
          regex_t rbuf;
          if(regcomp(&rbuf, regex, options) == 0){
            rv = tclistnew();
            int max = argc > 1 ? tcatoi(TCLISTVALPTR(args, 1)) : 0;
            if(max < 1) max = INT_MAX;
            tchdbiterinit(adb->hdb);
            TCXSTR *kxstr = tcxstrnew();
            TCXSTR *vxstr = tcxstrnew();
            while(max > 0 && tchdbiternext3(adb->hdb, kxstr, vxstr)){
              const char *kbuf = TCXSTRPTR(kxstr);
              if(regexec(&rbuf, kbuf, 0, NULL, 0) == 0){
                TCLISTPUSH(rv, kbuf, TCXSTRSIZE(kxstr));
                TCLISTPUSH(rv, TCXSTRPTR(vxstr), TCXSTRSIZE(vxstr));
                max--;
              }
            }
            tcxstrdel(vxstr);
            tcxstrdel(kxstr);
            regfree(&rbuf);
          } else {
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else {
        rv = NULL;
      }
      break;
    case ADBOBDB:
      if(!strcmp(name, "put") || !strcmp(name, "putkeep") || !strcmp(name, "putcat") ||
         !strcmp(name, "putdup") || !strcmp(name, "putdupback")){
        if(argc > 1){
          rv = tclistnew2(1);
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          const char *vbuf;
          int vsiz;
          TCLISTVAL(vbuf, args, 1, vsiz);
          bool err = false;
          if(!strcmp(name, "put")){
            if(!tcbdbput(adb->bdb, kbuf, ksiz, vbuf, vsiz)) err = true;
          } else if(!strcmp(name, "putkeep")){
            if(!tcbdbputkeep(adb->bdb, kbuf, ksiz, vbuf, vsiz)) err = true;
          } else if(!strcmp(name, "putcat")){
            if(!tcbdbputcat(adb->bdb, kbuf, ksiz, vbuf, vsiz)) err = true;
          } else if(!strcmp(name, "putdup")){
            if(!tcbdbputdup(adb->bdb, kbuf, ksiz, vbuf, vsiz)) err = true;
          } else if(!strcmp(name, "putdupback")){
            if(!tcbdbputdupback(adb->bdb, kbuf, ksiz, vbuf, vsiz)) err = true;
          }
          if(err){
            tclistdel(rv);
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "out")){
        if(argc > 0){
          rv = tclistnew2(1);
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          if(!tcbdbout(adb->bdb, kbuf, ksiz)){
            tclistdel(rv);
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "get")){
        if(argc > 0){
          rv = tclistnew2(1);
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          TCLIST *vals = tcbdbget4(adb->bdb, kbuf, ksiz);
          if(vals){
            tclistdel(rv);
            rv = vals;
          } else {
            tclistdel(rv);
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "putlist")){
        rv = tclistnew2(1);
        bool err = false;
        argc--;
        for(int i = 0; i < argc; i += 2){
          const char *kbuf, *vbuf;
          int ksiz, vsiz;
          TCLISTVAL(kbuf, args, i, ksiz);
          TCLISTVAL(vbuf, args, i + 1, vsiz);
          if(!tcbdbputdup(adb->bdb, kbuf, ksiz, vbuf, vsiz)){
            err = true;
            break;
          }
        }
        if(err){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "outlist")){
        rv = tclistnew2(1);
        bool err = false;
        for(int i = 0; i < argc; i++){
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, i, ksiz);
          if(!tcbdbout3(adb->bdb, kbuf, ksiz) && tcbdbecode(adb->bdb) != TCENOREC){
            err = true;
            break;
          }
        }
        if(err){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "getlist")){
        rv = tclistnew2(argc * 2);
        bool err = false;
        for(int i = 0; i < argc; i++){
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, i, ksiz);
          TCLIST *vals = tcbdbget4(adb->bdb, kbuf, ksiz);
          if(vals){
            int vnum = TCLISTNUM(vals);
            for(int j = 0; j < vnum; j++){
              TCLISTPUSH(rv, kbuf, ksiz);
              const char *vbuf;
              int vsiz;
              TCLISTVAL(vbuf, vals, j, vsiz);
              TCLISTPUSH(rv, vbuf, vsiz);
            }
            tclistdel(vals);
          } else if(tcbdbecode(adb->bdb) != TCENOREC){
            err = true;
          }
        }
        if(err){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "getpart")){
        if(argc > 0){
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          int off = argc > 1 ? tcatoi(TCLISTVALPTR(args, 1)) : 0;
          if(off < 0) off = 0;
          if(off > INT_MAX / 2 - 1) off = INT_MAX - 1;
          int len = argc > 2 ? tcatoi(TCLISTVALPTR(args, 2)) : -1;
          if(len < 0 || len > INT_MAX / 2) len = INT_MAX / 2;
          int vsiz;
          char *vbuf = tcbdbget(adb->bdb, kbuf, ksiz, &vsiz);
          if(vbuf){
            if(off < vsiz){
              rv = tclistnew2(1);
              vsiz -= off;
              if(vsiz > len) vsiz = len;
              if(off > 0) memmove(vbuf, vbuf + off, vsiz);
              tclistpushmalloc(rv, vbuf, vsiz);
            } else {
              rv = NULL;
              TCFREE(vbuf);
            }
          } else {
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "iterinit")){
        rv = tclistnew2(1);
        bool err = false;
        if(argc > 0){
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          if(!tcbdbcurjump(adb->cur, kbuf, ksiz)) err = true;
        } else {
          if(!tcbdbcurfirst(adb->cur)) err = true;
        }
        if(err){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "iternext")){
        rv = tclistnew2(1);
        int ksiz;
        const char *kbuf = tcbdbcurkey3(adb->cur, &ksiz);
        if(kbuf){
          TCLISTPUSH(rv, kbuf, ksiz);
          int vsiz;
          const char *vbuf = tcbdbcurval3(adb->cur, &vsiz);
          if(vbuf) TCLISTPUSH(rv, vbuf, vsiz);
          tcbdbcurnext(adb->cur);
        } else {
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "sync")){
        rv = tclistnew2(1);
        if(!tcadbsync(adb)){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "optimize")){
        rv = tclistnew2(1);
        const char *params = argc > 0 ? TCLISTVALPTR(args, 0) : NULL;
        if(!tcadboptimize(adb, params)){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "vanish")){
        rv = tclistnew2(1);
        if(!tcadbvanish(adb)){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "error")){
        rv = tclistnew2(1);
        int ecode = tcbdbecode(adb->bdb);
        tclistprintf(rv, "%d: %s", ecode, tcbdberrmsg(ecode));
        uint8_t flags = tcbdbflags(adb->bdb);
        if(flags & BDBFFATAL) tclistprintf(rv, "fatal");
      } else if(!strcmp(name, "defrag")){
        rv = tclistnew2(1);
        int64_t step = argc > 0 ? tcatoi(TCLISTVALPTR(args, 0)) : -1;
        if(!tcbdbdefrag(adb->bdb, step)){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "cacheclear")){
        rv = tclistnew2(1);
        if(!tcbdbcacheclear(adb->bdb)){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "regex")){
        if(argc > 0){
          const char *regex = TCLISTVALPTR(args, 0);
          int options = REG_EXTENDED | REG_NOSUB;
          if(*regex == '*'){
            options |= REG_ICASE;
            regex++;
          }
          regex_t rbuf;
          if(regcomp(&rbuf, regex, options) == 0){
            rv = tclistnew();
            int max = argc > 1 ? tcatoi(TCLISTVALPTR(args, 1)) : 0;
            if(max < 1) max = INT_MAX;
            BDBCUR *cur = tcbdbcurnew(adb->bdb);
            tcbdbcurfirst(cur);
            TCXSTR *kxstr = tcxstrnew();
            TCXSTR *vxstr = tcxstrnew();
            while(max > 0 && tcbdbcurrec(cur, kxstr, vxstr)){
              const char *kbuf = TCXSTRPTR(kxstr);
              if(regexec(&rbuf, kbuf, 0, NULL, 0) == 0){
                TCLISTPUSH(rv, kbuf, TCXSTRSIZE(kxstr));
                TCLISTPUSH(rv, TCXSTRPTR(vxstr), TCXSTRSIZE(vxstr));
                max--;
              }
              tcbdbcurnext(cur);
            }
            tcxstrdel(vxstr);
            tcxstrdel(kxstr);
            tcbdbcurdel(cur);
            regfree(&rbuf);
          } else {
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "range")){
        rv = tclistnew();
        int bksiz = 0;
        const char *bkbuf = NULL;
        if(argc > 0) TCLISTVAL(bkbuf, args, 0, bksiz);
        int max = argc > 1 ? tcatoi(TCLISTVALPTR(args, 1)) : 0;
        if(max < 1) max = INT_MAX;
        int eksiz = 0;
        const char *ekbuf = NULL;
        if(argc > 2) TCLISTVAL(ekbuf, args, 2, eksiz);
        TCCMP cmp = tcbdbcmpfunc(adb->bdb);
        void *cmpop = tcbdbcmpop(adb->bdb);
        BDBCUR *cur = tcbdbcurnew(adb->bdb);
        if(bkbuf){
          tcbdbcurjump(cur, bkbuf, bksiz);
        } else {
          tcbdbcurfirst(cur);
        }
        TCXSTR *kxstr = tcxstrnew();
        TCXSTR *vxstr = tcxstrnew();
        while(max > 0 && tcbdbcurrec(cur, kxstr, vxstr)){
          const char *kbuf = TCXSTRPTR(kxstr);
          int ksiz = TCXSTRSIZE(kxstr);
          if(ekbuf && cmp(kbuf, ksiz, ekbuf, eksiz, cmpop) >= 0) break;
          TCLISTPUSH(rv, kbuf, ksiz);
          TCLISTPUSH(rv, TCXSTRPTR(vxstr), TCXSTRSIZE(vxstr));
          max--;
          tcbdbcurnext(cur);
        }
        tcxstrdel(vxstr);
        tcxstrdel(kxstr);
        tcbdbcurdel(cur);
      } else {
        rv = NULL;
      }
      break;
    case ADBOFDB:
      if(!strcmp(name, "put") || !strcmp(name, "putkeep") || !strcmp(name, "putcat")){
        if(argc > 1){
          rv = tclistnew2(1);
          const char *kbuf, *vbuf;
          int ksiz, vsiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          TCLISTVAL(vbuf, args, 1, vsiz);
          bool err = false;
          if(!strcmp(name, "put")){
            if(!tcfdbput2(adb->fdb, kbuf, ksiz, vbuf, vsiz)) err = true;
          } else if(!strcmp(name, "putkeep")){
            if(!tcfdbputkeep2(adb->fdb, kbuf, ksiz, vbuf, vsiz)) err = true;
          } else if(!strcmp(name, "putcat")){
            if(!tcfdbputcat2(adb->fdb, kbuf, ksiz, vbuf, vsiz)) err = true;
          }
          if(err){
            tclistdel(rv);
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "out")){
        if(argc > 0){
          rv = tclistnew2(1);
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          if(!tcfdbout2(adb->fdb, kbuf, ksiz)){
            tclistdel(rv);
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "get")){
        if(argc > 0){
          rv = tclistnew2(1);
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          int vsiz;
          char *vbuf = tcfdbget2(adb->fdb, kbuf, ksiz, &vsiz);
          if(vbuf){
            TCLISTPUSH(rv, vbuf, vsiz);
            TCFREE(vbuf);
          } else {
            tclistdel(rv);
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "putlist")){
        rv = tclistnew2(1);
        bool err = false;
        argc--;
        for(int i = 0; i < argc; i += 2){
          const char *kbuf, *vbuf;
          int ksiz, vsiz;
          TCLISTVAL(kbuf, args, i, ksiz);
          TCLISTVAL(vbuf, args, i + 1, vsiz);
          if(!tcfdbput2(adb->fdb, kbuf, ksiz, vbuf, vsiz)){
            err = true;
            break;
          }
        }
        if(err){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "outlist")){
        rv = tclistnew2(1);
        bool err = false;
        for(int i = 0; i < argc; i++){
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, i, ksiz);
          if(!tcfdbout2(adb->fdb, kbuf, ksiz) && tcfdbecode(adb->fdb) != TCENOREC){
            err = true;
            break;
          }
        }
        if(err){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "getlist")){
        rv = tclistnew2(argc * 2);
        bool err = false;
        for(int i = 0; i < argc; i++){
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, i, ksiz);
          int vsiz;
          char *vbuf = tcfdbget2(adb->fdb, kbuf, ksiz, &vsiz);
          if(vbuf){
            TCLISTPUSH(rv, kbuf, ksiz);
            TCLISTPUSH(rv, vbuf, vsiz);
            TCFREE(vbuf);
          } else if(tcfdbecode(adb->fdb) != TCENOREC){
            err = true;
          }
        }
        if(err){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "getpart")){
        if(argc > 0){
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          int off = argc > 1 ? tcatoi(TCLISTVALPTR(args, 1)) : 0;
          if(off < 0) off = 0;
          if(off > INT_MAX / 2 - 1) off = INT_MAX - 1;
          int len = argc > 2 ? tcatoi(TCLISTVALPTR(args, 2)) : -1;
          if(len < 0 || len > INT_MAX / 2) len = INT_MAX / 2;
          int vsiz;
          char *vbuf = tcfdbget2(adb->fdb, kbuf, ksiz, &vsiz);
          if(vbuf){
            if(off < vsiz){
              rv = tclistnew2(1);
              vsiz -= off;
              if(vsiz > len) vsiz = len;
              if(off > 0) memmove(vbuf, vbuf + off, vsiz);
              tclistpushmalloc(rv, vbuf, vsiz);
            } else {
              rv = NULL;
              TCFREE(vbuf);
            }
          } else {
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "iterinit")){
        rv = tclistnew2(1);
        bool err = false;
        if(argc > 0){
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          if(!tcfdbiterinit3(adb->fdb, kbuf, ksiz)) err = true;
        } else {
          if(!tcfdbiterinit(adb->fdb)) err = true;
        }
        if(err){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "iternext")){
        rv = tclistnew2(1);
        int ksiz;
        char *kbuf = tcfdbiternext2(adb->fdb, &ksiz);
        if(kbuf){
          TCLISTPUSH(rv, kbuf, ksiz);
          int vsiz;
          char *vbuf = tcfdbget2(adb->fdb, kbuf, ksiz, &vsiz);
          if(vbuf){
            TCLISTPUSH(rv, vbuf, vsiz);
            TCFREE(vbuf);
          }
          TCFREE(kbuf);
        } else {
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "sync")){
        rv = tclistnew2(1);
        if(!tcadbsync(adb)){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "optimize")){
        rv = tclistnew2(1);
        const char *params = argc > 0 ? TCLISTVALPTR(args, 0) : NULL;
        if(!tcadboptimize(adb, params)){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "vanish")){
        rv = tclistnew2(1);
        if(!tcadbvanish(adb)){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "error")){
        rv = tclistnew2(1);
        int ecode = tcfdbecode(adb->fdb);
        tclistprintf(rv, "%d: %s", ecode, tcfdberrmsg(ecode));
        uint8_t flags = tcfdbflags(adb->fdb);
        if(flags & FDBFFATAL) tclistprintf(rv, "fatal");
      } else if(!strcmp(name, "regex")){
        if(argc > 0){
          const char *regex = TCLISTVALPTR(args, 0);
          int options = REG_EXTENDED | REG_NOSUB;
          if(*regex == '*'){
            options |= REG_ICASE;
            regex++;
          }
          regex_t rbuf;
          if(regcomp(&rbuf, regex, options) == 0){
            rv = tclistnew();
            int max = argc > 1 ? tcatoi(TCLISTVALPTR(args, 1)) : 0;
            if(max < 1) max = INT_MAX;
            tcfdbiterinit(adb->fdb);
            char *kbuf;
            int ksiz;
            while(max > 0 && (kbuf = tcfdbiternext2(adb->fdb, &ksiz))){
              if(regexec(&rbuf, kbuf, 0, NULL, 0) == 0){
                int vsiz;
                char *vbuf = tcfdbget2(adb->fdb, kbuf, ksiz, &vsiz);
                if(vbuf){
                  TCLISTPUSH(rv, kbuf, ksiz);
                  TCLISTPUSH(rv, vbuf, vsiz);
                  TCFREE(vbuf);
                  max--;
                }
              }
              TCFREE(kbuf);
            }
            regfree(&rbuf);
          } else {
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "range")){
        rv = tclistnew();
        int bksiz = 0;
        const char *bkbuf = NULL;
        if(argc > 0) TCLISTVAL(bkbuf, args, 0, bksiz);
        int max = argc > 1 ? tcatoi(TCLISTVALPTR(args, 1)) : 0;
        if(max < 1) max = INT_MAX;
        int eksiz = 0;
        const char *ekbuf = NULL;
        if(argc > 2) TCLISTVAL(ekbuf, args, 2, eksiz);
        if(bkbuf){
          tcfdbiterinit3(adb->fdb, bkbuf, bksiz);
        } else {
          tcfdbiterinit(adb->fdb);
        }
        int64_t eid = ekbuf ? tcfdbkeytoid(ekbuf, eksiz) : -1;
        char *kbuf;
        int ksiz;
        while(max > 0 && (kbuf = tcfdbiternext2(adb->fdb, &ksiz)) != NULL){
          if(eid > 0 && tcatoi(kbuf) >= eid){
            TCFREE(kbuf);
            break;
          }
          int vsiz;
          char *vbuf = tcfdbget2(adb->fdb, kbuf, ksiz, &vsiz);
          if(vbuf){
            TCLISTPUSH(rv, kbuf, ksiz);
            TCLISTPUSH(rv, vbuf, vsiz);
            TCFREE(vbuf);
            max--;
          }
          TCFREE(kbuf);
        }
      } else {
        rv = NULL;
      }
      break;
    case ADBOTDB:
      if(!strcmp(name, "put") || !strcmp(name, "putkeep") || !strcmp(name, "putcat")){
        if(argc > 0){
          rv = tclistnew2(1);
          char *pkbuf;
          int pksiz;
          TCLISTVAL(pkbuf, args, 0, pksiz);
          argc--;
          TCMAP *cols = tcmapnew2(argc);
          for(int i = 1; i < argc; i += 2){
            const char *kbuf, *vbuf;
            int ksiz, vsiz;
            TCLISTVAL(kbuf, args, i, ksiz);
            TCLISTVAL(vbuf, args, i + 1, vsiz);
            tcmapput(cols, kbuf, ksiz, vbuf, vsiz);
          }
          bool err = false;
          if(!strcmp(name, "put")){
            if(!tctdbput(adb->tdb, pkbuf, pksiz, cols)) err = true;
          } else if(!strcmp(name, "putkeep")){
            if(!tctdbputkeep(adb->tdb, pkbuf, pksiz, cols)) err = true;
          } else if(!strcmp(name, "putcat")){
            if(!tctdbputcat(adb->tdb, pkbuf, pksiz, cols)) err = true;
          }
          tcmapdel(cols);
          if(err){
            tclistdel(rv);
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "out")){
        if(argc > 0){
          rv = tclistnew2(1);
          char *pkbuf;
          int pksiz;
          TCLISTVAL(pkbuf, args, 0, pksiz);
          if(!tctdbout(adb->tdb, pkbuf, pksiz)){
            tclistdel(rv);
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "get")){
        if(argc > 0){
          rv = tclistnew2(1);
          char *pkbuf;
          int pksiz;
          TCLISTVAL(pkbuf, args, 0, pksiz);
          TCMAP *cols = tctdbget(adb->tdb, pkbuf, pksiz);
          if(cols){
            tcmapiterinit(cols);
            const char *kbuf;
            int ksiz;
            while((kbuf = tcmapiternext(cols, &ksiz)) != NULL){
              int vsiz;
              const char *vbuf = tcmapiterval(kbuf, &vsiz);
              TCLISTPUSH(rv, kbuf, ksiz);
              TCLISTPUSH(rv, vbuf, vsiz);
            }
            tcmapdel(cols);
          } else {
            tclistdel(rv);
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "putlist")){
        rv = tclistnew2(1);
        bool err = false;
        argc--;
        for(int i = 0; i < argc; i += 2){
          const char *kbuf, *vbuf;
          int ksiz, vsiz;
          TCLISTVAL(kbuf, args, i, ksiz);
          TCLISTVAL(vbuf, args, i + 1, vsiz);
          if(!tctdbput2(adb->tdb, kbuf, ksiz, vbuf, vsiz)){
            err = true;
            break;
          }
        }
        if(err){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "outlist")){
        rv = tclistnew2(1);
        bool err = false;
        for(int i = 0; i < argc; i++){
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, i, ksiz);
          if(!tctdbout(adb->tdb, kbuf, ksiz) && tctdbecode(adb->tdb) != TCENOREC){
            err = true;
            break;
          }
        }
        if(err){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "getlist")){
        rv = tclistnew2(argc * 2);
        bool err = false;
        for(int i = 0; i < argc; i++){
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, i, ksiz);
          int vsiz;
          char *vbuf = tctdbget2(adb->tdb, kbuf, ksiz, &vsiz);
          if(vbuf){
            TCLISTPUSH(rv, kbuf, ksiz);
            TCLISTPUSH(rv, vbuf, vsiz);
            TCFREE(vbuf);
          } else if(tctdbecode(adb->tdb) != TCENOREC){
            err = true;
          }
        }
        if(err){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "getpart")){
        if(argc > 0){
          const char *kbuf;
          int ksiz;
          TCLISTVAL(kbuf, args, 0, ksiz);
          int off = argc > 1 ? tcatoi(TCLISTVALPTR(args, 1)) : 0;
          if(off < 0) off = 0;
          if(off > INT_MAX / 2 - 1) off = INT_MAX - 1;
          int len = argc > 2 ? tcatoi(TCLISTVALPTR(args, 2)) : -1;
          if(len < 0 || len > INT_MAX / 2) len = INT_MAX / 2;
          int vsiz;
          char *vbuf = tctdbget2(adb->tdb, kbuf, ksiz, &vsiz);
          if(vbuf){
            if(off < vsiz){
              rv = tclistnew2(1);
              vsiz -= off;
              if(vsiz > len) vsiz = len;
              if(off > 0) memmove(vbuf, vbuf + off, vsiz);
              tclistpushmalloc(rv, vbuf, vsiz);
            } else {
              rv = NULL;
              TCFREE(vbuf);
            }
          } else {
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "iterinit")){
        rv = tclistnew2(1);
        bool err = false;
        if(argc > 0){
          const char *pkbuf;
          int pksiz;
          TCLISTVAL(pkbuf, args, 0, pksiz);
          if(!tctdbiterinit2(adb->tdb, pkbuf, pksiz)) err = true;
        } else {
          if(!tctdbiterinit(adb->tdb)) err = true;
        }
        if(err){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "iternext")){
        rv = tclistnew2(1);
        int pksiz;
        char *pkbuf = tctdbiternext(adb->tdb, &pksiz);
        if(pkbuf){
          TCLISTPUSH(rv, pkbuf, pksiz);
          int csiz;
          char *cbuf = tctdbget2(adb->tdb, pkbuf, pksiz, &csiz);
          if(cbuf){
            TCLISTPUSH(rv, cbuf, csiz);
            TCFREE(cbuf);
          }
          TCFREE(pkbuf);
        } else {
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "sync")){
        rv = tclistnew2(1);
        if(!tcadbsync(adb)){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "optimize")){
        rv = tclistnew2(1);
        const char *params = argc > 0 ? TCLISTVALPTR(args, 0) : NULL;
        if(!tcadboptimize(adb, params)){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "vanish")){
        rv = tclistnew2(1);
        if(!tcadbvanish(adb)){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "error")){
        rv = tclistnew2(1);
        int ecode = tctdbecode(adb->tdb);
        tclistprintf(rv, "%d: %s", ecode, tctdberrmsg(ecode));
        uint8_t flags = tctdbflags(adb->tdb);
        if(flags & TDBFFATAL) tclistprintf(rv, "fatal");
      } else if(!strcmp(name, "defrag")){
        rv = tclistnew2(1);
        int64_t step = argc > 0 ? tcatoi(TCLISTVALPTR(args, 0)) : -1;
        if(!tctdbdefrag(adb->tdb, step)){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "cacheclear")){
        rv = tclistnew2(1);
        if(!tctdbcacheclear(adb->tdb)){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "regex")){
        if(argc > 0){
          const char *regex = TCLISTVALPTR(args, 0);
          int options = REG_EXTENDED | REG_NOSUB;
          if(*regex == '*'){
            options |= REG_ICASE;
            regex++;
          }
          regex_t rbuf;
          if(regcomp(&rbuf, regex, options) == 0){
            rv = tclistnew();
            int max = argc > 1 ? tcatoi(TCLISTVALPTR(args, 1)) : 0;
            if(max < 1) max = INT_MAX;
            tctdbiterinit(adb->tdb);
            char *kbuf;
            int ksiz;
            while(max > 0 && (kbuf = tctdbiternext(adb->tdb, &ksiz))){
              if(regexec(&rbuf, kbuf, 0, NULL, 0) == 0){
                int vsiz;
                char *vbuf = tctdbget2(adb->tdb, kbuf, ksiz, &vsiz);
                if(vbuf){
                  TCLISTPUSH(rv, kbuf, ksiz);
                  TCLISTPUSH(rv, vbuf, vsiz);
                  TCFREE(vbuf);
                  max--;
                }
              }
              TCFREE(kbuf);
            }
            regfree(&rbuf);
          } else {
            rv = NULL;
          }
        } else {
          rv = NULL;
        }
      } else if(!strcmp(name, "setindex")){
        rv = tclistnew2(1);
        bool err = false;
        argc--;
        for(int i = 0; i < argc; i += 2){
          const char *kbuf, *vbuf;
          kbuf = TCLISTVALPTR(args, i);
          vbuf = TCLISTVALPTR(args, i + 1);
          int type = tctdbstrtoindextype(vbuf);
          if(type >= 0){
            if(!tctdbsetindex(adb->tdb, kbuf, type)) err = true;
          } else {
            err = true;
          }
        }
        if(err){
          tclistdel(rv);
          rv = NULL;
        }
      } else if(!strcmp(name, "search") || !strcmp(name, "metasearch")){
        bool toout = false;
        bool tocnt = false;
        bool tohint = false;
        TDBQRY *qry = tctdbqrynew(adb->tdb);
        TDBQRY **qrys = NULL;
        int qnum = 0;
        int mstype = TDBMSUNION;
        TCLIST *cnames = NULL;
        for(int i = 0; i < argc; i++){
          const char *arg;
          int asiz;
          TCLISTVAL(arg, args, i, asiz);
          TCLIST *tokens = tcstrsplit2(arg, asiz);
          int tnum = TCLISTNUM(tokens);
          if(tnum > 0){
            const char *cmd = TCLISTVALPTR(tokens, 0);
            if((!strcmp(cmd, "addcond") || !strcmp(cmd, "cond")) && tnum > 3){
              const char *name = TCLISTVALPTR(tokens, 1);
              const char *opstr = TCLISTVALPTR(tokens, 2);
              const char *expr = TCLISTVALPTR(tokens, 3);
              int op = tctdbqrystrtocondop(opstr);
              if(op >= 0) tctdbqryaddcond(qry, name, op, expr);
            } else if((!strcmp(cmd, "setorder") || !strcmp(cmd, "order")) && tnum > 2){
              const char *name = TCLISTVALPTR(tokens, 1);
              const char *typestr = TCLISTVALPTR(tokens, 2);
              int type = tctdbqrystrtoordertype(typestr);
              if(type >= 0) tctdbqrysetorder(qry, name, type);
            } else if((!strcmp(cmd, "setlimit") || !strcmp(cmd, "limit") ||
                       !strcmp(cmd, "setmax") || !strcmp(cmd, "max") ) && tnum > 1){
              const char *maxstr = TCLISTVALPTR(tokens, 1);
              int max = tcatoi(maxstr);
              int skip = 0;
              if(tnum > 2){
                maxstr = TCLISTVALPTR(tokens, 2);
                skip = tcatoi(maxstr);
              }
              tctdbqrysetlimit(qry, max, skip);
            } else if(!strcmp(cmd, "get") || !strcmp(cmd, "columns")){
              if(!cnames) cnames = tclistnew();
              for(int j = 1; j < tnum; j++){
                const char *token;
                int tsiz;
                TCLISTVAL(token, tokens, j, tsiz);
                TCLISTPUSH(cnames, token, tsiz);
              }
            } else if(!strcmp(cmd, "next")){
              if(qrys){
                TCREALLOC(qrys, qrys, sizeof(*qrys) * (qnum + 1));
              } else {
                TCMALLOC(qrys, sizeof(*qrys) * 2);
                qrys[0] = qry;
                qnum = 1;
              }
              qry = tctdbqrynew(adb->tdb);
              qrys[qnum++] = qry;
            } else if(!strcmp(cmd, "mstype") && tnum > 1){
              const char *typestr = TCLISTVALPTR(tokens, 1);
              mstype = tctdbstrtometasearcytype(typestr);
              if(mstype < 0) mstype = TDBMSUNION;
            } else if(!strcmp(cmd, "out") || !strcmp(cmd, "remove")){
              toout = true;
            } else if(!strcmp(cmd, "count")){
              tocnt = true;
            } else if(!strcmp(cmd, "hint")){
              tohint = true;
            }
          }
          tclistdel(tokens);
        }
        if(toout){
          if(cnames){
            rv = tclistnew2(1);
            void *opq[2];
            opq[0] = rv;
            opq[1] = cnames;
            if(!tctdbqryproc2(qry, tcadbtdbqrygetout, opq)){
              tclistdel(rv);
              rv = NULL;
            }
          } else {
            if(tctdbqrysearchout2(qry)){
              rv = tclistnew2(1);
            } else {
              rv = NULL;
            }
          }
        } else {
          if(qrys){
            rv = tctdbmetasearch(qrys, qnum, mstype);
          } else {
            rv = tctdbqrysearch(qry);
          }
          if(cnames){
            int cnnum = TCLISTNUM(cnames);
            int rnum = TCLISTNUM(rv);
            TCLIST *nrv = tclistnew2(rnum);
            for(int i = 0; i < rnum; i++){
              const char *pkbuf;
              int pksiz;
              TCLISTVAL(pkbuf, rv, i, pksiz);
              TCMAP *cols = tctdbget(adb->tdb, pkbuf, pksiz);
              if(cols){
                tcmapput(cols, "", 0, pkbuf, pksiz);
                tcmapmove(cols, "", 0, true);
                if(cnnum > 0){
                  TCMAP *ncols = tcmapnew2(cnnum + 1);
                  for(int j = 0; j < cnnum; j++){
                    const char *cname;
                    int cnsiz;
                    TCLISTVAL(cname, cnames, j, cnsiz);
                    int cvsiz;
                    const char *cvalue = tcmapget(cols, cname, cnsiz, &cvsiz);
                    if(cvalue) tcmapput(ncols, cname, cnsiz, cvalue, cvsiz);
                  }
                  tcmapdel(cols);
                  cols = ncols;
                }
                int csiz;
                char *cbuf = tcstrjoin4(cols, &csiz);
                tclistpushmalloc(nrv, cbuf, csiz);
                tcmapdel(cols);
              }
            }
            tclistdel(rv);
            rv = nrv;
          }
        }
        if(tocnt && rv){
          tclistclear(rv);
          char numbuf[TCNUMBUFSIZ];
          int len = sprintf(numbuf, "%d", tctdbqrycount(qry));
          TCLISTPUSH(rv, numbuf, len);
        }
        if(tohint && rv){
          TCXSTR *hbuf = tcxstrnew();
          TCXSTRCAT(hbuf, "", 1);
          TCXSTRCAT(hbuf, "", 1);
          TCXSTRCAT(hbuf, "[[HINT]]\n", 9);
          const char *hint = tctdbqryhint(qrys ? qrys[0] : qry);
          TCXSTRCAT(hbuf, hint, strlen(hint));
          TCLISTPUSH(rv, TCXSTRPTR(hbuf), TCXSTRSIZE(hbuf));
          tcxstrdel(hbuf);
        }
        if(cnames) tclistdel(cnames);
        if(qrys){
          for(int i = 0; i < qnum; i++){
            tctdbqrydel(qrys[i]);
          }
          TCFREE(qrys);
        } else {
          tctdbqrydel(qry);
        }
      } else if(!strcmp(name, "genuid")){
        rv = tclistnew2(1);
        char numbuf[TCNUMBUFSIZ];
        int nsiz = sprintf(numbuf, "%lld", (long long)tctdbgenuid(adb->tdb));
        TCLISTPUSH(rv, numbuf, nsiz);
      } else {
        rv = NULL;
      }
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->misc){
        rv = skel->misc(skel->opq, name, args);
      } else {
        rv = NULL;
      }
      break;
    default:
      rv = NULL;
      break;
  }
  return rv;
}



/*************************************************************************************************
 * features for experts
 *************************************************************************************************/


/* Set an extra database sleleton to an abstract database object. */
bool tcadbsetskel(TCADB *adb, ADBSKEL *skel){
  assert(skel);
  if(adb->omode != ADBOVOID) return false;
  if(adb->skel) TCFREE(adb->skel);
  adb->skel = tcmemdup(skel, sizeof(*skel));
  return true;
}


/* Set the multiple database skeleton to an abstract database object. */
bool tcadbsetskelmulti(TCADB *adb, int num){
  assert(adb && num >= 0);
  if(adb->omode != ADBOVOID) return false;
  if(num < 1) return false;
  if(num > CHAR_MAX) num = CHAR_MAX;
  ADBMUL *mul = tcadbmulnew(num);
  ADBSKEL skel;
  memset(&skel, 0, sizeof(skel));
  skel.opq = mul;
  skel.del = (void (*)(void *))tcadbmuldel;
  skel.open = (bool (*)(void *, const char *))tcadbmulopen;
  skel.close = (bool (*)(void *))tcadbmulclose;
  skel.put = (bool (*)(void *, const void *, int, const void *, int))tcadbmulput;
  skel.putkeep = (bool (*)(void *, const void *, int, const void *, int))tcadbmulputkeep;
  skel.putcat = (bool (*)(void *, const void *, int, const void *, int))tcadbmulputcat;
  skel.out = (bool (*)(void *, const void *, int))tcadbmulout;
  skel.get = (void *(*)(void *, const void *, int, int *))tcadbmulget;
  skel.vsiz = (int (*)(void *, const void *, int))tcadbmulvsiz;
  skel.iterinit = (bool (*)(void *))tcadbmuliterinit;
  skel.iternext = (void *(*)(void *, int *))tcadbmuliternext;
  skel.fwmkeys = (TCLIST *(*)(void *, const void *, int, int))tcadbmulfwmkeys;
  skel.addint = (int (*)(void *, const void *, int, int))tcadbmuladdint;
  skel.adddouble = (double (*)(void *, const void *, int, double))tcadbmuladddouble;
  skel.sync = (bool (*)(void *))tcadbmulsync;
  skel.optimize = (bool (*)(void *, const char *))tcadbmuloptimize;
  skel.vanish = (bool (*)(void *))tcadbmulvanish;
  skel.copy = (bool (*)(void *, const char *))tcadbmulcopy;
  skel.tranbegin = (bool (*)(void *))tcadbmultranbegin;
  skel.trancommit = (bool (*)(void *))tcadbmultrancommit;
  skel.tranabort = (bool (*)(void *))tcadbmultranabort;
  skel.path = (const char *(*)(void *))tcadbmulpath;
  skel.rnum = (uint64_t (*)(void *))tcadbmulrnum;
  skel.size = (uint64_t (*)(void *))tcadbmulsize;
  skel.misc = (TCLIST *(*)(void *, const char *, const TCLIST *))tcadbmulmisc;
  skel.putproc =
    (bool (*)(void *, const void *, int, const void *, int, TCPDPROC, void *))tcadbmulputproc;
  skel.foreach = (bool (*)(void *, TCITER, void *))tcadbmulforeach;
  if(!tcadbsetskel(adb, &skel)){
    tcadbmuldel(mul);
    return false;
  }
  return true;
}


/* Get the open mode of an abstract database object. */
int tcadbomode(TCADB *adb){
  assert(adb);
  return adb->omode;
}


/* Get the concrete database object of an abstract database object. */
void *tcadbreveal(TCADB *adb){
  assert(adb);
  void *rv;
  switch(adb->omode){
    case ADBOMDB:
      rv = adb->mdb;
      break;
    case ADBONDB:
      rv = adb->ndb;
      break;
    case ADBOHDB:
      rv = adb->hdb;
      break;
    case ADBOBDB:
      rv = adb->bdb;
      break;
    case ADBOFDB:
      rv = adb->fdb;
      break;
    case ADBOTDB:
      rv = adb->tdb;
      break;
    case ADBOSKEL:
      rv = adb->skel;
      break;
    default:
      rv = NULL;
      break;
  }
  return rv;
}


/* Store a record into an abstract database object with a duplication handler. */
bool tcadbputproc(TCADB *adb, const void *kbuf, int ksiz, const void *vbuf, int vsiz,
                  TCPDPROC proc, void *op){
  assert(adb && kbuf && ksiz >= 0 && proc);
  bool err = false;
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
      if(tcmdbputproc(adb->mdb, kbuf, ksiz, vbuf, vsiz, proc, op)){
        if(adb->capnum > 0 || adb->capsiz > 0){
          adb->capcnt++;
          if((adb->capcnt & 0xff) == 0){
            if(adb->capnum > 0 && tcmdbrnum(adb->mdb) > adb->capnum + 0x100)
              tcmdbcutfront(adb->mdb, 0x100);
            if(adb->capsiz > 0 && tcmdbmsiz(adb->mdb) > adb->capsiz)
              tcmdbcutfront(adb->mdb, 0x200);
          }
        }
      } else {
        err = true;
      }
      break;
    case ADBONDB:
      if(tcndbputproc(adb->ndb, kbuf, ksiz, vbuf, vsiz, proc, op)){
        if(adb->capnum > 0 || adb->capsiz > 0){
          adb->capcnt++;
          if((adb->capcnt & 0xff) == 0){
            if(adb->capnum > 0 && tcndbrnum(adb->ndb) > adb->capnum + 0x100)
              tcndbcutfringe(adb->ndb, 0x100);
            if(adb->capsiz > 0 && tcndbmsiz(adb->ndb) > adb->capsiz)
              tcndbcutfringe(adb->ndb, 0x200);
          }
        }
      } else {
        err = true;
      }
      break;
    case ADBOHDB:
      if(!tchdbputproc(adb->hdb, kbuf, ksiz, vbuf, vsiz, proc, op)) err = true;
      break;
    case ADBOBDB:
      if(!tcbdbputproc(adb->bdb, kbuf, ksiz, vbuf, vsiz, proc, op)) err = true;
      break;
    case ADBOFDB:
      if(!tcfdbputproc(adb->fdb, tcfdbkeytoid(kbuf, ksiz), vbuf, vsiz, proc, op)) err = true;
      break;
    case ADBOTDB:
      if(!tctdbputproc(adb->tdb, kbuf, ksiz, vbuf, vsiz, proc, op)) err = true;
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->putproc){
        if(!skel->putproc(skel->opq, kbuf, ksiz, vbuf, vsiz, proc, op)) err = true;
      } else {
        err = true;
      }
      break;
    default:
      err = true;
      break;
  }
  return !err;
}


/* Process each record atomically of an abstract database object. */
bool tcadbforeach(TCADB *adb, TCITER iter, void *op){
  assert(adb && iter);
  bool err = false;
  ADBSKEL *skel;
  switch(adb->omode){
    case ADBOMDB:
      tcmdbforeach(adb->mdb, iter, op);
      break;
    case ADBONDB:
      tcndbforeach(adb->ndb, iter, op);
      break;
    case ADBOHDB:
      if(!tchdbforeach(adb->hdb, iter, op)) err = true;
      break;
    case ADBOBDB:
      if(!tcbdbforeach(adb->bdb, iter, op)) err = true;
      break;
    case ADBOFDB:
      if(!tcfdbforeach(adb->fdb, iter, op)) err = true;
      break;
    case ADBOTDB:
      if(!tctdbforeach(adb->tdb, iter, op)) err = true;
      break;
    case ADBOSKEL:
      skel = adb->skel;
      if(skel->foreach){
        if(!skel->foreach(skel->opq, iter, op)) err = true;
      } else {
        err = true;
      }
      break;
    default:
      err = true;
      break;
  }
  return !err;
}


/* Map records of an abstract database object into another B+ tree database. */
bool tcadbmapbdb(TCADB *adb, TCLIST *keys, TCBDB *bdb, ADBMAPPROC proc, void *op, int64_t csiz){
  assert(adb && bdb && proc);
  if(csiz < 0) csiz = 256LL << 20;
  TCLIST *recs = tclistnew2(tclmin(csiz / 64 + 256, INT_MAX / 4));
  ADBMAPBDB map;
  map.adb = adb;
  map.bdb = bdb;
  map.recs = recs;
  map.proc = proc;
  map.op = op;
  map.rsiz = 0;
  map.csiz = csiz;
  bool err = false;
  if(keys){
    int knum = TCLISTNUM(keys);
    for(int i = 0; i < knum && !err; i++){
      const char *kbuf;
      int ksiz;
      TCLISTVAL(kbuf, keys, i, ksiz);
      int vsiz;
      char *vbuf = tcadbget(adb, kbuf, ksiz, &vsiz);
      if(vbuf){
        if(!tcadbmapbdbiter(kbuf, ksiz, vbuf, vsiz, &map)) err = true;
        TCFREE(vbuf);
        if(map.rsiz > map.csiz && !tcadbmapbdbdump(&map)) err = true;
      }
      if(map.rsiz > 0 && !tcadbmapbdbdump(&map)) err = true;
    }
  } else {
    if(!tcadbforeach(adb, tcadbmapbdbiter, &map)) err = true;
  }
  if(map.rsiz > 0 && !tcadbmapbdbdump(&map)) err = true;
  tclistdel(recs);
  return !err;
}


/* Emit records generated by the mapping function into the result map. */
bool tcadbmapbdbemit(void *map, const char *kbuf, int ksiz, const char *vbuf, int vsiz){
  assert(map && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  ADBMAPBDB *mymap = map;
  int rsiz = sizeof(ksiz) + ksiz + vsiz;
  char stack[TCNUMBUFSIZ*8];
  char *rbuf;
  if(rsiz <= sizeof(stack)){
    rbuf = stack;
  } else {
    TCMALLOC(rbuf, rsiz);
  }
  bool err = false;
  char *wp = rbuf;
  memcpy(wp, &ksiz, sizeof(ksiz));
  wp += sizeof(ksiz);
  memcpy(wp, kbuf, ksiz);
  wp += ksiz;
  memcpy(wp, vbuf, vsiz);
  TCLISTPUSH(mymap->recs, rbuf, rsiz);
  mymap->rsiz += rsiz + sizeof(TCLISTDATUM);
  if(rbuf != stack) TCFREE(rbuf);
  if(mymap->rsiz > mymap->csiz && !tcadbmapbdbdump(map)) err = true;
  return !err;
}



/*************************************************************************************************
 * private features
 *************************************************************************************************/


/* Create a multiple database object.
   `num' specifies the number of inner databases.
   The return value is the new multiple database object. */
static ADBMUL *tcadbmulnew(int num){
  assert(num > 0);
  ADBMUL *mul;
  TCMALLOC(mul, sizeof(*mul));
  mul->adbs = NULL;
  mul->num = num;
  mul->iter = -1;
  mul->path = NULL;
  return mul;
}


/* Delete a multiple database object.
   `mul' specifies the multiple database object. */
static void tcadbmuldel(ADBMUL *mul){
  assert(mul);
  if(mul->adbs) tcadbmulclose(mul);
  TCFREE(mul);
}


/* Open a multiple database.
   `mul' specifies the multiple database object.
   If successful, the return value is true, else, it is false. */
static bool tcadbmulopen(ADBMUL *mul, const char *name){
  assert(mul && name);
  if(mul->adbs) return false;
  mul->iter = -1;
  TCLIST *elems = tcstrsplit(name, "#");
  char *path = tclistshift2(elems);
  if(!path){
    tclistdel(elems);
    return false;
  }
  const char *ext = strrchr(path, MYEXTCHR);
  if(!ext) ext = "";
  const char *params = strchr(name, '#');
  if(!params) params = "";
  bool owmode = true;
  bool ocmode = true;
  bool otmode = false;
  int ln = TCLISTNUM(elems);
  for(int i = 0; i < ln; i++){
    const char *elem = TCLISTVALPTR(elems, i);
    char *pv = strchr(elem, '=');
    if(!pv) continue;
    *(pv++) = '\0';
    if(!tcstricmp(elem, "mode")){
      owmode = strchr(pv, 'w') || strchr(pv, 'W');
      ocmode = strchr(pv, 'c') || strchr(pv, 'C');
      otmode = strchr(pv, 't') || strchr(pv, 'T');
    }
  }
  tclistdel(elems);
  bool err = false;
  char *gpat = tcsprintf("%s%c%s*%s", path, MYPATHCHR, ADBMULPREFIX, ext);
  TCLIST *cpaths = tcglobpat(gpat);
  tclistsort(cpaths);
  int cnum = TCLISTNUM(cpaths);
  if(owmode){
    if(otmode){
      for(int i = 0; i < cnum; i++){
        const char *cpath = TCLISTVALPTR(cpaths, i);
        if(unlink(cpath) != 0) err = true;
      }
      tclistclear(cpaths);
      cnum = 0;
    }
    if(ocmode && cnum < 1){
      if(mkdir(path, ADBDIRMODE) != 0 && errno != EEXIST){
        err = true;
      } else {
        for(int i = 0; i < mul->num; i++){
          tclistprintf(cpaths, "%s%c%s%03d%s", path, MYPATHCHR, ADBMULPREFIX, i + 1, ext);
        }
        cnum = TCLISTNUM(cpaths);
      }
    }
  }
  if(!err && cnum > 0){
    TCADB **adbs;
    TCMALLOC(adbs, sizeof(*adbs) * cnum);
    for(int i = 0; i < cnum; i++){
      TCADB *adb = tcadbnew();
      const char *cpath = TCLISTVALPTR(cpaths, i);
      char *cname = tcsprintf("%s%s", cpath, params);
      if(!tcadbopen(adb, cname)) err = true;
      TCFREE(cname);
      adbs[i] = adb;
    }
    if(err){
      for(int i = cnum - 1; i >= 0; i--){
        tcadbdel(adbs[i]);
      }
      TCFREE(adbs);
    } else {
      mul->adbs = adbs;
      mul->num = cnum;
      mul->path = path;
      path = NULL;
    }
  }
  tclistdel(cpaths);
  TCFREE(gpat);
  TCFREE(path);
  return !err;
}


/* Close a multiple database object.
   `mul' specifies the multiple database object.
   If successful, the return value is true, else, it is false. */
static bool tcadbmulclose(ADBMUL *mul){
  assert(mul);
  if(!mul->adbs) return false;
  TCADB **adbs = mul->adbs;
  int num = mul->num;
  bool err = false;
  for(int i = num - 1; i >= 0; i--){
    TCADB *adb = adbs[i];
    if(!tcadbclose(adb)) err = true;
    tcadbdel(adb);
  }
  TCFREE(mul->path);
  TCFREE(adbs);
  mul->adbs = NULL;
  mul->path = NULL;
  return !err;
}


/* Store a record into a multiple database object.
   `mul' specifies the multiple database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   `vbuf' specifies the pointer to the region of the value.
   `vsiz' specifies the size of the region of the value.
   If successful, the return value is true, else, it is false. */
static bool tcadbmulput(ADBMUL *mul, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(mul && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  if(!mul->adbs) return false;
  int idx = tcadbmulidx(mul, kbuf, ksiz);
  TCADB *adb = mul->adbs[idx];
  return tcadbput(adb, kbuf, ksiz, vbuf, vsiz);
}


/* Store a new record into a multiple database object.
   `mul' specifies the multiple database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   `vbuf' specifies the pointer to the region of the value.
   `vsiz' specifies the size of the region of the value.
   If successful, the return value is true, else, it is false. */
static bool tcadbmulputkeep(ADBMUL *mul, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(mul && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  if(!mul->adbs) return false;
  int idx = tcadbmulidx(mul, kbuf, ksiz);
  TCADB *adb = mul->adbs[idx];
  return tcadbputkeep(adb, kbuf, ksiz, vbuf, vsiz);
}


/* Concatenate a value at the end of the existing record in a multiple database object.
   `mul' specifies the multiple database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   `vbuf' specifies the pointer to the region of the value.
   `vsiz' specifies the size of the region of the value.
   If successful, the return value is true, else, it is false. */
static bool tcadbmulputcat(ADBMUL *mul, const void *kbuf, int ksiz, const void *vbuf, int vsiz){
  assert(mul && kbuf && ksiz >= 0 && vbuf && vsiz >= 0);
  if(!mul->adbs) return false;
  int idx = tcadbmulidx(mul, kbuf, ksiz);
  TCADB *adb = mul->adbs[idx];
  return tcadbputcat(adb, kbuf, ksiz, vbuf, vsiz);
}


/* Remove a record of a multiple database object.
   `mul' specifies the multiple database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   If successful, the return value is true, else, it is false. */
static bool tcadbmulout(ADBMUL *mul, const void *kbuf, int ksiz){
  assert(mul && kbuf && ksiz >= 0);
  if(!mul->adbs) return false;
  int idx = tcadbmulidx(mul, kbuf, ksiz);
  TCADB *adb = mul->adbs[idx];
  return tcadbout(adb, kbuf, ksiz);
}


/* Retrieve a record in a multiple database object.
   `mul' specifies the multiple database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   `sp' specifies the pointer to the variable into which the size of the region of the return
   value is assigned.
   If successful, the return value is the pointer to the region of the value of the corresponding
   record. */
static void *tcadbmulget(ADBMUL *mul, const void *kbuf, int ksiz, int *sp){
  assert(mul && kbuf && ksiz >= 0 && sp);
  if(!mul->adbs) return false;
  int idx = tcadbmulidx(mul, kbuf, ksiz);
  TCADB *adb = mul->adbs[idx];
  return tcadbget(adb, kbuf, ksiz, sp);
}


/* Get the size of the value of a record in a multiple database object.
   `mul' specifies the multiple database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   If successful, the return value is the size of the value of the corresponding record, else,
   it is -1. */
static int tcadbmulvsiz(ADBMUL *mul, const void *kbuf, int ksiz){
  assert(mul && kbuf && ksiz >= 0);
  if(!mul->adbs) return false;
  int idx = tcadbmulidx(mul, kbuf, ksiz);
  TCADB *adb = mul->adbs[idx];
  return tcadbvsiz(adb, kbuf, ksiz);
}


/* Initialize the iterator of a multiple database object.
   `mul' specifies the multiple database object.
   If successful, the return value is true, else, it is false. */
static bool tcadbmuliterinit(ADBMUL *mul){
  assert(mul);
  if(!mul->adbs) return false;
  mul->iter = -1;
  TCADB **adbs = mul->adbs;
  int num = mul->num;
  bool err = false;
  for(int i = 0; i < num; i++){
    if(!tcadbiterinit(adbs[i])) err = true;
  }
  if(err) return false;
  mul->iter = 0;
  return true;
}


/* Get the next key of the iterator of a multiple database object.
   `mul' specifies the multiple database object.
   `sp' specifies the pointer to the variable into which the size of the region of the return
   value is assigned.
   If successful, the return value is the pointer to the region of the next key, else, it is
   `NULL'. */
static void *tcadbmuliternext(ADBMUL *mul, int *sp){
  assert(mul && sp);
  if(!mul->adbs || mul->iter < 0) return false;
  while(mul->iter < mul->num){
    TCADB *adb = mul->adbs[mul->iter];
    char *rv = tcadbiternext(adb, sp);
    if(rv) return rv;
    mul->iter++;
  }
  mul->iter = -1;
  return NULL;
}


/* Get forward matching keys in a multiple database object.
   `mul' specifies the multiple database object.
   `pbuf' specifies the pointer to the region of the prefix.
   `psiz' specifies the size of the region of the prefix.
   `max' specifies the maximum number of keys to be fetched.  If it is negative, no limit is
   specified.
   The return value is a list object of the corresponding keys. */
static TCLIST *tcadbmulfwmkeys(ADBMUL *mul, const void *pbuf, int psiz, int max){
  assert(mul && pbuf && psiz >= 0);
  if(!mul->adbs) return tclistnew2(1);
  if(max < 0) max = INT_MAX;
  TCADB **adbs = mul->adbs;
  int num = mul->num;
  TCLIST *rv = tclistnew();
  for(int i = 0; i < num && TCLISTNUM(rv) < max; i++){
    TCLIST *res = tcadbfwmkeys(adbs[i], pbuf, psiz, max);
    int rnum = TCLISTNUM(res);
    for(int j = 0; j < rnum && TCLISTNUM(rv) < max; j++){
      const char *vbuf;
      int vsiz;
      TCLISTVAL(vbuf, res, j, vsiz);
      TCLISTPUSH(rv, vbuf, vsiz);
    }
    tclistdel(res);
  }
  return rv;
}


/* Add an integer to a record in a multiple database object.
   `mul' specifies the multiple database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   `num' specifies the additional value.
   If successful, the return value is the summation value, else, it is `INT_MIN'. */
static int tcadbmuladdint(ADBMUL *mul, const void *kbuf, int ksiz, int num){
  assert(mul && kbuf && ksiz >= 0);
  if(!mul->adbs) return INT_MIN;
  int idx = tcadbmulidx(mul, kbuf, ksiz);
  TCADB *adb = mul->adbs[idx];
  return tcadbaddint(adb, kbuf, ksiz, num);
}


/* Add a real number to a record in a multiple database object.
   `mul' specifies the multiple database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   `num' specifies the additional value.
   If successful, the return value is the summation value, else, it is Not-a-Number. */
static double tcadbmuladddouble(ADBMUL *mul, const void *kbuf, int ksiz, double num){
  assert(mul && kbuf && ksiz >= 0);
  if(!mul->adbs) return nan("");
  int idx = tcadbmulidx(mul, kbuf, ksiz);
  TCADB *adb = mul->adbs[idx];
  return tcadbadddouble(adb, kbuf, ksiz, num);
}


/* Synchronize updated contents of a multiple database object with the file and the device.
   `mul' specifies the multiple database object.
   If successful, the return value is true, else, it is false. */
static bool tcadbmulsync(ADBMUL *mul){
  assert(mul);
  if(!mul->adbs) return false;
  TCADB **adbs = mul->adbs;
  int num = mul->num;
  bool err = false;
  for(int i = 0; i < num; i++){
    if(!tcadbsync(adbs[i])) err = true;
  }
  return !err;
}


/* Optimize the storage of a multiple database object.
   `mul' specifies the multiple database object.
   `params' specifies the string of the tuning parameters, which works as with the tuning
   of parameters the function `tcadbmulopen'.
   If successful, the return value is true, else, it is false. */
static bool tcadbmuloptimize(ADBMUL *mul, const char *params){
  assert(mul);
  if(!mul->adbs) return false;
  mul->iter = -1;
  TCADB **adbs = mul->adbs;
  int num = mul->num;
  bool err = false;
  for(int i = 0; i < num; i++){
    if(!tcadboptimize(adbs[i], params)) err = true;
  }
  return !err;
}


/* Remove all records of a multiple database object.
   `mul' specifies the multiple database object.
   If successful, the return value is true, else, it is false. */
static bool tcadbmulvanish(ADBMUL *mul){
  assert(mul);
  if(!mul->adbs) return false;
  mul->iter = -1;
  TCADB **adbs = mul->adbs;
  int num = mul->num;
  bool err = false;
  for(int i = 0; i < num; i++){
    if(!tcadbvanish(adbs[i])) err = true;
  }
  return !err;
}


/* Copy the database file of a multiple database object.
   `mul' specifies the multiple database object.
   `path' specifies the path of the destination file.
   If successful, the return value is true, else, it is false.  False is returned if the executed
   command returns non-zero code. */
static bool tcadbmulcopy(ADBMUL *mul, const char *path){
  assert(mul && path);
  TCADB **adbs = mul->adbs;
  int num = mul->num;
  bool err = false;
  if(*path == '@'){
    for(int i = 0; i < num; i++){
      if(!tcadbcopy(adbs[i], path)) err = true;
    }
  } else {
    if(mkdir(path, ADBDIRMODE) == -1 && errno != EEXIST) return false;
    for(int i = 0; i < num; i++){
      TCADB *adb = adbs[i];
      const char *cpath = tcadbpath(adb);
      if(cpath){
        const char *cname = strrchr(cpath, MYPATHCHR);
        cname = cname ? cname + 1 : cpath;
        const char *ext = strrchr(cname, MYEXTCHR);
        if(!ext) ext = "";
        char *npath = tcsprintf("%s%c%s%03d%s", path, MYPATHCHR, ADBMULPREFIX, i + 1, ext);
        if(!tcadbcopy(adb, npath)) err = true;
        TCFREE(npath);
      } else {
        err = true;
      }
    }
  }
  return !err;
}


/* Begin the transaction of a multiple database object.
   `mul' specifies the multiple database object.
   If successful, the return value is true, else, it is false. */
static bool tcadbmultranbegin(ADBMUL *mul){
  assert(mul);
  if(!mul->adbs) return false;
  TCADB **adbs = mul->adbs;
  int num = mul->num;
  bool err = false;
  for(int i = 0; i < num; i++){
    if(!tcadbtranbegin(adbs[i])){
      while(--i >= 0){
        tcadbtranabort(adbs[i]);
      }
      err = true;
      break;
    }
  }
  return !err;
}


/* Commit the transaction of a multiple database object.
   `mul' specifies the multiple database object.
   If successful, the return value is true, else, it is false. */
static bool tcadbmultrancommit(ADBMUL *mul){
  assert(mul);
  if(!mul->adbs) return false;
  TCADB **adbs = mul->adbs;
  int num = mul->num;
  bool err = false;
  for(int i = num - 1; i >= 0; i--){
    if(!tcadbtrancommit(adbs[i])) err = true;
  }
  return !err;
}


/* Abort the transaction of a multiple database object.
   `mul' specifies the multiple database object.
   If successful, the return value is true, else, it is false. */
static bool tcadbmultranabort(ADBMUL *mul){
  assert(mul);
  if(!mul->adbs) return false;
  TCADB **adbs = mul->adbs;
  int num = mul->num;
  bool err = false;
  for(int i = num - 1; i >= 0; i--){
    if(!tcadbtranabort(adbs[i])) err = true;
  }
  return !err;
}


/* Get the file path of a multiple database object.
   `mul' specifies the multiple database object.
   The return value is the path of the database file or `NULL' if the object does not connect to
   any database. */
static const char *tcadbmulpath(ADBMUL *mul){
  assert(mul);
  if(!mul->adbs) return NULL;
  return mul->path;
}


/* Get the number of records of a multiple database object.
   `mul' specifies the multiple database object.
   The return value is the number of records or 0 if the object does not connect to any database
   instance. */
static uint64_t tcadbmulrnum(ADBMUL *mul){
  assert(mul);
  if(!mul->adbs) return 0;
  TCADB **adbs = mul->adbs;
  int num = mul->num;
  uint64_t rnum = 0;
  for(int i = 0; i < num; i++){
    rnum += tcadbrnum(adbs[i]);
  }
  return rnum;
}


/* Get the size of the database of a multiple database object.
   `mul' specifies the multiple database object.
   The return value is the size of the database or 0 if the object does not connect to any
   database instance. */
static uint64_t tcadbmulsize(ADBMUL *mul){
  assert(mul);
  if(!mul->adbs) return 0;
  TCADB **adbs = mul->adbs;
  int num = mul->num;
  uint64_t size = 0;
  for(int i = 0; i < num; i++){
    size += tcadbsize(adbs[i]);
  }
  return size;
}


/* Call a versatile function for miscellaneous operations of a multiple database object.
   `mul' specifies the multiple database object.
   `name' specifies the name of the function.
   `args' specifies a list object containing arguments.
   If successful, the return value is a list object of the result. */
static TCLIST *tcadbmulmisc(ADBMUL *mul, const char *name, const TCLIST *args){
  assert(mul && name);
  if(!mul->adbs) return NULL;
  TCADB **adbs = mul->adbs;
  int num = mul->num;
  TCLIST *rv = tclistnew();
  if(*name == '@'){
    name++;
    int anum = TCLISTNUM(args) - 1;
    TCLIST *targs = tclistnew2(2);
    for(int i = 0; i < anum; i++){
      const char *kbuf;
      int ksiz;
      TCLISTVAL(kbuf, args, i, ksiz);
      tclistclear(targs);
      TCLISTPUSH(targs, kbuf, ksiz);
      int idx = tcadbmulidx(mul, kbuf, ksiz);
      TCADB *adb = mul->adbs[idx];
      TCLIST *res = tcadbmisc(adb, name, targs);
      if(res){
        int rnum = TCLISTNUM(res);
        for(int j = 0; j < rnum; j++){
          const char *vbuf;
          int vsiz;
          TCLISTVAL(vbuf, res, j, vsiz);
          TCLISTPUSH(rv, vbuf, vsiz);
        }
        tclistdel(res);
      }
    }
    tclistdel(targs);
  } else if(*name == '%'){
    name++;
    int anum = TCLISTNUM(args) - 1;
    TCLIST *targs = tclistnew2(2);
    for(int i = 0; i < anum; i += 2){
      const char *kbuf, *vbuf;
      int ksiz, vsiz;
      TCLISTVAL(kbuf, args, i, ksiz);
      TCLISTVAL(vbuf, args, i + 1, vsiz);
      tclistclear(targs);
      TCLISTPUSH(targs, kbuf, ksiz);
      TCLISTPUSH(targs, vbuf, vsiz);
      int idx = tcadbmulidx(mul, kbuf, ksiz);
      TCADB *adb = mul->adbs[idx];
      TCLIST *res = tcadbmisc(adb, name, targs);
      if(res){
        int rnum = TCLISTNUM(res);
        for(int j = 0; j < rnum; j++){
          TCLISTVAL(vbuf, res, j, vsiz);
          TCLISTPUSH(rv, vbuf, vsiz);
        }
        tclistdel(res);
      }
    }
    tclistdel(targs);
  } else {
    for(int i = 0; i < num; i++){
      TCLIST *res = tcadbmisc(adbs[i], name, args);
      if(res){
        int rnum = TCLISTNUM(res);
        for(int j = 0; j < rnum; j++){
          const char *vbuf;
          int vsiz;
          TCLISTVAL(vbuf, res, j, vsiz);
          TCLISTPUSH(rv, vbuf, vsiz);
        }
        tclistdel(res);
      } else {
        tclistdel(rv);
        rv = NULL;
        break;
      }
    }
  }
  return rv;
}


/* Store a record into a multiple database object with a duplication handler.
   `mul' specifies the multiple database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   `vbuf' specifies the pointer to the region of the value.
   `vsiz' specifies the size of the region of the value.
   `proc' specifies the pointer to the callback function to process duplication.
   `op' specifies an arbitrary pointer to be given as a parameter of the callback function.
   If successful, the return value is true, else, it is false. */
static bool tcadbmulputproc(ADBMUL *mul, const void *kbuf, int ksiz, const void *vbuf, int vsiz,
                            TCPDPROC proc, void *op){
  assert(mul && kbuf && ksiz >= 0 && proc);
  if(!mul->adbs) return false;
  int idx = tcadbmulidx(mul, kbuf, ksiz);
  TCADB *adb = mul->adbs[idx];
  return tcadbputproc(adb, kbuf, ksiz, vbuf, vsiz, proc, op);
}


/* Process each record atomically of a multiple database object.
   `mul' specifies the multiple database object.
   `iter' specifies the pointer to the iterator function called for each record.
   `op' specifies an arbitrary pointer to be given as a parameter of the iterator function.
   If successful, the return value is true, else, it is false. */
static bool tcadbmulforeach(ADBMUL *mul, TCITER iter, void *op){
  assert(mul && iter);
  if(!mul->adbs) return false;
  TCADB **adbs = mul->adbs;
  int num = mul->num;
  bool err = false;
  for(int i = 0; i < num; i++){
    if(!tcadbforeach(adbs[i], iter, op)){
      err = true;
      break;
    }
  }
  return !err;
}


/* Get the database index of a multiple database object.
   `mul' specifies the multiple database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   The return value is the bucket index. */
static int tcadbmulidx(ADBMUL *mul, const void *kbuf, int ksiz){
  assert(mul && kbuf && ksiz >= 0);
  uint32_t hash = 20090810;
  const char *rp = (char *)kbuf + ksiz;
  while(ksiz--){
    hash = (hash * 29) ^ *(uint8_t *)--rp;
  }
  return hash % mul->num;
}


/* Call the mapping function for every record of a multiple database object.
   `kbuf' specifies the pointer to the region of the key.
   `ksiz' specifies the size of the region of the key.
   `vbuf' specifies the pointer to the region of the value.
   `vsiz' specifies the size of the region of the value.
   `op' specifies the pointer to the optional opaque object.
   The return value is true to continue iteration or false to stop iteration. */
static bool tcadbmapbdbiter(const void *kbuf, int ksiz, const void *vbuf, int vsiz, void *op){
  assert(kbuf && ksiz >= 0 && vbuf && vsiz >= 0 && op);
  ADBMAPBDB *map = op;
  bool err = false;
  if(!map->proc(map, kbuf, ksiz, vbuf, vsiz, map->op)) err = true;
  return !err;
}


/* Dump all cached records into the B+ tree database.
   `map' specifies the mapper object for the B+ tree database.
   The return value is true if successful, else, it is false. */
static bool tcadbmapbdbdump(ADBMAPBDB *map){
  assert(map);
  TCBDB *bdb = map->bdb;
  TCLIST *recs = map->recs;
  int rnum = TCLISTNUM(recs);
  TCCMP cmp = tcbdbcmpfunc(bdb);
  if(cmp == tccmplexical){
    tclistsortex(recs, tcadbmapreccmplexical);
  } else if(cmp == tccmpdecimal){
    tclistsortex(recs, tcadbmapreccmpdecimal);
  } else if(cmp == tccmpint32){
    tclistsortex(recs, tcadbmapreccmpint32);
  } else if(cmp == tccmpint64){
    tclistsortex(recs, tcadbmapreccmpint64);
  }
  bool err = false;
  for(int i = 0; i < rnum; i++){
    const char *rbuf;
    int rsiz;
    TCLISTVAL(rbuf, recs, i, rsiz);
    int ksiz;
    memcpy(&ksiz, rbuf, sizeof(ksiz));
    const char *kbuf = rbuf + sizeof(ksiz);
    if(!tcbdbputdup(bdb, kbuf, ksiz, kbuf + ksiz, rsiz - sizeof(ksiz) - ksiz)){
      err = true;
      break;
    }
  }
  tclistclear(recs);
  map->rsiz = 0;
  return !err;
}


/* Compare two list elements by lexical order for mapping.
   `a' specifies the pointer to one element.
   `b' specifies the pointer to the other element.
   The return value is positive if the former is big, negative if the latter is big, 0 if both
   are equivalent. */
static int tcadbmapreccmplexical(const TCLISTDATUM *a, const TCLISTDATUM *b){
  assert(a && b);
  unsigned char *ao = (unsigned char *)((TCLISTDATUM *)a)->ptr;
  unsigned char *bo = (unsigned char *)((TCLISTDATUM *)b)->ptr;
  int size = (((TCLISTDATUM *)a)->size < ((TCLISTDATUM *)b)->size) ?
    ((TCLISTDATUM *)a)->size : ((TCLISTDATUM *)b)->size;
  for(int i = sizeof(int); i < size; i++){
    if(ao[i] > bo[i]) return 1;
    if(ao[i] < bo[i]) return -1;
  }
  return ((TCLISTDATUM *)a)->size - ((TCLISTDATUM *)b)->size;
}


/* Compare two keys as decimal strings of real numbers for mapping.
   `a' specifies the pointer to one element.
   `b' specifies the pointer to the other element.
   The return value is positive if the former is big, negative if the latter is big, 0 if both
   are equivalent. */
static int tcadbmapreccmpdecimal(const TCLISTDATUM *a, const TCLISTDATUM *b){
  assert(a && b);
  return tccmpdecimal(((TCLISTDATUM *)a)->ptr + sizeof(int), a->size - sizeof(int),
                      ((TCLISTDATUM *)b)->ptr + sizeof(int), b->size - sizeof(int), NULL);
}


/* Compare two list elements as 32-bit integers in the native byte order for mapping.
   `a' specifies the pointer to one element.
   `b' specifies the pointer to the other element.
   The return value is positive if the former is big, negative if the latter is big, 0 if both
   are equivalent. */
static int tcadbmapreccmpint32(const TCLISTDATUM *a, const TCLISTDATUM *b){
  assert(a && b);
  return tccmpint32(((TCLISTDATUM *)a)->ptr + sizeof(int), a->size - sizeof(int),
                    ((TCLISTDATUM *)b)->ptr + sizeof(int), b->size - sizeof(int), NULL);
}


/* Compare two list elements as 64-bit integers in the native byte order for mapping.
   `a' specifies the pointer to one element.
   `b' specifies the pointer to the other element.
   The return value is positive if the former is big, negative if the latter is big, 0 if both
   are equivalent. */
static int tcadbmapreccmpint64(const TCLISTDATUM *a, const TCLISTDATUM *b){
  assert(a && b);
  return tccmpint64(((TCLISTDATUM *)a)->ptr + sizeof(int), a->size - sizeof(int),
                    ((TCLISTDATUM *)b)->ptr + sizeof(int), b->size - sizeof(int), NULL);
}


/* Retrieve and remove each record corresponding to a query object.
   `pkbuf' specifies the pointer to the region of the primary key.
   `pksiz' specifies the size of the region of the primary key.
   `cols' specifies a map object containing columns.
   `op' specifies the pointer to the optional opaque object.
   The return value is flags of the post treatment by bitwise-or.
   If successful, the return value is true, else, it is false. */
static int tcadbtdbqrygetout(const void *pkbuf, int pksiz, TCMAP *cols, void *op){
  TCLIST *rv = ((void **)op)[0];
  TCLIST *cnames = ((void **)op)[1];
  int cnnum = TCLISTNUM(cnames);
  tcmapput(cols, "", 0, pkbuf, pksiz);
  tcmapmove(cols, "", 0, true);
  if(cnnum > 0){
    TCMAP *ncols = tcmapnew2(cnnum + 1);
    for(int j = 0; j < cnnum; j++){
      const char *cname;
      int cnsiz;
      TCLISTVAL(cname, cnames, j, cnsiz);
      int cvsiz;
      const char *cvalue = tcmapget(cols, cname, cnsiz, &cvsiz);
      if(cvalue) tcmapput(ncols, cname, cnsiz, cvalue, cvsiz);
    }
    int csiz;
    char *cbuf = tcstrjoin4(ncols, &csiz);
    tclistpushmalloc(rv, cbuf, csiz);
    tcmapdel(ncols);
  } else {
    int csiz;
    char *cbuf = tcstrjoin4(cols, &csiz);
    tclistpushmalloc(rv, cbuf, csiz);
  }
  return TDBQPOUT;
}



// END OF FILE
