initial import
authoradam <anton@adamansky.com>
Sun, 28 Oct 2012 13:43:15 +0000 (20:43 +0700)
committeradam <anton@adamansky.com>
Sun, 28 Oct 2012 13:43:15 +0000 (20:43 +0700)
147 files changed:
.gitignore [new file with mode: 0644]
.gitmodules [moved from t with 100% similarity]
.idea/.name [new file with mode: 0644]
.idea/compiler.xml [new file with mode: 0644]
.idea/copyright/profiles_settings.xml [new file with mode: 0644]
.idea/encodings.xml [new file with mode: 0644]
.idea/inspectionProfiles/Project_Default.xml [new file with mode: 0644]
.idea/inspectionProfiles/profiles_settings.xml [new file with mode: 0644]
.idea/misc.xml [new file with mode: 0644]
.idea/modules.xml [new file with mode: 0644]
.idea/scopes/scope_settings.xml [new file with mode: 0644]
.idea/uiDesigner.xml [new file with mode: 0644]
.idea/vcs.xml [new file with mode: 0644]
README.md [new file with mode: 0644]
tcejdb/COPYING [new file with mode: 0644]
tcejdb/ChangeLog [new file with mode: 0644]
tcejdb/Makefile.in [new file with mode: 0644]
tcejdb/README [new file with mode: 0644]
tcejdb/bros/Makefile [new file with mode: 0644]
tcejdb/bros/bdbtest.c [new file with mode: 0644]
tcejdb/bros/cdbtest.c [new file with mode: 0644]
tcejdb/bros/cmpsqltctest.c [new file with mode: 0644]
tcejdb/bros/gdbmtest.c [new file with mode: 0644]
tcejdb/bros/mapreporter [new file with mode: 0755]
tcejdb/bros/maptest.cc [new file with mode: 0644]
tcejdb/bros/ndbmtest.c [new file with mode: 0644]
tcejdb/bros/qdbmtest.c [new file with mode: 0644]
tcejdb/bros/reporter [new file with mode: 0755]
tcejdb/bros/result.xls [new file with mode: 0644]
tcejdb/bros/sdbmtest.c [new file with mode: 0644]
tcejdb/bros/sqltest.c [new file with mode: 0644]
tcejdb/bros/tctest.c [new file with mode: 0644]
tcejdb/bros/tdbtest.c [new file with mode: 0644]
tcejdb/bson.c [new file with mode: 0644]
tcejdb/bson.h [new file with mode: 0644]
tcejdb/configure [new file with mode: 0755]
tcejdb/configure.in [new file with mode: 0644]
tcejdb/doc/benchmark.pdf [new file with mode: 0644]
tcejdb/doc/common.css [new file with mode: 0644]
tcejdb/doc/icon16.png [new file with mode: 0644]
tcejdb/doc/index.html [new file with mode: 0644]
tcejdb/doc/index.ja.html [new file with mode: 0644]
tcejdb/doc/logo-ja.png [new file with mode: 0644]
tcejdb/doc/logo.png [new file with mode: 0644]
tcejdb/doc/spex-en.html [new file with mode: 0644]
tcejdb/doc/spex-ja.html [new file with mode: 0644]
tcejdb/doc/tokyoproducts.pdf [new file with mode: 0644]
tcejdb/doc/tokyoproducts.ppt [new file with mode: 0644]
tcejdb/ejdb.c [new file with mode: 0644]
tcejdb/ejdb.h [new file with mode: 0644]
tcejdb/ejdb_private.h [new file with mode: 0644]
tcejdb/ejdbutl.h [new file with mode: 0644]
tcejdb/encoding.c [new file with mode: 0644]
tcejdb/encoding.h [new file with mode: 0644]
tcejdb/lab/calccomp [new file with mode: 0755]
tcejdb/lab/datechange [new file with mode: 0755]
tcejdb/lab/diffcheck [new file with mode: 0755]
tcejdb/lab/htmltotsv [new file with mode: 0755]
tcejdb/lab/magic [new file with mode: 0644]
tcejdb/lab/printenv.cgi [new file with mode: 0755]
tcejdb/lab/stepcount [new file with mode: 0755]
tcejdb/lab/stopwatch [new file with mode: 0755]
tcejdb/lab/tabcheck [new file with mode: 0755]
tcejdb/lab/wgettsv [new file with mode: 0755]
tcejdb/lab/widthcheck [new file with mode: 0755]
tcejdb/man/htmltoman [new file with mode: 0755]
tcejdb/man/tcadb.3 [new file with mode: 0644]
tcejdb/man/tcamgr.1 [new file with mode: 0644]
tcejdb/man/tcamttest.1 [new file with mode: 0644]
tcejdb/man/tcatest.1 [new file with mode: 0644]
tcejdb/man/tcbdb.3 [new file with mode: 0644]
tcejdb/man/tcbmgr.1 [new file with mode: 0644]
tcejdb/man/tcbmttest.1 [new file with mode: 0644]
tcejdb/man/tcbtest.1 [new file with mode: 0644]
tcejdb/man/tcfdb.3 [new file with mode: 0644]
tcejdb/man/tcfmgr.1 [new file with mode: 0644]
tcejdb/man/tcfmttest.1 [new file with mode: 0644]
tcejdb/man/tcftest.1 [new file with mode: 0644]
tcejdb/man/tchdb.3 [new file with mode: 0644]
tcejdb/man/tchmgr.1 [new file with mode: 0644]
tcejdb/man/tchmttest.1 [new file with mode: 0644]
tcejdb/man/tchtest.1 [new file with mode: 0644]
tcejdb/man/tclist.3 [new file with mode: 0644]
tcejdb/man/tcmap.3 [new file with mode: 0644]
tcejdb/man/tcmdb.3 [new file with mode: 0644]
tcejdb/man/tcmpool.3 [new file with mode: 0644]
tcejdb/man/tctdb.3 [new file with mode: 0644]
tcejdb/man/tctmgr.1 [new file with mode: 0644]
tcejdb/man/tctmttest.1 [new file with mode: 0644]
tcejdb/man/tctree.3 [new file with mode: 0644]
tcejdb/man/tcttest.1 [new file with mode: 0644]
tcejdb/man/tcucodec.1 [new file with mode: 0644]
tcejdb/man/tcumttest.1 [new file with mode: 0644]
tcejdb/man/tcutest.1 [new file with mode: 0644]
tcejdb/man/tcutil.3 [new file with mode: 0644]
tcejdb/man/tcxstr.3 [new file with mode: 0644]
tcejdb/man/tokyocabinet.3 [new file with mode: 0644]
tcejdb/md5.c [new file with mode: 0644]
tcejdb/md5.h [new file with mode: 0644]
tcejdb/myconf.c [new file with mode: 0644]
tcejdb/myconf.h [new file with mode: 0644]
tcejdb/nbproject/Package-Default.bash [new file with mode: 0644]
tcejdb/nbproject/configurations.xml [new file with mode: 0644]
tcejdb/nbproject/project.xml [new file with mode: 0644]
tcejdb/numbers.c [new file with mode: 0644]
tcejdb/samples/sample1/Makefile [new file with mode: 0644]
tcejdb/samples/sample1/sample1.c [new file with mode: 0644]
tcejdb/tcadb.c [new file with mode: 0644]
tcejdb/tcadb.h [new file with mode: 0644]
tcejdb/tcamgr.c [new file with mode: 0644]
tcejdb/tcamttest.c [new file with mode: 0644]
tcejdb/tcatest.c [new file with mode: 0644]
tcejdb/tcawmgr.c [new file with mode: 0644]
tcejdb/tcbdb.c [new file with mode: 0644]
tcejdb/tcbdb.h [new file with mode: 0644]
tcejdb/tcbmgr.c [new file with mode: 0644]
tcejdb/tcbmttest.c [new file with mode: 0644]
tcejdb/tcbtest.c [new file with mode: 0644]
tcejdb/tcejdb.iml [new file with mode: 0644]
tcejdb/tcejdb.pc.in [new file with mode: 0644]
tcejdb/tcfdb.c [new file with mode: 0644]
tcejdb/tcfdb.h [new file with mode: 0644]
tcejdb/tcfmgr.c [new file with mode: 0644]
tcejdb/tcfmttest.c [new file with mode: 0644]
tcejdb/tcftest.c [new file with mode: 0644]
tcejdb/tchdb.c [new file with mode: 0644]
tcejdb/tchdb.h [new file with mode: 0644]
tcejdb/tchmgr.c [new file with mode: 0644]
tcejdb/tchmttest.c [new file with mode: 0644]
tcejdb/tchtest.c [new file with mode: 0644]
tcejdb/tctdb.c [new file with mode: 0644]
tcejdb/tctdb.h [new file with mode: 0644]
tcejdb/tctmgr.c [new file with mode: 0644]
tcejdb/tctmttest.c [new file with mode: 0644]
tcejdb/tcttest.c [new file with mode: 0644]
tcejdb/tcucodec.c [new file with mode: 0644]
tcejdb/tcumttest.c [new file with mode: 0644]
tcejdb/tcutest.c [new file with mode: 0644]
tcejdb/tcutil.c [new file with mode: 0644]
tcejdb/tcutil.h [new file with mode: 0644]
tcejdb/testejdb/Makefile [new file with mode: 0644]
tcejdb/testejdb/t1.c [new file with mode: 0644]
tcejdb/testejdb/t2.c [new file with mode: 0644]
tcejdb/testejdb/t3.c [new file with mode: 0644]
tcejdb/timsort-impl.h [new file with mode: 0644]
tcejdb/timsort.c [new file with mode: 0644]
tcejdb/tokyocabinet.idl [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..d3fa50c
--- /dev/null
@@ -0,0 +1,44 @@
+*.o
+*.vlog
+
+/.idea/workspace.xml
+
+/tcejdb/Makefile
+/tcejdb/autom4te.cache
+/tcejdb/config.log
+/tcejdb/config.status
+/tcejdb/libtcejdb.a
+/tcejdb/libtcejdb.so
+/tcejdb/libtcejdb.so.9
+/tcejdb/libtcejdb.so.9.11.0
+/tcejdb/tcamgr
+/tcejdb/tcamttest
+/tcejdb/tcatest
+/tcejdb/tcawmgr.cgi
+/tcejdb/tcbmgr
+/tcejdb/tcbmttest
+/tcejdb/tcbtest
+/tcejdb/tcfmgr
+/tcejdb/tcfmttest
+/tcejdb/tcftest
+/tcejdb/tchmgr
+/tcejdb/tchmttest
+/tcejdb/tchtest
+/tcejdb/tctmgr
+/tcejdb/tctmttest
+/tcejdb/tcttest
+/tcejdb/tcucodec
+/tcejdb/tcumttest
+/tcejdb/tcutest
+/tcejdb/tcejdb.pc
+/tcejdb/nbproject/private
+/tcejdb/check.in
+/tcejdb/check.out
+/tcejdb/casket*
+/tcejdb/samples/sample1/sample1
+/tcejdb/samples/sample1/addressbook*
+
+/tcejdb/testejdb/dbt*
+/tcejdb/testejdb/t1
+/tcejdb/testejdb/t2
+/tcejdb/testejdb/t3
diff --git a/t b/.gitmodules
similarity index 100%
rename from t
rename to .gitmodules
diff --git a/.idea/.name b/.idea/.name
new file mode 100644 (file)
index 0000000..2a106dd
--- /dev/null
@@ -0,0 +1 @@
+ejdb
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644 (file)
index 0000000..a1b41c5
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <option name="DEFAULT_COMPILER" value="Javac" />
+    <resourceExtensions />
+    <wildcardResourcePatterns>
+      <entry name="?*.properties" />
+      <entry name="?*.xml" />
+      <entry name="?*.gif" />
+      <entry name="?*.png" />
+      <entry name="?*.jpeg" />
+      <entry name="?*.jpg" />
+      <entry name="?*.html" />
+      <entry name="?*.dtd" />
+      <entry name="?*.tld" />
+      <entry name="?*.ftl" />
+    </wildcardResourcePatterns>
+    <annotationProcessing enabled="false" useClasspath="true" />
+  </component>
+</project>
+
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644 (file)
index 0000000..3572571
--- /dev/null
@@ -0,0 +1,5 @@
+<component name="CopyrightManager">
+  <settings default="">
+    <module2copyright />
+  </settings>
+</component>
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644 (file)
index 0000000..e206d70
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
+</project>
+
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644 (file)
index 0000000..42f45c3
--- /dev/null
@@ -0,0 +1,210 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0" is_locked="false">
+    <option name="myName" value="Project Default" />
+    <option name="myLocal" value="false" />
+    <inspection_tool class="BooleanConstructor" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="CanBeFinal" enabled="false" level="WARNING" enabled_by_default="false">
+      <option name="REPORT_CLASSES" value="true" />
+      <option name="REPORT_METHODS" value="true" />
+      <option name="REPORT_FIELDS" value="true" />
+    </inspection_tool>
+    <inspection_tool class="CastConflictsWithInstanceof" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="CastToIncompatibleInterface" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="CheckImageSize" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="CloneCallsSuperClone" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="CloneDeclaresCloneNotSupported" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="ConstantConditionalExpression" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="ConstantConditions" enabled="false" level="WARNING" enabled_by_default="false">
+      <option name="SUGGEST_NULLABLE_ANNOTATIONS" value="false" />
+      <option name="DONT_REPORT_TRUE_ASSERT_STATEMENTS" value="false" />
+    </inspection_tool>
+    <inspection_tool class="ConstantIfStatement" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="ContinueOrBreakFromFinallyBlock" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="CovariantEquals" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="CssNoGenericFontName" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="CssOverwrittenProperties" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="Deprecation" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="EmptyCatchBlock" enabled="false" level="WARNING" enabled_by_default="false">
+      <option name="m_includeComments" value="true" />
+      <option name="m_ignoreTestCases" value="true" />
+      <option name="m_ignoreIgnoreParameter" value="true" />
+    </inspection_tool>
+    <inspection_tool class="EmptyFinallyBlock" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="EmptyMethod" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="EmptyStatementBody" enabled="false" level="WARNING" enabled_by_default="false">
+      <option name="m_reportEmptyBlocks" value="false" />
+    </inspection_tool>
+    <inspection_tool class="EmptyTryBlock" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="ExtendsAnnotation" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="ExtendsObject" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="FieldCanBeLocal" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="FinalPrivateMethod" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="FinalStaticMethod" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="FinalizeCallsSuperFinalize" enabled="false" level="WARNING" enabled_by_default="false">
+      <option name="ignoreObjectSubclasses" value="false" />
+      <option name="ignoreTrivialFinalizers" value="true" />
+    </inspection_tool>
+    <inspection_tool class="FinallyBlockCannotCompleteNormally" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="Fix shebang" enabled="true" level="WARNING" enabled_by_default="true">/bin/sh#/bin/bash</inspection_tool>
+    <inspection_tool class="ForCanBeForeach" enabled="false" level="WARNING" enabled_by_default="false">
+      <option name="REPORT_INDEXED_LOOP" value="true" />
+      <option name="ignoreUntypedCollections" value="false" />
+    </inspection_tool>
+    <inspection_tool class="ForLoopThatDoesntUseLoopVariable" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="IgnoreResultOfCall" enabled="false" level="WARNING" enabled_by_default="false">
+      <option name="m_reportAllNonLibraryCalls" value="false" />
+      <option name="callCheckString" value="java.io.InputStream,read,java.io.InputStream,skip,java.lang.StringBuffer,toString,java.lang.StringBuilder,toString,java.lang.String,.*,java.math.BigInteger,.*,java.math.BigDecimal,.*,java.net.InetAddress,.*" />
+    </inspection_tool>
+    <inspection_tool class="IncompatibleMask" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="InfiniteLoopStatement" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="InstanceofIncompatibleInterface" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="InstantiatingObjectToGetClassObject" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="IteratorHasNextCallsIteratorNext" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="IteratorNextDoesNotThrowNoSuchElementException" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="JavaDoc" enabled="false" level="WARNING" enabled_by_default="false">
+      <option name="TOP_LEVEL_CLASS_OPTIONS">
+        <value>
+          <option name="ACCESS_JAVADOC_REQUIRED_FOR" value="public" />
+          <option name="REQUIRED_TAGS" value="" />
+        </value>
+      </option>
+      <option name="INNER_CLASS_OPTIONS">
+        <value>
+          <option name="ACCESS_JAVADOC_REQUIRED_FOR" value="protected" />
+          <option name="REQUIRED_TAGS" value="" />
+        </value>
+      </option>
+      <option name="METHOD_OPTIONS">
+        <value>
+          <option name="ACCESS_JAVADOC_REQUIRED_FOR" value="protected" />
+          <option name="REQUIRED_TAGS" value="@return@param@throws or @exception" />
+        </value>
+      </option>
+      <option name="FIELD_OPTIONS">
+        <value>
+          <option name="ACCESS_JAVADOC_REQUIRED_FOR" value="protected" />
+          <option name="REQUIRED_TAGS" value="" />
+        </value>
+      </option>
+      <option name="IGNORE_DEPRECATED" value="false" />
+      <option name="IGNORE_JAVADOC_PERIOD" value="true" />
+      <option name="IGNORE_DUPLICATED_THROWS" value="false" />
+      <option name="IGNORE_POINT_TO_ITSELF" value="false" />
+      <option name="myAdditionalJavadocTags" value="" />
+    </inspection_tool>
+    <inspection_tool class="LoopStatementsThatDontLoop" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="MalformedFormatString" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="MalformedRegex" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="MalformedXPath" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="ManualArrayCopy" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="MismatchedArrayReadWrite" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="MismatchedCollectionQueryUpdate" enabled="false" level="WARNING" enabled_by_default="false">
+      <option name="queryNames">
+        <value />
+      </option>
+      <option name="updateNames">
+        <value />
+      </option>
+    </inspection_tool>
+    <inspection_tool class="MisspelledHashcode" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="MisspelledToString" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="NoExplicitFinalizeCalls" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="NullArgumentToVariableArgMethod" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="NullableProblems" enabled="false" level="WARNING" enabled_by_default="false">
+      <option name="REPORT_NULLABLE_METHOD_OVERRIDES_NOTNULL" value="true" />
+      <option name="REPORT_NOT_ANNOTATED_METHOD_OVERRIDES_NOTNULL" value="true" />
+      <option name="REPORT_NOTNULL_PARAMETER_OVERRIDES_NULLABLE" value="true" />
+      <option name="REPORT_NOT_ANNOTATED_PARAMETER_OVERRIDES_NOTNULL" value="true" />
+      <option name="REPORT_NOT_ANNOTATED_GETTER" value="true" />
+      <option name="REPORT_NOT_ANNOTATED_SETTER_PARAMETER" value="true" />
+      <option name="REPORT_ANNOTATION_NOT_PROPAGATED_TO_OVERRIDERS" value="true" />
+      <option name="REPORT_NULLS_PASSED_TO_NON_ANNOTATED_METHOD" value="true" />
+    </inspection_tool>
+    <inspection_tool class="ObjectEquality" enabled="true" level="WARNING" enabled_by_default="true">
+      <option name="m_ignoreEnums" value="true" />
+      <option name="m_ignoreClassObjects" value="false" />
+      <option name="m_ignorePrivateConstructors" value="false" />
+    </inspection_tool>
+    <inspection_tool class="ObjectEqualsNull" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="OctalLiteral" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="PointlessArithmeticExpression" enabled="false" level="WARNING" enabled_by_default="false">
+      <option name="m_ignoreExpressionsContainingConstants" value="false" />
+    </inspection_tool>
+    <inspection_tool class="PointlessBitwiseExpression" enabled="false" level="WARNING" enabled_by_default="false">
+      <option name="m_ignoreExpressionsContainingConstants" value="false" />
+    </inspection_tool>
+    <inspection_tool class="PointlessBooleanExpression" enabled="false" level="WARNING" enabled_by_default="false">
+      <option name="m_ignoreExpressionsContainingConstants" value="false" />
+    </inspection_tool>
+    <inspection_tool class="RedundantArrayCreation" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="RedundantCast" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="RedundantTypeArguments" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="ReturnFromFinallyBlock" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="SameParameterValue" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="SameReturnValue" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="ShiftOutOfRange" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="SimplifiableConditionalExpression" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
+      <option name="processCode" value="true" />
+      <option name="processLiterals" value="true" />
+      <option name="processComments" value="true" />
+    </inspection_tool>
+    <inspection_tool class="StringConcatenationInsideStringBufferAppend" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="StringConstructor" enabled="false" level="WARNING" enabled_by_default="false">
+      <option name="ignoreSubstringArguments" value="false" />
+    </inspection_tool>
+    <inspection_tool class="StringEquality" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="StringToString" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="SynchronizeOnNonFinalField" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="ThrowFromFinallyBlock" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="TrivialIf" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="TypeParameterExtendsObject" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="UNCHECKED_WARNING" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="UNUSED_IMPORT" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="UNUSED_SYMBOL" enabled="false" level="WARNING" enabled_by_default="false">
+      <option name="LOCAL_VARIABLE" value="true" />
+      <option name="FIELD" value="true" />
+      <option name="METHOD" value="true" />
+      <option name="CLASS" value="true" />
+      <option name="PARAMETER" value="true" />
+      <option name="REPORT_PARAMETER_FOR_PUBLIC_METHODS" value="true" />
+    </inspection_tool>
+    <inspection_tool class="UnhandledExceptionInJSP" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="UnnecessaryBoxing" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="UnnecessaryConditionalExpression" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="UnnecessaryContinue" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="UnnecessaryLabelOnBreakStatement" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="UnnecessaryLabelOnContinueStatement" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="UnnecessaryLocalVariable" enabled="false" level="WARNING" enabled_by_default="false">
+      <option name="m_ignoreImmediatelyReturnedVariables" value="false" />
+      <option name="m_ignoreAnnotatedVariables" value="false" />
+    </inspection_tool>
+    <inspection_tool class="UnnecessaryReturn" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="UnnecessarySemicolon" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="UnnecessaryTemporaryOnConversionFromString" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="UnnecessaryTemporaryOnConversionToString" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="UnnecessaryUnboxing" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="UnresolvedPropertyKey" enabled="false" level="ERROR" enabled_by_default="false" />
+    <inspection_tool class="UnusedAssignment" enabled="false" level="WARNING" enabled_by_default="false">
+      <option name="REPORT_PREFIX_EXPRESSIONS" value="false" />
+      <option name="REPORT_POSTFIX_EXPRESSIONS" value="true" />
+      <option name="REPORT_REDUNDANT_INITIALIZER" value="true" />
+    </inspection_tool>
+    <inspection_tool class="UnusedDeclaration" enabled="false" level="WARNING" enabled_by_default="false">
+      <option name="ADD_MAINS_TO_ENTRIES" value="true" />
+      <option name="ADD_APPLET_TO_ENTRIES" value="true" />
+      <option name="ADD_SERVLET_TO_ENTRIES" value="true" />
+      <option name="ADD_NONJAVA_TO_ENTRIES" value="true" />
+    </inspection_tool>
+    <inspection_tool class="UnusedLabel" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="UnusedMessageFormatParameter" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="UnusedParameters" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="UnusedReturnValue" enabled="false" level="WARNING" enabled_by_default="false" />
+    <inspection_tool class="WeakerAccess" enabled="false" level="WARNING" enabled_by_default="false">
+      <option name="SUGGEST_PACKAGE_LOCAL_FOR_MEMBERS" value="true" />
+      <option name="SUGGEST_PACKAGE_LOCAL_FOR_TOP_CLASSES" value="true" />
+      <option name="SUGGEST_PRIVATE_FOR_INNERS" value="false" />
+    </inspection_tool>
+    <inspection_tool class="WhileCanBeForeach" enabled="false" level="WARNING" enabled_by_default="false" />
+  </profile>
+</component>
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644 (file)
index 0000000..3b31283
--- /dev/null
@@ -0,0 +1,7 @@
+<component name="InspectionProjectProfileManager">
+  <settings>
+    <option name="PROJECT_PROFILE" value="Project Default" />
+    <option name="USE_PROJECT_PROFILE" value="true" />
+    <version value="1.0" />
+  </settings>
+</component>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644 (file)
index 0000000..1e5cd7e
--- /dev/null
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GradleSettings">
+    <option name="gradleHome" value="$USER_HOME$/JavaSoft/gradle-1.1" />
+  </component>
+  <component name="GradleUISettings2">
+    <setting name="root" />
+  </component>
+  <component name="IdProvider" IDEtalkID="1E2BEF7F312F13008425AD019CB97F01" />
+  <component name="IvyIDEA.ProjectSettings">
+    <option name="artifactTypeSettings">
+      <ArtifactTypeSettings />
+    </option>
+    <option name="propertiesSettings">
+      <PropertiesSettings />
+    </option>
+  </component>
+  <component name="JavadocGenerationManager">
+    <option name="OUTPUT_DIRECTORY" />
+    <option name="OPTION_SCOPE" value="protected" />
+    <option name="OPTION_HIERARCHY" value="true" />
+    <option name="OPTION_NAVIGATOR" value="true" />
+    <option name="OPTION_INDEX" value="true" />
+    <option name="OPTION_SEPARATE_INDEX" value="true" />
+    <option name="OPTION_DOCUMENT_TAG_USE" value="false" />
+    <option name="OPTION_DOCUMENT_TAG_AUTHOR" value="false" />
+    <option name="OPTION_DOCUMENT_TAG_VERSION" value="false" />
+    <option name="OPTION_DOCUMENT_TAG_DEPRECATED" value="true" />
+    <option name="OPTION_DEPRECATED_LIST" value="true" />
+    <option name="OTHER_OPTIONS" value="" />
+    <option name="HEAP_SIZE" />
+    <option name="LOCALE" />
+    <option name="OPEN_IN_BROWSER" value="true" />
+  </component>
+  <component name="ModuleEditorState">
+    <option name="LAST_EDITED_MODULE_NAME" />
+    <option name="LAST_EDITED_TAB_NAME" />
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_5" assert-keyword="true" jdk-15="true" project-jdk-name="1.7" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/out" />
+  </component>
+  <component name="Regex">
+    <option name="pos1" value="218" />
+    <option name="pos2" value="218" />
+    <option name="pos3" value="162" />
+    <option name="pos4" value="444" />
+    <option name="pos5" value="162" />
+    <option name="autoUpdate" value="true" />
+    <option name="referenceOn" value="false" />
+    <option name="referencePos" value="0" />
+    <option name="showLabels" value="true" />
+  </component>
+  <component name="WebServicesPlugin" addRequiredLibraries="true" />
+</project>
+
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644 (file)
index 0000000..1f4c732
--- /dev/null
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/tcejdb/tcejdb.iml" filepath="$PROJECT_DIR$/tcejdb/tcejdb.iml" />
+    </modules>
+  </component>
+</project>
+
diff --git a/.idea/scopes/scope_settings.xml b/.idea/scopes/scope_settings.xml
new file mode 100644 (file)
index 0000000..922003b
--- /dev/null
@@ -0,0 +1,5 @@
+<component name="DependencyValidationManager">
+  <state>
+    <option name="SKIP_IMPORT_STATEMENTS" value="false" />
+  </state>
+</component>
\ No newline at end of file
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644 (file)
index 0000000..3b00020
--- /dev/null
@@ -0,0 +1,125 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Palette2">
+    <group name="Swing">
+      <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" />
+      </item>
+      <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" />
+      </item>
+      <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true">
+        <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" />
+        <initial-values>
+          <property name="text" value="Button" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="RadioButton" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="CheckBox" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" />
+        <initial-values>
+          <property name="text" value="Label" />
+        </initial-values>
+      </item>
+      <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1">
+          <preferred-size width="150" height="-1" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3">
+          <preferred-size width="150" height="50" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+          <preferred-size width="200" height="200" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3">
+          <preferred-size width="200" height="200" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" />
+      </item>
+      <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" />
+      </item>
+      <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" />
+      </item>
+      <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1">
+          <preferred-size width="-1" height="20" />
+        </default-constraints>
+      </item>
+      <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false">
+        <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" />
+      </item>
+      <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false">
+        <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" />
+      </item>
+    </group>
+  </component>
+</project>
+
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644 (file)
index 0000000..275077f
--- /dev/null
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+  </component>
+</project>
+
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..8035c41
--- /dev/null
+++ b/README.md
@@ -0,0 +1,210 @@
+
+
+EJDB - Embedded JSON Database engine
+====================================
+
+The main goal of this project to create the fast [MongoDB](http://mongodb.org)-like database engine library **which can be embed
+into C/C++ applications under terms of LGPL license.**
+
+EJDB is the C library based on modified version of [Tokio Cabinet](http://fallabs.com/tokyocabinet/).
+
+JSON representation of queries and data implemented with API based on [C BSON](https://github.com/mongodb/mongo-c-driver/tree/master/src/)
+
+
+One snippet intro
+-----------------------------------
+
+```C
+#include <tcejdb/ejdb.h>
+
+static EJDB* jb;
+
+int main() {
+    jb = ejdbnew();
+    if (!ejdbopen(jb, "addressbook", JDBOWRITER | JDBOCREAT | JDBOTRUNC)) {
+        return 1;
+    }
+    //Get or create collection 'contacts'
+    EJCOLL *coll = ejdbcreatecoll(jb, "contacts", NULL);
+
+    bson bsrec;
+    bson_oid_t oid;
+
+    //Insert one record:
+    //JSON: {'name' : 'John', 'phone' : '333-222-333', 'age' : 58}
+    bson_init(&bsrec);
+    bson_append_string(&bsrec, "name", "John");
+    bson_append_string(&bsrec, "phone", "333-222-333");
+    bson_append_int(&bsrec, "age", 58);
+    bson_finish(&bsrec);
+    //Save BSON
+    ejdbsavebson(coll, &bsrec, &oid);
+    fprintf(stderr, "\nSaved Travolta");
+    bson_destroy(&bsrec);
+
+    //Now execute query
+    //QUERY: {'name' : {'$begin' : 'Bru'}} //Name starts with 'Bru' string
+    bson bq1;
+    bson_init_as_query(&bq1);
+    bson_append_start_object(&bq1, "name");
+    bson_append_string(&bq1, "$begin", "Bru");
+    bson_append_finish_object(&bq1);
+    bson_finish(&bq1);
+
+    EJQ* q1 = ejdbcreatequery(jb, &bq1, NULL, 0, NULL);
+
+    uint32_t count;
+    TCLIST *res = ejdbqrysearch(coll, q1, &count, 0, NULL);
+    fprintf(stderr, "\n\nRecords found: %d\n", count);
+
+    //Now print the result set records
+    for (int i = 0; i < TCLISTNUM(res); ++i) {
+        void *bsdata = TCLISTVALPTR(res, i);
+        bson_print_raw(stderr, bsdata, 0);
+    }
+    fprintf(stderr, "\n");
+
+    //Dispose result set
+    tclistdel(res);
+
+    //Dispose query
+    ejdbquerydel(q1);
+    bson_destroy(&bq1);
+
+    //Close database
+    ejdbclose(jb);
+    ejdbdel(jb);
+    return 0;
+}
+```
+
+Features
+================================
+* LGPL license allows you to embed this library into proprietary software.
+* MongoDB like queries and overall philosophy.
+* Collection level write locking.
+
+
+Building & Installation
+================================
+
+Prerequisites
+--------------------------------
+**Libraries:**
+
+* cunit
+* zlib
+* bzip2
+
+On Debian/Ubuntu linux you can install it as following:
+
+```sh
+   sudo apt-get install libcunit1 libcunit1-dev libbz2-1.0 libbz2-dev
+```
+
+Building
+--------------------------------
+```sh
+   ./configure --prefix=<installation prefix> && make && make check
+   make install
+```
+* library name: **tcejdb** (with pkgconfig)
+* main include header: **<tcejdb/ejdb.h>**
+
+Usage
+===============================
+
+Basic EJDB architecture
+-------------------------------
+**EJDB database files structure**
+
+```
+.
+├── <dbname>
+├── <dbname>_<collection1>
+├── ...
+├── <dbname>_<collectionN>
+└── <dbname>_<collectionN>_<fieldpath>.<index ext>
+```
+
+Where
+
+* ```<dbname>``` - name of database. It is metadata DB.
+* ```<collectionN>``` - name of collection. Collection database.
+* ```<fieldpath>``` - JSON field path used in index
+* ```<index ext>``` - Collection index extension:
+    * ```.lex``` String index
+    * ```.dec``` Number index
+    * ```.tok``` Array index
+
+API
+---------------------------------
+EJDB API presented in **ejdb.h** C header file.
+
+JSON processing API: **bson.h**
+
+Queries
+---------------------------------
+
+```C
+**
+ * Create query object.
+ * Sucessfully created queries must be destroyed with ejdbquerydel().
+ *
+ * EJDB queries inspired by MongoDB (mongodb.org) and follows same philosophy.
+ *
+ *  - Supported queries:
+ *      - Simple matching of String OR Number OR Array value:
+ *          -   {'bson.field.path' : 'val', ...}
+ *      - $not Negate operation.
+ *          -   {'json.field.path' : {'$not' : val}} //Field not equal to val
+ *          -   {'json.field.path' : {'$not' : {'$begin' : prefix}}} //Field not begins with val
+ *      - $begin String starts with prefix
+ *          -   {'json.field.path' : {'$begin' : prefix}}
+ *      - $gt, $gte (>, >=) and $lt, $lte for number types:
+ *          -   {'json.field.path' : {'$gt' : number}, ...}
+ *      - $bt Between for number types:
+ *          -   {'json.field.path' : {'$bt' : [num1, num2]}}
+ *      - $in String OR Number OR Array val matches to value in specified array:
+ *          -   {'json.field.path' : {'$in' : [val1, val2, val3]}}
+ *      - $nin - Not IN
+ *      - $strand String tokens OR String array val matches all tokens in specified array:
+ *          -   {'json.field.path' : {'$strand' : [val1, val2, val3]}}
+ *      - $stror String tokens OR String array val matches any token in specified array:
+ *          -   {'json.field.path' : {'$stror' : [val1, val2, val3]}}
+ *
+ *  NOTE: Negate operations: $not and $nin not using indexes
+ *  so they can be slow in comparison to other matching operations.
+ *
+ *  NOTE: Only one index can be used in search query operation.
+ *
+ *  QUERY HINTS (specified by `hints` argument):
+ *      - $max Maximum number in the result set
+ *      - $skip Number of skipped results in the result set
+ *      - $orderby Sorting order of query fields.
+ *          Eg: ORDER BY field1 ASC, field2 DESC
+ *          hints:    {
+ *                      "$orderby" : {
+ *                          "field1" : 1,
+ *                          "field2" : -1
+ *                      }
+ *                    }
+ *
+ * Many query examples can be found in `testejdb/t2.c` test case.
+ *
+ * @param EJDB database handle.
+ * @param qobj Main BSON query object.
+ * @param orqobjs Array of additional OR query objects (joined with OR predicate).
+ * @param orqobjsnum Number of OR query objects.
+ * @param hints BSON object with query hints.
+ * @return On success return query handle. On error returns NULL.
+ */
+EJDB_EXPORT EJQ* ejdbcreatequery(EJDB *jb, bson *qobj, bson *orqobjs, int orqobjsnum, bson *hints);
+```
+
+Examples
+------------------------------------
+You can find some code samples in:
+
+* /samples
+* /testejdb test cases
diff --git a/tcejdb/COPYING b/tcejdb/COPYING
new file mode 100644 (file)
index 0000000..b1e3f5a
--- /dev/null
@@ -0,0 +1,504 @@
+                 GNU LESSER GENERAL PUBLIC LICENSE
+                      Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+\f
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+\f
+                 GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+  
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+\f
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+\f
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+\f
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+\f
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+\f
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+\f
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                           NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+\f
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/tcejdb/ChangeLog b/tcejdb/ChangeLog
new file mode 100644 (file)
index 0000000..ae20b39
--- /dev/null
@@ -0,0 +1,3 @@
+2012-10-27 Softmotions. <info@softmotions.com>
+    - Initial release based on Tokyo Cabinet v1.4.48
+    - Release 1.0.0
diff --git a/tcejdb/Makefile.in b/tcejdb/Makefile.in
new file mode 100644 (file)
index 0000000..33f9782
--- /dev/null
@@ -0,0 +1,839 @@
+# Makefile for Tokyo Cabinet
+
+
+
+#================================================================
+# Setting Variables
+#================================================================
+
+
+# Generic settings
+SHELL = @SHELL@
+
+# Package information
+PACKAGE = @PACKAGE_NAME@
+VERSION = @PACKAGE_VERSION@
+PACKAGEDIR = $(PACKAGE)
+PACKAGETGZ = $(PACKAGE)-$(VERSION).tar.gz
+LIBVER = @MYLIBVER@
+LIBREV = @MYLIBREV@
+FORMATVER = @MYFORMATVER@
+
+# Targets
+HEADERFILES = @MYHEADERFILES@
+LIBRARYFILES = @MYLIBRARYFILES@
+LIBOBJFILES = @MYLIBOBJFILES@
+COMMANDFILES = @MYCOMMANDFILES@
+CGIFILES = @MYCGIFILES@
+MAN1FILES = @MYMAN1FILES@
+MAN3FILES = @MYMAN3FILES@
+DOCUMENTFILES = @MYDOCUMENTFILES@
+PCFILES = @MYPCFILES@
+
+# Install destinations
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+datarootdir = @datarootdir@
+INCLUDEDIR = @includedir@
+LIBDIR = @libdir@
+BINDIR = @bindir@
+LIBEXECDIR = @libexecdir@
+DATADIR = @datadir@/$(PACKAGE)
+MAN1DIR = @mandir@/man1
+MAN3DIR = @mandir@/man3
+PCDIR = @libdir@/pkgconfig
+DESTDIR =
+
+# Building configuration
+CC = @CC@
+CPPFLAGS = @MYCPPFLAGS@ \
+  -D_TC_PREFIX="\"$(prefix)\"" -D_TC_INCLUDEDIR="\"$(INCLUDEDIR)\"" \
+  -D_TC_LIBDIR="\"$(LIBDIR)\"" -D_TC_BINDIR="\"$(BINDIR)\"" -D_TC_LIBEXECDIR="\"$(LIBEXECDIR)\"" \
+  -D_TC_APPINC="\"-I$(INCLUDEDIR)\"" -D_TC_APPLIBS="\"-L$(LIBDIR) -ltcejdb @LIBS@\""
+CFLAGS = @MYCFLAGS@
+LDFLAGS = @MYLDFLAGS@
+CMDLDFLAGS = @MYCMDLDFLAGS@
+LIBS = @LIBS@
+LDENV = LD_RUN_PATH=/lib:/usr/lib:$(LIBDIR):$(HOME)/lib:/usr/local/lib:@MYRUNPATH@:.
+RUNENV = @MYLDLIBPATHENV@=.:/lib:/usr/lib:$(LIBDIR):$(HOME)/lib:/usr/local/lib:@MYRUNPATH@
+POSTCMD = @MYPOSTCMD@
+
+
+
+#================================================================
+# Suffix rules
+#================================================================
+
+
+.SUFFIXES :
+.SUFFIXES : .c .o
+
+.c.o :
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $<
+
+
+
+#================================================================
+# Actions
+#================================================================
+
+
+all : $(LIBRARYFILES) $(COMMANDFILES) $(CGIFILES)
+       @$(POSTCMD)
+       @printf '\n'
+       @printf '#================================================================\n'
+       @printf '# Ready to install.\n'
+       @printf '#================================================================\n'
+
+
+clean :
+       rm -rf $(LIBRARYFILES) $(LIBOBJFILES) $(COMMANDFILES) $(CGIFILES) \
+         *.o a.out tokyocabinet_all.c check.in check.out gmon.out *.vlog words.tsv \
+         casket casket-* casket.* *.tch *.tcb *.tcf *.tct *.idx.* *.wal *~ hoge moge tako ika
+       make -C ./testejdb clean
+
+
+version :
+       vernum=`expr $(LIBVER)00 + $(LIBREV)` ; \
+         sed -e 's/_TC_VERSION.*/_TC_VERSION    "$(VERSION)"/' \
+           -e "s/_TC_LIBVER.*/_TC_LIBVER     $$vernum/" \
+           -e 's/_TC_FORMATVER.*/_TC_FORMATVER  "$(FORMATVER)"/' tcutil.h > tcutil.h~
+       [ -f tcutil.h~ ] && mv -f tcutil.h~ tcutil.h
+
+
+untabify :
+       ls *.c *.h *.idl | while read name ; \
+         do \
+           sed -e 's/\t/        /g' -e 's/ *$$//' $$name > $$name~; \
+           [ -f $$name~ ] && mv -f $$name~ $$name ; \
+         done
+
+
+install :
+       mkdir -p $(DESTDIR)$(INCLUDEDIR)/$(PACKAGE)
+       cp -Rf $(HEADERFILES) $(DESTDIR)$(INCLUDEDIR)/$(PACKAGE)
+       mkdir -p $(DESTDIR)$(LIBDIR)
+       cp -Rf $(LIBRARYFILES) $(DESTDIR)$(LIBDIR)
+       ## mkdir -p $(DESTDIR)$(BINDIR)
+       ## cp -Rf $(COMMANDFILES) $(DESTDIR)$(BINDIR)
+       ## mkdir -p $(DESTDIR)$(LIBEXECDIR)
+       ## cp -Rf $(CGIFILES) $(DESTDIR)$(LIBEXECDIR)
+       mkdir -p $(DESTDIR)$(DATADIR)
+       cp -Rf $(DOCUMENTFILES) $(DESTDIR)$(DATADIR)
+       ## mkdir -p $(DESTDIR)$(MAN1DIR)
+       ## cd man && cp -Rf $(MAN1FILES) $(DESTDIR)$(MAN1DIR)
+       ## mkdir -p $(DESTDIR)$(MAN3DIR)
+       ## cd man && cp -Rf $(MAN3FILES) $(DESTDIR)$(MAN3DIR)
+       mkdir -p $(DESTDIR)$(PCDIR)
+       cp -Rf $(PCFILES) $(DESTDIR)$(PCDIR)
+       -[ "$$UID" = 0 ] && PATH=/sbin:/usr/sbin:$(PATH) ldconfig 2>/dev/null || true
+       @printf '\n'
+       @printf '#================================================================\n'
+       @printf '# Thanks for using Tokyo Cabinet EJDB edition.\n'
+       @printf '#================================================================\n'
+
+
+install-strip :
+       make DESTDIR=$(DESTDIR) install
+       cd $(DESTDIR)$(BINDIR) && strip $(COMMANDFILES)
+
+
+uninstall :
+       - cd $(DESTDIR)$(INCLUDEDIR)/$(PACKAGE) && rm -f $(HEADERFILES)
+       - cd $(DESTDIR)$(LIBDIR) && rm -f $(LIBRARYFILES)
+       ## cd $(DESTDIR)$(BINDIR) && rm -f $(COMMANDFILES)
+       ## cd $(DESTDIR)$(LIBEXECDIR) && rm -f $(CGIFILES)
+       ## cd $(DESTDIR)$(MAN1DIR) && rm -f $(MAN1FILES)
+       ## cd $(DESTDIR)$(MAN3DIR) && rm -f $(MAN3FILES)
+       - rm -rf $(DESTDIR)$(DATADIR)
+       - cd $(DESTDIR)$(PCDIR) && rm -f $(PCFILES)
+       [ "$$UID" = 0 ] && PATH=/sbin:/usr/sbin:$(PATH) ldconfig 2>/dev/null || true
+
+
+dist :
+       make version
+       make untabify
+       make distclean
+       cd .. && tar cvf - $(PACKAGEDIR) | gzip -c > $(PACKAGETGZ)
+       sync ; sync
+
+
+distclean : clean
+       ## cd example && make clean
+       cd bros && make clean
+       rm -rf Makefile tcejdb.pc config.cache config.log config.status autom4te.cache
+
+
+check :
+       make check-util
+       make check-hdb
+       make check-bdb
+       make check-fdb
+       make check-tdb
+       make check-adb
+       make check-ejdb
+       rm -rf casket*
+       @printf '\n'
+       @printf '#================================================================\n'
+       @printf '# Checking completed.\n'
+       @printf '#================================================================\n'
+
+
+check-util :
+       rm -rf casket*
+       $(RUNENV) $(RUNCMD) ./tcamgr version
+       $(RUNENV) $(RUNCMD) ./tcutest xstr 50000
+       $(RUNENV) $(RUNCMD) ./tcutest list -rd 50000
+       $(RUNENV) $(RUNCMD) ./tcutest map -rd -tr 50000
+       $(RUNENV) $(RUNCMD) ./tcutest map -rd -tr -rnd -dc 50000
+       $(RUNENV) $(RUNCMD) ./tcutest tree -rd -tr 50000
+       $(RUNENV) $(RUNCMD) ./tcutest tree -rd -tr -rnd -dc 50000
+       $(RUNENV) $(RUNCMD) ./tcutest mdb -rd -tr 50000
+       $(RUNENV) $(RUNCMD) ./tcutest mdb -rd -tr -rnd -dc 50000
+       $(RUNENV) $(RUNCMD) ./tcutest mdb -rd -tr -rnd -dpr 50000
+       $(RUNENV) $(RUNCMD) ./tcutest ndb -rd -tr 50000
+       $(RUNENV) $(RUNCMD) ./tcutest ndb -rd -tr -rnd -dc 50000
+       $(RUNENV) $(RUNCMD) ./tcutest ndb -rd -tr -rnd -dpr 50000
+       $(RUNENV) $(RUNCMD) ./tcutest misc 500
+       $(RUNENV) $(RUNCMD) ./tcutest wicked 50000
+       $(RUNENV) $(RUNCMD) ./tcumttest combo 5 50000 500
+       $(RUNENV) $(RUNCMD) ./tcumttest combo -rnd 5 50000 500
+       $(RUNENV) $(RUNCMD) ./tcumttest typical 5 50000 5000
+       $(RUNENV) $(RUNCMD) ./tcumttest typical -rr 1000 5 50000 5000
+       $(RUNENV) $(RUNCMD) ./tcumttest typical -nc 5 50000 5000
+       $(RUNENV) $(RUNCMD) ./tcumttest combo -tr 5 50000 500
+       $(RUNENV) $(RUNCMD) ./tcumttest combo -tr -rnd 5 50000 500
+       $(RUNENV) $(RUNCMD) ./tcumttest typical -tr 5 50000 5000
+       $(RUNENV) $(RUNCMD) ./tcumttest typical -tr -rr 1000 5 50000 5000
+       $(RUNENV) $(RUNCMD) ./tcumttest typical -tr -nc 5 50000 5000
+       $(RUNENV) $(RUNCMD) ./tcucodec url Makefile > check.in
+       $(RUNENV) $(RUNCMD) ./tcucodec url -d check.in > check.out
+       $(RUNENV) $(RUNCMD) ./tcucodec base Makefile > check.in
+       $(RUNENV) $(RUNCMD) ./tcucodec base -d check.in > check.out
+       $(RUNENV) $(RUNCMD) ./tcucodec quote Makefile > check.in
+       $(RUNENV) $(RUNCMD) ./tcucodec quote -d check.in > check.out
+       $(RUNENV) $(RUNCMD) ./tcucodec mime Makefile > check.in
+       $(RUNENV) $(RUNCMD) ./tcucodec mime -d check.in > check.out
+       $(RUNENV) $(RUNCMD) ./tcucodec pack -bwt Makefile > check.in
+       $(RUNENV) $(RUNCMD) ./tcucodec pack -d -bwt check.in > check.out
+       $(RUNENV) $(RUNCMD) ./tcucodec tcbs Makefile > check.in
+       $(RUNENV) $(RUNCMD) ./tcucodec tcbs -d check.in > check.out
+       $(RUNENV) $(RUNCMD) ./tcucodec zlib Makefile > check.in
+       $(RUNENV) $(RUNCMD) ./tcucodec zlib -d check.in > check.out
+       $(RUNENV) $(RUNCMD) ./tcucodec xml Makefile > check.in
+       $(RUNENV) $(RUNCMD) ./tcucodec xml -d check.in > check.out
+       $(RUNENV) $(RUNCMD) ./tcucodec cstr Makefile > check.in
+       $(RUNENV) $(RUNCMD) ./tcucodec cstr -d check.in > check.out
+       $(RUNENV) $(RUNCMD) ./tcucodec ucs Makefile > check.in
+       $(RUNENV) $(RUNCMD) ./tcucodec ucs -d check.in > check.out
+       $(RUNENV) $(RUNCMD) ./tcucodec date -ds '1978-02-11T18:05:30+09:00' -rf > check.out
+       $(RUNENV) $(RUNCMD) ./tcucodec cipher -key "mikio" Makefile > check.in
+       $(RUNENV) $(RUNCMD) ./tcucodec cipher -key "mikio" check.in > check.out
+       $(RUNENV) $(RUNCMD) ./tcucodec tmpl -var name mikio -var nick micky \
+         '@name=[%name%][%IF nick%] nick=[%nick%][%END%][%IF hoge%][%ELSE%].[%END%]' > check.out
+       $(RUNENV) $(RUNCMD) ./tcucodec conf > check.out
+       rm -rf casket*
+
+
+check-hdb :
+       rm -rf casket*
+       $(RUNENV) $(RUNCMD) ./tchtest write casket 50000 5000 5 5
+       $(RUNENV) $(RUNCMD) ./tchtest read casket
+       $(RUNENV) $(RUNCMD) ./tchtest remove casket
+       $(RUNENV) $(RUNCMD) ./tchtest write -mt -tl -td -rc 50 -xm 500000 casket 50000 5000 5 5
+       $(RUNENV) $(RUNCMD) ./tchtest read -mt -nb -rc 50 -xm 500000 casket
+       $(RUNENV) $(RUNCMD) ./tchtest remove -mt -rc 50 -xm 500000 casket
+       $(RUNENV) $(RUNCMD) ./tchtest write -as -tb -rc 50 -xm 500000 casket 50000 50000 5 5
+       $(RUNENV) $(RUNCMD) ./tchtest read -nl -rc 50 -xm 500000 casket
+       $(RUNENV) $(RUNCMD) ./tchtest remove -rc 50 -xm 500000 -df 5 casket
+       $(RUNENV) $(RUNCMD) ./tchtest rcat -pn 500 -xm 50000 -df 5 casket 50000 5000 5 5
+       $(RUNENV) $(RUNCMD) ./tchtest rcat -tl -td -pn 5000 casket 50000 500 5 15
+       $(RUNENV) $(RUNCMD) ./tchtest rcat -nl -pn 500 -rl casket 5000 500 5 5
+       $(RUNENV) $(RUNCMD) ./tchtest rcat -tb -pn 500 casket 5000 500 5 5
+       $(RUNENV) $(RUNCMD) ./tchtest rcat -ru -pn 500 casket 5000 500 1 1
+       $(RUNENV) $(RUNCMD) ./tchtest rcat -tl -td -ru -pn 500 casket 5000 500 1 1
+       $(RUNENV) $(RUNCMD) ./tchmgr list -pv casket > check.out
+       $(RUNENV) $(RUNCMD) ./tchmgr list -pv -fm 1 -px casket > check.out
+       $(RUNENV) $(RUNCMD) ./tchtest misc casket 5000
+       $(RUNENV) $(RUNCMD) ./tchtest misc -tl -td casket 5000
+       $(RUNENV) $(RUNCMD) ./tchtest misc -mt -tb casket 500
+       $(RUNENV) $(RUNCMD) ./tchtest wicked casket 50000
+       $(RUNENV) $(RUNCMD) ./tchtest wicked -tl -td casket 50000
+       $(RUNENV) $(RUNCMD) ./tchtest wicked -mt -tb casket 5000
+       $(RUNENV) $(RUNCMD) ./tchtest wicked -tt casket 5000
+       $(RUNENV) $(RUNCMD) ./tchtest wicked -tx casket 5000
+       $(RUNENV) $(RUNCMD) ./tchmttest write -xm 500000 -df 5 -tl casket 5 5000 500 5
+       $(RUNENV) $(RUNCMD) ./tchmttest read -xm 500000 -df 5 casket 5
+       $(RUNENV) $(RUNCMD) ./tchmttest read -xm 500000 -rnd casket 5
+       $(RUNENV) $(RUNCMD) ./tchmttest remove -xm 500000 casket 5
+       $(RUNENV) $(RUNCMD) ./tchmttest wicked -nc casket 5 5000
+       $(RUNENV) $(RUNCMD) ./tchmttest wicked -tl -td casket 5 5000
+       $(RUNENV) $(RUNCMD) ./tchmttest wicked -tb casket 5 5000
+       $(RUNENV) $(RUNCMD) ./tchmttest typical -df 5 casket 5 50000 5000
+       $(RUNENV) $(RUNCMD) ./tchmttest typical -rr 1000 casket 5 50000 5000
+       $(RUNENV) $(RUNCMD) ./tchmttest typical -tl -rc 50000 -nc casket 5 50000 5000
+       $(RUNENV) $(RUNCMD) ./tchmttest race -df 5 casket 5 10000
+       $(RUNENV) $(RUNCMD) ./tchmgr create casket 3 1 1
+       $(RUNENV) $(RUNCMD) ./tchmgr inform casket
+       $(RUNENV) $(RUNCMD) ./tchmgr put casket one first
+       $(RUNENV) $(RUNCMD) ./tchmgr put casket two second
+       $(RUNENV) $(RUNCMD) ./tchmgr put -dk casket three third
+       $(RUNENV) $(RUNCMD) ./tchmgr put -dc casket three third
+       $(RUNENV) $(RUNCMD) ./tchmgr put -dc casket three third
+       $(RUNENV) $(RUNCMD) ./tchmgr put -dc casket three third
+       $(RUNENV) $(RUNCMD) ./tchmgr put casket four fourth
+       $(RUNENV) $(RUNCMD) ./tchmgr put -dk casket five fifth
+       $(RUNENV) $(RUNCMD) ./tchmgr out casket one
+       $(RUNENV) $(RUNCMD) ./tchmgr out casket two
+       $(RUNENV) $(RUNCMD) ./tchmgr get casket three > check.out
+       $(RUNENV) $(RUNCMD) ./tchmgr get casket four > check.out
+       $(RUNENV) $(RUNCMD) ./tchmgr get casket five > check.out
+       $(RUNENV) $(RUNCMD) ./tchmgr list -pv casket > check.out
+       $(RUNENV) $(RUNCMD) ./tchmgr optimize casket
+       $(RUNENV) $(RUNCMD) ./tchmgr put -dc casket three third
+       $(RUNENV) $(RUNCMD) ./tchmgr get casket three > check.out
+       $(RUNENV) $(RUNCMD) ./tchmgr get casket four > check.out
+       $(RUNENV) $(RUNCMD) ./tchmgr get casket five > check.out
+       $(RUNENV) $(RUNCMD) ./tchmgr list -pv casket > check.out
+       rm -rf casket*
+
+
+check-bdb :
+       rm -rf casket*
+       $(RUNENV) $(RUNCMD) ./tcbtest write casket 50000 5 5 5000 5 5
+       $(RUNENV) $(RUNCMD) ./tcbtest read casket
+       $(RUNENV) $(RUNCMD) ./tcbtest remove casket
+       $(RUNENV) $(RUNCMD) ./tcbmgr list -rb 00001000 00002000 casket > check.out
+       $(RUNENV) $(RUNCMD) ./tcbmgr list -fm 000001 casket > check.out
+       $(RUNENV) $(RUNCMD) ./tcbtest write -mt -tl -td -ls 1024 casket 50000 5000 5000 5000 5 5
+       $(RUNENV) $(RUNCMD) ./tcbtest read -mt -nb casket
+       $(RUNENV) $(RUNCMD) ./tcbtest remove -mt casket
+       $(RUNENV) $(RUNCMD) ./tcbtest write -tb -xm 50000 casket 50000 5 5 50000 5 5
+       $(RUNENV) $(RUNCMD) ./tcbtest read -nl casket
+       $(RUNENV) $(RUNCMD) ./tcbtest remove -df 5 casket
+       $(RUNENV) $(RUNCMD) ./tcbtest rcat -lc 5 -nc 5 -df 5 -pn 500 casket 50000 5 5 5000 5 5
+       $(RUNENV) $(RUNCMD) ./tcbtest rcat -tl -td -pn 5000 casket 50000 5 5 500 5 15
+       $(RUNENV) $(RUNCMD) ./tcbtest rcat -nl -pn 5000 -rl casket 15000 5 5 500 5 5
+       $(RUNENV) $(RUNCMD) ./tcbtest rcat -ca 1000 -tb -pn 5000 casket 15000 5 5 500 5 5
+       $(RUNENV) $(RUNCMD) ./tcbtest rcat -ru -pn 500 casket 5000 5 5 500 1 1
+       $(RUNENV) $(RUNCMD) ./tcbtest rcat -cd -tl -td -ru -pn 500 casket 5000 5 5 500 1 1
+       $(RUNENV) $(RUNCMD) ./tcbmgr list -pv casket > check.out
+       $(RUNENV) $(RUNCMD) ./tcbtest queue casket 15000 5 5
+       $(RUNENV) $(RUNCMD) ./tcbtest misc casket 5000
+       $(RUNENV) $(RUNCMD) ./tcbtest misc -tl -td casket 5000
+       $(RUNENV) $(RUNCMD) ./tcbtest misc -mt -tb casket 500
+       $(RUNENV) $(RUNCMD) ./tcbtest wicked casket 50000
+       $(RUNENV) $(RUNCMD) ./tcbtest wicked -tl -td casket 50000
+       $(RUNENV) $(RUNCMD) ./tcbtest wicked -mt -tb casket 5000
+       $(RUNENV) $(RUNCMD) ./tcbtest wicked -tt casket 5000
+       $(RUNENV) $(RUNCMD) ./tcbtest wicked -tx casket 5000
+       $(RUNENV) $(RUNCMD) ./tcbtest write -cd -lc 5 -nc 5 casket 5000 5 5 5 5 5
+       $(RUNENV) $(RUNCMD) ./tcbtest read -cd -lc 5 -nc 5 casket
+       $(RUNENV) $(RUNCMD) ./tcbtest remove -cd -lc 5 -nc 5 casket
+       $(RUNENV) $(RUNCMD) ./tcbmgr list -pv casket > check.out
+       $(RUNENV) $(RUNCMD) ./tcbtest write -ci -td -lc 5 -nc 5 casket 5000 5 5 5 5 5
+       $(RUNENV) $(RUNCMD) ./tcbtest read -ci -lc 5 -nc 5 casket
+       $(RUNENV) $(RUNCMD) ./tcbtest remove -ci -lc 5 -nc 5 casket
+       $(RUNENV) $(RUNCMD) ./tcbmgr list -pv casket > check.out
+       $(RUNENV) $(RUNCMD) ./tcbtest write -cj -tb -lc 5 -nc 5 casket 5000 5 5 5 5 5
+       $(RUNENV) $(RUNCMD) ./tcbtest read -cj -lc 5 -nc 5 casket
+       $(RUNENV) $(RUNCMD) ./tcbtest remove -cj -lc 5 -nc 5 casket
+       $(RUNENV) $(RUNCMD) ./tcbmgr list -pv casket > check.out
+       $(RUNENV) $(RUNCMD) ./tcbmttest write -df 5 -tl casket 5 5000 5 5 500 5
+       $(RUNENV) $(RUNCMD) ./tcbmttest read -df 5 casket 5
+       $(RUNENV) $(RUNCMD) ./tcbmttest read -rnd casket 5
+       $(RUNENV) $(RUNCMD) ./tcbmttest remove casket 5
+       $(RUNENV) $(RUNCMD) ./tcbmttest wicked -nc casket 5 5000
+       $(RUNENV) $(RUNCMD) ./tcbmttest wicked -tl -td casket 5 5000
+       $(RUNENV) $(RUNCMD) ./tchmttest wicked -tb casket 5 5000
+       $(RUNENV) $(RUNCMD) ./tcbmttest typical -df 5 casket 5 50000 5 5
+       $(RUNENV) $(RUNCMD) ./tcbmttest typical -rr 1000 casket 5 50000 5 5
+       $(RUNENV) $(RUNCMD) ./tcbmttest typical -tl -nc casket 5 50000 5 5
+       $(RUNENV) $(RUNCMD) ./tcbmttest race -df 5 casket 5 10000
+       $(RUNENV) $(RUNCMD) ./tcbmgr create casket 4 4 3 1 1
+       $(RUNENV) $(RUNCMD) ./tcbmgr inform casket
+       $(RUNENV) $(RUNCMD) ./tcbmgr put casket one first
+       $(RUNENV) $(RUNCMD) ./tcbmgr put casket two second
+       $(RUNENV) $(RUNCMD) ./tcbmgr put -dk casket three third
+       $(RUNENV) $(RUNCMD) ./tcbmgr put -dc casket three third
+       $(RUNENV) $(RUNCMD) ./tcbmgr put -dc casket three third
+       $(RUNENV) $(RUNCMD) ./tcbmgr put -dd casket three third
+       $(RUNENV) $(RUNCMD) ./tcbmgr put -dd casket three third
+       $(RUNENV) $(RUNCMD) ./tcbmgr put casket four fourth
+       $(RUNENV) $(RUNCMD) ./tcbmgr put -dk casket five fifth
+       $(RUNENV) $(RUNCMD) ./tcbmgr out casket one
+       $(RUNENV) $(RUNCMD) ./tcbmgr out casket two
+       $(RUNENV) $(RUNCMD) ./tcbmgr get casket three > check.out
+       $(RUNENV) $(RUNCMD) ./tcbmgr get casket four > check.out
+       $(RUNENV) $(RUNCMD) ./tcbmgr get casket five > check.out
+       $(RUNENV) $(RUNCMD) ./tcbmgr list -pv casket > check.out
+       $(RUNENV) $(RUNCMD) ./tcbmgr list -j three -pv casket > check.out
+       $(RUNENV) $(RUNCMD) ./tcbmgr optimize casket
+       $(RUNENV) $(RUNCMD) ./tcbmgr put -dc casket three third
+       $(RUNENV) $(RUNCMD) ./tcbmgr get casket three > check.out
+       $(RUNENV) $(RUNCMD) ./tcbmgr get casket four > check.out
+       $(RUNENV) $(RUNCMD) ./tcbmgr get casket five > check.out
+       $(RUNENV) $(RUNCMD) ./tcbmgr list -pv casket > check.out
+
+
+check-fdb :
+       rm -rf casket*
+       $(RUNENV) $(RUNCMD) ./tcftest write casket 50000 50
+       $(RUNENV) $(RUNCMD) ./tcftest read casket
+       $(RUNENV) $(RUNCMD) ./tcftest remove casket
+       $(RUNENV) $(RUNCMD) ./tcftest write casket 50000 50
+       $(RUNENV) $(RUNCMD) ./tcftest read -mt -nb casket
+       $(RUNENV) $(RUNCMD) ./tcftest remove -mt casket
+       $(RUNENV) $(RUNCMD) ./tcftest rcat -pn 500 casket 50000 50
+       $(RUNENV) $(RUNCMD) ./tcftest rcat -nl -pn 500 -rl casket 5000 500
+       $(RUNENV) $(RUNCMD) ./tcftest rcat -pn 500 -ru casket 5000 500
+       $(RUNENV) $(RUNCMD) ./tcfmgr list -pv casket > check.out
+       $(RUNENV) $(RUNCMD) ./tcfmgr list -pv -ri "[100,200)" -px casket > check.out
+       $(RUNENV) $(RUNCMD) ./tcftest misc casket 5000
+       $(RUNENV) $(RUNCMD) ./tcftest misc -mt -nl casket 500
+       $(RUNENV) $(RUNCMD) ./tcftest wicked casket 50000
+       $(RUNENV) $(RUNCMD) ./tcftest wicked -mt -nb casket 50000
+       $(RUNENV) $(RUNCMD) ./tcfmttest write casket 5 5000 50
+       $(RUNENV) $(RUNCMD) ./tcfmttest read casket 5
+       $(RUNENV) $(RUNCMD) ./tcfmttest read -rnd casket 5
+       $(RUNENV) $(RUNCMD) ./tcfmttest remove casket 5
+       $(RUNENV) $(RUNCMD) ./tcfmttest wicked -nc casket 5 5000
+       $(RUNENV) $(RUNCMD) ./tcfmttest wicked casket 5 5000
+       $(RUNENV) $(RUNCMD) ./tcfmttest typical casket 5 50000 50
+       $(RUNENV) $(RUNCMD) ./tcfmttest typical -rr 1000 casket 5 50000 50
+       $(RUNENV) $(RUNCMD) ./tcfmttest typical -nc casket 5 50000 50
+       $(RUNENV) $(RUNCMD) ./tcfmgr create casket 50
+       $(RUNENV) $(RUNCMD) ./tcfmgr inform casket
+       $(RUNENV) $(RUNCMD) ./tcfmgr put casket 1 first
+       $(RUNENV) $(RUNCMD) ./tcfmgr put casket 2 second
+       $(RUNENV) $(RUNCMD) ./tcfmgr put -dk casket 3 third
+       $(RUNENV) $(RUNCMD) ./tcfmgr put -dc casket 3 third
+       $(RUNENV) $(RUNCMD) ./tcfmgr put -dc casket 3 third
+       $(RUNENV) $(RUNCMD) ./tcfmgr put -dc casket 3 third
+       $(RUNENV) $(RUNCMD) ./tcfmgr put casket 4 fourth
+       $(RUNENV) $(RUNCMD) ./tcfmgr put -dk casket 5 fifth
+       $(RUNENV) $(RUNCMD) ./tcfmgr out casket 1
+       $(RUNENV) $(RUNCMD) ./tcfmgr out casket 2
+       $(RUNENV) $(RUNCMD) ./tcfmgr get casket 3 > check.out
+       $(RUNENV) $(RUNCMD) ./tcfmgr get casket 4 > check.out
+       $(RUNENV) $(RUNCMD) ./tcfmgr get casket 5 > check.out
+       $(RUNENV) $(RUNCMD) ./tcfmgr list -pv casket > check.out
+       $(RUNENV) $(RUNCMD) ./tcfmgr optimize casket 5
+       $(RUNENV) $(RUNCMD) ./tcfmgr put -dc casket 3 third
+       $(RUNENV) $(RUNCMD) ./tcfmgr get casket 3 > check.out
+       $(RUNENV) $(RUNCMD) ./tcfmgr get casket 4 > check.out
+       $(RUNENV) $(RUNCMD) ./tcfmgr get casket 5 > check.out
+       $(RUNENV) $(RUNCMD) ./tcfmgr list -pv casket > check.out
+
+
+check-tdb :
+       rm -rf casket*
+       $(RUNENV) $(RUNCMD) ./tcttest write casket 50000 5000 5 5
+       $(RUNENV) $(RUNCMD) ./tcttest read casket
+       $(RUNENV) $(RUNCMD) ./tcttest remove casket
+       $(RUNENV) $(RUNCMD) ./tcttest write -mt -tl -td -rc 50 -lc 5 -nc 5 -xm 500000 \
+         -is -in -it -if -ix casket 5000 5000 5 5
+       $(RUNENV) $(RUNCMD) ./tcttest read -mt -nb -rc 50 -lc 5 -nc 5 -xm 500000 casket
+       $(RUNENV) $(RUNCMD) ./tcttest remove -mt -rc 50 -lc 5 -nc 5 -xm 500000 -df 5 casket
+       $(RUNENV) $(RUNCMD) ./tcttest rcat -pn 500 -xm 50000 -df 5 -is casket 5000 5000 5 5
+       $(RUNENV) $(RUNCMD) ./tcttest rcat -tl -td -pn 5000 -is -in casket 5000 500 5 15
+       $(RUNENV) $(RUNCMD) ./tcttest rcat -nl -pn 500 -rl -is -in casket 5000 500 5 5
+       $(RUNENV) $(RUNCMD) ./tcttest rcat -tb -pn 500 -is -in casket 5000 500 5 5
+       $(RUNENV) $(RUNCMD) ./tcttest rcat -ru -pn 500 -is -in casket 5000 500 1 1
+       $(RUNENV) $(RUNCMD) ./tcttest rcat -tl -td -ru -pn 500 -is -in casket 5000 500 1 1
+       $(RUNENV) $(RUNCMD) ./tctmgr list -pv casket > check.out
+       $(RUNENV) $(RUNCMD) ./tctmgr list -pv -px casket > check.out
+       $(RUNENV) $(RUNCMD) ./tcttest misc casket 500
+       $(RUNENV) $(RUNCMD) ./tcttest misc -tl -td casket 500
+       $(RUNENV) $(RUNCMD) ./tcttest misc -mt -tb casket 500
+       $(RUNENV) $(RUNCMD) ./tcttest wicked casket 5000
+       $(RUNENV) $(RUNCMD) ./tcttest wicked -tl -td casket 5000
+       $(RUNENV) $(RUNCMD) ./tcttest wicked -mt -tb casket 5000
+       $(RUNENV) $(RUNCMD) ./tcttest wicked -tt casket 5000
+       $(RUNENV) $(RUNCMD) ./tcttest wicked -tx casket 5000
+       $(RUNENV) $(RUNCMD) ./tctmttest write -xm 500000 -df 5 -tl -is -in casket 5 5000 500 5
+       $(RUNENV) $(RUNCMD) ./tctmttest read -xm 500000 -df 5 casket 5
+       $(RUNENV) $(RUNCMD) ./tctmttest read -xm 500000 -rnd casket 5
+       $(RUNENV) $(RUNCMD) ./tctmttest remove -xm 500000 casket 5
+       $(RUNENV) $(RUNCMD) ./tctmttest wicked casket 5 5000
+       $(RUNENV) $(RUNCMD) ./tctmttest wicked -tl -td casket 5 5000
+       $(RUNENV) $(RUNCMD) ./tctmttest typical -df 5 casket 5 5000 500
+       $(RUNENV) $(RUNCMD) ./tctmttest typical -rr 1000 casket 5 5000 500
+       $(RUNENV) $(RUNCMD) ./tctmttest typical -tl -rc 50000 -lc 5 -nc 5 casket 5 5000 500
+       $(RUNENV) $(RUNCMD) ./tctmgr create casket 3 1 1
+       $(RUNENV) $(RUNCMD) ./tctmgr setindex casket name
+       $(RUNENV) $(RUNCMD) ./tctmgr inform casket
+       $(RUNENV) $(RUNCMD) ./tctmgr put casket "" name mikio birth 19780211 lang ja,en,c
+       $(RUNENV) $(RUNCMD) ./tctmgr put casket "" name fal birth 19771007 lang ja
+       $(RUNENV) $(RUNCMD) ./tctmgr put casket "" name banana price 100
+       $(RUNENV) $(RUNCMD) ./tctmgr put -dc casket 3 color yellow
+       $(RUNENV) $(RUNCMD) ./tctmgr put -dk casket "" name melon price 1200 color green
+       $(RUNENV) $(RUNCMD) ./tctmgr put casket "" name void birth 20010101 lang en
+       $(RUNENV) $(RUNCMD) ./tctmgr out casket 5
+       $(RUNENV) $(RUNCMD) ./tctmgr get casket 1 > check.out
+       $(RUNENV) $(RUNCMD) ./tctmgr get casket 2 > check.out
+       $(RUNENV) $(RUNCMD) ./tctmgr get casket 3 > check.out
+       $(RUNENV) $(RUNCMD) ./tctmgr search casket > check.out
+       $(RUNENV) $(RUNCMD) ./tctmgr search -m 10 -sk 1 -pv -ph casket > check.out
+       $(RUNENV) $(RUNCMD) ./tctmgr search -m 10 -ord name STRDESC -pv -ph casket > check.out
+       $(RUNENV) $(RUNCMD) ./tctmgr search -m 10 -ord name STRDESC -pv -ph casket \
+         name STRBW mi birth NUMBT 19700101,19791231 lang STRAND ja,en > check.out
+       $(RUNENV) $(RUNCMD) ./tctmgr search -ord birth NUMDESC -pv -ms UNION casket \
+         name STREQ mikio name STRINC fal name FTSEX "ba na na"
+       $(RUNENV) $(RUNCMD) ./tctmgr setindex casket name
+       $(RUNENV) $(RUNCMD) ./tctmgr setindex -it dec casket birth
+       $(RUNENV) $(RUNCMD) ./tctmgr setindex casket lang
+       $(RUNENV) $(RUNCMD) ./tctmgr list -pv casket > check.out
+       $(RUNENV) $(RUNCMD) ./tctmgr optimize casket
+       $(RUNENV) $(RUNCMD) ./tctmgr put casket "" name tokyo country japan lang ja
+       $(RUNENV) $(RUNCMD) ./tctmgr search -m 10 -sk 1 -pv -ph casket > check.out
+       $(RUNENV) $(RUNCMD) ./tctmgr search -m 10 -ord name STRDESC -pv -ph casket > check.out
+       $(RUNENV) $(RUNCMD) ./tctmgr search -m 10 -ord name STRDESC -pv -ph casket \
+         name STRBW mi birth NUMBT 19700101,19791231 lang STRAND ja,en > check.out
+       $(RUNENV) $(RUNCMD) ./tctmgr search -ord price NUMDESC -ph -rm casket name STRINC a
+       $(RUNENV) $(RUNCMD) ./tctmgr list -pv casket > check.out
+
+
+check-adb :
+       rm -rf casket*
+       $(RUNENV) $(RUNCMD) ./tcatest write 'casket.tch#mode=wct#bnum=5000' 50000
+       $(RUNENV) $(RUNCMD) ./tcatest read 'casket.tch#mode=r'
+       $(RUNENV) $(RUNCMD) ./tcatest remove 'casket.tch#mode=w'
+       $(RUNENV) $(RUNCMD) ./tcatest misc 'casket.tch#mode=wct#bnum=500#opts=ld' 5000
+       $(RUNENV) $(RUNCMD) ./tcatest wicked 'casket.tch#mode=wct' 5000
+       $(RUNENV) $(RUNCMD) ./tcatest write '@casket.tcb#mode=wct#lmemb=5#nmemb=5' 50000
+       $(RUNENV) $(RUNCMD) ./tcatest read '@casket.tcb#mode=r'
+       $(RUNENV) $(RUNCMD) ./tcatest remove '@casket.tcb#mode=w'
+       $(RUNENV) $(RUNCMD) ./tcatest misc '@casket.tcb#mode=wct#lmemb=5#nmemb=5#opts=ld' 5000
+       $(RUNENV) $(RUNCMD) ./tcatest wicked '@casket.tcb#mode=wct' 5000
+       $(RUNENV) $(RUNCMD) ./tcatest write 'casket.tcf#mode=wct#width=10' 50000
+       $(RUNENV) $(RUNCMD) ./tcatest read 'casket.tcf#mode=r'
+       $(RUNENV) $(RUNCMD) ./tcatest remove 'casket.tcf#mode=w'
+       $(RUNENV) $(RUNCMD) ./tcatest write '*#bnum=5000#cap=100' 50000
+       $(RUNENV) $(RUNCMD) ./tcatest misc '*' 5000
+       $(RUNENV) $(RUNCMD) ./tcatest wicked '*' 5000
+       $(RUNENV) $(RUNCMD) ./tcatest write '%casket-mul.tch#mode=wct#bnum=500' 50000
+       $(RUNENV) $(RUNCMD) ./tcatest read '%casket-mul.tch#mode=r'
+       $(RUNENV) $(RUNCMD) ./tcatest remove '%casket-mul.tch#mode=w'
+       $(RUNENV) $(RUNCMD) ./tcatest misc '%casket-mul.tch#mode=wct#bnum=500#opts=ld' 5000
+       $(RUNENV) $(RUNCMD) ./tcatest wicked '%casket-mul.tch#mode=wct' 5000
+       $(RUNENV) $(RUNCMD) ./tcatest compare casket 50 500
+       $(RUNENV) $(RUNCMD) ./tcatest compare casket 5 5000
+       $(RUNENV) $(RUNCMD) ./tcamttest write 'casket.tch#mode=wct#bnum=5000' 5 5000
+       $(RUNENV) $(RUNCMD) ./tcamttest read 'casket.tch#mode=r' 5
+       $(RUNENV) $(RUNCMD) ./tcamttest remove 'casket.tch#mode=w' 5
+       $(RUNENV) $(RUNCMD) ./tcamttest write '%casket-mul.tcb#mode=wct#bnum=5000' 5 5000
+       $(RUNENV) $(RUNCMD) ./tcamttest read '%casket-mul.tcb#mode=r' 5
+       $(RUNENV) $(RUNCMD) ./tcamttest remove '%casket-mul.tcb#mode=w' 5
+       $(RUNENV) $(RUNCMD) ./tcamgr create 'casket.tch#mode=wct#bnum=3'
+       $(RUNENV) $(RUNCMD) ./tcamgr inform 'casket.tch'
+       $(RUNENV) $(RUNCMD) ./tcamgr put casket.tch one first
+       $(RUNENV) $(RUNCMD) ./tcamgr put casket.tch two second
+       $(RUNENV) $(RUNCMD) ./tcamgr put -dk casket.tch three third
+       $(RUNENV) $(RUNCMD) ./tcamgr put -dc casket.tch three third
+       $(RUNENV) $(RUNCMD) ./tcamgr put -dc casket.tch three third
+       $(RUNENV) $(RUNCMD) ./tcamgr put -dc casket.tch three third
+       $(RUNENV) $(RUNCMD) ./tcamgr put casket.tch four fourth
+       $(RUNENV) $(RUNCMD) ./tcamgr put -dk casket.tch five fifth
+       $(RUNENV) $(RUNCMD) ./tcamgr out casket.tch one
+       $(RUNENV) $(RUNCMD) ./tcamgr out casket.tch two
+       $(RUNENV) $(RUNCMD) ./tcamgr get casket.tch three > check.out
+       $(RUNENV) $(RUNCMD) ./tcamgr get casket.tch four > check.out
+       $(RUNENV) $(RUNCMD) ./tcamgr get casket.tch five > check.out
+       $(RUNENV) $(RUNCMD) ./tcamgr list -pv -fm f casket.tch > check.out
+       $(RUNENV) $(RUNCMD) ./tcamgr optimize casket.tch
+       $(RUNENV) $(RUNCMD) ./tcamgr put -dc casket.tch three third
+       $(RUNENV) $(RUNCMD) ./tcamgr get casket.tch three > check.out
+       $(RUNENV) $(RUNCMD) ./tcamgr get casket.tch four > check.out
+       $(RUNENV) $(RUNCMD) ./tcamgr get casket.tch five > check.out
+       $(RUNENV) $(RUNCMD) ./tcamgr misc casket.tch putlist six sixth seven seventh
+       $(RUNENV) $(RUNCMD) ./tcamgr misc casket.tch outlist six
+       $(RUNENV) $(RUNCMD) ./tcamgr misc casket.tch getlist three four five six > check.out
+       $(RUNENV) $(RUNCMD) ./tcamgr list -pv casket.tch > check.out
+       $(RUNENV) $(RUNCMD) ./tcamgr create 'casket.tct#mode=wct#idx=name:lex#idx=age:dec'
+       $(RUNENV) $(RUNCMD) ./tcamgr put -sep '|' casket.tct 1 "name|mikio|age|30"
+       $(RUNENV) $(RUNCMD) ./tcamgr put -sep '|' casket.tct 2 "name|fal|age|31"
+       $(RUNENV) $(RUNCMD) ./tcamgr put -sep '|' casket.tct 3 "name|lupin|age|29"
+       $(RUNENV) $(RUNCMD) ./tcamgr get -sep '\t' casket.tct 1 > check.out
+       $(RUNENV) $(RUNCMD) ./tcamgr list -sep '\t' -pv casket.tct > check.out
+       $(RUNENV) $(RUNCMD) ./tcamgr misc -sep '|' casket.tct search \
+         "addcond|name|STRINC|i" "setorder|age|NUMASC" "setmax|1" "get" > check.out
+       $(RUNENV) $(RUNCMD) ./tcamgr misc -sep '|' casket.tct search "get" "out" > check.out
+       $(RUNENV) $(RUNCMD) ./tcamgr create '%casket-mul.tcb#mode=wct'
+       $(RUNENV) $(RUNCMD) ./tcamgr put '%casket-mul.tcb' one first
+       $(RUNENV) $(RUNCMD) ./tcamgr put '%casket-mul.tcb' two second
+       $(RUNENV) $(RUNCMD) ./tcamgr put -dk '%casket-mul.tcb' three third
+       $(RUNENV) $(RUNCMD) ./tcamgr put -dc '%casket-mul.tcb' three third
+       $(RUNENV) $(RUNCMD) ./tcamgr put -dc '%casket-mul.tcb' three third
+       $(RUNENV) $(RUNCMD) ./tcamgr put -dc '%casket-mul.tcb' three third
+       $(RUNENV) $(RUNCMD) ./tcamgr put '%casket-mul.tcb' four fourth
+       $(RUNENV) $(RUNCMD) ./tcamgr put -dk '%casket-mul.tcb' five fifth
+       $(RUNENV) $(RUNCMD) ./tcamgr out '%casket-mul.tcb' one
+       $(RUNENV) $(RUNCMD) ./tcamgr out '%casket-mul.tcb' two
+       $(RUNENV) $(RUNCMD) ./tcamgr get '%casket-mul.tcb' three > check.out
+       $(RUNENV) $(RUNCMD) ./tcamgr get '%casket-mul.tcb' four > check.out
+       $(RUNENV) $(RUNCMD) ./tcamgr get '%casket-mul.tcb' five > check.out
+       $(RUNENV) $(RUNCMD) ./tcamgr list -pv -fm f '%casket-mul.tcb' > check.out
+       $(RUNENV) $(RUNCMD) ./tcamgr optimize '%casket-mul.tcb'
+       $(RUNENV) $(RUNCMD) ./tcamgr put -dc '%casket-mul.tcb' three third
+       $(RUNENV) $(RUNCMD) ./tcamgr get '%casket-mul.tcb' three > check.out
+       $(RUNENV) $(RUNCMD) ./tcamgr get '%casket-mul.tcb' four > check.out
+       $(RUNENV) $(RUNCMD) ./tcamgr get '%casket-mul.tcb' five > check.out
+       $(RUNENV) $(RUNCMD) ./tcamgr misc '%casket-mul.tcb' '%putlist' six sixth seven seventh
+       $(RUNENV) $(RUNCMD) ./tcamgr misc '%casket-mul.tcb' '@outlist' six
+       $(RUNENV) $(RUNCMD) ./tcamgr misc '%casket-mul.tcb' '@getlist' \
+         three four five six > check.out
+       $(RUNENV) $(RUNCMD) ./tcamgr list -pv '%casket-mul.tcb' > check.out
+
+check-ejdb :
+       make -C ./testejdb check
+
+check-valgrind :
+       make RUNCMD="valgrind --tool=memcheck --log-file=%p.vlog" check
+       grep ERROR *.vlog | grep -v ' 0 errors' ; true
+       grep 'at exit' *.vlog | grep -v ' 0 bytes' ; true
+
+check-valgrind-ejdb :
+       make RUNCMD="valgrind --tool=memcheck --leak-check=full --log-file=%p.vlog" -C ./testejdb check-valgrind
+       grep ERROR *.vlog | grep -v ' 0 errors' ; true
+       grep 'at exit' *.vlog | grep -v ' 0 bytes' ; true
+
+check-large :
+       rm -rf casket*
+       $(RUNENV) $(RUNCMD) ./tchmttest typical casket 3 1000000 5000000 13 8
+       $(RUNENV) $(RUNCMD) ./tchmttest typical -nc casket 3 1000000 5000000 13 8
+       $(RUNENV) $(RUNCMD) ./tcbmttest typical casket 3 500000 8 8 500000 16 8
+       $(RUNENV) $(RUNCMD) ./tcbmttest typical -nc casket 3 500000 8 8 500000 16 8
+       $(RUNENV) $(RUNCMD) ./tcfmttest typical casket 3 500000 2048 4g
+       $(RUNENV) $(RUNCMD) ./tcfmttest typical -nc casket 3 500000 2048 4g
+       rm -rf casket*
+
+
+check-compare :
+       $(RUNENV) $(RUNCMD) ./tcatest compare casket 5 10000
+       $(RUNENV) $(RUNCMD) ./tcatest compare casket 10 5000
+       $(RUNENV) $(RUNCMD) ./tcatest compare casket 50 1000
+       $(RUNENV) $(RUNCMD) ./tcatest compare casket 100 500
+
+
+check-thread :
+       rm -rf casket*
+       $(RUNENV) $(RUNCMD) ./tcumttest typical 5 500000 500000
+       $(RUNENV) $(RUNCMD) ./tcumttest typical -nc -rr 1000 5 500000 500000
+       $(RUNENV) $(RUNCMD) ./tchmttest typical casket 5 500000 500000
+       $(RUNENV) $(RUNCMD) ./tchmttest typical -rc 500000 -nc -rr 1000 casket 5 500000 500000
+       $(RUNENV) $(RUNCMD) ./tcbmttest typical casket 5 100000 5 5
+       $(RUNENV) $(RUNCMD) ./tcbmttest typical -nc -rr 1000 casket 5 100000 5 5
+       $(RUNENV) $(RUNCMD) ./tcfmttest typical casket 5 500000 10
+       $(RUNENV) $(RUNCMD) ./tcfmttest typical -nc -rr 1000 casket 5 500000 10
+       rm -rf casket*
+
+
+check-race :
+       $(RUNENV) $(RUNCMD) ./tchmttest race casket 5 10000
+       $(RUNENV) $(RUNCMD) ./tcbmttest race casket 5 10000
+
+
+check-forever :
+       while true ; \
+         do \
+           make check || break ; \
+           make check || break ; \
+           make check-thread || break ; \
+           make check-race || break ; \
+           make check-race || break ; \
+           make check-compare || break ; \
+           make check-compare || break ; \
+         done
+
+
+words :
+       rm -f casket-* words.tsv
+       cat /usr/share/dict/words | \
+         tr '\t\r' '  ' | grep -v '^ *$$' | cat -n | sort | \
+         LC_ALL=C sed -e 's/^ *//' -e 's/\(^[0-9]*\)\t\(.*\)/\2\t\1/' > words.tsv
+       ./tchmgr create casket-hash -1 0 ; ./tchmgr importtsv casket-hash words.tsv
+       ./tcbmgr create casket-btree 8192 ; ./tcbmgr importtsv casket-btree words.tsv
+       ./tcbmgr create -td casket-btree-td 8192 ; ./tcbmgr importtsv casket-btree-td words.tsv
+       ./tcbmgr create -tb casket-btree-tb 8192 ; ./tcbmgr importtsv casket-btree-tb words.tsv
+       ./tcbmgr create -tt casket-btree-tt 8192 ; ./tcbmgr importtsv casket-btree-tt words.tsv
+       ./tcbmgr create -tx casket-btree-tx 8192 ; ./tcbmgr importtsv casket-btree-tx words.tsv
+       wc -c words.tsv casket-hash casket-btree \
+         casket-btree-td casket-btree-tb casket-btree-tt casket-btree-tx
+
+
+wordtable :
+       rm -rf casket* words.tsv
+       cat /usr/share/dict/words | \
+         tr '\t\r' '  ' | grep -v '^ *$$' | cat -n | sort | \
+         LC_ALL=C sed -e 's/^ *//' -e 's/\(^[0-9]*\)\t\(.*\)/\1\tword\t\2\tnum\t\1/' \
+           -e 's/$$/\txxx\tabc\tyyy\t123/' > words.tsv
+       ./tctmgr create casket
+       ./tctmgr setindex casket word
+       ./tctmgr setindex -it dec casket num
+       ./tctmgr importtsv casket words.tsv
+
+
+.PHONY : all clean install check
+
+
+
+#================================================================
+# Building binaries
+#================================================================
+
+
+libtcejdb.a : $(LIBOBJFILES)
+       $(AR) $(ARFLAGS) $@ $(LIBOBJFILES)
+
+
+libtcejdb.so.$(LIBVER).$(LIBREV).0 : $(LIBOBJFILES)
+       if uname -a | egrep -i 'SunOS' > /dev/null ; \
+         then \
+           $(CC) $(CFLAGS) -shared -Wl,-G,-h,libtcejdb.so.$(LIBVER) -o $@ \
+             $(LIBOBJFILES) $(LDFLAGS) $(LIBS) ; \
+         else \
+           $(CC) $(CFLAGS) -shared -Wl,-soname,libtcejdb.so.$(LIBVER) -o $@ \
+             $(LIBOBJFILES) $(LDFLAGS) $(LIBS) ; \
+         fi
+
+
+libtcejdb.so.$(LIBVER) : libtcejdb.so.$(LIBVER).$(LIBREV).0
+       ln -f -s libtcejdb.so.$(LIBVER).$(LIBREV).0 $@
+
+
+libtcejdb.so : libtcejdb.so.$(LIBVER).$(LIBREV).0
+       ln -f -s libtcejdb.so.$(LIBVER).$(LIBREV).0 $@
+
+
+libtcejdb.$(LIBVER).$(LIBREV).0.dylib : $(LIBOBJFILES)
+       $(CC) $(CFLAGS) -dynamiclib -o $@ \
+         -install_name $(LIBDIR)/libtcejdb.$(LIBVER).dylib \
+         -current_version $(LIBVER).$(LIBREV).0 -compatibility_version $(LIBVER) \
+         $(LIBOBJFILES) $(LDFLAGS) $(LIBS)
+
+
+libtcejdb.$(LIBVER).dylib : libtcejdb.$(LIBVER).$(LIBREV).0.dylib
+       ln -f -s libtcejdb.$(LIBVER).$(LIBREV).0.dylib $@
+
+
+libtcejdb.dylib : libtcejdb.$(LIBVER).$(LIBREV).0.dylib
+       ln -f -s libtcejdb.$(LIBVER).$(LIBREV).0.dylib $@
+
+
+tcutest : tcutest.o $(LIBRARYFILES)
+       $(LDENV) $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(CMDLDFLAGS) -ltcejdb $(LIBS)
+
+
+tcumttest : tcumttest.o $(LIBRARYFILES)
+       $(LDENV) $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(CMDLDFLAGS) -ltcejdb $(LIBS)
+
+
+tcucodec : tcucodec.o $(LIBRARYFILES)
+       $(LDENV) $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(CMDLDFLAGS) -ltcejdb $(LIBS)
+
+
+tchtest : tchtest.o $(LIBRARYFILES)
+       $(LDENV) $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(CMDLDFLAGS) -ltcejdb $(LIBS)
+
+
+tchmttest : tchmttest.o $(LIBRARYFILES)
+       $(LDENV) $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(CMDLDFLAGS) -ltcejdb $(LIBS)
+
+
+tchmgr : tchmgr.o $(LIBRARYFILES)
+       $(LDENV) $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(CMDLDFLAGS) -ltcejdb $(LIBS)
+
+
+tcbtest : tcbtest.o $(LIBRARYFILES)
+       $(LDENV) $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(CMDLDFLAGS) -ltcejdb $(LIBS)
+
+
+tcbmttest : tcbmttest.o $(LIBRARYFILES)
+       $(LDENV) $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(CMDLDFLAGS) -ltcejdb $(LIBS)
+
+
+tcbmgr : tcbmgr.o $(LIBRARYFILES)
+       $(LDENV) $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(CMDLDFLAGS) -ltcejdb $(LIBS)
+
+
+tcftest : tcftest.o $(LIBRARYFILES)
+       $(LDENV) $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(CMDLDFLAGS) -ltcejdb $(LIBS)
+
+
+tcfmttest : tcfmttest.o $(LIBRARYFILES)
+       $(LDENV) $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(CMDLDFLAGS) -ltcejdb $(LIBS)
+
+
+tcfmgr : tcfmgr.o $(LIBRARYFILES)
+       $(LDENV) $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(CMDLDFLAGS) -ltcejdb $(LIBS)
+
+
+tcttest : tcttest.o $(LIBRARYFILES)
+       $(LDENV) $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(CMDLDFLAGS) -ltcejdb $(LIBS)
+
+
+tctmttest : tctmttest.o $(LIBRARYFILES)
+       $(LDENV) $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(CMDLDFLAGS) -ltcejdb $(LIBS)
+
+
+tctmgr : tctmgr.o $(LIBRARYFILES)
+       $(LDENV) $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(CMDLDFLAGS) -ltcejdb $(LIBS)
+
+
+tcatest : tcatest.o $(LIBRARYFILES)
+       $(LDENV) $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(CMDLDFLAGS) -ltcejdb $(LIBS)
+
+
+tcamttest : tcamttest.o $(LIBRARYFILES)
+       $(LDENV) $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(CMDLDFLAGS) -ltcejdb $(LIBS)
+
+
+tcamgr : tcamgr.o $(LIBRARYFILES)
+       $(LDENV) $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(CMDLDFLAGS) -ltcejdb $(LIBS)
+
+
+tcawmgr.cgi : tcawmgr.o $(LIBRARYFILES)
+       $(LDENV) $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) $(CMDLDFLAGS) -ltcejdb $(LIBS)
+
+
+myconf.o : myconf.h
+
+tcutil.o : myconf.h tcutil.h md5.h
+
+tchdb.o : myconf.h tcutil.h tchdb.h
+
+tcbdb.o : myconf.h tcutil.h tchdb.h tcbdb.h
+
+tcfdb.o : myconf.h tcutil.h tcfdb.h
+
+tctdb.o : myconf.h tcutil.h tchdb.h tctdb.h
+
+tcadb.o : myconf.h tcutil.h tchdb.h tcbdb.h tcfdb.h tctdb.h tcadb.h
+
+tcutest.o tcucodec.o : myconf.h tcutil.h
+
+tchtest.o tchmttest.o tchmgr.o : myconf.h tcutil.h tchdb.h
+
+tcbtest.o tcbmttest.o tcbmgr.o : myconf.h tcutil.h tchdb.h tcbdb.h
+
+tcftest.o tcfmttest.o tcfmgr.o : myconf.h tcutil.h tcfdb.h
+
+tcttest.o tctmttest.o tctmgr.o : myconf.h tcutil.h tchdb.h tcbdb.h tctdb.h
+
+tcatest.o tcamttest.o tcamgr.o tcawmgr.o : \
+  myconf.h tcutil.h tchdb.h tcbdb.h tcfdb.h tctdb.h tcadb.h
+
+ejdb.o : myconf.h ejdb.h ejdb_private.h ejdbutl.h
+
+bson.o : myconf.h bson.h
+
+
+tokyocabinet_all.c : myconf.c tcutil.c md5.c tchdb.c tcbdb.c tcfdb.c tctdb.c tcadb.c
+       cat myconf.c tcutil.c md5.c tchdb.c tcbdb.c tcfdb.c tctdb.c tcadb.c > $@
+
+tokyocabinet_all.o : myconf.h tcutil.h tchdb.h tcbdb.h tcfdb.h tctdb.h tcadb.h
+
+
+
+# END OF FILE
diff --git a/tcejdb/README b/tcejdb/README
new file mode 100644 (file)
index 0000000..f810f4c
--- /dev/null
@@ -0,0 +1,37 @@
+================================================================
+ Tokyo Cabinet: a modern implementation of DBM
+ Copyright (C) 2006-2011 Fal Labs
+================================================================
+
+
+Please read the following documents with a WWW browser.
+How to install Tokyo Cabinet is explained in the specification.
+
+  README         - this file
+  COPYING        - license
+  ChangeLog      - history of enhancement
+  doc/index.html - index of documents
+
+
+Contents of the directory tree is below.
+
+  ./             - sources of Tokyo Cabinet
+  ./doc/         - manuals and specifications
+  ./man/         - manuals for nroff
+  ./example/     - sample code of tutorial
+  ./lab/         - for test and experiment
+  ./bros/        - for comparison with other database managers
+
+
+Tokyo Cabinet is released under the terms of the GNU Lesser General
+Public License.  See the file `COPYING' for details.
+
+Tokyo Cabinet was written by FAL Labs.  You can contact the author
+by e-mail to `hirarin@gmail.com'.
+
+
+Thanks.
+
+
+
+== END OF FILE ==
diff --git a/tcejdb/bros/Makefile b/tcejdb/bros/Makefile
new file mode 100644 (file)
index 0000000..619462e
--- /dev/null
@@ -0,0 +1,133 @@
+# Makefile for writing tests of DBM brothers
+
+
+
+#================================================================
+# Setting variables
+#================================================================
+
+
+# Generic settings
+SHELL = /bin/sh
+
+# Targets
+MYBINS = tctest qdbmtest ndbmtest sdbmtest gdbmtest tdbtest cdbtest bdbtest \
+  maptest sqltest cmpsqltctest
+
+
+
+#================================================================
+# Suffix rules
+#================================================================
+
+
+.SUFFIXES :
+
+
+
+#================================================================
+# Actions
+#================================================================
+
+
+mesg :
+       @echo "Here are writing tests of DBM brothers." 1>&2
+       @echo "select a target of {all clean $(MYBINS)}." 1>&2
+
+
+all : $(MYBINS)
+
+
+clean :
+       rm -rf $(MYBINS) *.o *.exe a.out casket* *~
+
+
+distclean : clean
+
+
+
+#================================================================
+# Building binaries
+#================================================================
+
+
+tctest : tctest.c
+       LD_LIBRARY_PATH="$(LD_LIBRARY_PATH):.:.." ; PATH="$(PATH):.:.." ; \
+         export LD_LIBRARY_PATH PATH ; ldflags=`tcucodec conf -l` ; \
+         [ -z "$$ldflags" ] && \
+           ldflags="-L/usr/local/lib -ltokyocabinet -lz -lpthread -lm -lc" ; \
+         gcc -I.. -I/usr/local/include -D_GNU_SOURCE=1 \
+           -Wall -ansi -pedantic -O3 -o $@ tctest.c \
+           -static -L. -L.. $$ldflags
+
+
+qdbmtest : qdbmtest.c
+       gcc -I.. -I/usr/local/include -D_GNU_SOURCE=1 \
+         -Wall -ansi -pedantic -O3 -o $@ qdbmtest.c \
+         -static -L.. -L/usr/local/lib -lqdbm -lz -lc
+
+
+ndbmtest : ndbmtest.c
+       gcc -I/usr/local/include -D_GNU_SOURCE=1 \
+         -Wall -ansi -pedantic -O3 -o $@ ndbmtest.c \
+         -static -L/usr/local/lib -lndbm -lc
+
+
+sdbmtest : sdbmtest.c
+       gcc -I/usr/local/include -D_GNU_SOURCE=1 \
+         -Wall -ansi -pedantic -O3 -o $@ sdbmtest.c \
+         -static -L/usr/local/lib -lsdbm -lc
+
+
+gdbmtest : gdbmtest.c
+       gcc -I/usr/local/include -D_GNU_SOURCE=1 \
+         -Wall -ansi -pedantic -O3 -o $@ gdbmtest.c \
+         -static -L/usr/local/lib -lgdbm -lc
+
+
+tdbtest : tdbtest.c
+       gcc -I/usr/local/include -D_GNU_SOURCE=1 \
+         -Wall -ansi -pedantic -O3 -o $@ tdbtest.c \
+         -static -L/usr/local/lib -ltdb -lc
+
+
+cdbtest : cdbtest.c
+       gcc -I/usr/local/include -D_GNU_SOURCE=1 \
+         -Wall -ansi -pedantic -O3 -o $@ cdbtest.c \
+         -static -L/usr/local/lib -lcdb -lc
+
+
+bdbtest : bdbtest.c
+       gcc -I/usr/local/bdb/include -D_GNU_SOURCE=1 \
+         -Wall -O3 -o $@ bdbtest.c \
+         -static -L/usr/local/bdb/lib -ldb -lpthread -lc
+
+
+maptest : maptest.cc
+       LD_LIBRARY_PATH="$(LD_LIBRARY_PATH):.:.." ; PATH="$(PATH):.:.." ; \
+         export LD_LIBRARY_PATH PATH ; ldflags=`tcucodec conf -l` ; \
+         [ -z "$$ldflags" ] && \
+           ldflags="-L/usr/local/lib -ltokyocabinet -lz -lpthread -lm -lc" ; \
+         g++ -I.. -I/usr/local/include -D_GNU_SOURCE=1 \
+           -Wall -ansi -pedantic -O3 -o $@ maptest.cc \
+           -static -L. -L.. -lstdc++ $$ldflags
+
+
+sqltest : sqltest.c
+       gcc -I/usr/local/include -D_GNU_SOURCE=1 \
+         -Wall -O3 -o $@ sqltest.c \
+         -static -L/usr/local/lib -lsqlite3 -lpthread -ldl -lc
+
+
+cmpsqltctest : cmpsqltctest.c
+       LD_LIBRARY_PATH="$(LD_LIBRARY_PATH):.:.." ; PATH="$(PATH):.:.." ; \
+         export LD_LIBRARY_PATH PATH ; ldflags=`tcucodec conf -l` ; \
+         [ -z "$$ldflags" ] && \
+           ldflags="-L/usr/local/lib -ltokyocabinet -lz -lpthread -lm -lc" ; \
+         gcc -std=c99 -I.. -I/usr/local/include -D_GNU_SOURCE=1 \
+           -Wall -pedantic -O3 -o $@ cmpsqltctest.c \
+           -static -L. -L.. -lsqlite3 -lpthread -ldl $$ldflags
+
+
+
+# END OF FILE
diff --git a/tcejdb/bros/bdbtest.c b/tcejdb/bros/bdbtest.c
new file mode 100644 (file)
index 0000000..55afb53
--- /dev/null
@@ -0,0 +1,438 @@
+/*************************************************************************************************
+ * Writing test of Berkeley DB
+ *************************************************************************************************/
+
+
+#include <db.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#undef TRUE
+#define TRUE           1                 /* boolean true */
+#undef FALSE
+#define FALSE          0                 /* boolean false */
+
+#define RECBUFSIZ       32               /* buffer for records */
+#define SMALL_PAGESIZE  512              /* small page size */
+#define BIG_PAGESIZE    65536            /* maximum page size */
+#define BIG_CACHESIZE   (rnum * 2 * 15)  /* maximum cache size < avail mem. */
+#define SMALL_CACHESIZE (5 * 1048576)    /* should be Btree fanout */
+
+
+/* global variables */
+const char *progname;                    /* program name */
+int showprgr;                            /* whether to show progression */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+int runwrite(int argc, char **argv);
+int runread(int argc, char **argv);
+int runbtwrite(int argc, char **argv);
+int runbtread(int argc, char **argv);
+int myrand(void);
+int dowrite(char *name, int rnum);
+int doread(char *name, int rnum);
+int dobtwrite(char *name, int rnum, int rnd);
+int dobtread(char *name, int rnum, int rnd);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  int rv;
+  progname = argv[0];
+  showprgr = TRUE;
+  if(getenv("HIDEPRGR")) showprgr = FALSE;
+  srand48(1978);
+  if(argc < 2) usage();
+  rv = 0;
+  if(!strcmp(argv[1], "write")){
+    rv = runwrite(argc, argv);
+  } else if(!strcmp(argv[1], "read")){
+    rv = runread(argc, argv);
+  } else if(!strcmp(argv[1], "btwrite")){
+    rv = runbtwrite(argc, argv);
+  } else if(!strcmp(argv[1], "btread")){
+    rv = runbtread(argc, argv);
+  } else {
+    usage();
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+  fprintf(stderr, "%s: test cases for Berkeley DB\n", progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s write name rnum\n", progname);
+  fprintf(stderr, "  %s read name rnum\n", progname);
+  fprintf(stderr, "  %s btwrite [-rnd] name rnum\n", progname);
+  fprintf(stderr, "  %s btread [-rnd] name rnum\n", progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* parse arguments of write command */
+int runwrite(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = dowrite(name, rnum);
+  return rv;
+}
+
+
+/* parse arguments of read command */
+int runread(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = doread(name, rnum);
+  return rv;
+}
+
+
+/* parse arguments of btwrite command */
+int runbtwrite(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rnd, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  rnd = FALSE;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      if(!name && !strcmp(argv[i], "-rnd")){
+        rnd = TRUE;
+      } else {
+        usage();
+      }
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = dobtwrite(name, rnum, rnd);
+  return rv;
+}
+
+
+/* parse arguments of btread command */
+int runbtread(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rnd, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  rnd = FALSE;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      if(!name && !strcmp(argv[i], "-rnd")){
+        rnd = TRUE;
+      } else {
+        usage();
+      }
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = dobtread(name, rnum, rnd);
+  return rv;
+}
+
+
+/* pseudo random number generator */
+int myrand(void){
+  static int cnt = 0;
+  return (lrand48() + cnt++) & 0x7FFFFFFF;
+}
+
+
+/* perform write command */
+int dowrite(char *name, int rnum){
+  DB *dbp;
+  DBT key, data;
+  int i, err, len;
+  char buf[RECBUFSIZ];
+  if(showprgr) printf("<Writing Test of Hash>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  if(db_create(&dbp, NULL, 0) != 0){
+    fprintf(stderr, "db_create failed\n");
+    return 1;
+  }
+  if(dbp->set_pagesize(dbp, SMALL_PAGESIZE) != 0){
+    fprintf(stderr, "DB->set_pagesize failed\n");
+    dbp->close(dbp, 0);
+    return 1;
+  }
+  if(dbp->set_cachesize(dbp, 0, BIG_CACHESIZE, 0) != 0){
+    fprintf(stderr, "DB->set_cachesize failed\n");
+    dbp->close(dbp, 0);
+    return 1;
+  }
+  if(dbp->open(dbp, NULL, name, NULL, DB_HASH, DB_CREATE | DB_TRUNCATE, 00644) != 0){
+    fprintf(stderr, "DB->open failed\n");
+    dbp->close(dbp, 0);
+    return 1;
+  }
+  err = FALSE;
+  memset(&key, 0, sizeof(key));
+  memset(&data, 0, sizeof(data));
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* store a record */
+    len = sprintf(buf, "%08d", i);
+    key.data = buf;
+    key.size = len;
+    data.data = buf;
+    data.size = len;
+    if(dbp->put(dbp, NULL, &key, &data, 0) != 0){
+      fprintf(stderr, "DB->put failed\n");
+      err = TRUE;
+      break;
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  if(dbp->close(dbp, 0) != 0){
+    fprintf(stderr, "DB->close failed\n");
+    err = TRUE;
+  }
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+/* perform read command */
+int doread(char *name, int rnum){
+  DB *dbp;
+  DBT key, data;
+  int i, err, len;
+  char buf[RECBUFSIZ];
+  if(showprgr) printf("<Reading Test of Hash>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  if(db_create(&dbp, NULL, 0) != 0){
+    fprintf(stderr, "db_create failed\n");
+    return 1;
+  }
+  if(dbp->set_cachesize(dbp, 0, BIG_CACHESIZE, 0) != 0){
+    fprintf(stderr, "DB->set_cachesize failed\n");
+    dbp->close(dbp, 0);
+    return 1;
+  }
+  if(dbp->open(dbp, NULL, name, NULL, DB_HASH, DB_RDONLY, 00644) != 0){
+    fprintf(stderr, "DB->open failed\n");
+    dbp->close(dbp, 0);
+    return 1;
+  }
+  err = FALSE;
+  memset(&key, 0, sizeof(key));
+  memset(&data, 0, sizeof(data));
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* store a record */
+    len = sprintf(buf, "%08d", i);
+    key.data = buf;
+    key.size = len;
+    if(dbp->get(dbp, NULL, &key, &data, 0) != 0){
+      fprintf(stderr, "DB->get failed\n");
+      err = TRUE;
+      break;
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  if(dbp->close(dbp, 0) != 0){
+    fprintf(stderr, "DB->close failed\n");
+    err = TRUE;
+  }
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+/* perform btwrite command */
+int dobtwrite(char *name, int rnum, int rnd){
+  DB *dbp;
+  DBT key, data;
+  int i, err, len;
+  char buf[RECBUFSIZ];
+  if(showprgr) printf("<Writing Test of B+ Tree>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  if(db_create(&dbp, NULL, 0) != 0){
+    fprintf(stderr, "db_create failed\n");
+    return 1;
+  }
+  if(dbp->set_pagesize(dbp, BIG_PAGESIZE) != 0){
+    fprintf(stderr, "DB->set_pagesize failed\n");
+    dbp->close(dbp, 0);
+    return 1;
+  }
+  if(dbp->set_cachesize(dbp, 0, rnd ? BIG_CACHESIZE : SMALL_CACHESIZE, 0) != 0){
+    fprintf(stderr, "DB->set_cachesize failed\n");
+    dbp->close(dbp, 0);
+    return 1;
+  }
+  if(dbp->open(dbp, NULL, name, NULL, DB_BTREE, DB_CREATE | DB_TRUNCATE, 00644) != 0){
+    fprintf(stderr, "DB->open failed\n");
+    dbp->close(dbp, 0);
+    return 1;
+  }
+  err = FALSE;
+  memset(&key, 0, sizeof(key));
+  memset(&data, 0, sizeof(data));
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* store a record */
+    len = sprintf(buf, "%08d", rnd ? myrand() % rnum + 1 : i);
+    key.data = data.data = buf;
+    key.size = data.size = len;
+    if(dbp->put(dbp, NULL, &key, &data, 0) != 0){
+      fprintf(stderr, "DB->put failed\n");
+      err = TRUE;
+      break;
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  if(dbp->close(dbp, 0) != 0){
+    fprintf(stderr, "DB->close failed\n");
+    err = TRUE;
+  }
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+/* perform btread command */
+int dobtread(char *name, int rnum, int rnd){
+  DB *dbp;
+  DBT key, data;
+  int i, err, len;
+  char buf[RECBUFSIZ];
+  if(showprgr) printf("<Reading Test of B+ Tree>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  if(db_create(&dbp, NULL, 0) != 0){
+    fprintf(stderr, "db_create failed\n");
+    return 1;
+  }
+  if(dbp->set_cachesize(dbp, 0, rnd ? BIG_CACHESIZE : SMALL_CACHESIZE, 0) != 0){
+    fprintf(stderr, "DB->set_cachesize failed\n");
+    dbp->close(dbp, 0);
+    return 1;
+  }
+  if(dbp->open(dbp, NULL, name, NULL, DB_BTREE, DB_RDONLY, 00644) != 0){
+    fprintf(stderr, "DB->open failed\n");
+    dbp->close(dbp, 0);
+    return 1;
+  }
+  err = FALSE;
+  memset(&key, 0, sizeof(key));
+  memset(&data, 0, sizeof(data));
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* store a record */
+    len = sprintf(buf, "%08d", rnd ? myrand() % rnum + 1 : i);
+    key.data = buf;
+    key.size = len;
+    if(dbp->get(dbp, NULL, &key, &data, 0) != 0){
+      fprintf(stderr, "DB->get failed\n");
+      err = TRUE;
+      break;
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  if(dbp->close(dbp, 0) != 0){
+    fprintf(stderr, "DB->close failed\n");
+    err = TRUE;
+  }
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/tcejdb/bros/cdbtest.c b/tcejdb/bros/cdbtest.c
new file mode 100644 (file)
index 0000000..b58a0d9
--- /dev/null
@@ -0,0 +1,219 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <cdb.h>
+
+#undef TRUE
+#define TRUE           1                 /* boolean true */
+#undef FALSE
+#define FALSE          0                 /* boolean false */
+
+#define RECBUFSIZ      32                /* buffer for records */
+
+
+/* global variables */
+const char *progname;                    /* program name */
+int showprgr;                            /* whether to show progression */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+int runwrite(int argc, char **argv);
+int runread(int argc, char **argv);
+int dowrite(char *name, int rnum);
+int doread(char *name, int rnum);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  int rv;
+  progname = argv[0];
+  showprgr = TRUE;
+  if(getenv("HIDEPRGR")) showprgr = FALSE;
+  if(argc < 2) usage();
+  rv = 0;
+  if(!strcmp(argv[1], "write")){
+    rv = runwrite(argc, argv);
+  } else if(!strcmp(argv[1], "read")){
+    rv = runread(argc, argv);
+  } else {
+    usage();
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+  fprintf(stderr, "%s: test cases for Constant Database\n", progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s write name rnum\n", progname);
+  fprintf(stderr, "  %s read name rnum\n", progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* parse arguments of write command */
+int runwrite(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = dowrite(name, rnum);
+  return rv;
+}
+
+
+/* parse arguments of read command */
+int runread(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = doread(name, rnum);
+  return rv;
+}
+
+
+/* perform write command */
+int dowrite(char *name, int rnum){
+  struct cdb_make cdb;
+  int i, fd, err, len;
+  char buf[32];
+  if(showprgr) printf("<Writing Test>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  if((fd = open(name, O_RDWR | O_CREAT | O_TRUNC, 0644)) == -1){
+    perror("open");
+    return 1;
+  }
+  if(cdb_make_start(&cdb, fd) == -1){
+    perror("cdb_make_start");
+    close(fd);
+    return 1;
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* store a record */
+    len = sprintf(buf, "%08d", i);
+    if(cdb_make_add(&cdb, buf, len, buf, len) == -1){
+      perror("cdb_add");
+      err = TRUE;
+      break;
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  if(cdb_make_finish(&cdb) == -1){
+    perror("cdb_make_finish");
+    close(fd);
+    return 1;
+  }
+  if(close(fd) == -1){
+    perror("close");
+    return 1;
+  }
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+/* perform read command */
+int doread(char *name, int rnum){
+  struct cdb cdb;
+  int i, fd, err, len;
+  char buf[RECBUFSIZ], *val;
+  if(showprgr) printf("<Reading Test>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  if((fd = open(name, O_RDONLY, 0644)) == -1){
+    perror("open");
+    return 1;
+  }
+  cdb_init(&cdb, fd);
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* retrieve a record */
+    len = sprintf(buf, "%08d", i);
+    if(cdb_find(&cdb, buf, len) == 0){
+      perror("cdb_find");
+      err = TRUE;
+      break;
+    }
+    len = cdb_datalen(&cdb);
+    if(!(val = malloc(len + 1))){
+      perror("malloc");
+      err = TRUE;
+      break;
+    }
+    cdb_read(&cdb, val, len, cdb_datapos(&cdb));
+    free(val);
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  cdb_free(&cdb);
+  if(close(fd) == -1){
+    perror("close");
+    return 1;
+  }
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/tcejdb/bros/cmpsqltctest.c b/tcejdb/bros/cmpsqltctest.c
new file mode 100644 (file)
index 0000000..168f932
--- /dev/null
@@ -0,0 +1,186 @@
+/*************************************************************************************************
+ * Comparison test of SQLite and Tokyo Cabinet
+ *************************************************************************************************/
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <assert.h>
+#include <sqlite3.h>
+#include <tctdb.h>
+
+#define SQLFILE    "casket.sql"
+#define TCTFILE    "casket.tct"
+#define SQLBUFSIZ   1024
+#define SAMPLENUM   10
+#define RECORDNUM   1000000
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+static void test_sqlite(void);
+static void test_tctdb(void);
+static int myrand(void);
+static int callback(void *opq, int argc, char **argv, char **colname);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  test_sqlite();
+  test_tctdb();
+  return 0;
+}
+
+
+/* perform SQLite test */
+static void test_sqlite(void){
+  double sum = 0.0;
+  for(int i = 1; i <= SAMPLENUM; i++){
+    unlink(SQLFILE);
+    sync(); sync();
+    printf("SQLite write sample:%d ... ", i);
+    fflush(stdout);
+    double stime = tctime();
+    sqlite3 *db;
+    if(sqlite3_open(SQLFILE, &db) != 0) assert(!__LINE__);
+    char sql[SQLBUFSIZ], *errmsg;
+    sprintf(sql, "CREATE TABLE tbl ( k TEXT PRIMARY KEY, a TEXT, b INTEGER,"
+            " c TEXT, d INTEGER );");
+    if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK) assert(!__LINE__);
+    sprintf(sql, "CREATE INDEX tbl_s ON tbl ( a );");
+    if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK) assert(!__LINE__);
+    sprintf(sql, "CREATE INDEX tbl_n ON tbl ( b );");
+    if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK) assert(!__LINE__);
+    sprintf(sql, "PRAGMA cache_size = %d;", RECORDNUM);
+    if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK) assert(!__LINE__);
+    sprintf(sql, "BEGIN TRANSACTION;");
+    if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK) assert(!__LINE__);
+    for(int j = 1; j <= RECORDNUM; j++){
+      sprintf(sql, "INSERT INTO tbl VALUES ( '%08d', '%08d', %d, '%08d', %d );",
+            j, j, myrand() % RECORDNUM, myrand() % RECORDNUM, j);
+      if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK) assert(!__LINE__);
+    }
+    sprintf(sql, "END TRANSACTION;");
+    if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK) assert(!__LINE__);
+    sqlite3_close(db);
+    double etime = tctime() - stime;
+    printf("%.6f\n", etime);
+    sum += etime;
+  }
+  printf("SQLite write average: %.6f\n", sum / SAMPLENUM);
+  sum = 0.0;
+  for(int i = 1; i <= SAMPLENUM; i++){
+    sync(); sync();
+    printf("SQLite read sample:%d ... ", i);
+    fflush(stdout);
+    double stime = tctime();
+    sqlite3 *db;
+    if(sqlite3_open(SQLFILE, &db) != 0) assert(!__LINE__);
+    char sql[SQLBUFSIZ], *errmsg;
+    sprintf(sql, "PRAGMA cache_size = %d;", RECORDNUM);
+    if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK) assert(!__LINE__);
+    for(int j = 1; j <= RECORDNUM; j++){
+      sprintf(sql, "SELECT * FROM tbl WHERE a = '%08d';", myrand() % RECORDNUM + 1);
+      if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK) assert(!__LINE__);
+    }
+    sqlite3_close(db);
+    double etime = tctime() - stime;
+    printf("%.6f\n", etime);
+    sum += etime;
+  }
+  printf("SQLite read average: %.6f\n", sum / SAMPLENUM);
+}
+
+
+/* perform TCHDB test */
+static void test_tctdb(void){
+  double sum = 0.0;
+  for(int i = 1; i <= SAMPLENUM; i++){
+    unlink(TCTFILE);
+    sync(); sync();
+    printf("TCTDB write sample:%d ... ", i);
+    fflush(stdout);
+    double stime = tctime();
+    TCTDB *tdb = tctdbnew();
+    if(!tctdbtune(tdb, RECORDNUM * 2, -1, -1, 0)) assert(!__LINE__);
+    if(!tctdbsetcache(tdb, 0, RECORDNUM, RECORDNUM)) assert(!__LINE__);
+    if(!tctdbopen(tdb, TCTFILE, TDBOWRITER | TDBOCREAT | TDBOTRUNC)) assert(!__LINE__);
+    if(!tctdbsetindex(tdb, "a", TDBITLEXICAL)) assert(!__LINE__);
+    if(!tctdbsetindex(tdb, "b", TDBITDECIMAL)) assert(!__LINE__);
+    if(!tctdbtranbegin(tdb)) assert(!__LINE__);
+    char buf[SQLBUFSIZ];
+    int size;
+    for(int j = 1; j <= RECORDNUM; j++){
+      TCMAP *cols = tcmapnew2(7);
+      size = sprintf(buf, "%08d", j);
+      tcmapput(cols, "a", 1, buf, size);
+      size = sprintf(buf, "%d", myrand() % RECORDNUM);
+      tcmapput(cols, "b", 1, buf, size);
+      size = sprintf(buf, "%08d", myrand() % RECORDNUM);
+      tcmapput(cols, "c", 1, buf, size);
+      size = sprintf(buf, "%d", j);
+      tcmapput(cols, "d", 1, buf, size);
+      size = sprintf(buf, "%08d", j);
+      if(!tctdbput(tdb, buf, size, cols)) assert(!__LINE__);
+      tcmapdel(cols);
+    }
+    if(!tctdbtrancommit(tdb)) assert(!__LINE__);
+    if(!tctdbclose(tdb)) assert(!__LINE__);
+    tctdbdel(tdb);
+    double etime = tctime() - stime;
+    printf("%.6f\n", etime);
+    sum += etime;
+  }
+  printf("TCTDB write average: %.6f\n", sum / SAMPLENUM);
+  sum = 0.0;
+  for(int i = 1; i <= SAMPLENUM; i++){
+    sync(); sync();
+    printf("TCTDB read sample:%d ... ", i);
+    fflush(stdout);
+    double stime = tctime();
+    TCTDB *tdb = tctdbnew();
+    if(!tctdbsetcache(tdb, 0, RECORDNUM, RECORDNUM)) assert(!__LINE__);
+    if(!tctdbopen(tdb, TCTFILE, TDBOREADER)) assert(!__LINE__);
+    char buf[SQLBUFSIZ];
+    for(int j = 1; j <= RECORDNUM; j++){
+      TDBQRY *qry = tctdbqrynew(tdb);
+      sprintf(buf, "%08d", myrand() % RECORDNUM + 1);
+      tctdbqryaddcond(qry, "a", TDBQCSTREQ, buf);
+      TCLIST *res = tctdbqrysearch(qry);
+      for(int k = 0; k < tclistnum(res); k++){
+        int ksiz;
+        const char *kbuf = tclistval(res, k, &ksiz);
+        TCMAP *cols = tctdbget(tdb, kbuf, ksiz);
+        if(!cols) assert(!__LINE__);
+        tcmapdel(cols);
+      }
+      tclistdel(res);
+      tctdbqrydel(qry);
+    }
+    if(!tctdbclose(tdb)) assert(!__LINE__);
+    double etime = tctime() - stime;
+    printf("%.6f\n", etime);
+    sum += etime;
+  }
+  printf("TCTDB read average: %.6f\n", sum / SAMPLENUM);
+}
+
+
+/* generate a random number */
+static int myrand(void){
+  static int cnt = 0;
+  return (lrand48() + cnt++) & 0x7FFFFFFF;
+}
+
+
+/* iterator of SQLite */
+static int callback(void *opq, int argc, char **argv, char **colname){
+  return 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/tcejdb/bros/gdbmtest.c b/tcejdb/bros/gdbmtest.c
new file mode 100644 (file)
index 0000000..eff692d
--- /dev/null
@@ -0,0 +1,216 @@
+/*************************************************************************************************
+ * Writing test of GNU Database Manager
+ *************************************************************************************************/
+
+
+#include <gdbm.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#undef TRUE
+#define TRUE           1                 /* boolean true */
+#undef FALSE
+#define FALSE          0                 /* boolean false */
+
+#define RECBUFSIZ      32                /* buffer for records */
+#define CACHESIZE      16                /* cache size */
+
+
+/* global variables */
+const char *progname;                    /* program name */
+int showprgr;                            /* whether to show progression */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+int runwrite(int argc, char **argv);
+int runread(int argc, char **argv);
+int dowrite(char *name, int rnum);
+int doread(char *name, int rnum);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  int rv;
+  progname = argv[0];
+  showprgr = TRUE;
+  if(getenv("HIDEPRGR")) showprgr = FALSE;
+  if(argc < 2) usage();
+  rv = 0;
+  if(!strcmp(argv[1], "write")){
+    rv = runwrite(argc, argv);
+  } else if(!strcmp(argv[1], "read")){
+    rv = runread(argc, argv);
+  } else {
+    usage();
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+  fprintf(stderr, "%s: test cases for GNU Database Manager\n", progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s write name rnum\n", progname);
+  fprintf(stderr, "  %s read name rnum\n", progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* parse arguments of write command */
+int runwrite(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = dowrite(name, rnum);
+  return rv;
+}
+
+
+/* parse arguments of read command */
+int runread(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = doread(name, rnum);
+  return rv;
+}
+
+
+/* perform write command */
+int dowrite(char *name, int rnum){
+  GDBM_FILE dbf;
+  datum key, content;
+  int i, err, optval, len;
+  char buf[RECBUFSIZ];
+  if(showprgr) printf("<Writing Test>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  if(!(dbf = gdbm_open(name, 0, GDBM_NEWDB, 00644, NULL))){
+    fprintf(stderr, "gdbm_open failed\n");
+    return 1;
+  }
+  optval = CACHESIZE;
+  if(gdbm_setopt(dbf, GDBM_CACHESIZE, &optval, sizeof(int)) != 0){
+    fprintf(stderr, "gdbm_setopt failed\n");
+    gdbm_close(dbf);
+    return 1;
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    len = sprintf(buf, "%08d", i);
+    key.dptr = buf;
+    key.dsize = len;
+    content.dptr = buf;
+    content.dsize = len;
+    /* store a record */
+    if(gdbm_store(dbf, key, content, GDBM_REPLACE) != 0){
+      fprintf(stderr, "gdbm_store failed\n");
+      err = TRUE;
+      break;
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  gdbm_close(dbf);
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+/* perform read command */
+int doread(char *name, int rnum){
+  GDBM_FILE dbf;
+  datum key, content;
+  int i, err, optval, len;
+  char buf[RECBUFSIZ];
+  if(showprgr) printf("<Reading Test>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  if(!(dbf = gdbm_open(name, 0, GDBM_READER, 00644, NULL))){
+    fprintf(stderr, "gdbm_open failed\n");
+    return 1;
+  }
+  optval = CACHESIZE;
+  if(gdbm_setopt(dbf, GDBM_CACHESIZE, &optval, sizeof(int)) != 0){
+    fprintf(stderr, "gdbm_setopt failed\n");
+    gdbm_close(dbf);
+    return 1;
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* retrieve a record */
+    len = sprintf(buf, "%08d", i);
+    key.dptr = buf;
+    key.dsize = len;
+    content = gdbm_fetch(dbf, key);
+    if(!content.dptr){
+      fprintf(stderr, "gdbm_fetch failed\n");
+      err = TRUE;
+      break;
+    }
+    free(content.dptr);
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  gdbm_close(dbf);
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/tcejdb/bros/mapreporter b/tcejdb/bros/mapreporter
new file mode 100755 (executable)
index 0000000..50f2131
--- /dev/null
@@ -0,0 +1,72 @@
+#! /usr/bin/perl
+
+#================================================================
+# mapreporter
+# Measure elapsed time of map utilities
+#================================================================
+
+use strict;
+use warnings;
+use Time::HiRes qw(gettimeofday);
+
+use constant {
+    RECNUM => 1000000,
+    TESTCOUNT => 20,
+    REMOVETOP => 2,
+    REMOVEBOTTOM => 8,
+};
+
+my @commands = (
+                "./maptest tcmap " . RECNUM,
+                "./maptest tctree " . RECNUM,
+                "./maptest stlmap " . RECNUM,
+                "./maptest stlmmap " . RECNUM,
+                "./maptest stlset " . RECNUM,
+                "./maptest gnuhash " . RECNUM,
+                "./maptest ggldh " . RECNUM,
+                "./maptest gglsh " . RECNUM,
+                "./maptest tcmap -rd " . RECNUM,
+                "./maptest tctree -rd " . RECNUM,
+                "./maptest stlmap -rd " . RECNUM,
+                "./maptest stlmmap -rd " . RECNUM,
+                "./maptest stlset -rd " . RECNUM,
+                "./maptest gnuhash -rd " . RECNUM,
+                "./maptest ggldh -rd " . RECNUM,
+                "./maptest gglsh -rd " . RECNUM,
+                );
+
+my @table;
+foreach my $command (@commands){
+    system("sync ; sync");
+    my @result;
+    for(my $i = 0; $i < TESTCOUNT; $i++){
+        my $stime = gettimeofday();
+        system("$command >/dev/null 2>&1");
+        $stime = gettimeofday() - $stime;
+        printf("%s\t%d\t%0.5f\n", $command, $i + 1, $stime);
+        push(@result, $stime);
+    }
+    @result = sort { $a <=> $b } @result;
+    for(my $i = 0; $i < REMOVETOP; $i++){
+        shift(@result);
+    }
+    for(my $i = 0; $i < REMOVEBOTTOM; $i++){
+        pop(@result);
+    }
+    my $sum = 0;
+    foreach my $result (@result){
+        $sum += $result;
+    }
+    my $avg = $sum / scalar(@result);
+    push(@table, [$command, $avg]);
+}
+
+printf("\n\nRESULT\n");
+foreach my $row (@table){
+    printf("%s\t%0.5f\n", $$row[0], $$row[1]);
+}
+printf("\n");
+
+
+
+# END OF FILE
diff --git a/tcejdb/bros/maptest.cc b/tcejdb/bros/maptest.cc
new file mode 100644 (file)
index 0000000..a1e0848
--- /dev/null
@@ -0,0 +1,679 @@
+/*************************************************************************************************
+ * Writing test of map utilities
+ *************************************************************************************************/
+
+
+#include <cstdio>
+#include <cstring>
+#include <cstdlib>
+#include <cstdarg>
+#include <tcutil.h>
+#include <map>
+#include <set>
+#include <tr1/unordered_map>
+#include <google/sparse_hash_map>
+#include <google/dense_hash_map>
+
+#define RECBUFSIZ      32                // buffer for records
+
+using namespace std;
+
+struct stringhash {                      // hash function for string
+  size_t operator()(const string& str) const {
+    const char *ptr = str.data();
+    int size = str.size();
+    size_t idx = 19780211;
+    while(size--){
+      idx = idx * 37 + *(uint8_t *)ptr++;
+    }
+    return idx;
+  }
+};
+
+
+// aliases of template instances
+typedef map<string, string> stlmap;
+typedef multimap<string, string> stlmmap;
+typedef set<string> stlset;
+typedef tr1::unordered_map<string, string, stringhash> trhash;
+typedef google::dense_hash_map<string, string, stringhash> ggldh;
+typedef google::sparse_hash_map<string, string, stringhash> gglsh;
+
+
+// global variables
+const char *g_progname;                  // program name
+
+
+// function prototypes
+int main(int argc, char **argv);
+static void usage(void);
+static void iprintf(const char *format, ...);
+static int runtcmap(int argc, char **argv);
+static int runtctree(int argc, char **argv);
+static int runstlmap(int argc, char **argv);
+static int runstlmmap(int argc, char **argv);
+static int runstlset(int argc, char **argv);
+static int runtrhash(int argc, char **argv);
+static int runggldh(int argc, char **argv);
+static int rungglsh(int argc, char **argv);
+static int proctcmap(int rnum, bool rd);
+static int proctctree(int rnum, bool rd);
+static int procstlmap(int rnum, bool rd);
+static int procstlmmap(int rnum, bool rd);
+static int procstlset(int rnum, bool rd);
+static int proctrhash(int rnum, bool rd);
+static int procggldh(int rnum, bool rd);
+static int procgglsh(int rnum, bool rd);
+
+
+// main routine
+int main(int argc, char **argv){
+  g_progname = argv[0];
+  if(argc < 2) usage();
+  int rv = 0;
+  if(!strcmp(argv[1], "tcmap")){
+    rv = runtcmap(argc, argv);
+  } else if(!strcmp(argv[1], "tctree")){
+    rv = runtctree(argc, argv);
+  } else if(!strcmp(argv[1], "stlmap")){
+    rv = runstlmap(argc, argv);
+  } else if(!strcmp(argv[1], "stlmmap")){
+    rv = runstlmmap(argc, argv);
+  } else if(!strcmp(argv[1], "stlset")){
+    rv = runstlset(argc, argv);
+  } else if(!strcmp(argv[1], "trhash")){
+    rv = runtrhash(argc, argv);
+  } else if(!strcmp(argv[1], "ggldh")){
+    rv = runggldh(argc, argv);
+  } else if(!strcmp(argv[1], "gglsh")){
+    rv = rungglsh(argc, argv);
+  } else {
+    usage();
+  }
+  return rv;
+}
+
+
+// print the usage and exit
+static void usage(void){
+  fprintf(stderr, "%s: speed checker of map utilities\n", g_progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s tcmap [-rd] rnum\n", g_progname);
+  fprintf(stderr, "  %s tctree [-rd] rnum\n", g_progname);
+  fprintf(stderr, "  %s stlmap [-rd] rnum\n", g_progname);
+  fprintf(stderr, "  %s stlmmap [-rd] rnum\n", g_progname);
+  fprintf(stderr, "  %s stlset [-rd] rnum\n", g_progname);
+  fprintf(stderr, "  %s trhash [-rd] rnum\n", g_progname);
+  fprintf(stderr, "  %s ggldh [-rd] rnum\n", g_progname);
+  fprintf(stderr, "  %s gglsh [-rd] rnum\n", g_progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+// print formatted information string and flush the buffer
+static void iprintf(const char *format, ...){
+  va_list ap;
+  va_start(ap, format);
+  vprintf(format, ap);
+  fflush(stdout);
+  va_end(ap);
+}
+
+
+// parse arguments of tcmap command
+static int runtcmap(int argc, char **argv){
+  char *rstr = NULL;
+  bool rd = false;
+  for(int i = 2; i < argc; i++){
+    if(!rstr && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-rd")){
+        rd = true;
+      } else {
+        usage();
+      }
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!rstr) usage();
+  int rnum = tcatoi(rstr);
+  if(rnum < 1) usage();
+  int rv = proctcmap(rnum, rd);
+  return rv;
+}
+
+
+// parse arguments of tctree command
+static int runtctree(int argc, char **argv){
+  char *rstr = NULL;
+  bool rd = false;
+  for(int i = 2; i < argc; i++){
+    if(!rstr && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-rd")){
+        rd = true;
+      } else {
+        usage();
+      }
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!rstr) usage();
+  int rnum = tcatoi(rstr);
+  if(rnum < 1) usage();
+  int rv = proctctree(rnum, rd);
+  return rv;
+}
+
+
+// parse arguments of stlmap command
+static int runstlmap(int argc, char **argv){
+  char *rstr = NULL;
+  bool rd = false;
+  for(int i = 2; i < argc; i++){
+    if(!rstr && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-rd")){
+        rd = true;
+      } else {
+        usage();
+      }
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!rstr) usage();
+  int rnum = tcatoi(rstr);
+  if(rnum < 1) usage();
+  int rv = procstlmap(rnum, rd);
+  return rv;
+}
+
+
+// parse arguments of stlmmap command
+static int runstlmmap(int argc, char **argv){
+  char *rstr = NULL;
+  bool rd = false;
+  for(int i = 2; i < argc; i++){
+    if(!rstr && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-rd")){
+        rd = true;
+      } else {
+        usage();
+      }
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!rstr) usage();
+  int rnum = tcatoi(rstr);
+  if(rnum < 1) usage();
+  int rv = procstlmmap(rnum, rd);
+  return rv;
+}
+
+
+// parse arguments of stlset command
+static int runstlset(int argc, char **argv){
+  char *rstr = NULL;
+  bool rd = false;
+  for(int i = 2; i < argc; i++){
+    if(!rstr && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-rd")){
+        rd = true;
+      } else {
+        usage();
+      }
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!rstr) usage();
+  int rnum = tcatoi(rstr);
+  if(rnum < 1) usage();
+  int rv = procstlset(rnum, rd);
+  return rv;
+}
+
+
+// parse arguments of trhash command
+static int runtrhash(int argc, char **argv){
+  char *rstr = NULL;
+  bool rd = false;
+  for(int i = 2; i < argc; i++){
+    if(!rstr && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-rd")){
+        rd = true;
+      } else {
+        usage();
+      }
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!rstr) usage();
+  int rnum = tcatoi(rstr);
+  if(rnum < 1) usage();
+  int rv = proctrhash(rnum, rd);
+  return rv;
+}
+
+
+// parse arguments of ggldh command
+static int runggldh(int argc, char **argv){
+  char *rstr = NULL;
+  bool rd = false;
+  for(int i = 2; i < argc; i++){
+    if(!rstr && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-rd")){
+        rd = true;
+      } else {
+        usage();
+      }
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!rstr) usage();
+  int rnum = tcatoi(rstr);
+  if(rnum < 1) usage();
+  int rv = procggldh(rnum, rd);
+  return rv;
+}
+
+
+// parse arguments of gglsh command
+static int rungglsh(int argc, char **argv){
+  char *rstr = NULL;
+  bool rd = false;
+  for(int i = 2; i < argc; i++){
+    if(!rstr && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-rd")){
+        rd = true;
+      } else {
+        usage();
+      }
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!rstr) usage();
+  int rnum = tcatoi(rstr);
+  if(rnum < 1) usage();
+  int rv = procgglsh(rnum, rd);
+  return rv;
+}
+
+
+// perform tcmap command
+static int proctcmap(int rnum, bool rd){
+  iprintf("<Tokyo Cabinet Map Writing Test>\n  rnum=%d  rd=%d\n\n", rnum, rd);
+  double stime = tctime();
+  {
+    TCMAP *mymap = tcmapnew2(rnum + 1);
+    for(int i = 1; i <= rnum; i++){
+      char buf[RECBUFSIZ];
+      int len = sprintf(buf, "%08d", i);
+      tcmapput(mymap, buf, len, buf, len);
+      if(rnum > 250 && i % (rnum / 250) == 0){
+        putchar('.');
+        fflush(stdout);
+        if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+      }
+    }
+    if(rd){
+      double itime = tctime();
+      iprintf("time: %.3f\n", itime - stime);
+      stime = itime;
+      for(int i = 1; i <= rnum; i++){
+        char buf[RECBUFSIZ];
+        int len = sprintf(buf, "%08d", i);
+        int vsiz;
+        const void *vbuf = tcmapget(mymap, buf, len, &vsiz);
+        if(!vbuf){
+          iprintf("not found\n");
+          break;
+        }
+        if(rnum > 250 && i % (rnum / 250) == 0){
+          putchar('.');
+          fflush(stdout);
+          if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+        }
+      }
+    }
+    iprintf("record number: %d\n", tcmaprnum(mymap));
+    tcmapdel(mymap);
+  }
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("ok\n\n");
+  return 0;
+}
+
+
+// perform tctree command
+static int proctctree(int rnum, bool rd){
+  iprintf("<Tokyo Cabinet Tree Writing Test>\n  rnum=%d  rd=%d\n\n", rnum, rd);
+  double stime = tctime();
+  {
+    TCTREE *mytree = tctreenew();
+    for(int i = 1; i <= rnum; i++){
+      char buf[RECBUFSIZ];
+      int len = sprintf(buf, "%08d", i);
+      tctreeput(mytree, buf, len, buf, len);
+      if(rnum > 250 && i % (rnum / 250) == 0){
+        putchar('.');
+        fflush(stdout);
+        if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+      }
+    }
+    if(rd){
+      double itime = tctime();
+      iprintf("time: %.3f\n", itime - stime);
+      stime = itime;
+      for(int i = 1; i <= rnum; i++){
+        char buf[RECBUFSIZ];
+        int len = sprintf(buf, "%08d", i);
+        int vsiz;
+        const void *vbuf = tctreeget(mytree, buf, len, &vsiz);
+        if(!vbuf){
+          iprintf("not found\n");
+          break;
+        }
+        if(rnum > 250 && i % (rnum / 250) == 0){
+          putchar('.');
+          fflush(stdout);
+          if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+        }
+      }
+    }
+    iprintf("record number: %d\n", tctreernum(mytree));
+    tctreedel(mytree);
+  }
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("ok\n\n");
+  return 0;
+}
+
+
+// perform stlmap command
+static int procstlmap(int rnum, bool rd){
+  iprintf("<STL Map Writing Test>\n  rnum=%d  rd=%d\n\n", rnum, rd);
+  double stime = tctime();
+  {
+    stlmap mymap;
+    for(int i = 1; i <= rnum; i++){
+      char buf[RECBUFSIZ];
+      sprintf(buf, "%08d", i);
+      mymap.insert(stlmap::value_type(buf, buf));
+      if(rnum > 250 && i % (rnum / 250) == 0){
+        putchar('.');
+        fflush(stdout);
+        if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+      }
+    }
+    if(rd){
+      double itime = tctime();
+      iprintf("time: %.3f\n", itime - stime);
+      stime = itime;
+      for(int i = 1; i <= rnum; i++){
+        char buf[RECBUFSIZ];
+        sprintf(buf, "%08d", i);
+        stlmap::const_iterator it = mymap.find(buf);
+        if(it == mymap.end()){
+          iprintf("not found\n");
+          break;
+        }
+        if(rnum > 250 && i % (rnum / 250) == 0){
+          putchar('.');
+          fflush(stdout);
+          if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+        }
+      }
+    }
+    iprintf("record number: %d\n", mymap.size());
+  }
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("ok\n\n");
+  return 0;
+}
+
+
+// perform multimap command
+static int procstlmmap(int rnum, bool rd){
+  iprintf("<STL Multi Map Writing Test>\n  rnum=%d  rd=%d\n\n", rnum, rd);
+  double stime = tctime();
+  {
+    stlmmap mymap;
+    for(int i = 1; i <= rnum; i++){
+      char buf[RECBUFSIZ];
+      sprintf(buf, "%08d", i);
+      mymap.insert(stlmmap::value_type(buf, buf));
+      if(rnum > 250 && i % (rnum / 250) == 0){
+        putchar('.');
+        fflush(stdout);
+        if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+      }
+    }
+    if(rd){
+      double itime = tctime();
+      iprintf("time: %.3f\n", itime - stime);
+      stime = itime;
+      for(int i = 1; i <= rnum; i++){
+        char buf[RECBUFSIZ];
+        sprintf(buf, "%08d", i);
+        stlmmap::const_iterator it = mymap.find(buf);
+        if(it == mymap.end()){
+          iprintf("not found\n");
+          break;
+        }
+        if(rnum > 250 && i % (rnum / 250) == 0){
+          putchar('.');
+          fflush(stdout);
+          if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+        }
+      }
+    }
+    iprintf("record number: %d\n", mymap.size());
+  }
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("ok\n\n");
+  return 0;
+}
+
+
+// perform stlset command
+static int procstlset(int rnum, bool rd){
+  iprintf("<STL Set Writing Test>\n  rnum=%d  rd=%d\n\n", rnum, rd);
+  double stime = tctime();
+  {
+    stlset mymap;
+    for(int i = 1; i <= rnum; i++){
+      char buf[RECBUFSIZ];
+      sprintf(buf, "%08d", i);
+      mymap.insert(stlset::value_type(buf));
+      if(rnum > 250 && i % (rnum / 250) == 0){
+        putchar('.');
+        fflush(stdout);
+        if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+      }
+    }
+    if(rd){
+      double itime = tctime();
+      iprintf("time: %.3f\n", itime - stime);
+      stime = itime;
+      for(int i = 1; i <= rnum; i++){
+        char buf[RECBUFSIZ];
+        sprintf(buf, "%08d", i);
+        stlset::const_iterator it = mymap.find(buf);
+        if(it == mymap.end()){
+          iprintf("not found\n");
+          break;
+        }
+        if(rnum > 250 && i % (rnum / 250) == 0){
+          putchar('.');
+          fflush(stdout);
+          if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+        }
+      }
+    }
+    iprintf("record number: %d\n", mymap.size());
+  }
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("ok\n\n");
+  return 0;
+}
+
+
+// perform trhash command
+static int proctrhash(int rnum, bool rd){
+  iprintf("<TR1 Hash Writing Test>\n  rnum=%d  rd=%d\n\n", rnum, rd);
+  double stime = tctime();
+  {
+    trhash mymap;
+    mymap.rehash(rnum + 1);
+    mymap.max_load_factor(1.0);
+    for(int i = 1; i <= rnum; i++){
+      char buf[RECBUFSIZ];
+      sprintf(buf, "%08d", i);
+      mymap.insert(trhash::value_type(buf, buf));
+      if(rnum > 250 && i % (rnum / 250) == 0){
+        putchar('.');
+        fflush(stdout);
+        if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+      }
+    }
+    if(rd){
+      double itime = tctime();
+      iprintf("time: %.3f\n", itime - stime);
+      stime = itime;
+      for(int i = 1; i <= rnum; i++){
+        char buf[RECBUFSIZ];
+        sprintf(buf, "%08d", i);
+        trhash::const_iterator it = mymap.find(buf);
+        if(it == mymap.end()){
+          iprintf("not found\n");
+          break;
+        }
+        if(rnum > 250 && i % (rnum / 250) == 0){
+          putchar('.');
+          fflush(stdout);
+          if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+        }
+      }
+    }
+    iprintf("record number: %d\n", mymap.size());
+  }
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("ok\n\n");
+  return 0;
+}
+
+
+// perform ggldh command
+static int procggldh(int rnum, bool rd){
+  iprintf("<Google Dense Hash Writing Test>\n  rnum=%d  rd=%d\n\n", rnum, rd);
+  double stime = tctime();
+  {
+    ggldh mymap;
+    mymap.set_empty_key("");
+    mymap.resize(rnum + 1);
+    for(int i = 1; i <= rnum; i++){
+      char buf[RECBUFSIZ];
+      sprintf(buf, "%08d", i);
+      mymap.insert(ggldh::value_type(buf, buf));
+      if(rnum > 250 && i % (rnum / 250) == 0){
+        putchar('.');
+        fflush(stdout);
+        if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+      }
+    }
+    if(rd){
+      double itime = tctime();
+      iprintf("time: %.3f\n", itime - stime);
+      stime = itime;
+      for(int i = 1; i <= rnum; i++){
+        char buf[RECBUFSIZ];
+        sprintf(buf, "%08d", i);
+        ggldh::const_iterator it = mymap.find(buf);
+        if(it == mymap.end()){
+          iprintf("not found\n");
+          break;
+        }
+        if(rnum > 250 && i % (rnum / 250) == 0){
+          putchar('.');
+          fflush(stdout);
+          if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+        }
+      }
+    }
+    iprintf("record number: %d\n", mymap.size());
+  }
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("ok\n\n");
+  return 0;
+}
+
+
+// perform gglsh command
+static int procgglsh(int rnum, bool rd){
+  iprintf("<Google Sparse Hash Writing Test>\n  rnum=%d  rd=%d\n\n", rnum, rd);
+  double stime = tctime();
+  {
+    gglsh mymap;
+    mymap.resize(rnum + 1);
+    for(int i = 1; i <= rnum; i++){
+      char buf[RECBUFSIZ];
+      sprintf(buf, "%08d", i);
+      mymap.insert(gglsh::value_type(buf, buf));
+      if(rnum > 250 && i % (rnum / 250) == 0){
+        putchar('.');
+        fflush(stdout);
+        if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+      }
+    }
+    if(rd){
+      double itime = tctime();
+      iprintf("time: %.3f\n", itime - stime);
+      stime = itime;
+      for(int i = 1; i <= rnum; i++){
+        char buf[RECBUFSIZ];
+        sprintf(buf, "%08d", i);
+        gglsh::const_iterator it = mymap.find(buf);
+        if(it == mymap.end()){
+          iprintf("not found\n");
+          break;
+        }
+        if(rnum > 250 && i % (rnum / 250) == 0){
+          putchar('.');
+          fflush(stdout);
+          if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+        }
+      }
+    }
+    iprintf("record number: %d\n", mymap.size());
+  }
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("ok\n\n");
+  return 0;
+}
+
+
+
+// END OF FILE
diff --git a/tcejdb/bros/ndbmtest.c b/tcejdb/bros/ndbmtest.c
new file mode 100644 (file)
index 0000000..5fb2644
--- /dev/null
@@ -0,0 +1,204 @@
+/*************************************************************************************************
+ * Writing test of New Database Manager
+ *************************************************************************************************/
+
+
+#include <ndbm.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#undef TRUE
+#define TRUE           1                 /* boolean true */
+#undef FALSE
+#define FALSE          0                 /* boolean false */
+
+#define RECBUFSIZ      32                /* buffer for records */
+
+
+/* global variables */
+const char *progname;                    /* program name */
+int showprgr;                            /* whether to show progression */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+int runwrite(int argc, char **argv);
+int runread(int argc, char **argv);
+int dowrite(char *name, int rnum);
+int doread(char *name, int rnum);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  int rv;
+  progname = argv[0];
+  showprgr = TRUE;
+  if(getenv("HIDEPRGR")) showprgr = FALSE;
+  if(argc < 2) usage();
+  rv = 0;
+  if(!strcmp(argv[1], "write")){
+    rv = runwrite(argc, argv);
+  } else if(!strcmp(argv[1], "read")){
+    rv = runread(argc, argv);
+  } else {
+    usage();
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+  fprintf(stderr, "%s: test cases for New Database Manager\n", progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s write name rnum\n", progname);
+  fprintf(stderr, "  %s read name rnum\n", progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* parse arguments of write command */
+int runwrite(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = dowrite(name, rnum);
+  return rv;
+}
+
+
+/* parse arguments of read command */
+int runread(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = doread(name, rnum);
+  return rv;
+}
+
+
+/* perform write command */
+int dowrite(char *name, int rnum){
+  DBM *db;
+  datum key, content;
+  int i, err, len;
+  char buf[RECBUFSIZ];
+  if(showprgr) printf("<Writing Test>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  if(!(db = dbm_open(name, O_RDWR | O_CREAT | O_TRUNC, 00644))){
+    fprintf(stderr, "dbm_open failed\n");
+    return 1;
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    len = sprintf(buf, "%08d", i);
+    key.dptr = buf;
+    key.dsize = len;
+    content.dptr = buf;
+    content.dsize = len;
+    /* store a record */
+    if(dbm_store(db, key, content, DBM_REPLACE) < 0){
+      fprintf(stderr, "dbm_store failed\n");
+      err = TRUE;
+      break;
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  dbm_close(db);
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+/* perform read command */
+int doread(char *name, int rnum){
+  DBM *db;
+  datum key, content;
+  int i, err, len;
+  char buf[RECBUFSIZ];
+  if(showprgr) printf("<Reading Test>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  if(!(db = dbm_open(name, O_RDONLY, 00644))){
+    fprintf(stderr, "dbm_open failed\n");
+    return 1;
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* retrieve a record */
+    len = sprintf(buf, "%08d", i);
+    key.dptr = buf;
+    key.dsize = len;
+    content = dbm_fetch(db, key);
+    if(!content.dptr){
+      fprintf(stderr, "dbm_fetch failed\n");
+      err = TRUE;
+      break;
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  dbm_close(db);
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/tcejdb/bros/qdbmtest.c b/tcejdb/bros/qdbmtest.c
new file mode 100644 (file)
index 0000000..f89fc04
--- /dev/null
@@ -0,0 +1,375 @@
+/*************************************************************************************************
+ * Writing test of Quick Database Manager
+ *************************************************************************************************/
+
+
+#include <depot.h>
+#include <cabin.h>
+#include <villa.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#undef TRUE
+#define TRUE           1                 /* boolean true */
+#undef FALSE
+#define FALSE          0                 /* boolean false */
+
+#define RECBUFSIZ      32                /* buffer for records */
+
+
+/* global variables */
+const char *progname;                    /* program name */
+int showprgr;                            /* whether to show progression */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+int runwrite(int argc, char **argv);
+int runread(int argc, char **argv);
+int runbtwrite(int argc, char **argv);
+int runbtread(int argc, char **argv);
+int myrand(void);
+int dowrite(char *name, int rnum);
+int doread(char *name, int rnum);
+int dobtwrite(char *name, int rnum, int rnd);
+int dobtread(char *name, int rnum, int rnd);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  int rv;
+  progname = argv[0];
+  showprgr = TRUE;
+  if(getenv("HIDEPRGR")) showprgr = FALSE;
+  srand48(1978);
+  if(argc < 2) usage();
+  rv = 0;
+  if(!strcmp(argv[1], "write")){
+    rv = runwrite(argc, argv);
+  } else if(!strcmp(argv[1], "read")){
+    rv = runread(argc, argv);
+  } else if(!strcmp(argv[1], "btwrite")){
+    rv = runbtwrite(argc, argv);
+  } else if(!strcmp(argv[1], "btread")){
+    rv = runbtread(argc, argv);
+  } else {
+    usage();
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+  fprintf(stderr, "%s: test cases for Quick Database Manager\n", progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s write name rnum\n", progname);
+  fprintf(stderr, "  %s read name rnum\n", progname);
+  fprintf(stderr, "  %s btwrite [-rnd] name rnum\n", progname);
+  fprintf(stderr, "  %s btread [-rnd] name rnum\n", progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* parse arguments of write command */
+int runwrite(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = dowrite(name, rnum);
+  return rv;
+}
+
+
+/* parse arguments of read command */
+int runread(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = doread(name, rnum);
+  return rv;
+}
+
+
+/* parse arguments of btwrite command */
+int runbtwrite(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rnd, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  rnd = FALSE;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      if(!name && !strcmp(argv[i], "-rnd")){
+        rnd = TRUE;
+      } else {
+        usage();
+      }
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = dobtwrite(name, rnum, rnd);
+  return rv;
+}
+
+
+/* parse arguments of btread command */
+int runbtread(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rnd, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  rnd = FALSE;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      if(!name && !strcmp(argv[i], "-rnd")){
+        rnd = TRUE;
+      } else {
+        usage();
+      }
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = dobtread(name, rnum, rnd);
+  return rv;
+}
+
+
+/* pseudo random number generator */
+int myrand(void){
+  static int cnt = 0;
+  return (lrand48() + cnt++) & 0x7FFFFFFF;
+}
+
+
+/* perform write command */
+int dowrite(char *name, int rnum){
+  DEPOT *depot;
+  int i, err, len;
+  char buf[RECBUFSIZ];
+  if(showprgr) printf("<Writing Test of Hash>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  if(!(depot = dpopen(name, DP_OWRITER | DP_OCREAT | DP_OTRUNC, rnum * 3))){
+    fprintf(stderr, "dpopen failed\n");
+    return 1;
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* store a record */
+    len = sprintf(buf, "%08d", i);
+    if(!dpput(depot, buf, len, buf, len, DP_DOVER)){
+      fprintf(stderr, "dpput failed\n");
+      err = TRUE;
+      break;
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  if(!dpclose(depot)){
+    fprintf(stderr, "dpclose failed\n");
+    return 1;
+  }
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+/* perform read command */
+int doread(char *name, int rnum){
+  DEPOT *depot;
+  int i, err, len;
+  char buf[RECBUFSIZ], vbuf[RECBUFSIZ];
+  if(showprgr) printf("<Reading Test of Hash>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  if(!(depot = dpopen(name, DP_OREADER, -1))){
+    fprintf(stderr, "dpopen failed\n");
+    return 1;
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* store a record */
+    len = sprintf(buf, "%08d", i);
+    if(dpgetwb(depot, buf, len, 0, RECBUFSIZ, vbuf) == -1){
+      fprintf(stderr, "dpget failed\n");
+      err = TRUE;
+      break;
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  if(!dpclose(depot)){
+    fprintf(stderr, "dpclose failed\n");
+    return 1;
+  }
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+/* perform btwrite command */
+int dobtwrite(char *name, int rnum, int rnd){
+  VILLA *villa;
+  int i, err, len;
+  char buf[RECBUFSIZ];
+  if(showprgr) printf("<Writing Test of B+ Tree>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  if(!(villa = vlopen(name, VL_OWRITER | VL_OCREAT | VL_OTRUNC, VL_CMPLEX))){
+    fprintf(stderr, "vlopen failed\n");
+    return 1;
+  }
+  if(rnd){
+    vlsettuning(villa, 77, 256, rnum / 77, -1);
+  } else {
+    vlsettuning(villa, 101, 256, 16, 16);
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* store a record */
+    len = sprintf(buf, "%08d", rnd ? myrand() % rnum + 1 : i);
+    if(!vlput(villa, buf, len, buf, len, VL_DOVER)){
+      fprintf(stderr, "vlput failed\n");
+      err = TRUE;
+      break;
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  if(!vlclose(villa)){
+    fprintf(stderr, "vlclose failed\n");
+    return 1;
+  }
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+/* perform btread command */
+int dobtread(char *name, int rnum, int rnd){
+  VILLA *villa;
+  int i, err, len;
+  const char *val;
+  char buf[RECBUFSIZ];
+  if(showprgr) printf("<Reading Test of B+ Tree>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  if(!(villa = vlopen(name, VL_OREADER, VL_CMPLEX))){
+    fprintf(stderr, "vlopen failed\n");
+    return 1;
+  }
+  if(rnd){
+    vlsettuning(villa, 37, 200, rnum / 45, 512);
+  } else {
+    vlsettuning(villa, 101, 256, 16, 16);
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* store a record */
+    len = sprintf(buf, "%08d", rnd ? myrand() % rnum + 1 : i);
+    if(!(val = vlgetcache(villa, buf, len, NULL))){
+      fprintf(stderr, "vlget failed\n");
+      err = TRUE;
+      break;
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  if(!vlclose(villa)){
+    fprintf(stderr, "vlclose failed\n");
+    return 1;
+  }
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/tcejdb/bros/reporter b/tcejdb/bros/reporter
new file mode 100755 (executable)
index 0000000..04d3478
--- /dev/null
@@ -0,0 +1,141 @@
+#! /usr/bin/perl
+
+#================================================================
+# reporter
+# Measure elapsed time and database size of DBM brothers
+#================================================================
+
+use strict;
+use warnings;
+use Time::HiRes qw(gettimeofday);
+
+use constant {
+    RECNUM => 1000000,
+    TESTCOUNT => 20,
+    REMOVETOP => 2,
+    REMOVEBOTTOM => 8,
+};
+
+my @commands = (
+                './tctest write casket.tch ' . RECNUM,
+                './tctest read casket.tch ' . RECNUM,
+                './qdbmtest write casket.qdbh ' . RECNUM,
+                './qdbmtest read casket.qdbh ' . RECNUM,
+                './ndbmtest write casket.ndbh ' . RECNUM,
+                './ndbmtest read casket.ndbh ' . RECNUM,
+                './sdbmtest write casket.sdbh ' . RECNUM,
+                './sdbmtest read casket.sdbh ' . RECNUM,
+                './gdbmtest write casket.gdbh ' . RECNUM,
+                './gdbmtest read casket.gdbh ' . RECNUM,
+                './tdbtest write casket.tdbh ' . RECNUM,
+                './tdbtest read casket.tdbh ' . RECNUM,
+                './cdbtest write casket.cdbh ' . RECNUM,
+                './cdbtest read casket.cdbh ' . RECNUM,
+                './bdbtest write casket.bdbh ' . RECNUM,
+                './bdbtest read casket.bdbh ' . RECNUM,
+                './tctest btwrite casket.tcb ' . RECNUM,
+                './tctest btread casket.tcb ' . RECNUM,
+                './tctest btwrite -rnd casket.tcb_r ' . RECNUM,
+                './tctest btread -rnd casket.tcb_r ' . RECNUM,
+                './qdbmtest btwrite casket.qdbb ' . RECNUM,
+                './qdbmtest btread casket.qdbb ' . RECNUM,
+                './qdbmtest btwrite -rnd casket.qdbb_r ' . RECNUM,
+                './qdbmtest btread -rnd casket.qdbb_r ' . RECNUM,
+                './bdbtest btwrite casket.bdbb ' . RECNUM,
+                './bdbtest btread casket.bdbb ' . RECNUM,
+                './bdbtest btwrite -rnd casket.bdbb_r ' . RECNUM,
+                './bdbtest btread -rnd casket.bdbb_r ' . RECNUM,
+                './tctest flwrite casket.tcf ' . RECNUM,
+                './tctest flread casket.tcf ' . RECNUM,
+                );
+
+my @names = (
+             'casket.tch',
+             'casket.qdbh',
+             'casket.ndbh',
+             'casket.sdbh',
+             'casket.gdbh',
+             'casket.tdbh',
+             'casket.cdbh',
+             'casket.bdbh',
+             'casket.tcb',
+             'casket.tcb_r',
+             'casket.qdbb',
+             'casket.qdbb_r',
+             'casket.bdbb',
+             'casket.bdbb_r',
+             'casket.tcf',
+             );
+
+foreach my $name (@names){
+    my @paths = glob("$name*");
+    foreach my $path (@paths){
+        unlink($path);
+    }
+}
+
+my @table;
+foreach my $command (@commands){
+    system('sync ; sync');
+    $ENV{'HIDEPRGR'} = 1;
+    my @result;
+    for(my $i = 0; $i < TESTCOUNT; $i++){
+        my $stime = gettimeofday();
+        system("$command >/dev/null 2>&1");
+        $stime = gettimeofday() - $stime;
+        printf("%s\t%d\t%0.5f\n", $command, $i + 1, $stime);
+        push(@result, $stime);
+    }
+    @result = sort { $a <=> $b } @result;
+    for(my $i = 0; $i < REMOVETOP; $i++){
+        shift(@result);
+    }
+    for(my $i = 0; $i < REMOVEBOTTOM; $i++){
+        pop(@result);
+    }
+    my $sum = 0;
+    foreach my $result (@result){
+        $sum += $result;
+    }
+    my $avg = $sum / scalar(@result);
+    push(@table, [$command, $avg]);
+}
+
+printf("\n\nRESULT\n\n");
+foreach my $row (@table){
+    printf("%s\t%0.5f\n", $$row[0], $$row[1]);
+}
+printf("\n");
+
+my @sizes;
+foreach my $name (@names){
+    my @paths = glob("$name*");
+    my $sum = 0;
+    foreach my $path (@paths){
+        my @sbuf = stat($path);
+        $sum += $sbuf[7];
+    }
+    printf("%s\t%s\n", $name, $sum);
+    push(@sizes, $sum);
+}
+printf("\n");
+
+printf("%s,%.5f,%.5f,%d\n", "TC", $table[0][1], $table[1][1], $sizes[0]);
+printf("%s,%.5f,%.5f,%d\n", "QDBM", $table[2][1], $table[3][1], $sizes[1]);
+printf("%s,%.5f,%.5f,%d\n", "NDBM", $table[4][1], $table[5][1], $sizes[2]);
+printf("%s,%.5f,%.5f,%d\n", "SDBM", $table[6][1], $table[7][1], $sizes[3]);
+printf("%s,%.5f,%.5f,%d\n", "GDBM", $table[8][1], $table[9][1], $sizes[4]);
+printf("%s,%.5f,%.5f,%d\n", "TDB", $table[10][1], $table[11][1], $sizes[5]);
+printf("%s,%.5f,%.5f,%d\n", "CDB", $table[12][1], $table[13][1], $sizes[6]);
+printf("%s,%.5f,%.5f,%d\n", "BDB", $table[14][1], $table[15][1], $sizes[7]);
+printf("%s,%.5f,%.5f,%d\n", "TC-BT-ASC", $table[16][1], $table[17][1], $sizes[8]);
+printf("%s,%.5f,%.5f,%d\n", "TC-BT-RND", $table[18][1], $table[19][1], $sizes[9]);
+printf("%s,%.5f,%.5f,%d\n", "QDBM-BT-ASC", $table[20][1], $table[21][1], $sizes[10]);
+printf("%s,%.5f,%.5f,%d\n", "QDBM-BT-RND", $table[22][1], $table[23][1], $sizes[11]);
+printf("%s,%.5f,%.5f,%d\n", "BDB-BT-ASC", $table[24][1], $table[25][1], $sizes[12]);
+printf("%s,%.5f,%.5f,%d\n", "BDB-BT-RND", $table[26][1], $table[27][1], $sizes[13]);
+printf("%s,%.5f,%.5f,%d\n", "TC-FIXED", $table[28][1], $table[29][1], $sizes[14]);
+
+
+
+# END OF FILE
diff --git a/tcejdb/bros/result.xls b/tcejdb/bros/result.xls
new file mode 100644 (file)
index 0000000..cbd2a43
Binary files /dev/null and b/tcejdb/bros/result.xls differ
diff --git a/tcejdb/bros/sdbmtest.c b/tcejdb/bros/sdbmtest.c
new file mode 100644 (file)
index 0000000..a3bd4fe
--- /dev/null
@@ -0,0 +1,204 @@
+/*************************************************************************************************
+ * Writing test of Substitute Database Manager
+ *************************************************************************************************/
+
+
+#include <sdbm.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#undef TRUE
+#define TRUE           1                 /* boolean true */
+#undef FALSE
+#define FALSE          0                 /* boolean false */
+
+#define RECBUFSIZ      32                /* buffer for records */
+
+
+/* global variables */
+const char *progname;                    /* program name */
+int showprgr;                            /* whether to show progression */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+int runwrite(int argc, char **argv);
+int runread(int argc, char **argv);
+int dowrite(char *name, int rnum);
+int doread(char *name, int rnum);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  int rv;
+  progname = argv[0];
+  showprgr = TRUE;
+  if(getenv("HIDEPRGR")) showprgr = FALSE;
+  if(argc < 2) usage();
+  rv = 0;
+  if(!strcmp(argv[1], "write")){
+    rv = runwrite(argc, argv);
+  } else if(!strcmp(argv[1], "read")){
+    rv = runread(argc, argv);
+  } else {
+    usage();
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+  fprintf(stderr, "%s: test cases for Substitute Database Manager\n", progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s write name rnum\n", progname);
+  fprintf(stderr, "  %s read name rnum\n", progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* parse arguments of write command */
+int runwrite(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = dowrite(name, rnum);
+  return rv;
+}
+
+
+/* parse arguments of read command */
+int runread(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = doread(name, rnum);
+  return rv;
+}
+
+
+/* perform write command */
+int dowrite(char *name, int rnum){
+  DBM *db;
+  datum key, val;
+  int i, err, len;
+  char buf[RECBUFSIZ];
+  if(showprgr) printf("<Writing Test>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  if(!(db = dbm_open(name, O_RDWR | O_CREAT | O_TRUNC, 00644))){
+    fprintf(stderr, "dbm_open failed\n");
+    return 1;
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    len = sprintf(buf, "%08d", i);
+    key.dptr = buf;
+    key.dsize = len;
+    val.dptr = buf;
+    val.dsize = len;
+    /* store a record */
+    if(dbm_store(db, key, val, DBM_REPLACE) < 0){
+      fprintf(stderr, "dbm_store failed\n");
+      err = TRUE;
+      break;
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  dbm_close(db);
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+/* perform read command */
+int doread(char *name, int rnum){
+  DBM *db;
+  datum key, val;
+  int i, err, len;
+  char buf[RECBUFSIZ];
+  if(showprgr) printf("<Reading Test>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  if(!(db = dbm_open(name, O_RDONLY, 00644))){
+    fprintf(stderr, "dbm_open failed\n");
+    return 1;
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* retrieve a record */
+    len = sprintf(buf, "%08d", i);
+    key.dptr = buf;
+    key.dsize = len;
+    val = dbm_fetch(db, key);
+    if(!val.dptr){
+      fprintf(stderr, "dbm_fetch failed\n");
+      err = TRUE;
+      break;
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  dbm_close(db);
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/tcejdb/bros/sqltest.c b/tcejdb/bros/sqltest.c
new file mode 100644 (file)
index 0000000..a6624d3
--- /dev/null
@@ -0,0 +1,404 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sqlite3.h>
+
+#undef TRUE
+#define TRUE           1                 /* boolean true */
+#undef FALSE
+#define FALSE          0                 /* boolean false */
+
+#define RECBUFSIZ      256               /* buffer for records */
+
+
+/* global variables */
+const char *progname;                    /* program name */
+int showprgr;                            /* whether to show progression */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+int runwrite(int argc, char **argv);
+int runread(int argc, char **argv);
+int runtblwrite(int argc, char **argv);
+int runtblread(int argc, char **argv);
+int myrand(void);
+int callback(void *opq, int argc, char **argv, char **colname);
+int dowrite(char *name, int rnum);
+int doread(char *name, int rnum);
+int dotblwrite(char *name, int rnum);
+int dotblread(char *name, int rnum);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  int rv;
+  progname = argv[0];
+  showprgr = TRUE;
+  if(getenv("HIDEPRGR")) showprgr = FALSE;
+  if(argc < 2) usage();
+  rv = 0;
+  if(!strcmp(argv[1], "write")){
+    rv = runwrite(argc, argv);
+  } else if(!strcmp(argv[1], "read")){
+    rv = runread(argc, argv);
+  } else if(!strcmp(argv[1], "tblwrite")){
+    rv = runtblwrite(argc, argv);
+  } else if(!strcmp(argv[1], "tblread")){
+    rv = runtblread(argc, argv);
+  } else {
+    usage();
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+  fprintf(stderr, "%s: test cases for SQLite\n", progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s write name rnum\n", progname);
+  fprintf(stderr, "  %s read name rnum\n", progname);
+  fprintf(stderr, "  %s tblwrite name rnum\n", progname);
+  fprintf(stderr, "  %s tblread name rnum\n", progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* parse arguments of write command */
+int runwrite(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = dowrite(name, rnum);
+  return rv;
+}
+
+
+/* parse arguments of read command */
+int runread(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = doread(name, rnum);
+  return rv;
+}
+
+
+/* parse arguments of tblwrite command */
+int runtblwrite(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = dotblwrite(name, rnum);
+  return rv;
+}
+
+
+/* parse arguments of tblread command */
+int runtblread(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = dotblread(name, rnum);
+  return rv;
+}
+
+
+/* pseudo random number generator */
+int myrand(void){
+  static int cnt = 0;
+  return (lrand48() + cnt++) & 0x7FFFFFFF;
+}
+
+
+/* call back function for select statement */
+int callback(void *opq, int argc, char **argv, char **colname){
+  return 0;
+}
+
+
+/* perform write command */
+int dowrite(char *name, int rnum){
+  sqlite3 *db;
+  char sql[RECBUFSIZ], *errmsg;
+  int i, err;
+  if(showprgr) printf("<Writing Test of Hash>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  unlink(name);
+  if(sqlite3_open(name, &db) != 0){
+    fprintf(stderr, "sqlite3_open failed\n");
+    return 1;
+  }
+  sprintf(sql, "CREATE TABLE tbl ( key TEXT PRIMARY KEY, val TEXT );");
+  if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK){
+    fprintf(stderr, "sqlite3_exec failed: %s\n", errmsg);
+    sqlite3_free(errmsg);
+  }
+  sprintf(sql, "PRAGMA cache_size = %d;", rnum);
+  if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK){
+    fprintf(stderr, "sqlite3_exec failed: %s\n", errmsg);
+    sqlite3_free(errmsg);
+  }
+  sprintf(sql, "BEGIN TRANSACTION;");
+  if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK){
+    fprintf(stderr, "sqlite3_exec failed: %s\n", errmsg);
+    sqlite3_free(errmsg);
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* store a record */
+    sprintf(sql, "INSERT INTO tbl VALUES ( '%08d', '%08d' );", i, i);
+    if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK){
+      fprintf(stderr, "sqlite3_exec failed: %s\n", errmsg);
+      sqlite3_free(errmsg);
+      err = TRUE;
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  sprintf(sql, "END TRANSACTION;");
+  if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK){
+    fprintf(stderr, "sqlite3_exec failed: %s\n", errmsg);
+    sqlite3_free(errmsg);
+  }
+  sqlite3_close(db);
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+/* perform read command */
+int doread(char *name, int rnum){
+  sqlite3 *db;
+  char sql[RECBUFSIZ], *errmsg;
+  int i, err;
+  if(showprgr) printf("<Reading Test of Hash>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  if(sqlite3_open(name, &db) != 0){
+    fprintf(stderr, "sqlite3_open failed\n");
+    return 1;
+  }
+  sprintf(sql, "PRAGMA cache_size = %d;", rnum);
+  if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK){
+    fprintf(stderr, "sqlite3_exec failed: %s\n", errmsg);
+    sqlite3_free(errmsg);
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* store a record */
+    sprintf(sql, "SELECT * FROM tbl WHERE key = '%08d';", i);
+    if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK){
+      fprintf(stderr, "sqlite3_exec failed: %s\n", errmsg);
+      sqlite3_free(errmsg);
+      err = TRUE;
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  sqlite3_close(db);
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+/* perform tblwrite command */
+int dotblwrite(char *name, int rnum){
+  sqlite3 *db;
+  char sql[RECBUFSIZ], *errmsg;
+  int i, err;
+  if(showprgr) printf("<Writing Test of Table>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  unlink(name);
+  if(sqlite3_open(name, &db) != 0){
+    fprintf(stderr, "sqlite3_open failed\n");
+    return 1;
+  }
+  sprintf(sql, "CREATE TABLE tbl ( key INTEGER PRIMARY KEY, s TEXT, n INTEGER,"
+          " t TEXT, f TEXT );");
+  if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK){
+    fprintf(stderr, "sqlite3_exec failed: %s\n", errmsg);
+    sqlite3_free(errmsg);
+  }
+  sprintf(sql, "CREATE INDEX tbl_s ON tbl ( s );");
+  if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK){
+    fprintf(stderr, "sqlite3_exec failed: %s\n", errmsg);
+    sqlite3_free(errmsg);
+  }
+  sprintf(sql, "CREATE INDEX tbl_n ON tbl ( n );");
+  if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK){
+    fprintf(stderr, "sqlite3_exec failed: %s\n", errmsg);
+    sqlite3_free(errmsg);
+  }
+  sprintf(sql, "PRAGMA cache_size = %d;", rnum);
+  if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK){
+    fprintf(stderr, "sqlite3_exec failed: %s\n", errmsg);
+    sqlite3_free(errmsg);
+  }
+  sprintf(sql, "BEGIN TRANSACTION;");
+  if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK){
+    fprintf(stderr, "sqlite3_exec failed: %s\n", errmsg);
+    sqlite3_free(errmsg);
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* store a record */
+    sprintf(sql, "INSERT INTO tbl VALUES ( %d, '%08d', %d, '%08d', '%08d' );",
+            i, i, myrand() % i, i, myrand() % rnum);
+    if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK){
+      fprintf(stderr, "sqlite3_exec failed: %s\n", errmsg);
+      sqlite3_free(errmsg);
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  sprintf(sql, "END TRANSACTION;");
+  if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK){
+    fprintf(stderr, "sqlite3_exec failed: %s\n", errmsg);
+    sqlite3_free(errmsg);
+  }
+  sqlite3_close(db);
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+/* perform tblread command */
+int dotblread(char *name, int rnum){
+  sqlite3 *db;
+  char sql[RECBUFSIZ], *errmsg;
+  int i, err;
+  if(showprgr) printf("<Reading Test of Table>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  if(sqlite3_open(name, &db) != 0){
+    fprintf(stderr, "sqlite3_open failed\n");
+    return 1;
+  }
+  sprintf(sql, "PRAGMA cache_size = %d;", rnum);
+  if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK){
+    fprintf(stderr, "sqlite3_exec failed: %s\n", errmsg);
+    sqlite3_free(errmsg);
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* store a record */
+    sprintf(sql, "SELECT * FROM tbl WHERE key = '%08d';", i);
+    if(sqlite3_exec(db, sql, callback, 0, &errmsg) != SQLITE_OK){
+      fprintf(stderr, "sqlite3_exec failed: %s\n", errmsg);
+      sqlite3_free(errmsg);
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  sqlite3_close(db);
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/tcejdb/bros/tctest.c b/tcejdb/bros/tctest.c
new file mode 100644 (file)
index 0000000..b09e566
--- /dev/null
@@ -0,0 +1,748 @@
+/*************************************************************************************************
+ * Writing test of Tokyo Cabinet
+ *************************************************************************************************/
+
+
+#include <tcutil.h>
+#include <tchdb.h>
+#include <tcbdb.h>
+#include <tcfdb.h>
+#include <tctdb.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#undef TRUE
+#define TRUE           1                 /* boolean true */
+#undef FALSE
+#define FALSE          0                 /* boolean false */
+
+#define RECBUFSIZ      32                /* buffer for records */
+
+
+/* global variables */
+const char *progname;                    /* program name */
+int showprgr;                            /* whether to show progression */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+int runwrite(int argc, char **argv);
+int runread(int argc, char **argv);
+int runbtwrite(int argc, char **argv);
+int runbtread(int argc, char **argv);
+int runflwrite(int argc, char **argv);
+int runflread(int argc, char **argv);
+int runtblwrite(int argc, char **argv);
+int runtblread(int argc, char **argv);
+int myrand(void);
+int dowrite(char *name, int rnum);
+int doread(char *name, int rnum);
+int dobtwrite(char *name, int rnum, int rnd);
+int dobtread(char *name, int rnum, int rnd);
+int doflwrite(char *name, int rnum);
+int doflread(char *name, int rnum);
+int dotblwrite(char *name, int rnum);
+int dotblread(char *name, int rnum);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  int rv;
+  progname = argv[0];
+  showprgr = TRUE;
+  if(getenv("HIDEPRGR")) showprgr = FALSE;
+  srand48(1978);
+  if(argc < 2) usage();
+  rv = 0;
+  if(!strcmp(argv[1], "write")){
+    rv = runwrite(argc, argv);
+  } else if(!strcmp(argv[1], "read")){
+    rv = runread(argc, argv);
+  } else if(!strcmp(argv[1], "btwrite")){
+    rv = runbtwrite(argc, argv);
+  } else if(!strcmp(argv[1], "btread")){
+    rv = runbtread(argc, argv);
+  } else if(!strcmp(argv[1], "flwrite")){
+    rv = runflwrite(argc, argv);
+  } else if(!strcmp(argv[1], "flread")){
+    rv = runflread(argc, argv);
+  } else if(!strcmp(argv[1], "tblwrite")){
+    rv = runtblwrite(argc, argv);
+  } else if(!strcmp(argv[1], "tblread")){
+    rv = runtblread(argc, argv);
+  } else {
+    usage();
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+  fprintf(stderr, "%s: test cases for Tokyo Cabinet\n", progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s write name rnum\n", progname);
+  fprintf(stderr, "  %s read name rnum\n", progname);
+  fprintf(stderr, "  %s btwrite [-rnd] name rnum\n", progname);
+  fprintf(stderr, "  %s btread [-rnd] name rnum\n", progname);
+  fprintf(stderr, "  %s flwrite name rnum\n", progname);
+  fprintf(stderr, "  %s flread name rnum\n", progname);
+  fprintf(stderr, "  %s tblwrite name rnum\n", progname);
+  fprintf(stderr, "  %s tblread name rnum\n", progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* parse arguments of write command */
+int runwrite(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = dowrite(name, rnum);
+  return rv;
+}
+
+
+/* parse arguments of read command */
+int runread(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = doread(name, rnum);
+  return rv;
+}
+
+
+/* parse arguments of btwrite command */
+int runbtwrite(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rnd, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  rnd = FALSE;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      if(!name && !strcmp(argv[i], "-rnd")){
+        rnd = TRUE;
+      } else {
+        usage();
+      }
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = dobtwrite(name, rnum, rnd);
+  return rv;
+}
+
+
+/* parse arguments of btread command */
+int runbtread(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rnd, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  rnd = FALSE;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      if(!name && !strcmp(argv[i], "-rnd")){
+        rnd = TRUE;
+      } else {
+        usage();
+      }
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = dobtread(name, rnum, rnd);
+  return rv;
+}
+
+
+/* parse arguments of flwrite command */
+int runflwrite(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = doflwrite(name, rnum);
+  return rv;
+}
+
+
+/* parse arguments of read command */
+int runflread(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = doflread(name, rnum);
+  return rv;
+}
+
+
+/* parse arguments of tblwrite command */
+int runtblwrite(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rnd, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  rnd = FALSE;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = dotblwrite(name, rnum);
+  return rv;
+}
+
+
+/* parse arguments of tblread command */
+int runtblread(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rnd, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  rnd = FALSE;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = dotblread(name, rnum);
+  return rv;
+}
+
+
+/* pseudo random number generator */
+int myrand(void){
+  static int cnt = 0;
+  return (lrand48() + cnt++) & 0x7FFFFFFF;
+}
+
+
+/* perform write command */
+int dowrite(char *name, int rnum){
+  TCHDB *hdb;
+  int i, err, len;
+  char buf[RECBUFSIZ];
+  if(showprgr) printf("<Writing Test of Hash>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  hdb = tchdbnew();
+  tchdbtune(hdb, rnum * 3, 0, 0, 0);
+  tchdbsetxmsiz(hdb, rnum * 48);
+  if(!tchdbopen(hdb, name, HDBOWRITER | HDBOCREAT | HDBOTRUNC)){
+    fprintf(stderr, "tchdbopen failed\n");
+    tchdbdel(hdb);
+    return 1;
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* store a record */
+    len = sprintf(buf, "%08d", i);
+    if(!tchdbputasync(hdb, buf, len, buf, len)){
+      fprintf(stderr, "tchdbputasync failed\n");
+      err = TRUE;
+      break;
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  if(!tchdbclose(hdb)){
+    fprintf(stderr, "tchdbclose failed\n");
+    tchdbdel(hdb);
+    return 1;
+  }
+  tchdbdel(hdb);
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+/* perform read command */
+int doread(char *name, int rnum){
+  TCHDB *hdb;
+  int i, err, len;
+  char buf[RECBUFSIZ], vbuf[RECBUFSIZ];
+  if(showprgr) printf("<Reading Test of Hash>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  hdb = tchdbnew();
+  tchdbsetxmsiz(hdb, rnum * 48);
+  if(!tchdbopen(hdb, name, HDBOREADER)){
+    fprintf(stderr, "tchdbopen failed\n");
+    tchdbdel(hdb);
+    return 1;
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* store a record */
+    len = sprintf(buf, "%08d", i);
+    if(tchdbget3(hdb, buf, len, vbuf, RECBUFSIZ) == -1){
+      fprintf(stderr, "tchdbget3 failed\n");
+      err = TRUE;
+      break;
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  if(!tchdbclose(hdb)){
+    fprintf(stderr, "tchdbclose failed\n");
+    tchdbdel(hdb);
+    return 1;
+  }
+  tchdbdel(hdb);
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+/* perform btwrite command */
+int dobtwrite(char *name, int rnum, int rnd){
+  TCBDB *bdb;
+  int i, err, len;
+  char buf[RECBUFSIZ];
+  if(showprgr) printf("<Writing Test of B+ Tree>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  bdb = tcbdbnew();
+  if(rnd){
+    tcbdbtune(bdb, 77, 256, -1, 0, 0, 0);
+    tcbdbsetcache(bdb, rnum / 77, -1);
+  } else {
+    tcbdbtune(bdb, 101, 256, -1, 0, 0, 0);
+    tcbdbsetcache(bdb, 256, 256);
+  }
+  tcbdbsetxmsiz(bdb, rnum * 32);
+  if(!tcbdbopen(bdb, name, BDBOWRITER | BDBOCREAT | BDBOTRUNC)){
+    fprintf(stderr, "tcbdbopen failed\n");
+    tcbdbdel(bdb);
+    return 1;
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* store a record */
+    len = sprintf(buf, "%08d", rnd ? myrand() % rnum + 1 : i);
+    if(!tcbdbput(bdb, buf, len, buf, len)){
+      fprintf(stderr, "tcbdbput failed\n");
+      err = TRUE;
+      break;
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  if(!tcbdbclose(bdb)){
+    fprintf(stderr, "tcbdbclose failed\n");
+    tcbdbdel(bdb);
+    return 1;
+  }
+  tcbdbdel(bdb);
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+/* perform btread command */
+int dobtread(char *name, int rnum, int rnd){
+  TCBDB *bdb;
+  int i, err, len, vlen;
+  const char *val;
+  char buf[RECBUFSIZ];
+  if(showprgr) printf("<Reading Test of B+ Tree>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  bdb = tcbdbnew();
+  if(rnd){
+    tcbdbsetcache(bdb, rnum / 77 + 1, -1);
+  } else {
+    tcbdbsetcache(bdb, 256, 256);
+  }
+  tcbdbsetxmsiz(bdb, rnum * 32);
+  if(!tcbdbopen(bdb, name, BDBOREADER)){
+    fprintf(stderr, "tcbdbopen failed\n");
+    tcbdbdel(bdb);
+    return 1;
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* store a record */
+    len = sprintf(buf, "%08d", rnd ? myrand() % rnum + 1 : i);
+    if(!(val = tcbdbget3(bdb, buf, len, &vlen))){
+      fprintf(stderr, "tdbdbget3 failed\n");
+      err = TRUE;
+      break;
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  if(!tcbdbclose(bdb)){
+    fprintf(stderr, "tcbdbclose failed\n");
+    tcbdbdel(bdb);
+    return 1;
+  }
+  tcbdbdel(bdb);
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+/* perform flwrite command */
+int doflwrite(char *name, int rnum){
+  TCFDB *fdb;
+  int i, err, len;
+  char buf[RECBUFSIZ];
+  if(showprgr) printf("<Writing Test of Fixed-Length>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  fdb = tcfdbnew();
+  tcfdbtune(fdb, 8, 1024 + rnum * 9);
+  if(!tcfdbopen(fdb, name, FDBOWRITER | FDBOCREAT | FDBOTRUNC)){
+    fprintf(stderr, "tcfdbopen failed\n");
+    tcfdbdel(fdb);
+    return 1;
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* store a record */
+    len = sprintf(buf, "%08d", i);
+    if(!tcfdbput(fdb, i, buf, len)){
+      fprintf(stderr, "tcfdbput failed\n");
+      err = TRUE;
+      break;
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  if(!tcfdbclose(fdb)){
+    fprintf(stderr, "tcfdbclose failed\n");
+    tcfdbdel(fdb);
+    return 1;
+  }
+  tcfdbdel(fdb);
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+/* perform flread command */
+int doflread(char *name, int rnum){
+  TCFDB *fdb;
+  int i, err;
+  char vbuf[RECBUFSIZ];
+  if(showprgr) printf("<Reading Test of Fixed-length>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  fdb = tcfdbnew();
+  if(!tcfdbopen(fdb, name, FDBOREADER)){
+    fprintf(stderr, "tcfdbopen failed\n");
+    tcfdbdel(fdb);
+    return 1;
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* store a record */
+    if(tcfdbget4(fdb, i, vbuf, RECBUFSIZ) == -1){
+      fprintf(stderr, "tcfdbget4 failed\n");
+      err = TRUE;
+      break;
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  if(!tcfdbclose(fdb)){
+    fprintf(stderr, "tcfdbclose failed\n");
+    tcfdbdel(fdb);
+    return 1;
+  }
+  tcfdbdel(fdb);
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+/* perform tblwrite command */
+int dotblwrite(char *name, int rnum){
+  TCTDB *tdb;
+  int i, err, pksiz, vsiz;
+  char pkbuf[RECBUFSIZ], vbuf[RECBUFSIZ];
+  TCMAP *cols;
+  if(showprgr) printf("<Writing Test of Table>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  tdb = tctdbnew();
+  tctdbtune(tdb, rnum * 3, 0, 0, 0);
+  tctdbsetxmsiz(tdb, rnum * 80);
+  tctdbsetcache(tdb, -1, rnum / 100, -1);
+  if(!tctdbopen(tdb, name, TDBOWRITER | TDBOCREAT | TDBOTRUNC)){
+    fprintf(stderr, "tctdbopen failed\n");
+    tctdbdel(tdb);
+    return 1;
+  }
+  if(!tctdbsetindex(tdb, "s", TDBITLEXICAL)){
+    fprintf(stderr, "tctdbsetindex failed\n");
+    tctdbdel(tdb);
+    return 1;
+  }
+  if(!tctdbsetindex(tdb, "n", TDBITDECIMAL)){
+    fprintf(stderr, "tctdbsetindex failed\n");
+    tctdbdel(tdb);
+    return 1;
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* store a record */
+    pksiz = sprintf(pkbuf, "%d", i);
+    cols = tcmapnew2(7);
+    vsiz = sprintf(vbuf, "%08d", i);
+    tcmapput(cols, "s", 1, vbuf, vsiz);
+    vsiz = sprintf(vbuf, "%08d", myrand() % i);
+    tcmapput(cols, "n", 1, vbuf, vsiz);
+    vsiz = sprintf(vbuf, "%08d", i);
+    tcmapput(cols, "t", 1, vbuf, vsiz);
+    vsiz = sprintf(vbuf, "%08d", myrand() % rnum);
+    tcmapput(cols, "f", 1, vbuf, vsiz);
+    if(!tctdbput(tdb, pkbuf, pksiz, cols)){
+      fprintf(stderr, "tctdbput failed\n");
+      err = TRUE;
+      break;
+    }
+    tcmapdel(cols);
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  if(!tctdbclose(tdb)){
+    fprintf(stderr, "tctdbclose failed\n");
+    tctdbdel(tdb);
+    return 1;
+  }
+  tctdbdel(tdb);
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+/* perform tblread command */
+int dotblread(char *name, int rnum){
+  TCTDB *tdb;
+  int i, j, err, pksiz, rsiz;
+  char pkbuf[RECBUFSIZ];
+  const char *rbuf;
+  TCMAP *cols;
+  TDBQRY *qry;
+  TCLIST *res;
+  if(showprgr) printf("<Reading Test of Table>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  tdb = tctdbnew();
+  tctdbsetxmsiz(tdb, rnum * 80);
+  tctdbsetcache(tdb, -1, rnum / 100, -1);
+  if(!tctdbopen(tdb, name, TDBOREADER)){
+    fprintf(stderr, "tctdbopen failed\n");
+    tctdbdel(tdb);
+    return 1;
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    /* search for a record */
+    pksiz = sprintf(pkbuf, "%08d", i);
+    qry = tctdbqrynew(tdb);
+    tctdbqryaddcond(qry, "s", TDBQCSTREQ, pkbuf);
+    res = tctdbqrysearch(qry);
+    for(j = 0; j < tclistnum(res); j++){
+      rbuf = tclistval(res, j, &rsiz);
+      cols = tctdbget(tdb, rbuf, rsiz);
+      if(cols){
+        tcmapdel(cols);
+      } else {
+        fprintf(stderr, "tctdbget failed\n");
+        err = TRUE;
+        break;
+      }
+    }
+    tclistdel(res);
+    tctdbqrydel(qry);
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  if(!tctdbclose(tdb)){
+    fprintf(stderr, "tctdbclose failed\n");
+    tctdbdel(tdb);
+    return 1;
+  }
+  tctdbdel(tdb);
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/tcejdb/bros/tdbtest.c b/tcejdb/bros/tdbtest.c
new file mode 100644 (file)
index 0000000..49d6ae4
--- /dev/null
@@ -0,0 +1,205 @@
+/*************************************************************************************************
+ * Writing test of Trivial Database
+ *************************************************************************************************/
+
+
+#include <sys/types.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <tdb.h>
+
+#undef TRUE
+#define TRUE           1                 /* boolean true */
+#undef FALSE
+#define FALSE          0                 /* boolean false */
+
+#define RECBUFSIZ      32                /* buffer for records */
+
+
+/* global variables */
+const char *progname;                    /* program name */
+int showprgr;                            /* whether to show progression */
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+void usage(void);
+int runwrite(int argc, char **argv);
+int runread(int argc, char **argv);
+int dowrite(char *name, int rnum);
+int doread(char *name, int rnum);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  int rv;
+  progname = argv[0];
+  showprgr = TRUE;
+  if(getenv("HIDEPRGR")) showprgr = FALSE;
+  if(argc < 2) usage();
+  rv = 0;
+  if(!strcmp(argv[1], "write")){
+    rv = runwrite(argc, argv);
+  } else if(!strcmp(argv[1], "read")){
+    rv = runread(argc, argv);
+  } else {
+    usage();
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+void usage(void){
+  fprintf(stderr, "%s: test cases for Trivial Database\n", progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s write name rnum\n", progname);
+  fprintf(stderr, "  %s read name rnum\n", progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* parse arguments of write command */
+int runwrite(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = dowrite(name, rnum);
+  return rv;
+}
+
+
+/* parse arguments of read command */
+int runread(int argc, char **argv){
+  char *name, *rstr;
+  int i, rnum, rv;
+  name = NULL;
+  rstr = NULL;
+  rnum = 0;
+  for(i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  rnum = atoi(rstr);
+  if(rnum < 1) usage();
+  rv = doread(name, rnum);
+  return rv;
+}
+
+
+/* perform write command */
+int dowrite(char *name, int rnum){
+  TDB_CONTEXT *tdb;
+  TDB_DATA key, record;
+  int i, err, len;
+  char buf[RECBUFSIZ];
+  if(showprgr) printf("<Writing Test>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  if(!(tdb = tdb_open(name, rnum * 2, 0, O_RDWR | O_CREAT | O_TRUNC, 00644))){
+    fprintf(stderr, "tdb_open failed\n");
+    return 1;
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    len = sprintf(buf, "%08d", i);
+    key.dptr = (unsigned char *)buf;
+    key.dsize = len;
+    record.dptr = (unsigned char *)buf;
+    record.dsize = len;
+    /* store a record */
+    if(tdb_store(tdb, key, record, TDB_REPLACE) != 0){
+      fprintf(stderr, "tdb_store failed\n");
+      err = TRUE;
+      break;
+    }
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  tdb_close(tdb);
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+/* perform read command */
+int doread(char *name, int rnum){
+  TDB_CONTEXT *tdb;
+  TDB_DATA key, record;
+  int i, err, len;
+  char buf[RECBUFSIZ];
+  if(showprgr) printf("<Writing Test>\n  name=%s  rnum=%d\n\n", name, rnum);
+  /* open a database */
+  if(!(tdb = tdb_open(name, rnum * 2, 0, O_RDONLY, 00644))){
+    fprintf(stderr, "tdb_open failed\n");
+    return 1;
+  }
+  err = FALSE;
+  /* loop for each record */
+  for(i = 1; i <= rnum; i++){
+    len = sprintf(buf, "%08d", i);
+    key.dptr = (unsigned char *)buf;
+    key.dsize = len;
+    /* retrieve a record */
+    record = tdb_fetch(tdb, key);
+    if(!record.dptr){
+      fprintf(stderr, "tdb_fetch failed\n");
+      err = TRUE;
+      break;
+    }
+    free(record.dptr);
+    /* print progression */
+    if(showprgr && rnum > 250 && i % (rnum / 250) == 0){
+      putchar('.');
+      fflush(stdout);
+      if(i == rnum || i % (rnum / 10) == 0){
+        printf(" (%08d)\n", i);
+        fflush(stdout);
+      }
+    }
+  }
+  /* close the database */
+  tdb_close(tdb);
+  if(showprgr && !err) printf("ok\n\n");
+  return err ? 1 : 0;
+}
+
+
+
+/* END OF FILE */
diff --git a/tcejdb/bson.c b/tcejdb/bson.c
new file mode 100644 (file)
index 0000000..96343a4
--- /dev/null
@@ -0,0 +1,1359 @@
+/* bson.c */
+
+/*    Copyright 2009, 2010 10gen Inc.
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <time.h>
+#include <limits.h>
+#include <assert.h>
+
+#include "bson.h"
+#include "encoding.h"
+#include "myconf.h"
+
+const int initialBufferSize = 128;
+
+#define        MIN(a,b) (((a)<(b))?(a):(b))
+#define        MAX(a,b) (((a)>(b))?(a):(b))
+
+/* only need one of these */
+static const int zero = 0;
+
+/* Custom standard function pointers. */
+void *(*bson_malloc_func)(size_t) = MYMALLOC;
+void *(*bson_realloc_func)(void *, size_t) = MYREALLOC;
+void ( *bson_free_func)(void *) = MYFREE;
+#ifdef R_SAFETY_NET
+bson_printf_func bson_printf;
+#else
+bson_printf_func bson_printf = printf;
+#endif
+bson_fprintf_func bson_fprintf = fprintf;
+bson_sprintf_func bson_sprintf = sprintf;
+
+static int _bson_errprintf(const char *, ...);
+bson_printf_func bson_errprintf = _bson_errprintf;
+
+/* ObjectId fuzz functions. */
+static int ( *oid_fuzz_func)(void) = NULL;
+static int ( *oid_inc_func)(void) = NULL;
+
+/* ----------------------------
+   READING
+   ------------------------------ */
+
+EJDB_EXPORT bson* bson_create(void) {
+    return (bson*) bson_malloc(sizeof (bson));
+}
+
+EJDB_EXPORT void bson_dispose(bson* b) {
+    bson_free(b);
+}
+
+EJDB_EXPORT bson *bson_empty(bson *obj) {
+    static char *data = "\005\0\0\0\0";
+    bson_init_data(obj, data);
+    obj->finished = 1;
+    obj->err = 0;
+    obj->errstr = NULL;
+    obj->stackPos = 0;
+    obj->flags = 0;
+    return obj;
+}
+
+EJDB_EXPORT int bson_copy(bson *out, const bson *in) {
+    if (!out || !in) return BSON_ERROR;
+    if (!in->finished) return BSON_ERROR;
+    bson_init_size(out, bson_size(in));
+    memcpy(out->data, in->data, bson_size(in));
+    out->finished = 1;
+
+    return BSON_OK;
+}
+
+int bson_init_data(bson *b, char *data) {
+    b->data = data;
+    return BSON_OK;
+}
+
+int bson_init_finished_data(bson *b, char *data) {
+    bson_init_data(b, data);
+    b->finished = 1;
+    return BSON_OK;
+}
+
+static void _bson_reset(bson *b) {
+    b->finished = 0;
+    b->stackPos = 0;
+    b->err = 0;
+    b->errstr = NULL;
+    b->flags = 0;
+}
+
+EJDB_EXPORT int bson_size(const bson *b) {
+    int i;
+    if (!b || !b->data)
+        return 0;
+    bson_little_endian32(&i, b->data);
+    return i;
+}
+
+EJDB_EXPORT int bson_buffer_size(const bson *b) {
+    return (b->cur - b->data + 1);
+}
+
+EJDB_EXPORT const char *bson_data(const bson *b) {
+    return (const char *) b->data;
+}
+
+static char hexbyte(char hex) {
+    if (hex >= '0' && hex <= '9')
+        return (hex - '0');
+    else if (hex >= 'A' && hex <= 'F')
+        return (hex - 'A' + 10);
+    else if (hex >= 'a' && hex <= 'f')
+        return (hex - 'a' + 10);
+    else
+        return 0x0;
+}
+
+EJDB_EXPORT void bson_oid_from_string(bson_oid_t *oid, const char *str) {
+    int i;
+    for (i = 0; i < 12; i++) {
+        oid->bytes[i] = (hexbyte(str[2 * i]) << 4) | hexbyte(str[2 * i + 1]);
+    }
+}
+
+EJDB_EXPORT void bson_oid_to_string(const bson_oid_t *oid, char *str) {
+    static const char hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+    int i;
+    for (i = 0; i < 12; i++) {
+        str[2 * i] = hex[(oid->bytes[i] & 0xf0) >> 4];
+        str[2 * i + 1] = hex[ oid->bytes[i] & 0x0f ];
+    }
+    str[24] = '\0';
+}
+
+EJDB_EXPORT void bson_set_oid_fuzz(int ( *func)(void)) {
+    oid_fuzz_func = func;
+}
+
+EJDB_EXPORT void bson_set_oid_inc(int ( *func)(void)) {
+    oid_inc_func = func;
+}
+
+EJDB_EXPORT void bson_oid_gen(bson_oid_t *oid) {
+    static int incr = 0;
+    static int fuzz = 0;
+    int i;
+    int t = time(NULL);
+
+    if (oid_inc_func)
+        i = oid_inc_func();
+    else
+        i = incr++;
+
+    if (!fuzz) {
+        if (oid_fuzz_func)
+            fuzz = oid_fuzz_func();
+        else {
+            srand(t);
+            fuzz = rand();
+        }
+    }
+
+    bson_big_endian32(&oid->ints[0], &t);
+    oid->ints[1] = fuzz;
+    bson_big_endian32(&oid->ints[2], &i);
+}
+
+EJDB_EXPORT time_t bson_oid_generated_time(bson_oid_t *oid) {
+    time_t out;
+    bson_big_endian32(&out, &oid->ints[0]);
+
+    return out;
+}
+
+EJDB_EXPORT void bson_print(FILE *f, const bson *b) {
+    bson_print_raw(f, b->data, 0);
+}
+
+EJDB_EXPORT void bson_print_raw(FILE *f, const char *data, int depth) {
+    bson_iterator i;
+    const char *key;
+    int temp;
+    bson_timestamp_t ts;
+    char oidhex[25];
+    bson scope;
+    bson_iterator_from_buffer(&i, data);
+
+    while (bson_iterator_next(&i)) {
+        bson_type t = bson_iterator_type(&i);
+        if (t == 0)
+            break;
+        key = bson_iterator_key(&i);
+
+        for (temp = 0; temp <= depth; temp++)
+            bson_fprintf(f, "\t");
+        bson_fprintf(f, "%s : %d \t ", key, t);
+        switch (t) {
+            case BSON_DOUBLE:
+                bson_fprintf(f, "%f", bson_iterator_double(&i));
+                break;
+            case BSON_STRING:
+                bson_fprintf(f, "%s", bson_iterator_string(&i));
+                break;
+            case BSON_SYMBOL:
+                bson_fprintf(f, "SYMBOL: %s", bson_iterator_string(&i));
+                break;
+            case BSON_OID:
+                bson_oid_to_string(bson_iterator_oid(&i), oidhex);
+                bson_fprintf(f, "%s", oidhex);
+                break;
+            case BSON_BOOL:
+                bson_fprintf(f, "%s", bson_iterator_bool(&i) ? "true" : "false");
+                break;
+            case BSON_DATE:
+                bson_fprintf(f, "%ld", (long int) bson_iterator_date(&i));
+                break;
+            case BSON_BINDATA:
+                bson_fprintf(f, "BSON_BINDATA");
+                break;
+            case BSON_UNDEFINED:
+                bson_fprintf(f, "BSON_UNDEFINED");
+                break;
+            case BSON_NULL:
+                bson_fprintf(f, "BSON_NULL");
+                break;
+            case BSON_REGEX:
+                bson_fprintf(f, "BSON_REGEX: %s", bson_iterator_regex(&i));
+                break;
+            case BSON_CODE:
+                bson_fprintf(f, "BSON_CODE: %s", bson_iterator_code(&i));
+                break;
+            case BSON_CODEWSCOPE:
+                bson_fprintf(f, "BSON_CODE_W_SCOPE: %s", bson_iterator_code(&i));
+                /* bson_init( &scope ); */ /* review - stepped on by bson_iterator_code_scope? */
+                bson_iterator_code_scope(&i, &scope);
+                bson_fprintf(f, "\n\t SCOPE: ");
+                bson_print(f, &scope);
+                /* bson_destroy( &scope ); */ /* review - causes free error */
+                break;
+            case BSON_INT:
+                bson_fprintf(f, "%d", bson_iterator_int(&i));
+                break;
+            case BSON_LONG:
+                bson_fprintf(f, "%lld", (uint64_t) bson_iterator_long(&i));
+                break;
+            case BSON_TIMESTAMP:
+                ts = bson_iterator_timestamp(&i);
+                bson_fprintf(f, "i: %d, t: %d", ts.i, ts.t);
+                break;
+            case BSON_OBJECT:
+            case BSON_ARRAY:
+                bson_fprintf(f, "\n");
+                bson_print_raw(f, bson_iterator_value(&i), depth + 1);
+                break;
+            default:
+                bson_errprintf("can't print type : %d\n", t);
+        }
+        bson_fprintf(f, "\n");
+    }
+}
+
+/* ----------------------------
+   ITERATOR
+   ------------------------------ */
+
+EJDB_EXPORT bson_iterator* bson_iterator_create(void) {
+    return (bson_iterator*) malloc(sizeof ( bson_iterator));
+}
+
+EJDB_EXPORT void bson_iterator_dispose(bson_iterator* i) {
+    free(i);
+}
+
+EJDB_EXPORT void bson_iterator_init(bson_iterator *i, const bson *b) {
+    i->cur = b->data + 4;
+    i->first = 1;
+}
+
+EJDB_EXPORT void bson_iterator_from_buffer(bson_iterator *i, const char *buffer) {
+    i->cur = buffer + 4;
+    i->first = 1;
+}
+
+EJDB_EXPORT bson_type bson_find(bson_iterator *it, const bson *obj, const char *name) {
+    bson_iterator_init(it, (bson *) obj);
+    while (bson_iterator_next(it)) {
+        if (strcmp(name, bson_iterator_key(it)) == 0)
+            break;
+    }
+    return bson_iterator_type(it);
+}
+
+EJDB_EXPORT bson_type bson_find_from_buffer(bson_iterator *it, const char *buffer, const char *name) {
+    bson_iterator_from_buffer(it, buffer);
+    while (bson_iterator_next(it)) {
+        if (strcmp(name, bson_iterator_key(it)) == 0)
+            break;
+    }
+    return bson_iterator_type(it);
+}
+
+static bson_type bson_find_fieldpath_value_impl(char* pstack, int curr, const char *fpath, int fplen, bson_iterator *it) {
+    int i;
+    int klen = 0;
+    bson_type t;
+    while ((t = bson_iterator_next(it)) != BSON_EOO) {
+        const char* key = bson_iterator_key(it);
+        klen = strlen(key);
+        if (curr + klen > fplen || curr + klen + 1 >= BSON_MAX_FPATH_LEN) {
+            continue;
+        }
+        //PUSH
+        if (curr > 0) { //add leading dot
+            memset(pstack + curr, '.', 1);
+            curr++;
+        }
+        memcpy(pstack + curr, key, klen);
+        curr += klen;
+        for (i = 0; i < curr && i < fplen && pstack[i] == fpath[i]; ++i);
+        if (i == curr && i == fplen) { //Position matched with field path
+            return t;
+        }
+        if (i == curr && i < fplen && (t == BSON_OBJECT || t == BSON_ARRAY)) { //Only prefix and we can go into nested objects
+            bson_iterator sit;
+            bson_iterator_subiterator(it, &sit);
+            bson_type st = bson_find_fieldpath_value_impl(pstack, curr, fpath, fplen, &sit);
+            if (st != BSON_EOO) { //Found in nested
+                *it = sit;
+                return st;
+            }
+        }
+        //POP
+        curr -= klen;
+        if (curr > 0) {
+            curr--; //remove leading dot
+        }
+    }
+    return BSON_EOO;
+}
+
+EJDB_EXPORT bson_type bson_find_fieldpath_value(const char *fpath, bson_iterator *it) {
+    return bson_find_fieldpath_value2(fpath, strlen(fpath), it);
+}
+
+EJDB_EXPORT bson_type bson_find_fieldpath_value2(const char *fpath, int fplen, bson_iterator *it) {
+    if (fplen >= BSON_MAX_FPATH_LEN) {
+        return BSON_EOO; //give up
+    }
+    char pstack[BSON_MAX_FPATH_LEN];
+    return bson_find_fieldpath_value_impl(pstack, 0, fpath, fplen, it);
+}
+
+EJDB_EXPORT bson_bool_t bson_iterator_more(const bson_iterator *i) {
+    return *(i->cur);
+}
+
+EJDB_EXPORT bson_type bson_iterator_next(bson_iterator *i) {
+    int ds;
+
+    if (i->first) {
+        i->first = 0;
+        return (bson_type) (*i->cur);
+    }
+
+    switch (bson_iterator_type(i)) {
+        case BSON_EOO:
+            return BSON_EOO; /* don't advance */
+        case BSON_UNDEFINED:
+        case BSON_NULL:
+            ds = 0;
+            break;
+        case BSON_BOOL:
+            ds = 1;
+            break;
+        case BSON_INT:
+            ds = 4;
+            break;
+        case BSON_LONG:
+        case BSON_DOUBLE:
+        case BSON_TIMESTAMP:
+        case BSON_DATE:
+            ds = 8;
+            break;
+        case BSON_OID:
+            ds = 12;
+            break;
+        case BSON_STRING:
+        case BSON_SYMBOL:
+        case BSON_CODE:
+            ds = 4 + bson_iterator_int_raw(i);
+            break;
+        case BSON_BINDATA:
+            ds = 5 + bson_iterator_int_raw(i);
+            break;
+        case BSON_OBJECT:
+        case BSON_ARRAY:
+        case BSON_CODEWSCOPE:
+            ds = bson_iterator_int_raw(i);
+            break;
+        case BSON_DBREF:
+            ds = 4 + 12 + bson_iterator_int_raw(i);
+            break;
+        case BSON_REGEX:
+        {
+            const char *s = bson_iterator_value(i);
+            const char *p = s;
+            p += strlen(p) + 1;
+            p += strlen(p) + 1;
+            ds = p - s;
+            break;
+        }
+
+        default:
+        {
+            char msg[] = "unknown type: 000000000000";
+            bson_numstr(msg + 14, (unsigned) (i->cur[0]));
+            bson_fatal_msg(0, msg);
+            return 0;
+        }
+    }
+
+    i->cur += 1 + strlen(i->cur + 1) + 1 + ds;
+
+    return (bson_type) (*i->cur);
+}
+
+EJDB_EXPORT bson_type bson_iterator_type(const bson_iterator *i) {
+    return (bson_type) i->cur[0];
+}
+
+EJDB_EXPORT const char *bson_iterator_key(const bson_iterator *i) {
+    return i->cur + 1;
+}
+
+EJDB_EXPORT const char *bson_iterator_value(const bson_iterator *i) {
+    const char *t = i->cur + 1;
+    t += strlen(t) + 1;
+    return t;
+}
+
+/* types */
+
+int bson_iterator_int_raw(const bson_iterator *i) {
+    int out;
+    bson_little_endian32(&out, bson_iterator_value(i));
+    return out;
+}
+
+double bson_iterator_double_raw(const bson_iterator *i) {
+    double out;
+    bson_little_endian64(&out, bson_iterator_value(i));
+    return out;
+}
+
+int64_t bson_iterator_long_raw(const bson_iterator *i) {
+    int64_t out;
+    bson_little_endian64(&out, bson_iterator_value(i));
+    return out;
+}
+
+bson_bool_t bson_iterator_bool_raw(const bson_iterator *i) {
+    return bson_iterator_value(i)[0];
+}
+
+EJDB_EXPORT bson_oid_t *bson_iterator_oid(const bson_iterator *i) {
+    return (bson_oid_t *) bson_iterator_value(i);
+}
+
+EJDB_EXPORT int bson_iterator_int(const bson_iterator *i) {
+    switch (bson_iterator_type(i)) {
+        case BSON_INT:
+            return bson_iterator_int_raw(i);
+        case BSON_LONG:
+            return bson_iterator_long_raw(i);
+        case BSON_DOUBLE:
+            return bson_iterator_double_raw(i);
+        default:
+            return 0;
+    }
+}
+
+EJDB_EXPORT double bson_iterator_double(const bson_iterator *i) {
+    switch (bson_iterator_type(i)) {
+        case BSON_INT:
+            return bson_iterator_int_raw(i);
+        case BSON_LONG:
+            return bson_iterator_long_raw(i);
+        case BSON_DOUBLE:
+            return bson_iterator_double_raw(i);
+        default:
+            return 0;
+    }
+}
+
+EJDB_EXPORT int64_t bson_iterator_long(const bson_iterator *i) {
+    switch (bson_iterator_type(i)) {
+        case BSON_INT:
+            return bson_iterator_int_raw(i);
+        case BSON_LONG:
+            return bson_iterator_long_raw(i);
+        case BSON_DOUBLE:
+            return bson_iterator_double_raw(i);
+        default:
+            return 0;
+    }
+}
+
+static int64_t bson_iterator_long_ext(const bson_iterator *i) {
+    switch (bson_iterator_type(i)) {
+        case BSON_INT:
+            return bson_iterator_int_raw(i);
+        case BSON_LONG:
+        case BSON_DATE:
+        case BSON_TIMESTAMP:
+            return bson_iterator_long_raw(i);
+        case BSON_DOUBLE:
+            return bson_iterator_double_raw(i);
+        default:
+            return 0;
+    }
+}
+
+EJDB_EXPORT bson_timestamp_t bson_iterator_timestamp(const bson_iterator *i) {
+    bson_timestamp_t ts;
+    bson_little_endian32(&(ts.i), bson_iterator_value(i));
+    bson_little_endian32(&(ts.t), bson_iterator_value(i) + 4);
+    return ts;
+}
+
+EJDB_EXPORT int bson_iterator_timestamp_time(const bson_iterator *i) {
+    int time;
+    bson_little_endian32(&time, bson_iterator_value(i) + 4);
+    return time;
+}
+
+EJDB_EXPORT int bson_iterator_timestamp_increment(const bson_iterator *i) {
+    int increment;
+    bson_little_endian32(&increment, bson_iterator_value(i));
+    return increment;
+}
+
+EJDB_EXPORT bson_bool_t bson_iterator_bool(const bson_iterator *i) {
+    switch (bson_iterator_type(i)) {
+        case BSON_BOOL:
+            return bson_iterator_bool_raw(i);
+        case BSON_INT:
+            return bson_iterator_int_raw(i) != 0;
+        case BSON_LONG:
+            return bson_iterator_long_raw(i) != 0;
+        case BSON_DOUBLE:
+            return bson_iterator_double_raw(i) != 0;
+        case BSON_EOO:
+        case BSON_NULL:
+        case BSON_UNDEFINED:
+            return 0;
+        default:
+            return 1;
+    }
+}
+
+EJDB_EXPORT const char *bson_iterator_string(const bson_iterator *i) {
+    switch (bson_iterator_type(i)) {
+        case BSON_STRING:
+        case BSON_SYMBOL:
+            return bson_iterator_value(i) + 4;
+        default:
+            return "";
+    }
+}
+
+int bson_iterator_string_len(const bson_iterator *i) {
+    return bson_iterator_int_raw(i);
+}
+
+EJDB_EXPORT const char *bson_iterator_code(const bson_iterator *i) {
+    switch (bson_iterator_type(i)) {
+        case BSON_STRING:
+        case BSON_CODE:
+            return bson_iterator_value(i) + 4;
+        case BSON_CODEWSCOPE:
+            return bson_iterator_value(i) + 8;
+        default:
+            return NULL;
+    }
+}
+
+EJDB_EXPORT void bson_iterator_code_scope(const bson_iterator *i, bson *scope) {
+    if (bson_iterator_type(i) == BSON_CODEWSCOPE) {
+        int code_len;
+        bson_little_endian32(&code_len, bson_iterator_value(i) + 4);
+        bson_init_data(scope, (void *) (bson_iterator_value(i) + 8 + code_len));
+        _bson_reset(scope);
+        scope->finished = 1;
+    } else {
+        bson_empty(scope);
+    }
+}
+
+EJDB_EXPORT bson_date_t bson_iterator_date(const bson_iterator *i) {
+    return bson_iterator_long_raw(i);
+}
+
+EJDB_EXPORT time_t bson_iterator_time_t(const bson_iterator *i) {
+    return bson_iterator_date(i) / 1000;
+}
+
+EJDB_EXPORT int bson_iterator_bin_len(const bson_iterator *i) {
+    return ( bson_iterator_bin_type(i) == BSON_BIN_BINARY_OLD)
+            ? bson_iterator_int_raw(i) - 4
+            : bson_iterator_int_raw(i);
+}
+
+EJDB_EXPORT char bson_iterator_bin_type(const bson_iterator *i) {
+    return bson_iterator_value(i)[4];
+}
+
+EJDB_EXPORT const char *bson_iterator_bin_data(const bson_iterator *i) {
+    return ( bson_iterator_bin_type(i) == BSON_BIN_BINARY_OLD)
+            ? bson_iterator_value(i) + 9
+            : bson_iterator_value(i) + 5;
+}
+
+EJDB_EXPORT const char *bson_iterator_regex(const bson_iterator *i) {
+    return bson_iterator_value(i);
+}
+
+EJDB_EXPORT const char *bson_iterator_regex_opts(const bson_iterator *i) {
+    const char *p = bson_iterator_value(i);
+    return p + strlen(p) + 1;
+
+}
+
+EJDB_EXPORT void bson_iterator_subobject(const bson_iterator *i, bson *sub) {
+    bson_init_data(sub, (char *) bson_iterator_value(i));
+    _bson_reset(sub);
+    sub->finished = 1;
+}
+
+EJDB_EXPORT void bson_iterator_subiterator(const bson_iterator *i, bson_iterator *sub) {
+    bson_iterator_from_buffer(sub, bson_iterator_value(i));
+}
+
+/* ----------------------------
+   BUILDING
+   ------------------------------ */
+
+static void _bson_init_size(bson *b, int size) {
+    if (size == 0)
+        b->data = NULL;
+    else
+        b->data = (char *) bson_malloc(size);
+    b->dataSize = size;
+    b->cur = b->data + 4;
+    _bson_reset(b);
+}
+
+EJDB_EXPORT void bson_init(bson *b) {
+    _bson_init_size(b, initialBufferSize);
+}
+
+EJDB_EXPORT void bson_init_as_query(bson *b) {
+    bson_init(b);
+    b->flags |= BSON_FLAG_QUERY_MODE;
+}
+
+void bson_init_size(bson *b, int size) {
+    _bson_init_size(b, size);
+}
+
+void bson_append_byte(bson *b, char c) {
+    b->cur[0] = c;
+    b->cur++;
+}
+
+EJDB_EXPORT void bson_append(bson *b, const void *data, int len) {
+    memcpy(b->cur, data, len);
+    b->cur += len;
+}
+
+void bson_append32(bson *b, const void *data) {
+    bson_little_endian32(b->cur, data);
+    b->cur += 4;
+}
+
+void bson_append64(bson *b, const void *data) {
+    bson_little_endian64(b->cur, data);
+    b->cur += 8;
+}
+
+int bson_ensure_space(bson *b, const int bytesNeeded) {
+    int pos = b->cur - b->data;
+    char *orig = b->data;
+    int new_size;
+
+    if (pos + bytesNeeded <= b->dataSize)
+        return BSON_OK;
+
+    new_size = 1.5 * (b->dataSize + bytesNeeded);
+
+    if (new_size < b->dataSize) {
+        if ((b->dataSize + bytesNeeded) < INT_MAX)
+            new_size = INT_MAX;
+        else {
+            b->err = BSON_SIZE_OVERFLOW;
+            return BSON_ERROR;
+        }
+    }
+
+    b->data = bson_realloc(b->data, new_size);
+    if (!b->data)
+        bson_fatal_msg(!!b->data, "realloc() failed");
+
+    b->dataSize = new_size;
+    b->cur += b->data - orig;
+
+    return BSON_OK;
+}
+
+EJDB_EXPORT int bson_finish(bson *b) {
+    int i;
+
+    if (b->err & BSON_NOT_UTF8)
+        return BSON_ERROR;
+
+    if (!b->finished) {
+        if (bson_ensure_space(b, 1) == BSON_ERROR) return BSON_ERROR;
+        bson_append_byte(b, 0);
+        i = b->cur - b->data;
+        bson_little_endian32(b->data, &i);
+        b->finished = 1;
+    }
+
+    return BSON_OK;
+}
+
+EJDB_EXPORT void bson_destroy(bson *b) {
+    if (b) {
+        bson_free(b->data);
+        b->err = 0;
+        b->data = 0;
+        b->cur = 0;
+        b->finished = 1;
+    }
+}
+
+EJDB_EXPORT void bson_del(bson *b) {
+    if (b) {
+        bson_destroy(b);
+        bson_free(b);
+    }
+}
+
+static int bson_append_estart(bson *b, int type, const char *name, const int dataSize) {
+    const int len = strlen(name) + 1;
+
+    if (b->finished) {
+        b->err |= BSON_ALREADY_FINISHED;
+        return BSON_ERROR;
+    }
+
+    if (bson_ensure_space(b, 1 + len + dataSize) == BSON_ERROR) {
+        return BSON_ERROR;
+    }
+
+    if (bson_check_field_name(b, (const char *) name, len - 1,
+            !(b->flags & BSON_FLAG_QUERY_MODE), !(b->flags & BSON_FLAG_QUERY_MODE)) == BSON_ERROR) {
+        bson_builder_error(b);
+        return BSON_ERROR;
+    }
+
+    bson_append_byte(b, (char) type);
+    bson_append(b, name, len);
+    return BSON_OK;
+}
+
+/* ----------------------------
+   BUILDING TYPES
+   ------------------------------ */
+
+EJDB_EXPORT int bson_append_int(bson *b, const char *name, const int i) {
+    if (bson_append_estart(b, BSON_INT, name, 4) == BSON_ERROR)
+        return BSON_ERROR;
+    bson_append32(b, &i);
+    return BSON_OK;
+}
+
+EJDB_EXPORT int bson_append_long(bson *b, const char *name, const int64_t i) {
+    if (bson_append_estart(b, BSON_LONG, name, 8) == BSON_ERROR)
+        return BSON_ERROR;
+    bson_append64(b, &i);
+    return BSON_OK;
+}
+
+EJDB_EXPORT int bson_append_double(bson *b, const char *name, const double d) {
+    if (bson_append_estart(b, BSON_DOUBLE, name, 8) == BSON_ERROR)
+        return BSON_ERROR;
+    bson_append64(b, &d);
+    return BSON_OK;
+}
+
+EJDB_EXPORT int bson_append_bool(bson *b, const char *name, const bson_bool_t i) {
+    if (bson_append_estart(b, BSON_BOOL, name, 1) == BSON_ERROR)
+        return BSON_ERROR;
+    bson_append_byte(b, i != 0);
+    return BSON_OK;
+}
+
+EJDB_EXPORT int bson_append_null(bson *b, const char *name) {
+    if (bson_append_estart(b, BSON_NULL, name, 0) == BSON_ERROR)
+        return BSON_ERROR;
+    return BSON_OK;
+}
+
+EJDB_EXPORT int bson_append_undefined(bson *b, const char *name) {
+    if (bson_append_estart(b, BSON_UNDEFINED, name, 0) == BSON_ERROR)
+        return BSON_ERROR;
+    return BSON_OK;
+}
+
+int bson_append_string_base(bson *b, const char *name,
+        const char *value, int len, bson_type type) {
+
+    int sl = len + 1;
+    if (bson_check_string(b, (const char *) value, sl - 1) == BSON_ERROR)
+        return BSON_ERROR;
+    if (bson_append_estart(b, type, name, 4 + sl) == BSON_ERROR) {
+        return BSON_ERROR;
+    }
+    bson_append32(b, &sl);
+    bson_append(b, value, sl - 1);
+    bson_append(b, "\0", 1);
+    return BSON_OK;
+}
+
+EJDB_EXPORT int bson_append_string(bson *b, const char *name, const char *value) {
+    return bson_append_string_base(b, name, value, strlen(value), BSON_STRING);
+}
+
+EJDB_EXPORT int bson_append_symbol(bson *b, const char *name, const char *value) {
+    return bson_append_string_base(b, name, value, strlen(value), BSON_SYMBOL);
+}
+
+EJDB_EXPORT int bson_append_code(bson *b, const char *name, const char *value) {
+    return bson_append_string_base(b, name, value, strlen(value), BSON_CODE);
+}
+
+EJDB_EXPORT int bson_append_string_n(bson *b, const char *name, const char *value, int len) {
+    return bson_append_string_base(b, name, value, len, BSON_STRING);
+}
+
+EJDB_EXPORT int bson_append_symbol_n(bson *b, const char *name, const char *value, int len) {
+    return bson_append_string_base(b, name, value, len, BSON_SYMBOL);
+}
+
+EJDB_EXPORT int bson_append_code_n(bson *b, const char *name, const char *value, int len) {
+    return bson_append_string_base(b, name, value, len, BSON_CODE);
+}
+
+EJDB_EXPORT int bson_append_code_w_scope_n(bson *b, const char *name,
+        const char *code, int len, const bson *scope) {
+
+    int sl, size;
+    if (!scope) return BSON_ERROR;
+    sl = len + 1;
+    size = 4 + 4 + sl + bson_size(scope);
+    if (bson_append_estart(b, BSON_CODEWSCOPE, name, size) == BSON_ERROR)
+        return BSON_ERROR;
+    bson_append32(b, &size);
+    bson_append32(b, &sl);
+    bson_append(b, code, sl);
+    bson_append(b, scope->data, bson_size(scope));
+    return BSON_OK;
+}
+
+EJDB_EXPORT int bson_append_code_w_scope(bson *b, const char *name, const char *code, const bson *scope) {
+    return bson_append_code_w_scope_n(b, name, code, strlen(code), scope);
+}
+
+EJDB_EXPORT int bson_append_binary(bson *b, const char *name, char type, const char *str, int len) {
+    if (type == BSON_BIN_BINARY_OLD) {
+        int subtwolen = len + 4;
+        if (bson_append_estart(b, BSON_BINDATA, name, 4 + 1 + 4 + len) == BSON_ERROR)
+            return BSON_ERROR;
+        bson_append32(b, &subtwolen);
+        bson_append_byte(b, type);
+        bson_append32(b, &len);
+        bson_append(b, str, len);
+    } else {
+        if (bson_append_estart(b, BSON_BINDATA, name, 4 + 1 + len) == BSON_ERROR)
+            return BSON_ERROR;
+        bson_append32(b, &len);
+        bson_append_byte(b, type);
+        bson_append(b, str, len);
+    }
+    return BSON_OK;
+}
+
+EJDB_EXPORT int bson_append_oid(bson *b, const char *name, const bson_oid_t *oid) {
+    if (bson_append_estart(b, BSON_OID, name, 12) == BSON_ERROR)
+        return BSON_ERROR;
+    bson_append(b, oid, 12);
+    return BSON_OK;
+}
+
+EJDB_EXPORT int bson_append_new_oid(bson *b, const char *name) {
+    bson_oid_t oid;
+    bson_oid_gen(&oid);
+    return bson_append_oid(b, name, &oid);
+}
+
+EJDB_EXPORT int bson_append_regex(bson *b, const char *name, const char *pattern, const char *opts) {
+    const int plen = strlen(pattern) + 1;
+    const int olen = strlen(opts) + 1;
+    if (bson_append_estart(b, BSON_REGEX, name, plen + olen) == BSON_ERROR)
+        return BSON_ERROR;
+    if (bson_check_string(b, pattern, plen - 1) == BSON_ERROR)
+        return BSON_ERROR;
+    bson_append(b, pattern, plen);
+    bson_append(b, opts, olen);
+    return BSON_OK;
+}
+
+EJDB_EXPORT int bson_append_bson(bson *b, const char *name, const bson *bson) {
+    if (!bson) return BSON_ERROR;
+    if (bson_append_estart(b, BSON_OBJECT, name, bson_size(bson)) == BSON_ERROR)
+        return BSON_ERROR;
+    bson_append(b, bson->data, bson_size(bson));
+    return BSON_OK;
+}
+
+EJDB_EXPORT int bson_append_element(bson *b, const char *name_or_null, const bson_iterator *elem) {
+    bson_iterator next = *elem;
+    int size;
+
+    bson_iterator_next(&next);
+    size = next.cur - elem->cur;
+
+    if (name_or_null == NULL) {
+        if (bson_ensure_space(b, size) == BSON_ERROR)
+            return BSON_ERROR;
+        bson_append(b, elem->cur, size);
+    } else {
+        int data_size = size - 2 - strlen(bson_iterator_key(elem));
+        bson_append_estart(b, elem->cur[0], name_or_null, data_size);
+        bson_append(b, bson_iterator_value(elem), data_size);
+    }
+
+    return BSON_OK;
+}
+
+EJDB_EXPORT int bson_append_timestamp(bson *b, const char *name, bson_timestamp_t *ts) {
+    if (bson_append_estart(b, BSON_TIMESTAMP, name, 8) == BSON_ERROR) return BSON_ERROR;
+
+    bson_append32(b, &(ts->i));
+    bson_append32(b, &(ts->t));
+
+    return BSON_OK;
+}
+
+EJDB_EXPORT int bson_append_timestamp2(bson *b, const char *name, int time, int increment) {
+    if (bson_append_estart(b, BSON_TIMESTAMP, name, 8) == BSON_ERROR) return BSON_ERROR;
+
+    bson_append32(b, &increment);
+    bson_append32(b, &time);
+    return BSON_OK;
+}
+
+EJDB_EXPORT int bson_append_date(bson *b, const char *name, bson_date_t millis) {
+    if (bson_append_estart(b, BSON_DATE, name, 8) == BSON_ERROR) return BSON_ERROR;
+    bson_append64(b, &millis);
+    return BSON_OK;
+}
+
+EJDB_EXPORT int bson_append_time_t(bson *b, const char *name, time_t secs) {
+    return bson_append_date(b, name, (bson_date_t) secs * 1000);
+}
+
+EJDB_EXPORT int bson_append_start_object(bson *b, const char *name) {
+    if (bson_append_estart(b, BSON_OBJECT, name, 5) == BSON_ERROR) return BSON_ERROR;
+    b->stack[ b->stackPos++ ] = b->cur - b->data;
+    bson_append32(b, &zero);
+    return BSON_OK;
+}
+
+EJDB_EXPORT int bson_append_start_array(bson *b, const char *name) {
+    if (bson_append_estart(b, BSON_ARRAY, name, 5) == BSON_ERROR) return BSON_ERROR;
+    b->stack[ b->stackPos++ ] = b->cur - b->data;
+    bson_append32(b, &zero);
+    return BSON_OK;
+}
+
+EJDB_EXPORT int bson_append_finish_object(bson *b) {
+    char *start;
+    int i;
+    if (bson_ensure_space(b, 1) == BSON_ERROR) return BSON_ERROR;
+    bson_append_byte(b, 0);
+
+    start = b->data + b->stack[ --b->stackPos ];
+    i = b->cur - start;
+    bson_little_endian32(start, &i);
+
+    return BSON_OK;
+}
+
+EJDB_EXPORT double bson_int64_to_double(int64_t i64) {
+    return (double) i64;
+}
+
+EJDB_EXPORT int bson_append_finish_array(bson *b) {
+    return bson_append_finish_object(b);
+}
+
+/* Error handling and allocators. */
+
+static bson_err_handler err_handler = NULL;
+
+EJDB_EXPORT bson_err_handler set_bson_err_handler(bson_err_handler func) {
+    bson_err_handler old = err_handler;
+    err_handler = func;
+    return old;
+}
+
+EJDB_EXPORT void bson_free(void *ptr) {
+    bson_free_func(ptr);
+}
+
+EJDB_EXPORT void *bson_malloc(int size) {
+    void *p;
+    p = bson_malloc_func(size);
+    bson_fatal_msg(!!p, "malloc() failed");
+    return p;
+}
+
+void *bson_realloc(void *ptr, int size) {
+    void *p;
+    p = bson_realloc_func(ptr, size);
+    bson_fatal_msg(!!p, "realloc() failed");
+    return p;
+}
+
+int _bson_errprintf(const char *format, ...) {
+    va_list ap;
+    int ret;
+    va_start(ap, format);
+#ifndef R_SAFETY_NET
+    ret = vfprintf(stderr, format, ap);
+#endif
+    va_end(ap);
+
+    return ret;
+}
+
+/**
+ * This method is invoked when a non-fatal bson error is encountered.
+ * Calls the error handler if available.
+ *
+ *  @param
+ */
+void bson_builder_error(bson *b) {
+    if (err_handler)
+        err_handler("BSON error.");
+}
+
+void bson_fatal(int ok) {
+    bson_fatal_msg(ok, "");
+}
+
+void bson_fatal_msg(int ok, const char *msg) {
+    if (ok)
+        return;
+
+    if (err_handler) {
+        err_handler(msg);
+    }
+#ifndef R_SAFETY_NET
+    bson_errprintf("error: %s\n", msg);
+    exit(-5);
+#endif
+}
+
+
+/* Efficiently copy an integer to a string. */
+extern const char bson_numstrs[1000][4];
+
+EJDB_EXPORT void bson_numstr(char *str, long long int i) {
+    if (i < 1000)
+        memcpy(str, bson_numstrs[i], 4);
+    else
+        bson_sprintf(str, "%lld", i);
+}
+
+EJDB_EXPORT int bson_numstrn(char *str, int maxbuf, long long int i) {
+    if (i < 1000 && maxbuf > 4) {
+        memcpy(str, bson_numstrs[i], 4);
+        return strlen(bson_numstrs[i]);
+    } else {
+        return snprintf(str, maxbuf, "%lld", i);
+    }
+}
+
+EJDB_EXPORT void bson_swap_endian64(void *outp, const void *inp) {
+    const char *in = (const char *) inp;
+    char *out = (char *) outp;
+
+    out[0] = in[7];
+    out[1] = in[6];
+    out[2] = in[5];
+    out[3] = in[4];
+    out[4] = in[3];
+    out[5] = in[2];
+    out[6] = in[1];
+    out[7] = in[0];
+
+}
+
+EJDB_EXPORT void bson_swap_endian32(void *outp, const void *inp) {
+    const char *in = (const char *) inp;
+    char *out = (char *) outp;
+
+    out[0] = in[3];
+    out[1] = in[2];
+    out[2] = in[1];
+    out[3] = in[0];
+}
+
+EJDB_EXPORT void bson_append_field_from_iterator(bson_iterator *from, bson *into) {
+    bson_type t = bson_iterator_type(from);
+    if (t == BSON_EOO) {
+        return;
+    }
+    const char* key = bson_iterator_key(from);
+    switch (t) {
+        case BSON_STRING:
+        case BSON_SYMBOL:
+            bson_append_string(into, key, bson_iterator_string(from));
+            break;
+        case BSON_CODE:
+            bson_append_code(into, key, bson_iterator_code(from));
+            break;
+        case BSON_INT:
+            bson_append_int(into, key, bson_iterator_int_raw(from));
+            break;
+        case BSON_DOUBLE:
+            bson_append_double(into, key, bson_iterator_double_raw(from));
+            break;
+        case BSON_LONG:
+            bson_append_long(into, key, bson_iterator_long_raw(from));
+            break;
+        case BSON_UNDEFINED:
+            bson_append_undefined(into, key);
+            break;
+        case BSON_NULL:
+            bson_append_null(into, key);
+            break;
+        case BSON_BOOL:
+            bson_append_bool(into, key, bson_iterator_bool_raw(from));
+            break;
+        case BSON_TIMESTAMP:
+        {
+            bson_timestamp_t ts = bson_iterator_timestamp(from);
+            bson_append_timestamp(into, key, &ts);
+            break;
+        }
+        case BSON_DATE:
+            bson_append_date(into, key, bson_iterator_date(from));
+            break;
+        case BSON_REGEX:
+            bson_append_regex(into, key, bson_iterator_regex(from), bson_iterator_regex_opts(from));
+            break;
+        case BSON_OID:
+            bson_append_oid(into, key, bson_iterator_oid(from));
+            break;
+        case BSON_OBJECT:
+        {
+            bson_iterator sit;
+            bson_iterator_subiterator(from, &sit);
+            bson_append_start_object(into, key);
+            while (bson_iterator_next(&sit) != BSON_EOO) {
+                bson_append_field_from_iterator(&sit, into);
+            }
+            bson_append_finish_object(into);
+            break;
+        }
+        case BSON_ARRAY:
+        {
+            bson_iterator sit;
+            bson_iterator_subiterator(from, &sit);
+            bson_append_start_array(into, key);
+            while (bson_iterator_next(&sit) != BSON_EOO) {
+                bson_append_field_from_iterator(&sit, into);
+            }
+            bson_append_finish_array(into);
+            break;
+        }
+        case BSON_DBREF:
+        case BSON_CODEWSCOPE:
+            break;
+        default:
+            break;
+    }
+
+}
+
+EJDB_EXPORT int bson_merge(bson *b1, bson *b2, bson_bool_t overwrite, bson *out) {
+    assert(b1 && b2 && out);
+    if (!b1->finished || !b2->finished || out->finished) {
+        return BSON_ERROR;
+    }
+
+    bson_iterator it1, it2;
+    bson_type bt1, bt2;
+
+    bson_iterator_init(&it1, b1);
+    bson_iterator_init(&it2, b2);
+
+    //Append all fields in B1 overwrited by B2
+    while ((bt1 = bson_iterator_next(&it1)) != BSON_EOO) {
+        const char* k1 = bson_iterator_key(&it1);
+        if (overwrite && (bt2 = bson_find(&it2, b2, k1)) != BSON_EOO) {
+            bson_append_field_from_iterator(&it2, out);
+        } else {
+            bson_append_field_from_iterator(&it1, out);
+        }
+    }
+
+    bson_iterator_init(&it1, b1);
+    bson_iterator_init(&it2, b2);
+
+    //Append all fields from B2 missing in B1
+    while ((bt2 = bson_iterator_next(&it2)) != BSON_EOO) {
+        const char* k2 = bson_iterator_key(&it2);
+        if ((bt1 = bson_find(&it1, b1, k2)) == BSON_EOO) {
+            bson_append_field_from_iterator(&it2, out);
+        }
+    }
+
+    return BSON_OK;
+}
+
+EJDB_EXPORT int bson_compare_fpaths(const void *bsdata1, const void *bsdata2, const char *fpath1, int fplen1, const char *fpath2, int fplen2) {
+    assert(bsdata1 && bsdata2 && fpath1 && fpath2);
+    bson_iterator it1, it2;
+    bson_type t1, t2;
+    bson_iterator_from_buffer(&it1, bsdata1);
+    bson_iterator_from_buffer(&it2, bsdata2);
+    t1 = bson_find_fieldpath_value2(fpath1, fplen1, &it1);
+    t2 = bson_find_fieldpath_value2(fpath2, fplen2, &it2);
+    if (t1 == BSON_BOOL || t1 == BSON_EOO || t1 == BSON_NULL || t1 == BSON_UNDEFINED) {
+        int v1 = bson_iterator_bool(&it1);
+        int v2 = bson_iterator_bool(&it2);
+        return (v1 > v2) ? 1 : ((v1 < v2) ? -1 : 0);
+    } else if (t1 == BSON_INT || t1 == BSON_LONG || t1 == BSON_DATE || t1 == BSON_TIMESTAMP) {
+        int64_t v1 = bson_iterator_long_ext(&it1);
+        int64_t v2 = bson_iterator_long_ext(&it2);
+        return (v1 > v2) ? 1 : ((v1 < v2) ? -1 : 0);
+    } else if (t1 == BSON_DOUBLE) {
+        double v1 = bson_iterator_double_raw(&it1);
+        double v2 = bson_iterator_double(&it2);
+        return (v1 > v2) ? 1 : ((v1 < v2) ? -1 : 0);
+    } else if (t1 == BSON_STRING || t1 == BSON_SYMBOL) {
+        const char* v1 = bson_iterator_string(&it1);
+        int l1 = bson_iterator_string_len(&it1);
+        const char* v2 = bson_iterator_string(&it2);
+        int l2 = (t2 == BSON_STRING || t2 == BSON_SYMBOL) ? bson_iterator_string_len(&it2) : strlen(v2);
+        int rv;
+        TCCMPLEXICAL(rv, v1, l1, v2, l2);
+        return rv;
+    } else if (t1 == BSON_BINDATA && t2 == BSON_BINDATA) {
+        int l1 = bson_iterator_bin_len(&it1);
+        int l2 = bson_iterator_bin_len(&it2);
+        return memcmp(bson_iterator_bin_data(&it1), bson_iterator_bin_data(&it2), MIN(l1, l2));
+    }
+    return 0;
+}
+
+EJDB_EXPORT int bson_compare(const void *bsdata1, const void *bsdata2, const char* fpath, int fplen) {
+    return bson_compare_fpaths(bsdata1, bsdata2, fpath, fplen, fpath, fplen);
+}
+
+EJDB_EXPORT int bson_compare_string(const char *cv, const void *bsdata, const char *fpath) {
+    assert(cv && bsdata && fpath);
+    bson *bs1 = bson_create();
+    bson_init(bs1);
+    bson_append_string(bs1, "$", cv);
+    bson_finish(bs1);
+    int res = bson_compare_fpaths(bson_data(bs1), bsdata, "$", 1, fpath, strlen(fpath));
+    bson_del(bs1);
+    return res;
+}
+
+EJDB_EXPORT int bson_compare_long(long cv, const void *bsdata, const char *fpath) {
+    bson *bs1 = bson_create();
+    bson_init(bs1);
+    bson_append_long(bs1, "$", cv);
+    bson_finish(bs1);
+    int res = bson_compare_fpaths(bson_data(bs1), bsdata, "$", 1, fpath, strlen(fpath));
+    bson_del(bs1);
+    return res;
+}
+
+EJDB_EXPORT int bson_compare_double(double cv, const void *bsdata, const char *fpath) {
+    bson *bs1 = bson_create();
+    bson_init(bs1);
+    bson_append_double(bs1, "$", cv);
+    bson_finish(bs1);
+    int res = bson_compare_fpaths(bson_data(bs1), bsdata, "$", 1, fpath, strlen(fpath));
+    bson_del(bs1);
+    return res;
+}
+
+EJDB_EXPORT int bson_compare_bool(bson_bool_t cv, const void *bsdata, const char *fpath) {
+    bson *bs1 = bson_create();
+    bson_init(bs1);
+    bson_append_bool(bs1, "$", cv);
+    bson_finish(bs1);
+    int res = bson_compare_fpaths(bson_data(bs1), bsdata, "$", 1, fpath, strlen(fpath));
+    bson_del(bs1);
+    return res;
+}
+
+EJDB_EXPORT bson* bson_dup(const bson* src) {
+    assert(src);
+    bson *rv = bson_create();
+    const char *raw = bson_data(src);
+    int sz = bson_size(src);
+    bson_init_size(rv, sz);
+    bson_ensure_space(rv, sz - 4);
+    bson_append(rv, raw + 4, sz - (4 + 1/*BSON_EOO*/));
+    bson_finish(rv);
+    return rv;
+}
+
+EJDB_EXPORT bson* bson_create_from_buffer(const void* buf, int bufsz) {
+    assert(buf);
+    assert(bufsz - 4 > 0);
+    bson *rv = bson_create();
+    bson_init_size(rv, bufsz);
+    bson_ensure_space(rv, bufsz - 4);
+    bson_append(rv, buf + 4, bufsz - (4 + 1/*BSON_EOO*/));
+    bson_finish(rv);
+    return rv;
+}
diff --git a/tcejdb/bson.h b/tcejdb/bson.h
new file mode 100644 (file)
index 0000000..2b0fb30
--- /dev/null
@@ -0,0 +1,1089 @@
+/**
+ * @file bson.h
+ * @brief BSON Declarations
+ */
+
+/*    Copyright 2009-2012 10gen Inc.
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#ifndef BSON_H_
+#define BSON_H_
+
+#include "myconf.h"
+
+#ifdef EJDB_BIG_ENDIAN
+#define bson_little_endian64(out, in) ( bson_swap_endian64(out, in) )
+#define bson_little_endian32(out, in) ( bson_swap_endian32(out, in) )
+#define bson_big_endian64(out, in) ( memcpy(out, in, 8) )
+#define bson_big_endian32(out, in) ( memcpy(out, in, 4) )
+#else
+#define bson_little_endian64(out, in) ( memcpy(out, in, 8) )
+#define bson_little_endian32(out, in) ( memcpy(out, in, 4) )
+#define bson_big_endian64(out, in) ( bson_swap_endian64(out, in) )
+#define bson_big_endian32(out, in) ( bson_swap_endian32(out, in) )
+#endif
+
+#define BSON_IS_NUM_TYPE(atype) (atype == BSON_INT || atype == BSON_LONG || atype == BSON_DOUBLE)
+
+EJDB_EXTERN_C_START
+
+#define BSON_OK 0
+#define BSON_ERROR -1
+
+//Maxim field path length
+#define BSON_MAX_FPATH_LEN 255
+
+enum bson_error_t {
+    BSON_SIZE_OVERFLOW = 1 /**< Trying to create a BSON object larger than INT_MAX. */
+};
+
+enum bson_validity_t {
+    BSON_VALID = 0, /**< BSON is valid and UTF-8 compliant. */
+    BSON_NOT_UTF8 = (1 << 1), /**< A key or a string is not valid UTF-8. */
+    BSON_FIELD_HAS_DOT = (1 << 2), /**< Warning: key contains '.' character. */
+    BSON_FIELD_INIT_DOLLAR = (1 << 3), /**< Warning: key starts with '$' character. */
+    BSON_ALREADY_FINISHED = (1 << 4) /**< Trying to modify a finished BSON object. */
+};
+
+enum bson_binary_subtype_t {
+    BSON_BIN_BINARY = 0,
+    BSON_BIN_FUNC = 1,
+    BSON_BIN_BINARY_OLD = 2,
+    BSON_BIN_UUID = 3,
+    BSON_BIN_MD5 = 5,
+    BSON_BIN_USER = 128
+};
+
+enum bson_flags_t {
+    BSON_FLAG_QUERY_MODE = 1
+};
+
+typedef enum {
+    BSON_EOO = 0,
+    BSON_DOUBLE = 1,
+    BSON_STRING = 2,
+    BSON_OBJECT = 3,
+    BSON_ARRAY = 4,
+    BSON_BINDATA = 5,
+    BSON_UNDEFINED = 6,
+    BSON_OID = 7,
+    BSON_BOOL = 8,
+    BSON_DATE = 9,
+    BSON_NULL = 10,
+    BSON_REGEX = 11,
+    BSON_DBREF = 12, /**< Deprecated. */
+    BSON_CODE = 13,
+    BSON_SYMBOL = 14,
+    BSON_CODEWSCOPE = 15,
+    BSON_INT = 16,
+    BSON_TIMESTAMP = 17,
+    BSON_LONG = 18
+} bson_type;
+
+typedef int bson_bool_t;
+
+typedef struct {
+    const char *cur;
+    bson_bool_t first;
+} bson_iterator;
+
+typedef struct {
+    char *data; /**< Pointer to a block of data in this BSON object. */
+    char *cur; /**< Pointer to the current position. */
+    int dataSize; /**< The number of bytes allocated to char *data. */
+    bson_bool_t finished; /**< When finished, the BSON object can no longer be modified. */
+    int stack[32]; /**< A stack used to keep track of nested BSON elements. */
+    int stackPos; /**< Index of current stack position. */
+    int err; /**< Bitfield representing errors or warnings on this buffer */
+    char *errstr; /**< A string representation of the most recent error or warning. */
+    int flags;
+} bson;
+
+#pragma pack(1)
+
+typedef union {
+    char bytes[12];
+    int ints[3];
+} bson_oid_t;
+#pragma pack()
+
+typedef int64_t bson_date_t; /* milliseconds since epoch UTC */
+
+typedef struct {
+    int i; /* increment */
+    int t; /* time in seconds */
+} bson_timestamp_t;
+
+/* ----------------------------
+   READING
+   ------------------------------ */
+
+EJDB_EXPORT bson* bson_create(void);
+EJDB_EXPORT void bson_dispose(bson* b);
+
+/**
+ * Size of a BSON object.
+ *
+ * @param b the BSON object.
+ *
+ * @return the size.
+ */
+EJDB_EXPORT int bson_size(const bson *b);
+EJDB_EXPORT int bson_buffer_size(const bson *b);
+
+/**
+ * Print a string representation of a BSON object.
+ *
+ * @param b the BSON object to print.
+ */
+EJDB_EXPORT void bson_print(FILE *f, const bson *b);
+
+/**
+ * Return a pointer to the raw buffer stored by this bson object.
+ *
+ * @param b a BSON object
+ */
+EJDB_EXPORT const char *bson_data(const bson *b);
+
+/**
+ * Print a string representation of a BSON object.
+ *
+ * @param bson the raw data to print.
+ * @param depth the depth to recurse the object.x
+ */
+EJDB_EXPORT void bson_print_raw(FILE *f, const char *bson, int depth);
+
+/**
+ * Advance a bson_iterator to the named field.
+ *
+ * @param it the bson_iterator to use.
+ * @param obj the BSON object to use.
+ * @param name the name of the field to find.
+ *
+ * @return the type of the found object or BSON_EOO if it is not found.
+ */
+EJDB_EXPORT bson_type bson_find(bson_iterator *it, const bson *obj, const char *name);
+
+EJDB_EXPORT bson_type bson_find_from_buffer(bson_iterator *it, const char *buffer, const char *name);
+
+/**
+ * Advance specified iterator 'it' to field value pointing by 'fieldpath'/
+ * Field path string format: 'field1.nestedfield1.nestedfield.2'.
+ * If specified field not found BSON_EOO will be returned.
+ *
+ * @param fieldpath Path specification to the BSON field.
+ * @param it the bson_iterator to use.
+ * @return the type of the found object or BSON_EOO if it is not found.
+ */
+EJDB_EXPORT bson_type bson_find_fieldpath_value(const char *fieldpath, bson_iterator *it);
+EJDB_EXPORT bson_type bson_find_fieldpath_value2(const char *fpath, int fplen, bson_iterator *it);
+
+
+EJDB_EXPORT bson_iterator* bson_iterator_create(void);
+EJDB_EXPORT void bson_iterator_dispose(bson_iterator*);
+/**
+ * Initialize a bson_iterator.
+ *
+ * @param i the bson_iterator to initialize.
+ * @param bson the BSON object to associate with the iterator.
+ */
+EJDB_EXPORT void bson_iterator_init(bson_iterator *i, const bson *b);
+
+/**
+ * Initialize a bson iterator from a const char* buffer. Note
+ * that this is mostly used internally.
+ *
+ * @param i the bson_iterator to initialize.
+ * @param buffer the buffer to point to.
+ */
+EJDB_EXPORT void bson_iterator_from_buffer(bson_iterator *i, const char *buffer);
+
+/* more returns true for eoo. best to loop with bson_iterator_next(&it) */
+/**
+ * Check to see if the bson_iterator has more data.
+ *
+ * @param i the iterator.
+ *
+ * @return  returns true if there is more data.
+ */
+EJDB_EXPORT bson_bool_t bson_iterator_more(const bson_iterator *i);
+
+/**
+ * Point the iterator at the next BSON object.
+ *
+ * @param i the bson_iterator.
+ *
+ * @return the type of the next BSON object.
+ */
+EJDB_EXPORT bson_type bson_iterator_next(bson_iterator *i);
+
+/**
+ * Get the type of the BSON object currently pointed to by the iterator.
+ *
+ * @param i the bson_iterator
+ *
+ * @return  the type of the current BSON object.
+ */
+EJDB_EXPORT bson_type bson_iterator_type(const bson_iterator *i);
+
+/**
+ * Get the key of the BSON object currently pointed to by the iterator.
+ *
+ * @param i the bson_iterator
+ *
+ * @return the key of the current BSON object.
+ */
+EJDB_EXPORT const char *bson_iterator_key(const bson_iterator *i);
+
+/**
+ * Get the value of the BSON object currently pointed to by the iterator.
+ *
+ * @param i the bson_iterator
+ *
+ * @return  the value of the current BSON object.
+ */
+EJDB_EXPORT const char *bson_iterator_value(const bson_iterator *i);
+
+/* these convert to the right type (return 0 if non-numeric) */
+/**
+ * Get the double value of the BSON object currently pointed to by the
+ * iterator.
+ *
+ * @param i the bson_iterator
+ *
+ * @return  the value of the current BSON object.
+ */
+EJDB_EXPORT double bson_iterator_double(const bson_iterator *i);
+
+/**
+ * Get the int value of the BSON object currently pointed to by the iterator.
+ *
+ * @param i the bson_iterator
+ *
+ * @return  the value of the current BSON object.
+ */
+EJDB_EXPORT int bson_iterator_int(const bson_iterator *i);
+
+/**
+ * Get the long value of the BSON object currently pointed to by the iterator.
+ *
+ * @param i the bson_iterator
+ *
+ * @return the value of the current BSON object.
+ */
+EJDB_EXPORT int64_t bson_iterator_long(const bson_iterator *i);
+
+/* return the bson timestamp as a whole or in parts */
+/**
+ * Get the timestamp value of the BSON object currently pointed to by
+ * the iterator.
+ *
+ * @param i the bson_iterator
+ *
+ * @return the value of the current BSON object.
+ */
+EJDB_EXPORT bson_timestamp_t bson_iterator_timestamp(const bson_iterator *i);
+EJDB_EXPORT int bson_iterator_timestamp_time(const bson_iterator *i);
+EJDB_EXPORT int bson_iterator_timestamp_increment(const bson_iterator *i);
+
+/**
+ * Get the boolean value of the BSON object currently pointed to by
+ * the iterator.
+ *
+ * @param i the bson_iterator
+ *
+ * @return the value of the current BSON object.
+ */
+/* false: boolean false, 0 in any type, or null */
+/* true: anything else (even empty strings and objects) */
+EJDB_EXPORT bson_bool_t bson_iterator_bool(const bson_iterator *i);
+
+/**
+ * Get the double value of the BSON object currently pointed to by the
+ * iterator. Assumes the correct type is used.
+ *
+ * @param i the bson_iterator
+ *
+ * @return the value of the current BSON object.
+ */
+/* these assume you are using the right type */
+double bson_iterator_double_raw(const bson_iterator *i);
+
+/**
+ * Get the int value of the BSON object currently pointed to by the
+ * iterator. Assumes the correct type is used.
+ *
+ * @param i the bson_iterator
+ *
+ * @return the value of the current BSON object.
+ */
+int bson_iterator_int_raw(const bson_iterator *i);
+
+/**
+ * Get the long value of the BSON object currently pointed to by the
+ * iterator. Assumes the correct type is used.
+ *
+ * @param i the bson_iterator
+ *
+ * @return the value of the current BSON object.
+ */
+int64_t bson_iterator_long_raw(const bson_iterator *i);
+
+/**
+ * Get the bson_bool_t value of the BSON object currently pointed to by the
+ * iterator. Assumes the correct type is used.
+ *
+ * @param i the bson_iterator
+ *
+ * @return the value of the current BSON object.
+ */
+bson_bool_t bson_iterator_bool_raw(const bson_iterator *i);
+
+/**
+ * Get the bson_oid_t value of the BSON object currently pointed to by the
+ * iterator.
+ *
+ * @param i the bson_iterator
+ *
+ * @return the value of the current BSON object.
+ */
+EJDB_EXPORT bson_oid_t *bson_iterator_oid(const bson_iterator *i);
+
+/**
+ * Get the string value of the BSON object currently pointed to by the
+ * iterator.
+ *
+ * @param i the bson_iterator
+ *
+ * @return  the value of the current BSON object.
+ */
+/* these can also be used with bson_code and bson_symbol*/
+EJDB_EXPORT const char *bson_iterator_string(const bson_iterator *i);
+
+/**
+ * Get the string length of the BSON object currently pointed to by the
+ * iterator.
+ *
+ * @param i the bson_iterator
+ *
+ * @return the length of the current BSON object.
+ */
+int bson_iterator_string_len(const bson_iterator *i);
+
+/**
+ * Get the code value of the BSON object currently pointed to by the
+ * iterator. Works with bson_code, bson_codewscope, and BSON_STRING
+ * returns NULL for everything else.
+ *
+ * @param i the bson_iterator
+ *
+ * @return the code value of the current BSON object.
+ */
+/* works with bson_code, bson_codewscope, and BSON_STRING */
+/* returns NULL for everything else */
+EJDB_EXPORT const char *bson_iterator_code(const bson_iterator *i);
+
+/**
+ * Calls bson_empty on scope if not a bson_codewscope
+ *
+ * @param i the bson_iterator.
+ * @param scope the bson scope.
+ */
+/* calls bson_empty on scope if not a bson_codewscope */
+EJDB_EXPORT void bson_iterator_code_scope(const bson_iterator *i, bson *scope);
+
+/**
+ * Get the date value of the BSON object currently pointed to by the
+ * iterator.
+ *
+ * @param i the bson_iterator
+ *
+ * @return the date value of the current BSON object.
+ */
+/* both of these only work with bson_date */
+EJDB_EXPORT bson_date_t bson_iterator_date(const bson_iterator *i);
+
+/**
+ * Get the time value of the BSON object currently pointed to by the
+ * iterator.
+ *
+ * @param i the bson_iterator
+ *
+ * @return the time value of the current BSON object.
+ */
+EJDB_EXPORT time_t bson_iterator_time_t(const bson_iterator *i);
+
+/**
+ * Get the length of the BSON binary object currently pointed to by the
+ * iterator.
+ *
+ * @param i the bson_iterator
+ *
+ * @return the length of the current BSON binary object.
+ */
+EJDB_EXPORT int bson_iterator_bin_len(const bson_iterator *i);
+
+/**
+ * Get the type of the BSON binary object currently pointed to by the
+ * iterator.
+ *
+ * @param i the bson_iterator
+ *
+ * @return the type of the current BSON binary object.
+ */
+EJDB_EXPORT char bson_iterator_bin_type(const bson_iterator *i);
+
+/**
+ * Get the value of the BSON binary object currently pointed to by the
+ * iterator.
+ *
+ * @param i the bson_iterator
+ *
+ * @return the value of the current BSON binary object.
+ */
+EJDB_EXPORT const char *bson_iterator_bin_data(const bson_iterator *i);
+
+/**
+ * Get the value of the BSON regex object currently pointed to by the
+ * iterator.
+ *
+ * @param i the bson_iterator
+ *
+ * @return the value of the current BSON regex object.
+ */
+EJDB_EXPORT const char *bson_iterator_regex(const bson_iterator *i);
+
+/**
+ * Get the options of the BSON regex object currently pointed to by the
+ * iterator.
+ *
+ * @param i the bson_iterator.
+ *
+ * @return the options of the current BSON regex object.
+ */
+EJDB_EXPORT const char *bson_iterator_regex_opts(const bson_iterator *i);
+
+/* these work with BSON_OBJECT and BSON_ARRAY */
+/**
+ * Get the BSON subobject currently pointed to by the
+ * iterator.
+ *
+ * @param i the bson_iterator.
+ * @param sub the BSON subobject destination.
+ */
+EJDB_EXPORT void bson_iterator_subobject(const bson_iterator *i, bson *sub);
+
+/**
+ * Get a bson_iterator that on the BSON subobject.
+ *
+ * @param i the bson_iterator.
+ * @param sub the iterator to point at the BSON subobject.
+ */
+EJDB_EXPORT void bson_iterator_subiterator(const bson_iterator *i, bson_iterator *sub);
+
+/* str must be at least 24 hex chars + null byte */
+/**
+ * Create a bson_oid_t from a string.
+ *
+ * @param oid the bson_oid_t destination.
+ * @param str a null terminated string comprised of at least 24 hex chars.
+ */
+EJDB_EXPORT void bson_oid_from_string(bson_oid_t *oid, const char *str);
+
+/**
+ * Create a string representation of the bson_oid_t.
+ *
+ * @param oid the bson_oid_t source.
+ * @param str the string representation destination.
+ */
+EJDB_EXPORT void bson_oid_to_string(const bson_oid_t *oid, char *str);
+
+/**
+ * Create a bson_oid object.
+ *
+ * @param oid the destination for the newly created bson_oid_t.
+ */
+EJDB_EXPORT void bson_oid_gen(bson_oid_t *oid);
+
+/**
+ * Set a function to be used to generate the second four bytes
+ * of an object id.
+ *
+ * @param func a pointer to a function that returns an int.
+ */
+EJDB_EXPORT void bson_set_oid_fuzz(int ( *func)(void));
+
+/**
+ * Set a function to be used to generate the incrementing part
+ * of an object id (last four bytes). If you need thread-safety
+ * in generating object ids, you should set this function.
+ *
+ * @param func a pointer to a function that returns an int.
+ */
+EJDB_EXPORT void bson_set_oid_inc(int ( *func)(void));
+
+/**
+ * Get the time a bson_oid_t was created.
+ *
+ * @param oid the bson_oid_t.
+ */
+EJDB_EXPORT time_t bson_oid_generated_time(bson_oid_t *oid); /* Gives the time the OID was created */
+
+/* ----------------------------
+   BUILDING
+   ------------------------------ */
+
+
+EJDB_EXPORT void bson_append(bson *b, const void *data, int len);
+
+/**
+ *  Initialize a new bson object. If not created
+ *  with bson_new, you must initialize each new bson
+ *  object using this function.
+ *
+ *  @note When finished, you must pass the bson object to
+ *      bson_destroy( ).
+ */
+EJDB_EXPORT void bson_init(bson *b);
+
+
+/**
+ * Intialize a new bson object. In query contruction mode allowing
+ * dot and dollar chars in field names.
+ * @param b
+ */
+EJDB_EXPORT void bson_init_as_query(bson *b);
+
+/**
+ * Initialize a BSON object, and point its data
+ * pointer to the provided char*.
+ *
+ * @param b the BSON object to initialize.
+ * @param data the raw BSON data.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+int bson_init_data(bson *b, char *data);
+int bson_init_finished_data(bson *b, char *data);
+
+/**
+ * Initialize a BSON object, and set its
+ * buffer to the given size.
+ *
+ * @param b the BSON object to initialize.
+ * @param size the initial size of the buffer.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+void bson_init_size(bson *b, int size);
+
+/**
+ * Grow a bson object.
+ *
+ * @param b the bson to grow.
+ * @param bytesNeeded the additional number of bytes needed.
+ *
+ * @return BSON_OK or BSON_ERROR with the bson error object set.
+ *   Exits if allocation fails.
+ */
+int bson_ensure_space(bson *b, const int bytesNeeded);
+
+/**
+ * Finalize a bson object.
+ *
+ * @param b the bson object to finalize.
+ *
+ * @return the standard error code. To deallocate memory,
+ *   call bson_destroy on the bson object.
+ */
+EJDB_EXPORT int bson_finish(bson *b);
+
+/**
+ * Destroy a bson object.
+ *
+ * @param b the bson object to destroy.
+ *
+ */
+EJDB_EXPORT void bson_destroy(bson *b);
+
+EJDB_EXPORT void bson_del(bson *b);
+
+/**
+ * Returns a pointer to a static empty BSON object.
+ *
+ * @param obj the BSON object to initialize.
+ *
+ * @return the empty initialized BSON object.
+ */
+/* returns pointer to static empty bson object */
+EJDB_EXPORT bson *bson_empty(bson *obj);
+
+/**
+ * Make a complete copy of the a BSON object.
+ * The source bson object must be in a finished
+ * state; otherwise, the copy will fail.
+ *
+ * @param out the copy destination BSON object.
+ * @param in the copy source BSON object.
+ */
+EJDB_EXPORT int bson_copy(bson *out, const bson *in); /* puts data in new buffer. NOOP if out==NULL */
+
+/**
+ * Append a previously created bson_oid_t to a bson object.
+ *
+ * @param b the bson to append to.
+ * @param name the key for the bson_oid_t.
+ * @param oid the bson_oid_t to append.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_oid(bson *b, const char *name, const bson_oid_t *oid);
+
+/**
+ * Append a bson_oid_t to a bson.
+ *
+ * @param b the bson to append to.
+ * @param name the key for the bson_oid_t.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_new_oid(bson *b, const char *name);
+
+/**
+ * Append an int to a bson.
+ *
+ * @param b the bson to append to.
+ * @param name the key for the int.
+ * @param i the int to append.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_int(bson *b, const char *name, const int i);
+
+/**
+ * Append an long to a bson.
+ *
+ * @param b the bson to append to.
+ * @param name the key for the long.
+ * @param i the long to append.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_long(bson *b, const char *name, const int64_t i);
+
+/**
+ * Append an double to a bson.
+ *
+ * @param b the bson to append to.
+ * @param name the key for the double.
+ * @param d the double to append.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_double(bson *b, const char *name, const double d);
+
+/**
+ * Append a string to a bson.
+ *
+ * @param b the bson to append to.
+ * @param name the key for the string.
+ * @param str the string to append.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_string(bson *b, const char *name, const char *str);
+
+/**
+ * Append len bytes of a string to a bson.
+ *
+ * @param b the bson to append to.
+ * @param name the key for the string.
+ * @param str the string to append.
+ * @param len the number of bytes from str to append.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_string_n(bson *b, const char *name, const char *str, int len);
+
+/**
+ * Append a symbol to a bson.
+ *
+ * @param b the bson to append to.
+ * @param name the key for the symbol.
+ * @param str the symbol to append.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_symbol(bson *b, const char *name, const char *str);
+
+/**
+ * Append len bytes of a symbol to a bson.
+ *
+ * @param b the bson to append to.
+ * @param name the key for the symbol.
+ * @param str the symbol to append.
+ * @param len the number of bytes from str to append.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_symbol_n(bson *b, const char *name, const char *str, int len);
+
+/**
+ * Append code to a bson.
+ *
+ * @param b the bson to append to.
+ * @param name the key for the code.
+ * @param str the code to append.
+ * @param len the number of bytes from str to append.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_code(bson *b, const char *name, const char *str);
+
+/**
+ * Append len bytes of code to a bson.
+ *
+ * @param b the bson to append to.
+ * @param name the key for the code.
+ * @param str the code to append.
+ * @param len the number of bytes from str to append.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_code_n(bson *b, const char *name, const char *str, int len);
+
+/**
+ * Append code to a bson with scope.
+ *
+ * @param b the bson to append to.
+ * @param name the key for the code.
+ * @param str the string to append.
+ * @param scope a BSON object containing the scope.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_code_w_scope(bson *b, const char *name, const char *code, const bson *scope);
+
+/**
+ * Append len bytes of code to a bson with scope.
+ *
+ * @param b the bson to append to.
+ * @param name the key for the code.
+ * @param str the string to append.
+ * @param len the number of bytes from str to append.
+ * @param scope a BSON object containing the scope.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_code_w_scope_n(bson *b, const char *name, const char *code, int size, const bson *scope);
+
+/**
+ * Append binary data to a bson.
+ *
+ * @param b the bson to append to.
+ * @param name the key for the data.
+ * @param type the binary data type.
+ * @param str the binary data.
+ * @param len the length of the data.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_binary(bson *b, const char *name, char type, const char *str, int len);
+
+/**
+ * Append a bson_bool_t to a bson.
+ *
+ * @param b the bson to append to.
+ * @param name the key for the boolean value.
+ * @param v the bson_bool_t to append.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_bool(bson *b, const char *name, const bson_bool_t v);
+
+/**
+ * Append a null value to a bson.
+ *
+ * @param b the bson to append to.
+ * @param name the key for the null value.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_null(bson *b, const char *name);
+
+/**
+ * Append an undefined value to a bson.
+ *
+ * @param b the bson to append to.
+ * @param name the key for the undefined value.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_undefined(bson *b, const char *name);
+
+/**
+ * Append a regex value to a bson.
+ *
+ * @param b the bson to append to.
+ * @param name the key for the regex value.
+ * @param pattern the regex pattern to append.
+ * @param the regex options.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_regex(bson *b, const char *name, const char *pattern, const char *opts);
+
+/**
+ * Append bson data to a bson.
+ *
+ * @param b the bson to append to.
+ * @param name the key for the bson data.
+ * @param bson the bson object to append.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_bson(bson *b, const char *name, const bson *bson);
+
+/**
+ * Append a BSON element to a bson from the current point of an iterator.
+ *
+ * @param b the bson to append to.
+ * @param name_or_null the key for the BSON element, or NULL.
+ * @param elem the bson_iterator.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_element(bson *b, const char *name_or_null, const bson_iterator *elem);
+
+/**
+ * Append a bson_timestamp_t value to a bson.
+ *
+ * @param b the bson to append to.
+ * @param name the key for the timestampe value.
+ * @param ts the bson_timestamp_t value to append.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_timestamp(bson *b, const char *name, bson_timestamp_t *ts);
+EJDB_EXPORT int bson_append_timestamp2(bson *b, const char *name, int time, int increment);
+
+/* these both append a bson_date */
+/**
+ * Append a bson_date_t value to a bson.
+ *
+ * @param b the bson to append to.
+ * @param name the key for the date value.
+ * @param millis the bson_date_t to append.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_date(bson *b, const char *name, bson_date_t millis);
+
+/**
+ * Append a time_t value to a bson.
+ *
+ * @param b the bson to append to.
+ * @param name the key for the date value.
+ * @param secs the time_t to append.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_time_t(bson *b, const char *name, time_t secs);
+
+/**
+ * Start appending a new object to a bson.
+ *
+ * @param b the bson to append to.
+ * @param name the name of the new object.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_start_object(bson *b, const char *name);
+
+/**
+ * Start appending a new array to a bson.
+ *
+ * @param b the bson to append to.
+ * @param name the name of the new array.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_start_array(bson *b, const char *name);
+
+/**
+ * Finish appending a new object or array to a bson.
+ *
+ * @param b the bson to append to.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_finish_object(bson *b);
+
+/**
+ * Finish appending a new object or array to a bson. This
+ * is simply an alias for bson_append_finish_object.
+ *
+ * @param b the bson to append to.
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_append_finish_array(bson *b);
+
+EJDB_EXPORT void bson_numstr(char *str, long long int i);
+EJDB_EXPORT int bson_numstrn(char *str, int maxbuf, long long int i);
+
+//void bson_incnumstr(char *str);
+
+/* Error handling and standard library function over-riding. */
+/* -------------------------------------------------------- */
+
+/* bson_err_handlers shouldn't return!!! */
+typedef void( *bson_err_handler)(const char *errmsg);
+
+typedef int (*bson_printf_func)(const char *, ...);
+typedef int (*bson_fprintf_func)(FILE *, const char *, ...);
+typedef int (*bson_sprintf_func)(char *, const char *, ...);
+
+extern void *(*bson_malloc_func)(size_t);
+extern void *(*bson_realloc_func)(void *, size_t);
+extern void ( *bson_free_func)(void *);
+
+extern bson_printf_func bson_printf;
+extern bson_fprintf_func bson_fprintf;
+extern bson_sprintf_func bson_sprintf;
+extern bson_printf_func bson_errprintf;
+
+EJDB_EXPORT void bson_free(void *ptr);
+
+/**
+ * Allocates memory and checks return value, exiting fatally if malloc() fails.
+ *
+ * @param size bytes to allocate.
+ *
+ * @return a pointer to the allocated memory.
+ *
+ * @sa malloc(3)
+ */
+EJDB_EXPORT void *bson_malloc(int size);
+
+/**
+ * Changes the size of allocated memory and checks return value,
+ * exiting fatally if realloc() fails.
+ *
+ * @param ptr pointer to the space to reallocate.
+ * @param size bytes to allocate.
+ *
+ * @return a pointer to the allocated memory.
+ *
+ * @sa realloc()
+ */
+void *bson_realloc(void *ptr, int size);
+
+/**
+ * Set a function for error handling.
+ *
+ * @param func a bson_err_handler function.
+ *
+ * @return the old error handling function, or NULL.
+ */
+EJDB_EXPORT bson_err_handler set_bson_err_handler(bson_err_handler func);
+
+/* does nothing if ok != 0 */
+/**
+ * Exit fatally.
+ *
+ * @param ok exits if ok is equal to 0.
+ */
+void bson_fatal(int ok);
+
+/**
+ * Exit fatally with an error message.
+ *
+ * @param ok exits if ok is equal to 0.
+ * @param msg prints to stderr before exiting.
+ */
+void bson_fatal_msg(int ok, const char *msg);
+
+/**
+ * Invoke the error handler, but do not exit.
+ *
+ * @param b the buffer object.
+ */
+void bson_builder_error(bson *b);
+
+/**
+ * Cast an int64_t to double. This is necessary for embedding in
+ * certain environments.
+ *
+ */
+EJDB_EXPORT double bson_int64_to_double(int64_t i64);
+
+EJDB_EXPORT void bson_swap_endian32(void *outp, const void *inp);
+EJDB_EXPORT void bson_swap_endian64(void *outp, const void *inp);
+
+
+
+/**
+ * Append current field from iterator into bson object.
+ * @param from
+ * @param into
+ */
+EJDB_EXPORT void bson_append_field_from_iterator(bson_iterator *from, bson *into);
+
+
+/**
+ * Merge bson  'b2' into 'b1' saving result the 'out' object.
+ * 'b1' & 'b2' bson must be finished BSONS.
+ * Resulting 'out' bson must be allocated and not finished.
+ *
+ * Nested object skipped and usupported.
+ *
+ * @param b1 BSON to to be merged into out
+ * @param b2 BSON to to be merged into out
+ * @param overwrite if True All b1 fields will be overwriten by corresponding b2 fields
+ *
+ * @return BSON_OK or BSON_ERROR.
+ */
+EJDB_EXPORT int bson_merge(bson *b1, bson *b2, bson_bool_t overwrite, bson *out);
+
+
+/**
+ * Compares field path primitive values of two BSONs
+ * @param bsdata1 BSON raw data
+ * @param bsdata2 BSON raw data
+ * @param fpath Field path to the field
+ * @param fplen Length of fpath value
+ */
+EJDB_EXPORT int bson_compare(const void *bsdata1, const void *bsdata2, const char* fpath, int fplen);
+EJDB_EXPORT int bson_compare_fpaths(const void *bsdata1, const void *bsdata2, const char *fpath1, int fplen1, const char *fpath2, int fplen2);
+EJDB_EXPORT int bson_compare_string(const char* cv, const void *bsdata, const char *fpath);
+EJDB_EXPORT int bson_compare_long(long cv, const void *bsdata, const char *fpath);
+EJDB_EXPORT int bson_compare_double(double cv, const void *bsdata, const char *fpath);
+EJDB_EXPORT int bson_compare_bool(bson_bool_t cv, const void *bsdata, const char *fpath);
+
+
+/**
+ * Duplicates BSON object.
+ * @param src BSON
+ * @return Finished copy of src
+ */
+EJDB_EXPORT bson* bson_dup(const bson* src);
+
+
+EJDB_EXPORT bson* bson_create_from_buffer(const void* buf, int bufsz);
+
+
+EJDB_EXTERN_C_END
+#endif
diff --git a/tcejdb/configure b/tcejdb/configure
new file mode 100755 (executable)
index 0000000..74b3f32
--- /dev/null
@@ -0,0 +1,5493 @@
+#! /bin/sh
+# Guess values for system-dependent variables and create Makefiles.
+# Generated by GNU Autoconf 2.69 for tcejdb 1.0.0.
+#
+#
+# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc.
+#
+#
+# This configure script is free software; the Free Software Foundation
+# gives unlimited permission to copy, distribute and modify it.
+## -------------------- ##
+## M4sh Initialization. ##
+## -------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
+  emulate sh
+  NULLCMD=:
+  # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in #(
+  *posix*) :
+    set -o posix ;; #(
+  *) :
+     ;;
+esac
+fi
+
+
+as_nl='
+'
+export as_nl
+# Printing a long string crashes Solaris 7 /usr/bin/printf.
+as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
+# Prefer a ksh shell builtin over an external printf program on Solaris,
+# but without wasting forks for bash or zsh.
+if test -z "$BASH_VERSION$ZSH_VERSION" \
+    && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='print -r --'
+  as_echo_n='print -rn --'
+elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='printf %s\n'
+  as_echo_n='printf %s'
+else
+  if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
+    as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
+    as_echo_n='/usr/ucb/echo -n'
+  else
+    as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
+    as_echo_n_body='eval
+      arg=$1;
+      case $arg in #(
+      *"$as_nl"*)
+       expr "X$arg" : "X\\(.*\\)$as_nl";
+       arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
+      esac;
+      expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
+    '
+    export as_echo_n_body
+    as_echo_n='sh -c $as_echo_n_body as_echo'
+  fi
+  export as_echo_body
+  as_echo='sh -c $as_echo_body as_echo'
+fi
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+  PATH_SEPARATOR=:
+  (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
+    (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
+      PATH_SEPARATOR=';'
+  }
+fi
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.  Quoting is
+# there to prevent editors from complaining about space-tab.
+# (If _AS_PATH_WALK were called with IFS unset, it would disable word
+# splitting by setting IFS to empty value.)
+IFS=" ""       $as_nl"
+
+# Find who we are.  Look in the path if we contain no directory separator.
+as_myself=
+case $0 in #((
+  *[\\/]* ) as_myself=$0 ;;
+  *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+  done
+IFS=$as_save_IFS
+
+     ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+  as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+  $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+  exit 1
+fi
+
+# Unset variables that we do not need and which cause bugs (e.g. in
+# pre-3.0 UWIN ksh).  But do not cause bugs in bash 2.01; the "|| exit 1"
+# suppresses any "Segmentation fault" message there.  '((' could
+# trigger a bug in pdksh 5.2.14.
+for as_var in BASH_ENV ENV MAIL MAILPATH
+do eval test x\${$as_var+set} = xset \
+  && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# CDPATH.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+# Use a proper internal environment variable to ensure we don't fall
+  # into an infinite loop, continuously re-executing ourselves.
+  if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then
+    _as_can_reexec=no; export _as_can_reexec;
+    # We cannot yet assume a decent shell, so we have to provide a
+# neutralization value for shells without unset; and this also
+# works around shells that cannot unset nonexistent variables.
+# Preserve -v and -x to the replacement shell.
+BASH_ENV=/dev/null
+ENV=/dev/null
+(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
+case $- in # ((((
+  *v*x* | *x*v* ) as_opts=-vx ;;
+  *v* ) as_opts=-v ;;
+  *x* ) as_opts=-x ;;
+  * ) as_opts= ;;
+esac
+exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"}
+# Admittedly, this is quite paranoid, since all the known shells bail
+# out after a failed `exec'.
+$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2
+as_fn_exit 255
+  fi
+  # We don't want this to propagate to other subprocesses.
+          { _as_can_reexec=; unset _as_can_reexec;}
+if test "x$CONFIG_SHELL" = x; then
+  as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then :
+  emulate sh
+  NULLCMD=:
+  # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '\${1+\"\$@\"}'='\"\$@\"'
+  setopt NO_GLOB_SUBST
+else
+  case \`(set -o) 2>/dev/null\` in #(
+  *posix*) :
+    set -o posix ;; #(
+  *) :
+     ;;
+esac
+fi
+"
+  as_required="as_fn_return () { (exit \$1); }
+as_fn_success () { as_fn_return 0; }
+as_fn_failure () { as_fn_return 1; }
+as_fn_ret_success () { return 0; }
+as_fn_ret_failure () { return 1; }
+
+exitcode=0
+as_fn_success || { exitcode=1; echo as_fn_success failed.; }
+as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; }
+as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; }
+as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; }
+if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then :
+
+else
+  exitcode=1; echo positional parameters were not saved.
+fi
+test x\$exitcode = x0 || exit 1
+test -x / || exit 1"
+  as_suggested="  as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO
+  as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO
+  eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" &&
+  test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1
+test \$(( 1 + 1 )) = 2 || exit 1"
+  if (eval "$as_required") 2>/dev/null; then :
+  as_have_required=yes
+else
+  as_have_required=no
+fi
+  if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then :
+
+else
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+as_found=false
+for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+  as_found=:
+  case $as_dir in #(
+        /*)
+          for as_base in sh bash ksh sh5; do
+            # Try only shells that exist, to save several forks.
+            as_shell=$as_dir/$as_base
+            if { test -f "$as_shell" || test -f "$as_shell.exe"; } &&
+                   { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then :
+  CONFIG_SHELL=$as_shell as_have_required=yes
+                  if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then :
+  break 2
+fi
+fi
+          done;;
+       esac
+  as_found=false
+done
+$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } &&
+             { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then :
+  CONFIG_SHELL=$SHELL as_have_required=yes
+fi; }
+IFS=$as_save_IFS
+
+
+      if test "x$CONFIG_SHELL" != x; then :
+  export CONFIG_SHELL
+             # We cannot yet assume a decent shell, so we have to provide a
+# neutralization value for shells without unset; and this also
+# works around shells that cannot unset nonexistent variables.
+# Preserve -v and -x to the replacement shell.
+BASH_ENV=/dev/null
+ENV=/dev/null
+(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV
+case $- in # ((((
+  *v*x* | *x*v* ) as_opts=-vx ;;
+  *v* ) as_opts=-v ;;
+  *x* ) as_opts=-x ;;
+  * ) as_opts= ;;
+esac
+exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"}
+# Admittedly, this is quite paranoid, since all the known shells bail
+# out after a failed `exec'.
+$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2
+exit 255
+fi
+
+    if test x$as_have_required = xno; then :
+  $as_echo "$0: This script requires a shell more modern than all"
+  $as_echo "$0: the shells that I found on your system."
+  if test x${ZSH_VERSION+set} = xset ; then
+    $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should"
+    $as_echo "$0: be upgraded to zsh 4.3.4 or later."
+  else
+    $as_echo "$0: Please tell bug-autoconf@gnu.org about your system,
+$0: including any error possibly output before this
+$0: message. Then install a modern shell, or manually run
+$0: the script under such a shell if you do have one."
+  fi
+  exit 1
+fi
+fi
+fi
+SHELL=${CONFIG_SHELL-/bin/sh}
+export SHELL
+# Unset more variables known to interfere with behavior of common tools.
+CLICOLOR_FORCE= GREP_OPTIONS=
+unset CLICOLOR_FORCE GREP_OPTIONS
+
+## --------------------- ##
+## M4sh Shell Functions. ##
+## --------------------- ##
+# as_fn_unset VAR
+# ---------------
+# Portably unset VAR.
+as_fn_unset ()
+{
+  { eval $1=; unset $1;}
+}
+as_unset=as_fn_unset
+
+# as_fn_set_status STATUS
+# -----------------------
+# Set $? to STATUS, without forking.
+as_fn_set_status ()
+{
+  return $1
+} # as_fn_set_status
+
+# as_fn_exit STATUS
+# -----------------
+# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
+as_fn_exit ()
+{
+  set +e
+  as_fn_set_status $1
+  exit $1
+} # as_fn_exit
+
+# as_fn_mkdir_p
+# -------------
+# Create "$as_dir" as a directory, including parents if necessary.
+as_fn_mkdir_p ()
+{
+
+  case $as_dir in #(
+  -*) as_dir=./$as_dir;;
+  esac
+  test -d "$as_dir" || eval $as_mkdir_p || {
+    as_dirs=
+    while :; do
+      case $as_dir in #(
+      *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+      *) as_qdir=$as_dir;;
+      esac
+      as_dirs="'$as_qdir' $as_dirs"
+      as_dir=`$as_dirname -- "$as_dir" ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$as_dir" : 'X\(//\)[^/]' \| \
+        X"$as_dir" : 'X\(//\)$' \| \
+        X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_dir" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)[^/].*/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+      test -d "$as_dir" && break
+    done
+    test -z "$as_dirs" || eval "mkdir $as_dirs"
+  } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
+
+
+} # as_fn_mkdir_p
+
+# as_fn_executable_p FILE
+# -----------------------
+# Test if FILE is an executable regular file.
+as_fn_executable_p ()
+{
+  test -f "$1" && test -x "$1"
+} # as_fn_executable_p
+# as_fn_append VAR VALUE
+# ----------------------
+# Append the text in VALUE to the end of the definition contained in VAR. Take
+# advantage of any shell optimizations that allow amortized linear growth over
+# repeated appends, instead of the typical quadratic growth present in naive
+# implementations.
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
+  eval 'as_fn_append ()
+  {
+    eval $1+=\$2
+  }'
+else
+  as_fn_append ()
+  {
+    eval $1=\$$1\$2
+  }
+fi # as_fn_append
+
+# as_fn_arith ARG...
+# ------------------
+# Perform arithmetic evaluation on the ARGs, and store the result in the
+# global $as_val. Take advantage of shells that can avoid forks. The arguments
+# must be portable across $(()) and expr.
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
+  eval 'as_fn_arith ()
+  {
+    as_val=$(( $* ))
+  }'
+else
+  as_fn_arith ()
+  {
+    as_val=`expr "$@" || test $? -eq 1`
+  }
+fi # as_fn_arith
+
+
+# as_fn_error STATUS ERROR [LINENO LOG_FD]
+# ----------------------------------------
+# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
+# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
+# script with STATUS, using 1 if that was 0.
+as_fn_error ()
+{
+  as_status=$1; test $as_status -eq 0 && as_status=1
+  if test "$4"; then
+    as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+    $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+  fi
+  $as_echo "$as_me: error: $2" >&2
+  as_fn_exit $as_status
+} # as_fn_error
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+   test "X`expr 00001 : '.*\(...\)'`" = X001; then
+  as_expr=expr
+else
+  as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+  as_basename=basename
+else
+  as_basename=false
+fi
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+  as_dirname=dirname
+else
+  as_dirname=false
+fi
+
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+        X"$0" : 'X\(//\)$' \| \
+        X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X/"$0" |
+    sed '/^.*\/\([^/][^/]*\)\/*$/{
+           s//\1/
+           q
+         }
+         /^X\/\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\/\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+
+  as_lineno_1=$LINENO as_lineno_1a=$LINENO
+  as_lineno_2=$LINENO as_lineno_2a=$LINENO
+  eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" &&
+  test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || {
+  # Blame Lee E. McMahon (1931-1989) for sed's syntax.  :-)
+  sed -n '
+    p
+    /[$]LINENO/=
+  ' <$as_myself |
+    sed '
+      s/[$]LINENO.*/&-/
+      t lineno
+      b
+      :lineno
+      N
+      :loop
+      s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/
+      t loop
+      s/-\n.*//
+    ' >$as_me.lineno &&
+  chmod +x "$as_me.lineno" ||
+    { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; }
+
+  # If we had to re-execute with $CONFIG_SHELL, we're ensured to have
+  # already done that, so ensure we don't try to do so again and fall
+  # in an infinite loop.  This has already happened in practice.
+  _as_can_reexec=no; export _as_can_reexec
+  # Don't try to exec as it changes $[0], causing all sort of problems
+  # (the dirname of $[0] is not the place where we might find the
+  # original and so on.  Autoconf is especially sensitive to this).
+  . "./$as_me.lineno"
+  # Exit status is that of the last command.
+  exit
+}
+
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in #(((((
+-n*)
+  case `echo 'xy\c'` in
+  *c*) ECHO_T='        ';;     # ECHO_T is single tab character.
+  xy)  ECHO_C='\c';;
+  *)   echo `echo ksh88 bug on AIX 6.1` > /dev/null
+       ECHO_T='        ';;
+  esac;;
+*)
+  ECHO_N='-n';;
+esac
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+  rm -f conf$$.dir/conf$$.file
+else
+  rm -f conf$$.dir
+  mkdir conf$$.dir 2>/dev/null
+fi
+if (echo >conf$$.file) 2>/dev/null; then
+  if ln -s conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s='ln -s'
+    # ... but there are two gotchas:
+    # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+    # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+    # In both cases, we have to default to `cp -pR'.
+    ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+      as_ln_s='cp -pR'
+  elif ln conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s=ln
+  else
+    as_ln_s='cp -pR'
+  fi
+else
+  as_ln_s='cp -pR'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+if mkdir -p . 2>/dev/null; then
+  as_mkdir_p='mkdir -p "$as_dir"'
+else
+  test -d ./-p && rmdir ./-p
+  as_mkdir_p=false
+fi
+
+as_test_x='test -x'
+as_executable_p=as_fn_executable_p
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+test -n "$DJDIR" || exec 7<&0 </dev/null
+exec 6>&1
+
+# Name of the host.
+# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status,
+# so uname gets run too.
+ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q`
+
+#
+# Initializations.
+#
+ac_default_prefix=/usr/local
+ac_clean_files=
+ac_config_libobj_dir=.
+LIBOBJS=
+cross_compiling=no
+subdirs=
+MFLAGS=
+MAKEFLAGS=
+
+# Identity of this package.
+PACKAGE_NAME='tcejdb'
+PACKAGE_TARNAME='tcejdb'
+PACKAGE_VERSION='1.0.0'
+PACKAGE_STRING='tcejdb 1.0.0'
+PACKAGE_BUGREPORT=''
+PACKAGE_URL=''
+
+# Factoring default headers for most tests.
+ac_includes_default="\
+#include <stdio.h>
+#ifdef HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
+#ifdef STDC_HEADERS
+# include <stdlib.h>
+# include <stddef.h>
+#else
+# ifdef HAVE_STDLIB_H
+#  include <stdlib.h>
+# endif
+#endif
+#ifdef HAVE_STRING_H
+# if !defined STDC_HEADERS && defined HAVE_MEMORY_H
+#  include <memory.h>
+# endif
+# include <string.h>
+#endif
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif
+#ifdef HAVE_INTTYPES_H
+# include <inttypes.h>
+#endif
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif"
+
+ac_subst_vars='LTLIBOBJS
+LIBOBJS
+MYPOSTCMD
+MYLDLIBPATHENV
+MYRUNPATH
+MYCMDLDFLAGS
+MYLDFLAGS
+MYCPPFLAGS
+MYCFLAGS
+MYPCFILES
+MYDOCUMENTFILES
+MYMAN3FILES
+MYMAN1FILES
+MYCGIFILES
+MYCOMMANDFILES
+MYLIBOBJFILES
+MYLIBRARYFILES
+MYHEADERFILES
+MYFORMATVER
+MYLIBREV
+MYLIBVER
+EGREP
+GREP
+CPP
+OBJEXT
+EXEEXT
+ac_ct_CC
+CPPFLAGS
+LDFLAGS
+CFLAGS
+CC
+target_alias
+host_alias
+build_alias
+LIBS
+ECHO_T
+ECHO_N
+ECHO_C
+DEFS
+mandir
+localedir
+libdir
+psdir
+pdfdir
+dvidir
+htmldir
+infodir
+docdir
+oldincludedir
+includedir
+localstatedir
+sharedstatedir
+sysconfdir
+datadir
+datarootdir
+libexecdir
+sbindir
+bindir
+program_transform_name
+prefix
+exec_prefix
+PACKAGE_URL
+PACKAGE_BUGREPORT
+PACKAGE_STRING
+PACKAGE_VERSION
+PACKAGE_TARNAME
+PACKAGE_NAME
+PATH_SEPARATOR
+SHELL'
+ac_subst_files=''
+ac_user_opts='
+enable_option_checking
+enable_debug
+enable_devel
+enable_profile
+enable_static
+enable_fastest
+enable_off64
+enable_swab
+enable_uyield
+enable_ubc
+enable_zlib
+enable_bzip
+enable_pthread
+enable_shared
+enable_exlzma
+enable_exlzo
+with_zlib
+with_bzip
+'
+      ac_precious_vars='build_alias
+host_alias
+target_alias
+CC
+CFLAGS
+LDFLAGS
+LIBS
+CPPFLAGS
+CPP'
+
+
+# Initialize some variables set by options.
+ac_init_help=
+ac_init_version=false
+ac_unrecognized_opts=
+ac_unrecognized_sep=
+# The variables have the same names as the options, with
+# dashes changed to underlines.
+cache_file=/dev/null
+exec_prefix=NONE
+no_create=
+no_recursion=
+prefix=NONE
+program_prefix=NONE
+program_suffix=NONE
+program_transform_name=s,x,x,
+silent=
+site=
+srcdir=
+verbose=
+x_includes=NONE
+x_libraries=NONE
+
+# Installation directory options.
+# These are left unexpanded so users can "make install exec_prefix=/foo"
+# and all the variables that are supposed to be based on exec_prefix
+# by default will actually change.
+# Use braces instead of parens because sh, perl, etc. also accept them.
+# (The list follows the same order as the GNU Coding Standards.)
+bindir='${exec_prefix}/bin'
+sbindir='${exec_prefix}/sbin'
+libexecdir='${exec_prefix}/libexec'
+datarootdir='${prefix}/share'
+datadir='${datarootdir}'
+sysconfdir='${prefix}/etc'
+sharedstatedir='${prefix}/com'
+localstatedir='${prefix}/var'
+includedir='${prefix}/include'
+oldincludedir='/usr/include'
+docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
+infodir='${datarootdir}/info'
+htmldir='${docdir}'
+dvidir='${docdir}'
+pdfdir='${docdir}'
+psdir='${docdir}'
+libdir='${exec_prefix}/lib'
+localedir='${datarootdir}/locale'
+mandir='${datarootdir}/man'
+
+ac_prev=
+ac_dashdash=
+for ac_option
+do
+  # If the previous option needs an argument, assign it.
+  if test -n "$ac_prev"; then
+    eval $ac_prev=\$ac_option
+    ac_prev=
+    continue
+  fi
+
+  case $ac_option in
+  *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;;
+  *=)   ac_optarg= ;;
+  *)    ac_optarg=yes ;;
+  esac
+
+  # Accept the important Cygnus configure options, so we can diagnose typos.
+
+  case $ac_dashdash$ac_option in
+  --)
+    ac_dashdash=yes ;;
+
+  -bindir | --bindir | --bindi | --bind | --bin | --bi)
+    ac_prev=bindir ;;
+  -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*)
+    bindir=$ac_optarg ;;
+
+  -build | --build | --buil | --bui | --bu)
+    ac_prev=build_alias ;;
+  -build=* | --build=* | --buil=* | --bui=* | --bu=*)
+    build_alias=$ac_optarg ;;
+
+  -cache-file | --cache-file | --cache-fil | --cache-fi \
+  | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c)
+    ac_prev=cache_file ;;
+  -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \
+  | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*)
+    cache_file=$ac_optarg ;;
+
+  --config-cache | -C)
+    cache_file=config.cache ;;
+
+  -datadir | --datadir | --datadi | --datad)
+    ac_prev=datadir ;;
+  -datadir=* | --datadir=* | --datadi=* | --datad=*)
+    datadir=$ac_optarg ;;
+
+  -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \
+  | --dataroo | --dataro | --datar)
+    ac_prev=datarootdir ;;
+  -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \
+  | --dataroot=* | --dataroo=* | --dataro=* | --datar=*)
+    datarootdir=$ac_optarg ;;
+
+  -disable-* | --disable-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid feature name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"enable_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig"
+        ac_unrecognized_sep=', ';;
+    esac
+    eval enable_$ac_useropt=no ;;
+
+  -docdir | --docdir | --docdi | --doc | --do)
+    ac_prev=docdir ;;
+  -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*)
+    docdir=$ac_optarg ;;
+
+  -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv)
+    ac_prev=dvidir ;;
+  -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*)
+    dvidir=$ac_optarg ;;
+
+  -enable-* | --enable-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid feature name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"enable_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig"
+        ac_unrecognized_sep=', ';;
+    esac
+    eval enable_$ac_useropt=\$ac_optarg ;;
+
+  -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \
+  | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \
+  | --exec | --exe | --ex)
+    ac_prev=exec_prefix ;;
+  -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \
+  | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \
+  | --exec=* | --exe=* | --ex=*)
+    exec_prefix=$ac_optarg ;;
+
+  -gas | --gas | --ga | --g)
+    # Obsolete; use --with-gas.
+    with_gas=yes ;;
+
+  -help | --help | --hel | --he | -h)
+    ac_init_help=long ;;
+  -help=r* | --help=r* | --hel=r* | --he=r* | -hr*)
+    ac_init_help=recursive ;;
+  -help=s* | --help=s* | --hel=s* | --he=s* | -hs*)
+    ac_init_help=short ;;
+
+  -host | --host | --hos | --ho)
+    ac_prev=host_alias ;;
+  -host=* | --host=* | --hos=* | --ho=*)
+    host_alias=$ac_optarg ;;
+
+  -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht)
+    ac_prev=htmldir ;;
+  -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \
+  | --ht=*)
+    htmldir=$ac_optarg ;;
+
+  -includedir | --includedir | --includedi | --included | --include \
+  | --includ | --inclu | --incl | --inc)
+    ac_prev=includedir ;;
+  -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \
+  | --includ=* | --inclu=* | --incl=* | --inc=*)
+    includedir=$ac_optarg ;;
+
+  -infodir | --infodir | --infodi | --infod | --info | --inf)
+    ac_prev=infodir ;;
+  -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*)
+    infodir=$ac_optarg ;;
+
+  -libdir | --libdir | --libdi | --libd)
+    ac_prev=libdir ;;
+  -libdir=* | --libdir=* | --libdi=* | --libd=*)
+    libdir=$ac_optarg ;;
+
+  -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \
+  | --libexe | --libex | --libe)
+    ac_prev=libexecdir ;;
+  -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \
+  | --libexe=* | --libex=* | --libe=*)
+    libexecdir=$ac_optarg ;;
+
+  -localedir | --localedir | --localedi | --localed | --locale)
+    ac_prev=localedir ;;
+  -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*)
+    localedir=$ac_optarg ;;
+
+  -localstatedir | --localstatedir | --localstatedi | --localstated \
+  | --localstate | --localstat | --localsta | --localst | --locals)
+    ac_prev=localstatedir ;;
+  -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \
+  | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*)
+    localstatedir=$ac_optarg ;;
+
+  -mandir | --mandir | --mandi | --mand | --man | --ma | --m)
+    ac_prev=mandir ;;
+  -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*)
+    mandir=$ac_optarg ;;
+
+  -nfp | --nfp | --nf)
+    # Obsolete; use --without-fp.
+    with_fp=no ;;
+
+  -no-create | --no-create | --no-creat | --no-crea | --no-cre \
+  | --no-cr | --no-c | -n)
+    no_create=yes ;;
+
+  -no-recursion | --no-recursion | --no-recursio | --no-recursi \
+  | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r)
+    no_recursion=yes ;;
+
+  -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \
+  | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \
+  | --oldin | --oldi | --old | --ol | --o)
+    ac_prev=oldincludedir ;;
+  -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \
+  | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \
+  | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*)
+    oldincludedir=$ac_optarg ;;
+
+  -prefix | --prefix | --prefi | --pref | --pre | --pr | --p)
+    ac_prev=prefix ;;
+  -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*)
+    prefix=$ac_optarg ;;
+
+  -program-prefix | --program-prefix | --program-prefi | --program-pref \
+  | --program-pre | --program-pr | --program-p)
+    ac_prev=program_prefix ;;
+  -program-prefix=* | --program-prefix=* | --program-prefi=* \
+  | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*)
+    program_prefix=$ac_optarg ;;
+
+  -program-suffix | --program-suffix | --program-suffi | --program-suff \
+  | --program-suf | --program-su | --program-s)
+    ac_prev=program_suffix ;;
+  -program-suffix=* | --program-suffix=* | --program-suffi=* \
+  | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*)
+    program_suffix=$ac_optarg ;;
+
+  -program-transform-name | --program-transform-name \
+  | --program-transform-nam | --program-transform-na \
+  | --program-transform-n | --program-transform- \
+  | --program-transform | --program-transfor \
+  | --program-transfo | --program-transf \
+  | --program-trans | --program-tran \
+  | --progr-tra | --program-tr | --program-t)
+    ac_prev=program_transform_name ;;
+  -program-transform-name=* | --program-transform-name=* \
+  | --program-transform-nam=* | --program-transform-na=* \
+  | --program-transform-n=* | --program-transform-=* \
+  | --program-transform=* | --program-transfor=* \
+  | --program-transfo=* | --program-transf=* \
+  | --program-trans=* | --program-tran=* \
+  | --progr-tra=* | --program-tr=* | --program-t=*)
+    program_transform_name=$ac_optarg ;;
+
+  -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd)
+    ac_prev=pdfdir ;;
+  -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*)
+    pdfdir=$ac_optarg ;;
+
+  -psdir | --psdir | --psdi | --psd | --ps)
+    ac_prev=psdir ;;
+  -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*)
+    psdir=$ac_optarg ;;
+
+  -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+  | -silent | --silent | --silen | --sile | --sil)
+    silent=yes ;;
+
+  -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
+    ac_prev=sbindir ;;
+  -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
+  | --sbi=* | --sb=*)
+    sbindir=$ac_optarg ;;
+
+  -sharedstatedir | --sharedstatedir | --sharedstatedi \
+  | --sharedstated | --sharedstate | --sharedstat | --sharedsta \
+  | --sharedst | --shareds | --shared | --share | --shar \
+  | --sha | --sh)
+    ac_prev=sharedstatedir ;;
+  -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \
+  | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \
+  | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \
+  | --sha=* | --sh=*)
+    sharedstatedir=$ac_optarg ;;
+
+  -site | --site | --sit)
+    ac_prev=site ;;
+  -site=* | --site=* | --sit=*)
+    site=$ac_optarg ;;
+
+  -srcdir | --srcdir | --srcdi | --srcd | --src | --sr)
+    ac_prev=srcdir ;;
+  -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*)
+    srcdir=$ac_optarg ;;
+
+  -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \
+  | --syscon | --sysco | --sysc | --sys | --sy)
+    ac_prev=sysconfdir ;;
+  -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \
+  | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*)
+    sysconfdir=$ac_optarg ;;
+
+  -target | --target | --targe | --targ | --tar | --ta | --t)
+    ac_prev=target_alias ;;
+  -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*)
+    target_alias=$ac_optarg ;;
+
+  -v | -verbose | --verbose | --verbos | --verbo | --verb)
+    verbose=yes ;;
+
+  -version | --version | --versio | --versi | --vers | -V)
+    ac_init_version=: ;;
+
+  -with-* | --with-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid package name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"with_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig"
+        ac_unrecognized_sep=', ';;
+    esac
+    eval with_$ac_useropt=\$ac_optarg ;;
+
+  -without-* | --without-*)
+    ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'`
+    # Reject names that are not valid shell variable names.
+    expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null &&
+      as_fn_error $? "invalid package name: $ac_useropt"
+    ac_useropt_orig=$ac_useropt
+    ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'`
+    case $ac_user_opts in
+      *"
+"with_$ac_useropt"
+"*) ;;
+      *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig"
+        ac_unrecognized_sep=', ';;
+    esac
+    eval with_$ac_useropt=no ;;
+
+  --x)
+    # Obsolete; use --with-x.
+    with_x=yes ;;
+
+  -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \
+  | --x-incl | --x-inc | --x-in | --x-i)
+    ac_prev=x_includes ;;
+  -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \
+  | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*)
+    x_includes=$ac_optarg ;;
+
+  -x-libraries | --x-libraries | --x-librarie | --x-librari \
+  | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l)
+    ac_prev=x_libraries ;;
+  -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \
+  | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*)
+    x_libraries=$ac_optarg ;;
+
+  -*) as_fn_error $? "unrecognized option: \`$ac_option'
+Try \`$0 --help' for more information"
+    ;;
+
+  *=*)
+    ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='`
+    # Reject names that are not valid shell variable names.
+    case $ac_envvar in #(
+      '' | [0-9]* | *[!_$as_cr_alnum]* )
+      as_fn_error $? "invalid variable name: \`$ac_envvar'" ;;
+    esac
+    eval $ac_envvar=\$ac_optarg
+    export $ac_envvar ;;
+
+  *)
+    # FIXME: should be removed in autoconf 3.0.
+    $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2
+    expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null &&
+      $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2
+    : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}"
+    ;;
+
+  esac
+done
+
+if test -n "$ac_prev"; then
+  ac_option=--`echo $ac_prev | sed 's/_/-/g'`
+  as_fn_error $? "missing argument to $ac_option"
+fi
+
+if test -n "$ac_unrecognized_opts"; then
+  case $enable_option_checking in
+    no) ;;
+    fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;;
+    *)     $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;;
+  esac
+fi
+
+# Check all directory arguments for consistency.
+for ac_var in  exec_prefix prefix bindir sbindir libexecdir datarootdir \
+               datadir sysconfdir sharedstatedir localstatedir includedir \
+               oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
+               libdir localedir mandir
+do
+  eval ac_val=\$$ac_var
+  # Remove trailing slashes.
+  case $ac_val in
+    */ )
+      ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'`
+      eval $ac_var=\$ac_val;;
+  esac
+  # Be sure to have absolute directory names.
+  case $ac_val in
+    [\\/$]* | ?:[\\/]* )  continue;;
+    NONE | '' ) case $ac_var in *prefix ) continue;; esac;;
+  esac
+  as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val"
+done
+
+# There might be people who depend on the old broken behavior: `$host'
+# used to hold the argument of --host etc.
+# FIXME: To remove some day.
+build=$build_alias
+host=$host_alias
+target=$target_alias
+
+# FIXME: To remove some day.
+if test "x$host_alias" != x; then
+  if test "x$build_alias" = x; then
+    cross_compiling=maybe
+  elif test "x$build_alias" != "x$host_alias"; then
+    cross_compiling=yes
+  fi
+fi
+
+ac_tool_prefix=
+test -n "$host_alias" && ac_tool_prefix=$host_alias-
+
+test "$silent" = yes && exec 6>/dev/null
+
+
+ac_pwd=`pwd` && test -n "$ac_pwd" &&
+ac_ls_di=`ls -di .` &&
+ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` ||
+  as_fn_error $? "working directory cannot be determined"
+test "X$ac_ls_di" = "X$ac_pwd_ls_di" ||
+  as_fn_error $? "pwd does not report name of working directory"
+
+
+# Find the source files, if location was not specified.
+if test -z "$srcdir"; then
+  ac_srcdir_defaulted=yes
+  # Try the directory containing this script, then the parent directory.
+  ac_confdir=`$as_dirname -- "$as_myself" ||
+$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$as_myself" : 'X\(//\)[^/]' \| \
+        X"$as_myself" : 'X\(//\)$' \| \
+        X"$as_myself" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_myself" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)[^/].*/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+  srcdir=$ac_confdir
+  if test ! -r "$srcdir/$ac_unique_file"; then
+    srcdir=..
+  fi
+else
+  ac_srcdir_defaulted=no
+fi
+if test ! -r "$srcdir/$ac_unique_file"; then
+  test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .."
+  as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir"
+fi
+ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work"
+ac_abs_confdir=`(
+       cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg"
+       pwd)`
+# When building in place, set srcdir=.
+if test "$ac_abs_confdir" = "$ac_pwd"; then
+  srcdir=.
+fi
+# Remove unnecessary trailing slashes from srcdir.
+# Double slashes in file names in object file debugging info
+# mess up M-x gdb in Emacs.
+case $srcdir in
+*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;;
+esac
+for ac_var in $ac_precious_vars; do
+  eval ac_env_${ac_var}_set=\${${ac_var}+set}
+  eval ac_env_${ac_var}_value=\$${ac_var}
+  eval ac_cv_env_${ac_var}_set=\${${ac_var}+set}
+  eval ac_cv_env_${ac_var}_value=\$${ac_var}
+done
+
+#
+# Report the --help message.
+#
+if test "$ac_init_help" = "long"; then
+  # Omit some internal or obsolete options to make the list less imposing.
+  # This message is too long to be a string in the A/UX 3.1 sh.
+  cat <<_ACEOF
+\`configure' configures tcejdb 1.0.0 to adapt to many kinds of systems.
+
+Usage: $0 [OPTION]... [VAR=VALUE]...
+
+To assign environment variables (e.g., CC, CFLAGS...), specify them as
+VAR=VALUE.  See below for descriptions of some of the useful variables.
+
+Defaults for the options are specified in brackets.
+
+Configuration:
+  -h, --help              display this help and exit
+      --help=short        display options specific to this package
+      --help=recursive    display the short help of all the included packages
+  -V, --version           display version information and exit
+  -q, --quiet, --silent   do not print \`checking ...' messages
+      --cache-file=FILE   cache test results in FILE [disabled]
+  -C, --config-cache      alias for \`--cache-file=config.cache'
+  -n, --no-create         do not create output files
+      --srcdir=DIR        find the sources in DIR [configure dir or \`..']
+
+Installation directories:
+  --prefix=PREFIX         install architecture-independent files in PREFIX
+                          [$ac_default_prefix]
+  --exec-prefix=EPREFIX   install architecture-dependent files in EPREFIX
+                          [PREFIX]
+
+By default, \`make install' will install all the files in
+\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc.  You can specify
+an installation prefix other than \`$ac_default_prefix' using \`--prefix',
+for instance \`--prefix=\$HOME'.
+
+For better control, use the options below.
+
+Fine tuning of the installation directories:
+  --bindir=DIR            user executables [EPREFIX/bin]
+  --sbindir=DIR           system admin executables [EPREFIX/sbin]
+  --libexecdir=DIR        program executables [EPREFIX/libexec]
+  --sysconfdir=DIR        read-only single-machine data [PREFIX/etc]
+  --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
+  --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
+  --libdir=DIR            object code libraries [EPREFIX/lib]
+  --includedir=DIR        C header files [PREFIX/include]
+  --oldincludedir=DIR     C header files for non-gcc [/usr/include]
+  --datarootdir=DIR       read-only arch.-independent data root [PREFIX/share]
+  --datadir=DIR           read-only architecture-independent data [DATAROOTDIR]
+  --infodir=DIR           info documentation [DATAROOTDIR/info]
+  --localedir=DIR         locale-dependent data [DATAROOTDIR/locale]
+  --mandir=DIR            man documentation [DATAROOTDIR/man]
+  --docdir=DIR            documentation root [DATAROOTDIR/doc/tcejdb]
+  --htmldir=DIR           html documentation [DOCDIR]
+  --dvidir=DIR            dvi documentation [DOCDIR]
+  --pdfdir=DIR            pdf documentation [DOCDIR]
+  --psdir=DIR             ps documentation [DOCDIR]
+_ACEOF
+
+  cat <<\_ACEOF
+_ACEOF
+fi
+
+if test -n "$ac_init_help"; then
+  case $ac_init_help in
+     short | recursive ) echo "Configuration of tcejdb 1.0.0:";;
+   esac
+  cat <<\_ACEOF
+
+Optional Features:
+  --disable-option-checking  ignore unrecognized --enable/--with options
+  --disable-FEATURE       do not include FEATURE (same as --enable-FEATURE=no)
+  --enable-FEATURE[=ARG]  include FEATURE [ARG=yes]
+  --enable-debug          build for debugging
+  --enable-devel          build for development
+  --enable-profile        build for profiling
+  --enable-static         build by static linking
+  --enable-fastest        build for fastest run
+  --enable-off64          build with 64-bit file offset on 32-bit system
+  --enable-swab           build for swapping byte-orders
+  --enable-uyield         build for detecting race conditions
+  --disable-ubc           build without the unified buffer cache assumption
+  --disable-zlib          build without ZLIB compression
+  --disable-bzip          build without BZIP2 compression
+  --disable-pthread       build without POSIX thread support
+  --disable-shared        avoid to build shared libraries
+  --disable-exlzma        build with the custom codec of LZMA
+  --disable-exlzo         build with the custom codec of LZO
+
+Optional Packages:
+  --with-PACKAGE[=ARG]    use PACKAGE [ARG=yes]
+  --without-PACKAGE       do not use PACKAGE (same as --with-PACKAGE=no)
+  --with-zlib=DIR         search DIR/include and DIR/lib for ZLIB
+  --with-bzip=DIR         search DIR/include and DIR/lib for BZIP2
+
+Some influential environment variables:
+  CC          C compiler command
+  CFLAGS      C compiler flags
+  LDFLAGS     linker flags, e.g. -L<lib dir> if you have libraries in a
+              nonstandard directory <lib dir>
+  LIBS        libraries to pass to the linker, e.g. -l<library>
+  CPPFLAGS    (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if
+              you have headers in a nonstandard directory <include dir>
+  CPP         C preprocessor
+
+Use these variables to override the choices made by `configure' or to help
+it to find libraries and programs with nonstandard names/locations.
+
+Report bugs to the package provider.
+_ACEOF
+ac_status=$?
+fi
+
+if test "$ac_init_help" = "recursive"; then
+  # If there are subdirs, report their specific --help.
+  for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue
+    test -d "$ac_dir" ||
+      { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } ||
+      continue
+    ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+  ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
+  # A ".." for each directory in $ac_dir_suffix.
+  ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+  case $ac_top_builddir_sub in
+  "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+  *)  ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+  esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+  .)  # We are building in place.
+    ac_srcdir=.
+    ac_top_srcdir=$ac_top_builddir_sub
+    ac_abs_top_srcdir=$ac_pwd ;;
+  [\\/]* | ?:[\\/]* )  # Absolute name.
+    ac_srcdir=$srcdir$ac_dir_suffix;
+    ac_top_srcdir=$srcdir
+    ac_abs_top_srcdir=$srcdir ;;
+  *) # Relative name.
+    ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+    ac_top_srcdir=$ac_top_build_prefix$srcdir
+    ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+    cd "$ac_dir" || { ac_status=$?; continue; }
+    # Check for guested configure.
+    if test -f "$ac_srcdir/configure.gnu"; then
+      echo &&
+      $SHELL "$ac_srcdir/configure.gnu" --help=recursive
+    elif test -f "$ac_srcdir/configure"; then
+      echo &&
+      $SHELL "$ac_srcdir/configure" --help=recursive
+    else
+      $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2
+    fi || ac_status=$?
+    cd "$ac_pwd" || { ac_status=$?; break; }
+  done
+fi
+
+test -n "$ac_init_help" && exit $ac_status
+if $ac_init_version; then
+  cat <<\_ACEOF
+tcejdb configure 1.0.0
+generated by GNU Autoconf 2.69
+
+Copyright (C) 2012 Free Software Foundation, Inc.
+This configure script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it.
+_ACEOF
+  exit
+fi
+
+## ------------------------ ##
+## Autoconf initialization. ##
+## ------------------------ ##
+
+# ac_fn_c_try_compile LINENO
+# --------------------------
+# Try to compile conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_compile ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  rm -f conftest.$ac_objext
+  if { { ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compile") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && {
+        test -z "$ac_c_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest.$ac_objext; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_retval=1
+fi
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_compile
+
+# ac_fn_c_try_run LINENO
+# ----------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes
+# that executables *can* be run.
+ac_fn_c_try_run ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  if { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && { ac_try='./conftest$ac_exeext'
+  { { case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: program exited with status $ac_status" >&5
+       $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_retval=$ac_status
+fi
+  rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_run
+
+# ac_fn_c_try_cpp LINENO
+# ----------------------
+# Try to preprocess conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_cpp ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  if { { ac_try="$ac_cpp conftest.$ac_ext"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_cpp conftest.$ac_ext") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } > conftest.i && {
+        test -z "$ac_c_preproc_warn_flag$ac_c_werror_flag" ||
+        test ! -s conftest.err
+       }; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+    ac_retval=1
+fi
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_cpp
+
+# ac_fn_c_check_header_compile LINENO HEADER VAR INCLUDES
+# -------------------------------------------------------
+# Tests whether HEADER exists and can be compiled using the include files in
+# INCLUDES, setting the cache variable VAR accordingly.
+ac_fn_c_check_header_compile ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+#include <$2>
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  eval "$3=yes"
+else
+  eval "$3=no"
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+eval ac_res=\$$3
+              { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_header_compile
+
+# ac_fn_c_try_link LINENO
+# -----------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_link ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  rm -f conftest.$ac_objext conftest$ac_exeext
+  if { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    grep -v '^ *+' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+    mv -f conftest.er1 conftest.err
+  fi
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; } && {
+        test -z "$ac_c_werror_flag" ||
+        test ! -s conftest.err
+       } && test -s conftest$ac_exeext && {
+        test "$cross_compiling" = yes ||
+        test -x conftest$ac_exeext
+       }; then :
+  ac_retval=0
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+       ac_retval=1
+fi
+  # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information
+  # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would
+  # interfere with the next link command; also delete a directory that is
+  # left behind by Apple's compiler.  We do this before executing the actions.
+  rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+  as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_link
+
+# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES
+# -------------------------------------------------------
+# Tests whether HEADER exists, giving a warning if it cannot be compiled using
+# the include files in INCLUDES and setting the cache variable VAR
+# accordingly.
+ac_fn_c_check_header_mongrel ()
+{
+  as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+  if eval \${$3+:} false; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+  $as_echo_n "(cached) " >&6
+fi
+eval ac_res=\$$3
+              { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+else
+  # Is the header compilable?
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 usability" >&5
+$as_echo_n "checking $2 usability... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$4
+#include <$2>
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_header_compiler=yes
+else
+  ac_header_compiler=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_compiler" >&5
+$as_echo "$ac_header_compiler" >&6; }
+
+# Is the header present?
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking $2 presence" >&5
+$as_echo_n "checking $2 presence... " >&6; }
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <$2>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+  ac_header_preproc=yes
+else
+  ac_header_preproc=no
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_header_preproc" >&5
+$as_echo "$ac_header_preproc" >&6; }
+
+# So?  What about this header?
+case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in #((
+  yes:no: )
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&5
+$as_echo "$as_me: WARNING: $2: accepted by the compiler, rejected by the preprocessor!" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
+$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
+    ;;
+  no:yes:* )
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: present but cannot be compiled" >&5
+$as_echo "$as_me: WARNING: $2: present but cannot be compiled" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2:     check for missing prerequisite headers?" >&5
+$as_echo "$as_me: WARNING: $2:     check for missing prerequisite headers?" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: see the Autoconf documentation" >&5
+$as_echo "$as_me: WARNING: $2: see the Autoconf documentation" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2:     section \"Present But Cannot Be Compiled\"" >&5
+$as_echo "$as_me: WARNING: $2:     section \"Present But Cannot Be Compiled\"" >&2;}
+    { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $2: proceeding with the compiler's result" >&5
+$as_echo "$as_me: WARNING: $2: proceeding with the compiler's result" >&2;}
+    ;;
+esac
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $2" >&5
+$as_echo_n "checking for $2... " >&6; }
+if eval \${$3+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  eval "$3=\$ac_header_compiler"
+fi
+eval ac_res=\$$3
+              { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+fi
+  eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+
+} # ac_fn_c_check_header_mongrel
+cat >config.log <<_ACEOF
+This file contains any messages produced by compilers while
+running configure, to aid debugging if configure makes a mistake.
+
+It was created by tcejdb $as_me 1.0.0, which was
+generated by GNU Autoconf 2.69.  Invocation command line was
+
+  $ $0 $@
+
+_ACEOF
+exec 5>>config.log
+{
+cat <<_ASUNAME
+## --------- ##
+## Platform. ##
+## --------- ##
+
+hostname = `(hostname || uname -n) 2>/dev/null | sed 1q`
+uname -m = `(uname -m) 2>/dev/null || echo unknown`
+uname -r = `(uname -r) 2>/dev/null || echo unknown`
+uname -s = `(uname -s) 2>/dev/null || echo unknown`
+uname -v = `(uname -v) 2>/dev/null || echo unknown`
+
+/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown`
+/bin/uname -X     = `(/bin/uname -X) 2>/dev/null     || echo unknown`
+
+/bin/arch              = `(/bin/arch) 2>/dev/null              || echo unknown`
+/usr/bin/arch -k       = `(/usr/bin/arch -k) 2>/dev/null       || echo unknown`
+/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown`
+/usr/bin/hostinfo      = `(/usr/bin/hostinfo) 2>/dev/null      || echo unknown`
+/bin/machine           = `(/bin/machine) 2>/dev/null           || echo unknown`
+/usr/bin/oslevel       = `(/usr/bin/oslevel) 2>/dev/null       || echo unknown`
+/bin/universe          = `(/bin/universe) 2>/dev/null          || echo unknown`
+
+_ASUNAME
+
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    $as_echo "PATH: $as_dir"
+  done
+IFS=$as_save_IFS
+
+} >&5
+
+cat >&5 <<_ACEOF
+
+
+## ----------- ##
+## Core tests. ##
+## ----------- ##
+
+_ACEOF
+
+
+# Keep a trace of the command line.
+# Strip out --no-create and --no-recursion so they do not pile up.
+# Strip out --silent because we don't want to record it for future runs.
+# Also quote any args containing shell meta-characters.
+# Make two passes to allow for proper duplicate-argument suppression.
+ac_configure_args=
+ac_configure_args0=
+ac_configure_args1=
+ac_must_keep_next=false
+for ac_pass in 1 2
+do
+  for ac_arg
+  do
+    case $ac_arg in
+    -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;;
+    -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+    | -silent | --silent | --silen | --sile | --sil)
+      continue ;;
+    *\'*)
+      ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;;
+    esac
+    case $ac_pass in
+    1) as_fn_append ac_configure_args0 " '$ac_arg'" ;;
+    2)
+      as_fn_append ac_configure_args1 " '$ac_arg'"
+      if test $ac_must_keep_next = true; then
+       ac_must_keep_next=false # Got value, back to normal.
+      else
+       case $ac_arg in
+         *=* | --config-cache | -C | -disable-* | --disable-* \
+         | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \
+         | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \
+         | -with-* | --with-* | -without-* | --without-* | --x)
+           case "$ac_configure_args0 " in
+             "$ac_configure_args1"*" '$ac_arg' "* ) continue ;;
+           esac
+           ;;
+         -* ) ac_must_keep_next=true ;;
+       esac
+      fi
+      as_fn_append ac_configure_args " '$ac_arg'"
+      ;;
+    esac
+  done
+done
+{ ac_configure_args0=; unset ac_configure_args0;}
+{ ac_configure_args1=; unset ac_configure_args1;}
+
+# When interrupted or exit'd, cleanup temporary files, and complete
+# config.log.  We remove comments because anyway the quotes in there
+# would cause problems or look ugly.
+# WARNING: Use '\'' to represent an apostrophe within the trap.
+# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug.
+trap 'exit_status=$?
+  # Save into config.log some information that might help in debugging.
+  {
+    echo
+
+    $as_echo "## ---------------- ##
+## Cache variables. ##
+## ---------------- ##"
+    echo
+    # The following way of writing the cache mishandles newlines in values,
+(
+  for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do
+    eval ac_val=\$$ac_var
+    case $ac_val in #(
+    *${as_nl}*)
+      case $ac_var in #(
+      *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+      esac
+      case $ac_var in #(
+      _ | IFS | as_nl) ;; #(
+      BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
+      *) { eval $ac_var=; unset $ac_var;} ;;
+      esac ;;
+    esac
+  done
+  (set) 2>&1 |
+    case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #(
+    *${as_nl}ac_space=\ *)
+      sed -n \
+       "s/'\''/'\''\\\\'\'''\''/g;
+         s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p"
+      ;; #(
+    *)
+      sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+      ;;
+    esac |
+    sort
+)
+    echo
+
+    $as_echo "## ----------------- ##
+## Output variables. ##
+## ----------------- ##"
+    echo
+    for ac_var in $ac_subst_vars
+    do
+      eval ac_val=\$$ac_var
+      case $ac_val in
+      *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+      esac
+      $as_echo "$ac_var='\''$ac_val'\''"
+    done | sort
+    echo
+
+    if test -n "$ac_subst_files"; then
+      $as_echo "## ------------------- ##
+## File substitutions. ##
+## ------------------- ##"
+      echo
+      for ac_var in $ac_subst_files
+      do
+       eval ac_val=\$$ac_var
+       case $ac_val in
+       *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;;
+       esac
+       $as_echo "$ac_var='\''$ac_val'\''"
+      done | sort
+      echo
+    fi
+
+    if test -s confdefs.h; then
+      $as_echo "## ----------- ##
+## confdefs.h. ##
+## ----------- ##"
+      echo
+      cat confdefs.h
+      echo
+    fi
+    test "$ac_signal" != 0 &&
+      $as_echo "$as_me: caught signal $ac_signal"
+    $as_echo "$as_me: exit $exit_status"
+  } >&5
+  rm -f core *.core core.conftest.* &&
+    rm -f -r conftest* confdefs* conf$$* $ac_clean_files &&
+    exit $exit_status
+' 0
+for ac_signal in 1 2 13 15; do
+  trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal
+done
+ac_signal=0
+
+# confdefs.h avoids OS command line length limits that DEFS can exceed.
+rm -f -r conftest* confdefs.h
+
+$as_echo "/* confdefs.h */" > confdefs.h
+
+# Predefined preprocessor variables.
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_NAME "$PACKAGE_NAME"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_TARNAME "$PACKAGE_TARNAME"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_VERSION "$PACKAGE_VERSION"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_STRING "$PACKAGE_STRING"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT"
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
+#define PACKAGE_URL "$PACKAGE_URL"
+_ACEOF
+
+
+# Let the site file select an alternate cache file if it wants to.
+# Prefer an explicitly selected file to automatically selected ones.
+ac_site_file1=NONE
+ac_site_file2=NONE
+if test -n "$CONFIG_SITE"; then
+  # We do not want a PATH search for config.site.
+  case $CONFIG_SITE in #((
+    -*)  ac_site_file1=./$CONFIG_SITE;;
+    */*) ac_site_file1=$CONFIG_SITE;;
+    *)   ac_site_file1=./$CONFIG_SITE;;
+  esac
+elif test "x$prefix" != xNONE; then
+  ac_site_file1=$prefix/share/config.site
+  ac_site_file2=$prefix/etc/config.site
+else
+  ac_site_file1=$ac_default_prefix/share/config.site
+  ac_site_file2=$ac_default_prefix/etc/config.site
+fi
+for ac_site_file in "$ac_site_file1" "$ac_site_file2"
+do
+  test "x$ac_site_file" = xNONE && continue
+  if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5
+$as_echo "$as_me: loading site script $ac_site_file" >&6;}
+    sed 's/^/| /' "$ac_site_file" >&5
+    . "$ac_site_file" \
+      || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "failed to load site script $ac_site_file
+See \`config.log' for more details" "$LINENO" 5; }
+  fi
+done
+
+if test -r "$cache_file"; then
+  # Some versions of bash will fail to source /dev/null (special files
+  # actually), so we avoid doing that.  DJGPP emulates it as a regular file.
+  if test /dev/null != "$cache_file" && test -f "$cache_file"; then
+    { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5
+$as_echo "$as_me: loading cache $cache_file" >&6;}
+    case $cache_file in
+      [\\/]* | ?:[\\/]* ) . "$cache_file";;
+      *)                      . "./$cache_file";;
+    esac
+  fi
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5
+$as_echo "$as_me: creating cache $cache_file" >&6;}
+  >$cache_file
+fi
+
+# Check that the precious variables saved in the cache have kept the same
+# value.
+ac_cache_corrupted=false
+for ac_var in $ac_precious_vars; do
+  eval ac_old_set=\$ac_cv_env_${ac_var}_set
+  eval ac_new_set=\$ac_env_${ac_var}_set
+  eval ac_old_val=\$ac_cv_env_${ac_var}_value
+  eval ac_new_val=\$ac_env_${ac_var}_value
+  case $ac_old_set,$ac_new_set in
+    set,)
+      { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5
+$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;}
+      ac_cache_corrupted=: ;;
+    ,set)
+      { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5
+$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;}
+      ac_cache_corrupted=: ;;
+    ,);;
+    *)
+      if test "x$ac_old_val" != "x$ac_new_val"; then
+       # differences in whitespace do not lead to failure.
+       ac_old_val_w=`echo x $ac_old_val`
+       ac_new_val_w=`echo x $ac_new_val`
+       if test "$ac_old_val_w" != "$ac_new_val_w"; then
+         { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5
+$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;}
+         ac_cache_corrupted=:
+       else
+         { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5
+$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;}
+         eval $ac_var=\$ac_old_val
+       fi
+       { $as_echo "$as_me:${as_lineno-$LINENO}:   former value:  \`$ac_old_val'" >&5
+$as_echo "$as_me:   former value:  \`$ac_old_val'" >&2;}
+       { $as_echo "$as_me:${as_lineno-$LINENO}:   current value: \`$ac_new_val'" >&5
+$as_echo "$as_me:   current value: \`$ac_new_val'" >&2;}
+      fi;;
+  esac
+  # Pass precious variables to config.status.
+  if test "$ac_new_set" = set; then
+    case $ac_new_val in
+    *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;;
+    *) ac_arg=$ac_var=$ac_new_val ;;
+    esac
+    case " $ac_configure_args " in
+      *" '$ac_arg' "*) ;; # Avoid dups.  Use of quotes ensures accuracy.
+      *) as_fn_append ac_configure_args " '$ac_arg'" ;;
+    esac
+  fi
+done
+if $ac_cache_corrupted; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+  { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5
+$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;}
+  as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5
+fi
+## -------------------- ##
+## Main body of script. ##
+## -------------------- ##
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+
+# Package information
+MYLIBVER=9
+MYLIBREV=11
+MYFORMATVER="1.0"
+
+# Targets
+MYHEADERFILES="tcutil.h tchdb.h tcbdb.h tcfdb.h tctdb.h tcadb.h ejdb.h ejdefs.h bson.h"
+MYLIBRARYFILES="libtcejdb.a"
+MYLIBOBJFILES="tcutil.o tchdb.o tcbdb.o tcfdb.o tctdb.o tcadb.o myconf.o md5.o ejdb.o bson.o timsort.o numbers.o encoding.o"
+MYCOMMANDFILES="tcutest tcumttest tcucodec tchtest tchmttest tchmgr"
+MYCOMMANDFILES="$MYCOMMANDFILES tcbtest tcbmttest tcbmgr tcftest tcfmttest tcfmgr"
+MYCOMMANDFILES="$MYCOMMANDFILES tcttest tctmttest tctmgr tcatest tcamttest tcamgr"
+MYCGIFILES="tcawmgr.cgi"
+MYMAN1FILES="tcutest.1 tcumttest.1 tcucodec.1 tchtest.1 tchmttest.1 tchmgr.1"
+MYMAN1FILES="$MYMAN1FILES tcbtest.1 tcbmttest.1 tcbmgr.1 tcftest.1 tcfmttest.1 tcfmgr.1"
+MYMAN1FILES="$MYMAN1FILES tcttest.1 tctmttest.1 tctmgr.1 tcatest.1 tcamttest.1 tcamgr.1"
+MYMAN3FILES="tokyocabinet.3 tcutil.3 tcxstr.3 tclist.3 tcmap.3 tctree.3 tcmdb.3 tcmpool.3"
+MYMAN3FILES="$MYMAN3FILES tchdb.3 tcbdb.3 tcfdb.3 tctdb.3 tcadb.3"
+#MYDOCUMENTFILES="COPYING ChangeLog doc tokyocabinet.idl"
+MYDOCUMENTFILES="COPYING ChangeLog"
+MYPCFILES="tcejdb.pc"
+
+# Building flags
+MYCFLAGS="$MYCFLAGS -std=c99 -Wall -fPIC -fsigned-char -O2"
+MYCPPFLAGS="-I. -I\$(INCLUDEDIR) -I$HOME/include -I/usr/local/include"
+MYCPPFLAGS="$MYCPPFLAGS -DNDEBUG -D_GNU_SOURCE=1 -D_REENTRANT -D__EXTENSIONS__"
+MYLDFLAGS="-L. -L\$(LIBDIR) -L$HOME/lib -L/usr/local/lib"
+MYCMDLDFLAGS=""
+MYRUNPATH="\$(LIBDIR)"
+MYLDLIBPATHENV="LD_LIBRARY_PATH"
+MYPOSTCMD="true"
+
+# Building paths
+PATH="$PATH:$HOME/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin"
+PATH="$PATH:/opt/SUNWspro/bin:/usr/ccs/bin:/usr/xpg4/bin:/usr/xpg6/bin:/usr/ucb"
+CPATH="$HOME/include:/usr/local/include:$CPATH"
+LIBRARY_PATH="$HOME/lib:/usr/local/lib:$LIBRARY_PATH"
+LD_LIBRARY_PATH="$HOME/lib:/usr/local/lib:$LD_LIBRARY_PATH"
+PKG_CONFIG_PATH="$HOME/lib/pkgconfig:/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH"
+export PATH CPATH LIBRARY_PATH LD_LIBRARY_PATH PKG_CONFIG_PATH
+
+
+#================================================================
+# Options
+#================================================================
+
+
+# Internal variables
+enables=""
+
+# Debug mode
+# Check whether --enable-debug was given.
+if test "${enable_debug+set}" = set; then :
+  enableval=$enable_debug;
+fi
+
+if test "$enable_debug" = "yes"
+then
+  MYCFLAGS="-std=c99 -Wall -fPIC -pedantic -fsigned-char -g -O0"
+  MYCPPFLAGS="$MYCPPFLAGS -UNDEBUG"
+  MYCMDLDFLAGS="$MYCMDLDFLAGS -static"
+  enables="$enables (debug)"
+fi
+
+# Developping mode
+# Check whether --enable-devel was given.
+if test "${enable_devel+set}" = set; then :
+  enableval=$enable_devel;
+fi
+
+if test "$enable_devel" = "yes"
+then
+  MYCFLAGS="-std=c99 -Wall -fPIC -pedantic -fsigned-char -g -O2 -pipe"
+  MYCPPFLAGS="$MYCPPFLAGS -UNDEBUG"
+  enables="$enables (devel)"
+fi
+
+# Profiling mode
+# Check whether --enable-profile was given.
+if test "${enable_profile+set}" = set; then :
+  enableval=$enable_profile;
+fi
+
+if test "$enable_profile" = "yes"
+then
+  MYCFLAGS="-std=c99 -Wall -fPIC -pedantic -fsigned-char -g -pg -O2 -pipe"
+  enables="$enables (profile)"
+fi
+
+# Static mode
+# Check whether --enable-static was given.
+if test "${enable_static+set}" = set; then :
+  enableval=$enable_static;
+fi
+
+if test "$enable_static" = "yes"
+then
+  MYCMDLDFLAGS="$MYCMDLDFLAGS -static"
+  enables="$enables (static)"
+fi
+
+# Fastest mode
+# Check whether --enable-fastest was given.
+if test "${enable_fastest+set}" = set; then :
+  enableval=$enable_fastest;
+fi
+
+if test "$enable_fastest" = "yes"
+then
+  MYLIBOBJFILES="tokyocabinet_all.o"
+  MYCFLAGS="-std=c99 -Wall -fPIC -pedantic -fsigned-char -O3"
+  MYCFLAGS="$MYCFLAGS -fomit-frame-pointer -fforce-addr -minline-all-stringops"
+  MYCPPFLAGS="$MYCPPFLAGS -D_MYFASTEST"
+  enables="$enables (fastest)"
+fi
+
+# 64-bit offset mode
+# Check whether --enable-off64 was given.
+if test "${enable_off64+set}" = set; then :
+  enableval=$enable_off64;
+fi
+
+if test "$enable_off64" = "yes"
+then
+  MYCPPFLAGS="$MYCPPFLAGS -D_FILE_OFFSET_BITS=64"
+  enables="$enables (off64)"
+fi
+
+# Swapping byte-orders mode
+# Check whether --enable-swab was given.
+if test "${enable_swab+set}" = set; then :
+  enableval=$enable_swab;
+fi
+
+if test "$enable_swab" = "yes"
+then
+  MYCPPFLAGS="$MYCPPFLAGS -D_MYSWAB"
+  enables="$enables (swab)"
+fi
+
+# Micro yield mode
+# Check whether --enable-uyield was given.
+if test "${enable_uyield+set}" = set; then :
+  enableval=$enable_uyield;
+fi
+
+if test "$enable_uyield" = "yes"
+then
+  MYCPPFLAGS="$MYCPPFLAGS -D_MYMICROYIELD"
+  enables="$enables (uyield)"
+fi
+
+# Disable the unified buffer cache assumption
+# Check whether --enable-ubc was given.
+if test "${enable_ubc+set}" = set; then :
+  enableval=$enable_ubc;
+fi
+
+if test "$enable_ubc" = "no"
+then
+  MYCPPFLAGS="$MYCPPFLAGS -D_MYNOUBC"
+  enables="$enables (no-ubc)"
+fi
+
+# Disable ZLIB compression
+# Check whether --enable-zlib was given.
+if test "${enable_zlib+set}" = set; then :
+  enableval=$enable_zlib;
+fi
+
+if test "$enable_zlib" = "no"
+then
+  MYCPPFLAGS="$MYCPPFLAGS -D_MYNOZLIB"
+  enables="$enables (no-zlib)"
+fi
+
+# Disable BZIP2 compression
+# Check whether --enable-bzip was given.
+if test "${enable_bzip+set}" = set; then :
+  enableval=$enable_bzip;
+fi
+
+if test "$enable_bzip" = "no"
+then
+  MYCPPFLAGS="$MYCPPFLAGS -D_MYNOBZIP"
+  enables="$enables (no-bzip)"
+fi
+
+# Disable POSIX thread
+# Check whether --enable-pthread was given.
+if test "${enable_pthread+set}" = set; then :
+  enableval=$enable_pthread;
+fi
+
+if test "$enable_pthread" = "no"
+then
+  MYCPPFLAGS="$MYCPPFLAGS -D_MYNOPTHREAD"
+  enables="$enables (no-pthread)"
+fi
+
+# Disable shared object
+# Check whether --enable-shared was given.
+if test "${enable_shared+set}" = set; then :
+  enableval=$enable_shared;
+fi
+
+if test "$enable_shared" = "no"
+then
+  enables="$enables (no-shared)"
+fi
+
+# Enable custom codec functions of LZMA
+# Check whether --enable-exlzma was given.
+if test "${enable_exlzma+set}" = set; then :
+  enableval=$enable_exlzma;
+fi
+
+if test "$enable_exlzma" = "yes"
+then
+  MYCPPFLAGS="$MYCPPFLAGS -D_MYEXLZMA"
+  enables="$enables (exlzma)"
+fi
+
+# Enable custom codec functions of LZO
+# Check whether --enable-exlzo was given.
+if test "${enable_exlzo+set}" = set; then :
+  enableval=$enable_exlzo;
+fi
+
+if test "$enable_exlzo" = "yes"
+then
+  MYCPPFLAGS="$MYCPPFLAGS -D_MYEXLZO"
+  enables="$enables (exlzo)"
+fi
+
+# Specify the installation path of ZLIB
+
+# Check whether --with-zlib was given.
+if test "${with_zlib+set}" = set; then :
+  withval=$with_zlib;
+fi
+
+if test -n "$with_zlib"
+then
+  MYCPPFLAGS="$MYCPPFLAGS -I$with_zlib/include"
+  MYLDFLAGS="$MYLDFLAGS -L$with_zlib/lib"
+  MYRUNPATH="$MYRUNPATH:$with_zlib/lib"
+  CPATH="$CPATH:$with_zlib/include"
+  LIBRARY_PATH="$LIBRARY_PATH:$with_zlib/lib"
+  LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$with_zlib/lib"
+fi
+
+# Specify the installation path of BZIP2
+
+# Check whether --with-bzip was given.
+if test "${with_bzip+set}" = set; then :
+  withval=$with_bzip;
+fi
+
+if test -n "$with_bzip"
+then
+  MYCPPFLAGS="$MYCPPFLAGS -I$with_bzip/include"
+  MYLDFLAGS="$MYLDFLAGS -L$with_bzip/lib"
+  MYRUNPATH="$MYRUNPATH:$with_bzip/lib"
+  CPATH="$CPATH:$with_bzip/include"
+  LIBRARY_PATH="$LIBRARY_PATH:$with_bzip/lib"
+  LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$with_bzip/lib"
+fi
+
+# Messages
+printf '#================================================================\n'
+printf '# Configuring Tokyo Cabinet version %s%s.\n' "$PACKAGE_VERSION" "$enables"
+printf '#================================================================\n'
+
+
+
+#================================================================
+# Checking Commands and Libraries
+#================================================================
+
+# C compiler
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+if test -n "$ac_tool_prefix"; then
+  # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}gcc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_CC="${ac_tool_prefix}gcc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_CC"; then
+  ac_ct_CC=$CC
+  # Extract the first word of "gcc", so it can be a program name with args.
+set dummy gcc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_CC"; then
+  ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_CC="gcc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+$as_echo "$ac_ct_CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+  if test "x$ac_ct_CC" = x; then
+    CC=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    CC=$ac_ct_CC
+  fi
+else
+  CC="$ac_cv_prog_CC"
+fi
+
+if test -z "$CC"; then
+          if test -n "$ac_tool_prefix"; then
+    # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args.
+set dummy ${ac_tool_prefix}cc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_CC="${ac_tool_prefix}cc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  fi
+fi
+if test -z "$CC"; then
+  # Extract the first word of "cc", so it can be a program name with args.
+set dummy cc; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+  ac_prog_rejected=no
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then
+       ac_prog_rejected=yes
+       continue
+     fi
+    ac_cv_prog_CC="cc"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+if test $ac_prog_rejected = yes; then
+  # We found a bogon in the path, so make sure we never use it.
+  set dummy $ac_cv_prog_CC
+  shift
+  if test $# != 0; then
+    # We chose a different compiler from the bogus one.
+    # However, it has the same basename, so the bogon will be chosen
+    # first if we set CC to just the basename; use the full file name.
+    shift
+    ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@"
+  fi
+fi
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$CC"; then
+  if test -n "$ac_tool_prefix"; then
+  for ac_prog in cl.exe
+  do
+    # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$CC"; then
+  ac_cv_prog_CC="$CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_CC="$ac_tool_prefix$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+CC=$ac_cv_prog_CC
+if test -n "$CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5
+$as_echo "$CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+    test -n "$CC" && break
+  done
+fi
+if test -z "$CC"; then
+  ac_ct_CC=$CC
+  for ac_prog in cl.exe
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_CC+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$ac_ct_CC"; then
+  ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_ac_ct_CC="$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CC=$ac_cv_prog_ac_ct_CC
+if test -n "$ac_ct_CC"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5
+$as_echo "$ac_ct_CC" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$ac_ct_CC" && break
+done
+
+  if test "x$ac_ct_CC" = x; then
+    CC=""
+  else
+    case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+    CC=$ac_ct_CC
+  fi
+fi
+
+fi
+
+
+test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "no acceptable C compiler found in \$PATH
+See \`config.log' for more details" "$LINENO" 5; }
+
+# Provide some information about the compiler.
+$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5
+set X $ac_compile
+ac_compiler=$2
+for ac_option in --version -v -V -qversion; do
+  { { ac_try="$ac_compiler $ac_option >&5"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compiler $ac_option >&5") 2>conftest.err
+  ac_status=$?
+  if test -s conftest.err; then
+    sed '10a\
+... rest of stderr output deleted ...
+         10q' conftest.err >conftest.er1
+    cat conftest.er1 >&5
+  fi
+  rm -f conftest.er1 conftest.err
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+done
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out"
+# Try to create an executable without -o first, disregard a.out.
+# It will help us diagnose broken compilers, and finding out an intuition
+# of exeext.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5
+$as_echo_n "checking whether the C compiler works... " >&6; }
+ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'`
+
+# The possible output files:
+ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*"
+
+ac_rmfiles=
+for ac_file in $ac_files
+do
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
+    * ) ac_rmfiles="$ac_rmfiles $ac_file";;
+  esac
+done
+rm -f $ac_rmfiles
+
+if { { ac_try="$ac_link_default"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link_default") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then :
+  # Autoconf-2.13 could set the ac_cv_exeext variable to `no'.
+# So ignore a value of `no', otherwise this would lead to `EXEEXT = no'
+# in a Makefile.  We should not override ac_cv_exeext if it was cached,
+# so that the user can short-circuit this test for compilers unknown to
+# Autoconf.
+for ac_file in $ac_files ''
+do
+  test -f "$ac_file" || continue
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj )
+       ;;
+    [ab].out )
+       # We found the default executable, but exeext='' is most
+       # certainly right.
+       break;;
+    *.* )
+       if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no;
+       then :; else
+          ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+       fi
+       # We set ac_cv_exeext here because the later test for it is not
+       # safe: cross compilers may not add the suffix if given an `-o'
+       # argument, so we may need to know it at that point already.
+       # Even if this section looks crufty: it has the advantage of
+       # actually working.
+       break;;
+    * )
+       break;;
+  esac
+done
+test "$ac_cv_exeext" = no && ac_cv_exeext=
+
+else
+  ac_file=''
+fi
+if test -z "$ac_file"; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+$as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "C compiler cannot create executables
+See \`config.log' for more details" "$LINENO" 5; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5
+$as_echo_n "checking for C compiler default output file name... " >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5
+$as_echo "$ac_file" >&6; }
+ac_exeext=$ac_cv_exeext
+
+rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out
+ac_clean_files=$ac_clean_files_save
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5
+$as_echo_n "checking for suffix of executables... " >&6; }
+if { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then :
+  # If both `conftest.exe' and `conftest' are `present' (well, observable)
+# catch `conftest.exe'.  For instance with Cygwin, `ls conftest' will
+# work properly (i.e., refer to `conftest.exe'), while it won't with
+# `rm'.
+for ac_file in conftest.exe conftest conftest.*; do
+  test -f "$ac_file" || continue
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;;
+    *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'`
+         break;;
+    * ) break;;
+  esac
+done
+else
+  { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot compute suffix of executables: cannot compile and link
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest conftest$ac_cv_exeext
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5
+$as_echo "$ac_cv_exeext" >&6; }
+
+rm -f conftest.$ac_ext
+EXEEXT=$ac_cv_exeext
+ac_exeext=$EXEEXT
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdio.h>
+int
+main ()
+{
+FILE *f = fopen ("conftest.out", "w");
+ return ferror (f) || fclose (f) != 0;
+
+  ;
+  return 0;
+}
+_ACEOF
+ac_clean_files="$ac_clean_files conftest.out"
+# Check that the compiler produces executables we can run.  If not, either
+# the compiler is broken, or we cross compile.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5
+$as_echo_n "checking whether we are cross compiling... " >&6; }
+if test "$cross_compiling" != yes; then
+  { { ac_try="$ac_link"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_link") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }
+  if { ac_try='./conftest$ac_cv_exeext'
+  { { case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_try") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }; then
+    cross_compiling=no
+  else
+    if test "$cross_compiling" = maybe; then
+       cross_compiling=yes
+    else
+       { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot run C compiled programs.
+If you meant to cross compile, use \`--host'.
+See \`config.log' for more details" "$LINENO" 5; }
+    fi
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5
+$as_echo "$cross_compiling" >&6; }
+
+rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out
+ac_clean_files=$ac_clean_files_save
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5
+$as_echo_n "checking for suffix of object files... " >&6; }
+if ${ac_cv_objext+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.o conftest.obj
+if { { ac_try="$ac_compile"
+case "(($ac_try" in
+  *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+  *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+  (eval "$ac_compile") 2>&5
+  ac_status=$?
+  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; then :
+  for ac_file in conftest.o conftest.obj conftest.*; do
+  test -f "$ac_file" || continue;
+  case $ac_file in
+    *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;;
+    *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'`
+       break;;
+  esac
+done
+else
+  $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "cannot compute suffix of object files: cannot compile
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+rm -f conftest.$ac_cv_objext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5
+$as_echo "$ac_cv_objext" >&6; }
+OBJEXT=$ac_cv_objext
+ac_objext=$OBJEXT
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5
+$as_echo_n "checking whether we are using the GNU C compiler... " >&6; }
+if ${ac_cv_c_compiler_gnu+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+#ifndef __GNUC__
+       choke me
+#endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_compiler_gnu=yes
+else
+  ac_compiler_gnu=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_cv_c_compiler_gnu=$ac_compiler_gnu
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5
+$as_echo "$ac_cv_c_compiler_gnu" >&6; }
+if test $ac_compiler_gnu = yes; then
+  GCC=yes
+else
+  GCC=
+fi
+ac_test_CFLAGS=${CFLAGS+set}
+ac_save_CFLAGS=$CFLAGS
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5
+$as_echo_n "checking whether $CC accepts -g... " >&6; }
+if ${ac_cv_prog_cc_g+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_save_c_werror_flag=$ac_c_werror_flag
+   ac_c_werror_flag=yes
+   ac_cv_prog_cc_g=no
+   CFLAGS="-g"
+   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_g=yes
+else
+  CFLAGS=""
+      cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+else
+  ac_c_werror_flag=$ac_save_c_werror_flag
+        CFLAGS="-g"
+        cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_g=yes
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+   ac_c_werror_flag=$ac_save_c_werror_flag
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5
+$as_echo "$ac_cv_prog_cc_g" >&6; }
+if test "$ac_test_CFLAGS" = set; then
+  CFLAGS=$ac_save_CFLAGS
+elif test $ac_cv_prog_cc_g = yes; then
+  if test "$GCC" = yes; then
+    CFLAGS="-g -O2"
+  else
+    CFLAGS="-g"
+  fi
+else
+  if test "$GCC" = yes; then
+    CFLAGS="-O2"
+  else
+    CFLAGS=
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5
+$as_echo_n "checking for $CC option to accept ISO C89... " >&6; }
+if ${ac_cv_prog_cc_c89+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_cv_prog_cc_c89=no
+ac_save_CC=$CC
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdarg.h>
+#include <stdio.h>
+struct stat;
+/* Most of the following tests are stolen from RCS 5.7's src/conf.sh.  */
+struct buf { int x; };
+FILE * (*rcsopen) (struct buf *, struct stat *, int);
+static char *e (p, i)
+     char **p;
+     int i;
+{
+  return p[i];
+}
+static char *f (char * (*g) (char **, int), char **p, ...)
+{
+  char *s;
+  va_list v;
+  va_start (v,p);
+  s = g (p, va_arg (v,int));
+  va_end (v);
+  return s;
+}
+
+/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default.  It has
+   function prototypes and stuff, but not '\xHH' hex character constants.
+   These don't provoke an error unfortunately, instead are silently treated
+   as 'x'.  The following induces an error, until -std is added to get
+   proper ANSI mode.  Curiously '\x00'!='x' always comes out true, for an
+   array size at least.  It's necessary to write '\x00'==0 to get something
+   that's true only with -std.  */
+int osf4_cc_array ['\x00' == 0 ? 1 : -1];
+
+/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters
+   inside strings and character constants.  */
+#define FOO(x) 'x'
+int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1];
+
+int test (int i, double x);
+struct s1 {int (*f) (int a);};
+struct s2 {int (*f) (double a);};
+int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int);
+int argc;
+char **argv;
+int
+main ()
+{
+return f (e, argv, 0) != argv[0]  ||  f (e, argv, 1) != argv[1];
+  ;
+  return 0;
+}
+_ACEOF
+for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \
+       -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__"
+do
+  CC="$ac_save_CC $ac_arg"
+  if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_prog_cc_c89=$ac_arg
+fi
+rm -f core conftest.err conftest.$ac_objext
+  test "x$ac_cv_prog_cc_c89" != "xno" && break
+done
+rm -f conftest.$ac_ext
+CC=$ac_save_CC
+
+fi
+# AC_CACHE_VAL
+case "x$ac_cv_prog_cc_c89" in
+  x)
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5
+$as_echo "none needed" >&6; } ;;
+  xno)
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
+$as_echo "unsupported" >&6; } ;;
+  *)
+    CC="$CC $ac_cv_prog_cc_c89"
+    { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5
+$as_echo "$ac_cv_prog_cc_c89" >&6; } ;;
+esac
+if test "x$ac_cv_prog_cc_c89" != xno; then :
+
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+# Reset variables
+if test "$GCC" != "yes"
+then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: another compiler except for GCC was detected" >&5
+$as_echo "$as_me: WARNING: another compiler except for GCC was detected" >&2;}
+  MYCFLAGS=""
+fi
+
+# Byte order
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking how to run the C preprocessor" >&5
+$as_echo_n "checking how to run the C preprocessor... " >&6; }
+# On Suns, sometimes $CPP names a directory.
+if test -n "$CPP" && test -d "$CPP"; then
+  CPP=
+fi
+if test -z "$CPP"; then
+  if ${ac_cv_prog_CPP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+      # Double quotes because CPP needs to be expanded
+    for CPP in "$CC -E" "$CC -E -traditional-cpp" "/lib/cpp"
+    do
+      ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+  # Use a header file that comes with gcc, so configuring glibc
+  # with a fresh cross-compiler works.
+  # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+  # <limits.h> exists even on freestanding compilers.
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp. "Syntax error" is here to catch this case.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+                    Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+
+else
+  # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+  # OK, works on sane cases.  Now check whether nonexistent headers
+  # can be detected and how.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+  # Broken: success on invalid input.
+continue
+else
+  # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+  break
+fi
+
+    done
+    ac_cv_prog_CPP=$CPP
+
+fi
+  CPP=$ac_cv_prog_CPP
+else
+  ac_cv_prog_CPP=$CPP
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $CPP" >&5
+$as_echo "$CPP" >&6; }
+ac_preproc_ok=false
+for ac_c_preproc_warn_flag in '' yes
+do
+  # Use a header file that comes with gcc, so configuring glibc
+  # with a fresh cross-compiler works.
+  # Prefer <limits.h> to <assert.h> if __STDC__ is defined, since
+  # <limits.h> exists even on freestanding compilers.
+  # On the NeXT, cc -E runs the code through the compiler's parser,
+  # not just through cpp. "Syntax error" is here to catch this case.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#ifdef __STDC__
+# include <limits.h>
+#else
+# include <assert.h>
+#endif
+                    Syntax error
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+
+else
+  # Broken: fails on valid input.
+continue
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+  # OK, works on sane cases.  Now check whether nonexistent headers
+  # can be detected and how.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ac_nonexistent.h>
+_ACEOF
+if ac_fn_c_try_cpp "$LINENO"; then :
+  # Broken: success on invalid input.
+continue
+else
+  # Passes both tests.
+ac_preproc_ok=:
+break
+fi
+rm -f conftest.err conftest.i conftest.$ac_ext
+
+done
+# Because of `break', _AC_PREPROC_IFELSE's cleaning code was skipped.
+rm -f conftest.i conftest.err conftest.$ac_ext
+if $ac_preproc_ok; then :
+
+else
+  { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error $? "C preprocessor \"$CPP\" fails sanity check
+See \`config.log' for more details" "$LINENO" 5; }
+fi
+
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5
+$as_echo_n "checking for grep that handles long lines and -e... " >&6; }
+if ${ac_cv_path_GREP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -z "$GREP"; then
+  ac_path_GREP_found=false
+  # Loop through the user's path and test for each of PROGNAME-LIST
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_prog in grep ggrep; do
+    for ac_exec_ext in '' $ac_executable_extensions; do
+      ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext"
+      as_fn_executable_p "$ac_path_GREP" || continue
+# Check for GNU ac_path_GREP and select it if it is found.
+  # Check for GNU $ac_path_GREP
+case `"$ac_path_GREP" --version 2>&1` in
+*GNU*)
+  ac_cv_path_GREP="$ac_path_GREP" ac_path_GREP_found=:;;
+*)
+  ac_count=0
+  $as_echo_n 0123456789 >"conftest.in"
+  while :
+  do
+    cat "conftest.in" "conftest.in" >"conftest.tmp"
+    mv "conftest.tmp" "conftest.in"
+    cp "conftest.in" "conftest.nl"
+    $as_echo 'GREP' >> "conftest.nl"
+    "$ac_path_GREP" -e 'GREP$' -e '-(cannot match)-' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+    diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+    as_fn_arith $ac_count + 1 && ac_count=$as_val
+    if test $ac_count -gt ${ac_path_GREP_max-0}; then
+      # Best one so far, save it but keep looking for a better one
+      ac_cv_path_GREP="$ac_path_GREP"
+      ac_path_GREP_max=$ac_count
+    fi
+    # 10*(2^10) chars as input seems more than enough
+    test $ac_count -gt 10 && break
+  done
+  rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+      $ac_path_GREP_found && break 3
+    done
+  done
+  done
+IFS=$as_save_IFS
+  if test -z "$ac_cv_path_GREP"; then
+    as_fn_error $? "no acceptable grep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+  fi
+else
+  ac_cv_path_GREP=$GREP
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_GREP" >&5
+$as_echo "$ac_cv_path_GREP" >&6; }
+ GREP="$ac_cv_path_GREP"
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for egrep" >&5
+$as_echo_n "checking for egrep... " >&6; }
+if ${ac_cv_path_EGREP+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if echo a | $GREP -E '(a|b)' >/dev/null 2>&1
+   then ac_cv_path_EGREP="$GREP -E"
+   else
+     if test -z "$EGREP"; then
+  ac_path_EGREP_found=false
+  # Loop through the user's path and test for each of PROGNAME-LIST
+  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH$PATH_SEPARATOR/usr/xpg4/bin
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_prog in egrep; do
+    for ac_exec_ext in '' $ac_executable_extensions; do
+      ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext"
+      as_fn_executable_p "$ac_path_EGREP" || continue
+# Check for GNU ac_path_EGREP and select it if it is found.
+  # Check for GNU $ac_path_EGREP
+case `"$ac_path_EGREP" --version 2>&1` in
+*GNU*)
+  ac_cv_path_EGREP="$ac_path_EGREP" ac_path_EGREP_found=:;;
+*)
+  ac_count=0
+  $as_echo_n 0123456789 >"conftest.in"
+  while :
+  do
+    cat "conftest.in" "conftest.in" >"conftest.tmp"
+    mv "conftest.tmp" "conftest.in"
+    cp "conftest.in" "conftest.nl"
+    $as_echo 'EGREP' >> "conftest.nl"
+    "$ac_path_EGREP" 'EGREP$' < "conftest.nl" >"conftest.out" 2>/dev/null || break
+    diff "conftest.out" "conftest.nl" >/dev/null 2>&1 || break
+    as_fn_arith $ac_count + 1 && ac_count=$as_val
+    if test $ac_count -gt ${ac_path_EGREP_max-0}; then
+      # Best one so far, save it but keep looking for a better one
+      ac_cv_path_EGREP="$ac_path_EGREP"
+      ac_path_EGREP_max=$ac_count
+    fi
+    # 10*(2^10) chars as input seems more than enough
+    test $ac_count -gt 10 && break
+  done
+  rm -f conftest.in conftest.tmp conftest.nl conftest.out;;
+esac
+
+      $ac_path_EGREP_found && break 3
+    done
+  done
+  done
+IFS=$as_save_IFS
+  if test -z "$ac_cv_path_EGREP"; then
+    as_fn_error $? "no acceptable egrep could be found in $PATH$PATH_SEPARATOR/usr/xpg4/bin" "$LINENO" 5
+  fi
+else
+  ac_cv_path_EGREP=$EGREP
+fi
+
+   fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_path_EGREP" >&5
+$as_echo "$ac_cv_path_EGREP" >&6; }
+ EGREP="$ac_cv_path_EGREP"
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for ANSI C header files" >&5
+$as_echo_n "checking for ANSI C header files... " >&6; }
+if ${ac_cv_header_stdc+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <float.h>
+
+int
+main ()
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_header_stdc=yes
+else
+  ac_cv_header_stdc=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+
+if test $ac_cv_header_stdc = yes; then
+  # SunOS 4.x string.h does not declare mem*, contrary to ANSI.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <string.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  $EGREP "memchr" >/dev/null 2>&1; then :
+
+else
+  ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+  # ISC 2.0.2 stdlib.h does not declare free, contrary to ANSI.
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <stdlib.h>
+
+_ACEOF
+if (eval "$ac_cpp conftest.$ac_ext") 2>&5 |
+  $EGREP "free" >/dev/null 2>&1; then :
+
+else
+  ac_cv_header_stdc=no
+fi
+rm -f conftest*
+
+fi
+
+if test $ac_cv_header_stdc = yes; then
+  # /bin/cc in Irix-4.0.5 gets non-ANSI ctype macros unless using -ansi.
+  if test "$cross_compiling" = yes; then :
+  :
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <ctype.h>
+#include <stdlib.h>
+#if ((' ' & 0x0FF) == 0x020)
+# define ISLOWER(c) ('a' <= (c) && (c) <= 'z')
+# define TOUPPER(c) (ISLOWER(c) ? 'A' + ((c) - 'a') : (c))
+#else
+# define ISLOWER(c) \
+                  (('a' <= (c) && (c) <= 'i') \
+                    || ('j' <= (c) && (c) <= 'r') \
+                    || ('s' <= (c) && (c) <= 'z'))
+# define TOUPPER(c) (ISLOWER(c) ? ((c) | 0x40) : (c))
+#endif
+
+#define XOR(e, f) (((e) && !(f)) || (!(e) && (f)))
+int
+main ()
+{
+  int i;
+  for (i = 0; i < 256; i++)
+    if (XOR (islower (i), ISLOWER (i))
+       || toupper (i) != TOUPPER (i))
+      return 2;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+
+else
+  ac_cv_header_stdc=no
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+  conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_header_stdc" >&5
+$as_echo "$ac_cv_header_stdc" >&6; }
+if test $ac_cv_header_stdc = yes; then
+
+$as_echo "#define STDC_HEADERS 1" >>confdefs.h
+
+fi
+
+# On IRIX 5.3, sys/types and inttypes.h are conflicting.
+for ac_header in sys/types.h sys/stat.h stdlib.h string.h memory.h strings.h \
+                 inttypes.h stdint.h unistd.h
+do :
+  as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh`
+ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default
+"
+if eval test \"x\$"$as_ac_Header"\" = x"yes"; then :
+  cat >>confdefs.h <<_ACEOF
+#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1
+_ACEOF
+
+fi
+
+done
+
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether byte ordering is bigendian" >&5
+$as_echo_n "checking whether byte ordering is bigendian... " >&6; }
+if ${ac_cv_c_bigendian+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_cv_c_bigendian=unknown
+    # See if we're dealing with a universal compiler.
+    cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#ifndef __APPLE_CC__
+              not a universal capable compiler
+            #endif
+            typedef int dummy;
+
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+
+       # Check for potential -arch flags.  It is not universal unless
+       # there are at least two -arch flags with different values.
+       ac_arch=
+       ac_prev=
+       for ac_word in $CC $CFLAGS $CPPFLAGS $LDFLAGS; do
+        if test -n "$ac_prev"; then
+          case $ac_word in
+            i?86 | x86_64 | ppc | ppc64)
+              if test -z "$ac_arch" || test "$ac_arch" = "$ac_word"; then
+                ac_arch=$ac_word
+              else
+                ac_cv_c_bigendian=universal
+                break
+              fi
+              ;;
+          esac
+          ac_prev=
+        elif test "x$ac_word" = "x-arch"; then
+          ac_prev=arch
+        fi
+       done
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+    if test $ac_cv_c_bigendian = unknown; then
+      # See if sys/param.h defines the BYTE_ORDER macro.
+      cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <sys/types.h>
+            #include <sys/param.h>
+
+int
+main ()
+{
+#if ! (defined BYTE_ORDER && defined BIG_ENDIAN \
+                    && defined LITTLE_ENDIAN && BYTE_ORDER && BIG_ENDIAN \
+                    && LITTLE_ENDIAN)
+             bogus endian macros
+            #endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  # It does; now see whether it defined to BIG_ENDIAN or not.
+        cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <sys/types.h>
+               #include <sys/param.h>
+
+int
+main ()
+{
+#if BYTE_ORDER != BIG_ENDIAN
+                not big endian
+               #endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_c_bigendian=yes
+else
+  ac_cv_c_bigendian=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+    fi
+    if test $ac_cv_c_bigendian = unknown; then
+      # See if <limits.h> defines _LITTLE_ENDIAN or _BIG_ENDIAN (e.g., Solaris).
+      cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <limits.h>
+
+int
+main ()
+{
+#if ! (defined _LITTLE_ENDIAN || defined _BIG_ENDIAN)
+             bogus endian macros
+            #endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  # It does; now see whether it defined to _BIG_ENDIAN or not.
+        cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+#include <limits.h>
+
+int
+main ()
+{
+#ifndef _BIG_ENDIAN
+                not big endian
+               #endif
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  ac_cv_c_bigendian=yes
+else
+  ac_cv_c_bigendian=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+    fi
+    if test $ac_cv_c_bigendian = unknown; then
+      # Compile a test program.
+      if test "$cross_compiling" = yes; then :
+  # Try to guess by grepping values from an object file.
+        cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+short int ascii_mm[] =
+                 { 0x4249, 0x4765, 0x6E44, 0x6961, 0x6E53, 0x7953, 0 };
+               short int ascii_ii[] =
+                 { 0x694C, 0x5454, 0x656C, 0x6E45, 0x6944, 0x6E61, 0 };
+               int use_ascii (int i) {
+                 return ascii_mm[i] + ascii_ii[i];
+               }
+               short int ebcdic_ii[] =
+                 { 0x89D3, 0xE3E3, 0x8593, 0x95C5, 0x89C4, 0x9581, 0 };
+               short int ebcdic_mm[] =
+                 { 0xC2C9, 0xC785, 0x95C4, 0x8981, 0x95E2, 0xA8E2, 0 };
+               int use_ebcdic (int i) {
+                 return ebcdic_mm[i] + ebcdic_ii[i];
+               }
+               extern int foo;
+
+int
+main ()
+{
+return use_ascii (foo) == use_ebcdic (foo);
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  if grep BIGenDianSyS conftest.$ac_objext >/dev/null; then
+             ac_cv_c_bigendian=yes
+           fi
+           if grep LiTTleEnDian conftest.$ac_objext >/dev/null ; then
+             if test "$ac_cv_c_bigendian" = unknown; then
+               ac_cv_c_bigendian=no
+             else
+               # finding both strings is unlikely to happen, but who knows?
+               ac_cv_c_bigendian=unknown
+             fi
+           fi
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+else
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+$ac_includes_default
+int
+main ()
+{
+
+            /* Are we little or big endian?  From Harbison&Steele.  */
+            union
+            {
+              long int l;
+              char c[sizeof (long int)];
+            } u;
+            u.l = 1;
+            return u.c[sizeof (long int) - 1] == 1;
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+  ac_cv_c_bigendian=no
+else
+  ac_cv_c_bigendian=yes
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+  conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+    fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_bigendian" >&5
+$as_echo "$ac_cv_c_bigendian" >&6; }
+ case $ac_cv_c_bigendian in #(
+   yes)
+     MYCPPFLAGS="$MYCPPFLAGS -D_MYBIGEND";; #(
+   no)
+      ;; #(
+   universal)
+
+$as_echo "#define AC_APPLE_UNIVERSAL_BUILD 1" >>confdefs.h
+
+     ;; #(
+   *)
+     as_fn_error $? "unknown endianness
+ presetting ac_cv_c_bigendian=no (or yes) will help" "$LINENO" 5 ;;
+ esac
+
+
+# Underlying libraries
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for main in -lc" >&5
+$as_echo_n "checking for main in -lc... " >&6; }
+if ${ac_cv_lib_c_main+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lc  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+
+int
+main ()
+{
+return main ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_c_main=yes
+else
+  ac_cv_lib_c_main=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_c_main" >&5
+$as_echo "$ac_cv_lib_c_main" >&6; }
+if test "x$ac_cv_lib_c_main" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBC 1
+_ACEOF
+
+  LIBS="-lc $LIBS"
+
+fi
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for main in -lm" >&5
+$as_echo_n "checking for main in -lm... " >&6; }
+if ${ac_cv_lib_m_main+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lm  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+
+int
+main ()
+{
+return main ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_m_main=yes
+else
+  ac_cv_lib_m_main=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_m_main" >&5
+$as_echo "$ac_cv_lib_m_main" >&6; }
+if test "x$ac_cv_lib_m_main" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBM 1
+_ACEOF
+
+  LIBS="-lm $LIBS"
+
+fi
+
+if test "$enable_pthread" != "no"
+then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for main in -lpthread" >&5
+$as_echo_n "checking for main in -lpthread... " >&6; }
+if ${ac_cv_lib_pthread_main+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lpthread  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+
+int
+main ()
+{
+return main ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_pthread_main=yes
+else
+  ac_cv_lib_pthread_main=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthread_main" >&5
+$as_echo "$ac_cv_lib_pthread_main" >&6; }
+if test "x$ac_cv_lib_pthread_main" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBPTHREAD 1
+_ACEOF
+
+  LIBS="-lpthread $LIBS"
+
+fi
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for main in -lrt" >&5
+$as_echo_n "checking for main in -lrt... " >&6; }
+if ${ac_cv_lib_rt_main+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lrt  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+
+int
+main ()
+{
+return main ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_rt_main=yes
+else
+  ac_cv_lib_rt_main=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_rt_main" >&5
+$as_echo "$ac_cv_lib_rt_main" >&6; }
+if test "x$ac_cv_lib_rt_main" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBRT 1
+_ACEOF
+
+  LIBS="-lrt $LIBS"
+
+fi
+
+fi
+if test "$enable_zlib" != "no"
+then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for main in -lz" >&5
+$as_echo_n "checking for main in -lz... " >&6; }
+if ${ac_cv_lib_z_main+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lz  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+
+int
+main ()
+{
+return main ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_z_main=yes
+else
+  ac_cv_lib_z_main=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_z_main" >&5
+$as_echo "$ac_cv_lib_z_main" >&6; }
+if test "x$ac_cv_lib_z_main" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBZ 1
+_ACEOF
+
+  LIBS="-lz $LIBS"
+
+fi
+
+fi
+if test "$enable_bzip" != "no"
+then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for main in -lbz2" >&5
+$as_echo_n "checking for main in -lbz2... " >&6; }
+if ${ac_cv_lib_bz2_main+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lbz2  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+
+int
+main ()
+{
+return main ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_bz2_main=yes
+else
+  ac_cv_lib_bz2_main=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_bz2_main" >&5
+$as_echo "$ac_cv_lib_bz2_main" >&6; }
+if test "x$ac_cv_lib_bz2_main" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBBZ2 1
+_ACEOF
+
+  LIBS="-lbz2 $LIBS"
+
+fi
+
+fi
+if test "$enable_exlzma" = "yes"
+then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for main in -llzma" >&5
+$as_echo_n "checking for main in -llzma... " >&6; }
+if ${ac_cv_lib_lzma_main+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-llzma  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+
+int
+main ()
+{
+return main ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_lzma_main=yes
+else
+  ac_cv_lib_lzma_main=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_lzma_main" >&5
+$as_echo "$ac_cv_lib_lzma_main" >&6; }
+if test "x$ac_cv_lib_lzma_main" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBLZMA 1
+_ACEOF
+
+  LIBS="-llzma $LIBS"
+
+fi
+
+fi
+if test "$enable_exlzo" = "yes"
+then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for main in -llzo2" >&5
+$as_echo_n "checking for main in -llzo2... " >&6; }
+if ${ac_cv_lib_lzo2_main+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-llzo2  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+
+int
+main ()
+{
+return main ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_lzo2_main=yes
+else
+  ac_cv_lib_lzo2_main=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_lzo2_main" >&5
+$as_echo "$ac_cv_lib_lzo2_main" >&6; }
+if test "x$ac_cv_lib_lzo2_main" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBLZO2 1
+_ACEOF
+
+  LIBS="-llzo2 $LIBS"
+
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for main in -ltcejdb" >&5
+$as_echo_n "checking for main in -ltcejdb... " >&6; }
+if ${ac_cv_lib_tcejdb_main+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-ltcejdb  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+
+int
+main ()
+{
+return main ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_tcejdb_main=yes
+else
+  ac_cv_lib_tcejdb_main=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_tcejdb_main" >&5
+$as_echo "$ac_cv_lib_tcejdb_main" >&6; }
+if test "x$ac_cv_lib_tcejdb_main" = xyes; then :
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: old version of EJDB was found" >&5
+$as_echo "$as_me: WARNING: old version of EJDB was found" >&2;}
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for main in -lcunit" >&5
+$as_echo_n "checking for main in -lcunit... " >&6; }
+if ${ac_cv_lib_cunit_main+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lcunit  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+
+int
+main ()
+{
+return main ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_cunit_main=yes
+else
+  ac_cv_lib_cunit_main=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_cunit_main" >&5
+$as_echo "$ac_cv_lib_cunit_main" >&6; }
+if test "x$ac_cv_lib_cunit_main" = xyes; then :
+  true
+else
+  as_fn_error $? "CUnit lubrary is required" "$LINENO" 5
+fi
+
+
+# Necessary headers
+ac_fn_c_check_header_mongrel "$LINENO" "stdlib.h" "ac_cv_header_stdlib_h" "$ac_includes_default"
+if test "x$ac_cv_header_stdlib_h" = xyes; then :
+  true
+else
+  as_fn_error $? "stdlib.h is required" "$LINENO" 5
+fi
+
+
+ac_fn_c_check_header_mongrel "$LINENO" "stdint.h" "ac_cv_header_stdint_h" "$ac_includes_default"
+if test "x$ac_cv_header_stdint_h" = xyes; then :
+  true
+else
+  as_fn_error $? "stdint.h is required" "$LINENO" 5
+fi
+
+
+ac_fn_c_check_header_mongrel "$LINENO" "unistd.h" "ac_cv_header_unistd_h" "$ac_includes_default"
+if test "x$ac_cv_header_unistd_h" = xyes; then :
+  true
+else
+  as_fn_error $? "unistd.h is required" "$LINENO" 5
+fi
+
+
+ac_fn_c_check_header_mongrel "$LINENO" "dirent.h" "ac_cv_header_dirent_h" "$ac_includes_default"
+if test "x$ac_cv_header_dirent_h" = xyes; then :
+  true
+else
+  as_fn_error $? "dirent.h is required" "$LINENO" 5
+fi
+
+
+ac_fn_c_check_header_mongrel "$LINENO" "regex.h" "ac_cv_header_regex_h" "$ac_includes_default"
+if test "x$ac_cv_header_regex_h" = xyes; then :
+  true
+else
+  as_fn_error $? "regex.h is required" "$LINENO" 5
+fi
+
+
+ac_fn_c_check_header_mongrel "$LINENO" "glob.h" "ac_cv_header_glob_h" "$ac_includes_default"
+if test "x$ac_cv_header_glob_h" = xyes; then :
+  true
+else
+  as_fn_error $? "glob.h is required" "$LINENO" 5
+fi
+
+
+if test "$enable_pthread" != "no"
+then
+  ac_fn_c_check_header_mongrel "$LINENO" "pthread.h" "ac_cv_header_pthread_h" "$ac_includes_default"
+if test "x$ac_cv_header_pthread_h" = xyes; then :
+  true
+else
+  as_fn_error $? "pthread.h is required" "$LINENO" 5
+fi
+
+
+fi
+if test "$enable_zlib" != "no"
+then
+  ac_fn_c_check_header_mongrel "$LINENO" "zlib.h" "ac_cv_header_zlib_h" "$ac_includes_default"
+if test "x$ac_cv_header_zlib_h" = xyes; then :
+  true
+else
+  as_fn_error $? "zlib.h is required" "$LINENO" 5
+fi
+
+
+fi
+if test "$enable_bzip" != "no"
+then
+  ac_fn_c_check_header_mongrel "$LINENO" "bzlib.h" "ac_cv_header_bzlib_h" "$ac_includes_default"
+if test "x$ac_cv_header_bzlib_h" = xyes; then :
+  true
+else
+  as_fn_error $? "bzlib.h is required" "$LINENO" 5
+fi
+
+
+fi
+if test "$enable_exlzma" = "yes"
+then
+  ac_fn_c_check_header_mongrel "$LINENO" "lzmalib.h" "ac_cv_header_lzmalib_h" "$ac_includes_default"
+if test "x$ac_cv_header_lzmalib_h" = xyes; then :
+  true
+else
+  as_fn_error $? "lzmalib.h is required" "$LINENO" 5
+fi
+
+
+fi
+if test "$enable_exlzo" = "yes"
+then
+  ac_fn_c_check_header_mongrel "$LINENO" "lzo/lzo1x.h" "ac_cv_header_lzo_lzo1x_h" "$ac_includes_default"
+if test "x$ac_cv_header_lzo_lzo1x_h" = xyes; then :
+  true
+else
+  as_fn_error $? "lzo/lzo1x.h is required" "$LINENO" 5
+fi
+
+
+fi
+
+# Shared libraries
+if test "$enable_shared" != "no" && test "$enable_profile" != "yes"
+then
+  if uname | grep Darwin >/dev/null
+  then
+    MYLIBRARYFILES="$MYLIBRARYFILES libtcejdb.$MYLIBVER.$MYLIBREV.0.dylib"
+    MYLIBRARYFILES="$MYLIBRARYFILES libtcejdb.$MYLIBVER.dylib"
+    MYLIBRARYFILES="$MYLIBRARYFILES libtcejdb.dylib"
+    MYLDLIBPATHENV="DYLD_LIBRARY_PATH"
+  else
+    MYLIBRARYFILES="$MYLIBRARYFILES libtcejdb.so.$MYLIBVER.$MYLIBREV.0"
+    MYLIBRARYFILES="$MYLIBRARYFILES libtcejdb.so.$MYLIBVER"
+    MYLIBRARYFILES="$MYLIBRARYFILES libtcejdb.so"
+  fi
+fi
+
+
+
+#================================================================
+# Generic Settings
+#================================================================
+
+# Export variables
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# Targets
+ac_config_files="$ac_config_files Makefile tcejdb.pc"
+
+cat >confcache <<\_ACEOF
+# This file is a shell script that caches the results of configure
+# tests run on this system so they can be shared between configure
+# scripts and configure runs, see configure's option --config-cache.
+# It is not useful on other systems.  If it contains results you don't
+# want to keep, you may remove or edit it.
+#
+# config.status only pays attention to the cache file if you give it
+# the --recheck option to rerun configure.
+#
+# `ac_cv_env_foo' variables (set or unset) will be overridden when
+# loading this file, other *unset* `ac_cv_foo' will be assigned the
+# following values.
+
+_ACEOF
+
+# The following way of writing the cache mishandles newlines in values,
+# but we know of no workaround that is simple, portable, and efficient.
+# So, we kill variables containing newlines.
+# Ultrix sh set writes to stderr and can't be redirected directly,
+# and sets the high bit in the cache file unless we assign to the vars.
+(
+  for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do
+    eval ac_val=\$$ac_var
+    case $ac_val in #(
+    *${as_nl}*)
+      case $ac_var in #(
+      *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5
+$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;;
+      esac
+      case $ac_var in #(
+      _ | IFS | as_nl) ;; #(
+      BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #(
+      *) { eval $ac_var=; unset $ac_var;} ;;
+      esac ;;
+    esac
+  done
+
+  (set) 2>&1 |
+    case $as_nl`(ac_space=' '; set) 2>&1` in #(
+    *${as_nl}ac_space=\ *)
+      # `set' does not quote correctly, so add quotes: double-quote
+      # substitution turns \\\\ into \\, and sed turns \\ into \.
+      sed -n \
+       "s/'/'\\\\''/g;
+         s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p"
+      ;; #(
+    *)
+      # `set' quotes correctly as required by POSIX, so do not add quotes.
+      sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p"
+      ;;
+    esac |
+    sort
+) |
+  sed '
+     /^ac_cv_env_/b end
+     t clear
+     :clear
+     s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/
+     t end
+     s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/
+     :end' >>confcache
+if diff "$cache_file" confcache >/dev/null 2>&1; then :; else
+  if test -w "$cache_file"; then
+    if test "x$cache_file" != "x/dev/null"; then
+      { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5
+$as_echo "$as_me: updating cache $cache_file" >&6;}
+      if test ! -f "$cache_file" || test -h "$cache_file"; then
+       cat confcache >"$cache_file"
+      else
+        case $cache_file in #(
+        */* | ?:*)
+         mv -f confcache "$cache_file"$$ &&
+         mv -f "$cache_file"$$ "$cache_file" ;; #(
+        *)
+         mv -f confcache "$cache_file" ;;
+       esac
+      fi
+    fi
+  else
+    { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5
+$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;}
+  fi
+fi
+rm -f confcache
+
+test "x$prefix" = xNONE && prefix=$ac_default_prefix
+# Let make expand exec_prefix.
+test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
+
+# Transform confdefs.h into DEFS.
+# Protect against shell expansion while executing Makefile rules.
+# Protect against Makefile macro expansion.
+#
+# If the first sed substitution is executed (which looks for macros that
+# take arguments), then branch to the quote section.  Otherwise,
+# look for a macro that doesn't take arguments.
+ac_script='
+:mline
+/\\$/{
+ N
+ s,\\\n,,
+ b mline
+}
+t clear
+:clear
+s/^[    ]*#[    ]*define[       ][      ]*\([^  (][^    (]*([^)]*)\)[   ]*\(.*\)/-D\1=\2/g
+t quote
+s/^[    ]*#[    ]*define[       ][      ]*\([^  ][^     ]*\)[   ]*\(.*\)/-D\1=\2/g
+t quote
+b any
+:quote
+s/[     `~#$^&*(){}\\|;'\''"<>?]/\\&/g
+s/\[/\\&/g
+s/\]/\\&/g
+s/\$/$$/g
+H
+:any
+${
+       g
+       s/^\n//
+       s/\n/ /g
+       p
+}
+'
+DEFS=`sed -n "$ac_script" confdefs.h`
+
+
+ac_libobjs=
+ac_ltlibobjs=
+U=
+for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue
+  # 1. Remove the extension, and $U if already installed.
+  ac_script='s/\$U\././;s/\.o$//;s/\.obj$//'
+  ac_i=`$as_echo "$ac_i" | sed "$ac_script"`
+  # 2. Prepend LIBOBJDIR.  When used with automake>=1.10 LIBOBJDIR
+  #    will be set to the directory where LIBOBJS objects are built.
+  as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext"
+  as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo'
+done
+LIBOBJS=$ac_libobjs
+
+LTLIBOBJS=$ac_ltlibobjs
+
+
+
+
+: "${CONFIG_STATUS=./config.status}"
+ac_write_fail=0
+ac_clean_files_save=$ac_clean_files
+ac_clean_files="$ac_clean_files $CONFIG_STATUS"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5
+$as_echo "$as_me: creating $CONFIG_STATUS" >&6;}
+as_write_fail=0
+cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1
+#! $SHELL
+# Generated by $as_me.
+# Run this file to recreate the current configuration.
+# Compiler output produced by configure, useful for debugging
+# configure, is in config.log if it exists.
+
+debug=false
+ac_cs_recheck=false
+ac_cs_silent=false
+
+SHELL=\${CONFIG_SHELL-$SHELL}
+export SHELL
+_ASEOF
+cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1
+## -------------------- ##
+## M4sh Initialization. ##
+## -------------------- ##
+
+# Be more Bourne compatible
+DUALCASE=1; export DUALCASE # for MKS sh
+if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then :
+  emulate sh
+  NULLCMD=:
+  # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which
+  # is contrary to our usage.  Disable this feature.
+  alias -g '${1+"$@"}'='"$@"'
+  setopt NO_GLOB_SUBST
+else
+  case `(set -o) 2>/dev/null` in #(
+  *posix*) :
+    set -o posix ;; #(
+  *) :
+     ;;
+esac
+fi
+
+
+as_nl='
+'
+export as_nl
+# Printing a long string crashes Solaris 7 /usr/bin/printf.
+as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo
+as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo
+# Prefer a ksh shell builtin over an external printf program on Solaris,
+# but without wasting forks for bash or zsh.
+if test -z "$BASH_VERSION$ZSH_VERSION" \
+    && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='print -r --'
+  as_echo_n='print -rn --'
+elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then
+  as_echo='printf %s\n'
+  as_echo_n='printf %s'
+else
+  if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then
+    as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"'
+    as_echo_n='/usr/ucb/echo -n'
+  else
+    as_echo_body='eval expr "X$1" : "X\\(.*\\)"'
+    as_echo_n_body='eval
+      arg=$1;
+      case $arg in #(
+      *"$as_nl"*)
+       expr "X$arg" : "X\\(.*\\)$as_nl";
+       arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;;
+      esac;
+      expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl"
+    '
+    export as_echo_n_body
+    as_echo_n='sh -c $as_echo_n_body as_echo'
+  fi
+  export as_echo_body
+  as_echo='sh -c $as_echo_body as_echo'
+fi
+
+# The user is always right.
+if test "${PATH_SEPARATOR+set}" != set; then
+  PATH_SEPARATOR=:
+  (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && {
+    (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 ||
+      PATH_SEPARATOR=';'
+  }
+fi
+
+
+# IFS
+# We need space, tab and new line, in precisely that order.  Quoting is
+# there to prevent editors from complaining about space-tab.
+# (If _AS_PATH_WALK were called with IFS unset, it would disable word
+# splitting by setting IFS to empty value.)
+IFS=" ""       $as_nl"
+
+# Find who we are.  Look in the path if we contain no directory separator.
+as_myself=
+case $0 in #((
+  *[\\/]* ) as_myself=$0 ;;
+  *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break
+  done
+IFS=$as_save_IFS
+
+     ;;
+esac
+# We did not find ourselves, most probably we were run as `sh COMMAND'
+# in which case we are not to be found in the path.
+if test "x$as_myself" = x; then
+  as_myself=$0
+fi
+if test ! -f "$as_myself"; then
+  $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2
+  exit 1
+fi
+
+# Unset variables that we do not need and which cause bugs (e.g. in
+# pre-3.0 UWIN ksh).  But do not cause bugs in bash 2.01; the "|| exit 1"
+# suppresses any "Segmentation fault" message there.  '((' could
+# trigger a bug in pdksh 5.2.14.
+for as_var in BASH_ENV ENV MAIL MAILPATH
+do eval test x\${$as_var+set} = xset \
+  && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || :
+done
+PS1='$ '
+PS2='> '
+PS4='+ '
+
+# NLS nuisances.
+LC_ALL=C
+export LC_ALL
+LANGUAGE=C
+export LANGUAGE
+
+# CDPATH.
+(unset CDPATH) >/dev/null 2>&1 && unset CDPATH
+
+
+# as_fn_error STATUS ERROR [LINENO LOG_FD]
+# ----------------------------------------
+# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are
+# provided, also output the error to LOG_FD, referencing LINENO. Then exit the
+# script with STATUS, using 1 if that was 0.
+as_fn_error ()
+{
+  as_status=$1; test $as_status -eq 0 && as_status=1
+  if test "$4"; then
+    as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+    $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4
+  fi
+  $as_echo "$as_me: error: $2" >&2
+  as_fn_exit $as_status
+} # as_fn_error
+
+
+# as_fn_set_status STATUS
+# -----------------------
+# Set $? to STATUS, without forking.
+as_fn_set_status ()
+{
+  return $1
+} # as_fn_set_status
+
+# as_fn_exit STATUS
+# -----------------
+# Exit the shell with STATUS, even in a "trap 0" or "set -e" context.
+as_fn_exit ()
+{
+  set +e
+  as_fn_set_status $1
+  exit $1
+} # as_fn_exit
+
+# as_fn_unset VAR
+# ---------------
+# Portably unset VAR.
+as_fn_unset ()
+{
+  { eval $1=; unset $1;}
+}
+as_unset=as_fn_unset
+# as_fn_append VAR VALUE
+# ----------------------
+# Append the text in VALUE to the end of the definition contained in VAR. Take
+# advantage of any shell optimizations that allow amortized linear growth over
+# repeated appends, instead of the typical quadratic growth present in naive
+# implementations.
+if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then :
+  eval 'as_fn_append ()
+  {
+    eval $1+=\$2
+  }'
+else
+  as_fn_append ()
+  {
+    eval $1=\$$1\$2
+  }
+fi # as_fn_append
+
+# as_fn_arith ARG...
+# ------------------
+# Perform arithmetic evaluation on the ARGs, and store the result in the
+# global $as_val. Take advantage of shells that can avoid forks. The arguments
+# must be portable across $(()) and expr.
+if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then :
+  eval 'as_fn_arith ()
+  {
+    as_val=$(( $* ))
+  }'
+else
+  as_fn_arith ()
+  {
+    as_val=`expr "$@" || test $? -eq 1`
+  }
+fi # as_fn_arith
+
+
+if expr a : '\(a\)' >/dev/null 2>&1 &&
+   test "X`expr 00001 : '.*\(...\)'`" = X001; then
+  as_expr=expr
+else
+  as_expr=false
+fi
+
+if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then
+  as_basename=basename
+else
+  as_basename=false
+fi
+
+if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then
+  as_dirname=dirname
+else
+  as_dirname=false
+fi
+
+as_me=`$as_basename -- "$0" ||
+$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \
+        X"$0" : 'X\(//\)$' \| \
+        X"$0" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X/"$0" |
+    sed '/^.*\/\([^/][^/]*\)\/*$/{
+           s//\1/
+           q
+         }
+         /^X\/\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\/\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+
+# Avoid depending upon Character Ranges.
+as_cr_letters='abcdefghijklmnopqrstuvwxyz'
+as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+as_cr_Letters=$as_cr_letters$as_cr_LETTERS
+as_cr_digits='0123456789'
+as_cr_alnum=$as_cr_Letters$as_cr_digits
+
+ECHO_C= ECHO_N= ECHO_T=
+case `echo -n x` in #(((((
+-n*)
+  case `echo 'xy\c'` in
+  *c*) ECHO_T='        ';;     # ECHO_T is single tab character.
+  xy)  ECHO_C='\c';;
+  *)   echo `echo ksh88 bug on AIX 6.1` > /dev/null
+       ECHO_T='        ';;
+  esac;;
+*)
+  ECHO_N='-n';;
+esac
+
+rm -f conf$$ conf$$.exe conf$$.file
+if test -d conf$$.dir; then
+  rm -f conf$$.dir/conf$$.file
+else
+  rm -f conf$$.dir
+  mkdir conf$$.dir 2>/dev/null
+fi
+if (echo >conf$$.file) 2>/dev/null; then
+  if ln -s conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s='ln -s'
+    # ... but there are two gotchas:
+    # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail.
+    # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable.
+    # In both cases, we have to default to `cp -pR'.
+    ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe ||
+      as_ln_s='cp -pR'
+  elif ln conf$$.file conf$$ 2>/dev/null; then
+    as_ln_s=ln
+  else
+    as_ln_s='cp -pR'
+  fi
+else
+  as_ln_s='cp -pR'
+fi
+rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file
+rmdir conf$$.dir 2>/dev/null
+
+
+# as_fn_mkdir_p
+# -------------
+# Create "$as_dir" as a directory, including parents if necessary.
+as_fn_mkdir_p ()
+{
+
+  case $as_dir in #(
+  -*) as_dir=./$as_dir;;
+  esac
+  test -d "$as_dir" || eval $as_mkdir_p || {
+    as_dirs=
+    while :; do
+      case $as_dir in #(
+      *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'(
+      *) as_qdir=$as_dir;;
+      esac
+      as_dirs="'$as_qdir' $as_dirs"
+      as_dir=`$as_dirname -- "$as_dir" ||
+$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$as_dir" : 'X\(//\)[^/]' \| \
+        X"$as_dir" : 'X\(//\)$' \| \
+        X"$as_dir" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$as_dir" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)[^/].*/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+      test -d "$as_dir" && break
+    done
+    test -z "$as_dirs" || eval "mkdir $as_dirs"
+  } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir"
+
+
+} # as_fn_mkdir_p
+if mkdir -p . 2>/dev/null; then
+  as_mkdir_p='mkdir -p "$as_dir"'
+else
+  test -d ./-p && rmdir ./-p
+  as_mkdir_p=false
+fi
+
+
+# as_fn_executable_p FILE
+# -----------------------
+# Test if FILE is an executable regular file.
+as_fn_executable_p ()
+{
+  test -f "$1" && test -x "$1"
+} # as_fn_executable_p
+as_test_x='test -x'
+as_executable_p=as_fn_executable_p
+
+# Sed expression to map a string onto a valid CPP name.
+as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'"
+
+# Sed expression to map a string onto a valid variable name.
+as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'"
+
+
+exec 6>&1
+## ----------------------------------- ##
+## Main body of $CONFIG_STATUS script. ##
+## ----------------------------------- ##
+_ASEOF
+test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# Save the log message, to keep $0 and so on meaningful, and to
+# report actual input values of CONFIG_FILES etc. instead of their
+# values after options handling.
+ac_log="
+This file was extended by tcejdb $as_me 1.0.0, which was
+generated by GNU Autoconf 2.69.  Invocation command line was
+
+  CONFIG_FILES    = $CONFIG_FILES
+  CONFIG_HEADERS  = $CONFIG_HEADERS
+  CONFIG_LINKS    = $CONFIG_LINKS
+  CONFIG_COMMANDS = $CONFIG_COMMANDS
+  $ $0 $@
+
+on `(hostname || uname -n) 2>/dev/null | sed 1q`
+"
+
+_ACEOF
+
+case $ac_config_files in *"
+"*) set x $ac_config_files; shift; ac_config_files=$*;;
+esac
+
+
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+# Files that config.status was made for.
+config_files="$ac_config_files"
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+ac_cs_usage="\
+\`$as_me' instantiates files and other configuration actions
+from templates according to the current configuration.  Unless the files
+and actions are specified as TAGs, all are instantiated by default.
+
+Usage: $0 [OPTION]... [TAG]...
+
+  -h, --help       print this help, then exit
+  -V, --version    print version number and configuration settings, then exit
+      --config     print configuration, then exit
+  -q, --quiet, --silent
+                   do not print progress messages
+  -d, --debug      don't remove temporary files
+      --recheck    update $as_me by reconfiguring in the same conditions
+      --file=FILE[:TEMPLATE]
+                   instantiate the configuration file FILE
+
+Configuration files:
+$config_files
+
+Report bugs to the package provider."
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
+ac_cs_version="\\
+tcejdb config.status 1.0.0
+configured by $0, generated by GNU Autoconf 2.69,
+  with options \\"\$ac_cs_config\\"
+
+Copyright (C) 2012 Free Software Foundation, Inc.
+This config.status script is free software; the Free Software Foundation
+gives unlimited permission to copy, distribute and modify it."
+
+ac_pwd='$ac_pwd'
+srcdir='$srcdir'
+test -n "\$AWK" || AWK=awk
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# The default lists apply if the user does not specify any file.
+ac_need_defaults=:
+while test $# != 0
+do
+  case $1 in
+  --*=?*)
+    ac_option=`expr "X$1" : 'X\([^=]*\)='`
+    ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'`
+    ac_shift=:
+    ;;
+  --*=)
+    ac_option=`expr "X$1" : 'X\([^=]*\)='`
+    ac_optarg=
+    ac_shift=:
+    ;;
+  *)
+    ac_option=$1
+    ac_optarg=$2
+    ac_shift=shift
+    ;;
+  esac
+
+  case $ac_option in
+  # Handling of the options.
+  -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r)
+    ac_cs_recheck=: ;;
+  --version | --versio | --versi | --vers | --ver | --ve | --v | -V )
+    $as_echo "$ac_cs_version"; exit ;;
+  --config | --confi | --conf | --con | --co | --c )
+    $as_echo "$ac_cs_config"; exit ;;
+  --debug | --debu | --deb | --de | --d | -d )
+    debug=: ;;
+  --file | --fil | --fi | --f )
+    $ac_shift
+    case $ac_optarg in
+    *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;;
+    '') as_fn_error $? "missing file argument" ;;
+    esac
+    as_fn_append CONFIG_FILES " '$ac_optarg'"
+    ac_need_defaults=false;;
+  --he | --h |  --help | --hel | -h )
+    $as_echo "$ac_cs_usage"; exit ;;
+  -q | -quiet | --quiet | --quie | --qui | --qu | --q \
+  | -silent | --silent | --silen | --sile | --sil | --si | --s)
+    ac_cs_silent=: ;;
+
+  # This is an error.
+  -*) as_fn_error $? "unrecognized option: \`$1'
+Try \`$0 --help' for more information." ;;
+
+  *) as_fn_append ac_config_targets " $1"
+     ac_need_defaults=false ;;
+
+  esac
+  shift
+done
+
+ac_configure_extra_args=
+
+if $ac_cs_silent; then
+  exec 6>/dev/null
+  ac_configure_extra_args="$ac_configure_extra_args --silent"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+if \$ac_cs_recheck; then
+  set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion
+  shift
+  \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6
+  CONFIG_SHELL='$SHELL'
+  export CONFIG_SHELL
+  exec "\$@"
+fi
+
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+exec 5>>config.log
+{
+  echo
+  sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX
+## Running $as_me. ##
+_ASBOX
+  $as_echo "$ac_log"
+} >&5
+
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+
+# Handling of arguments.
+for ac_config_target in $ac_config_targets
+do
+  case $ac_config_target in
+    "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;;
+    "tcejdb.pc") CONFIG_FILES="$CONFIG_FILES tcejdb.pc" ;;
+
+  *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;;
+  esac
+done
+
+
+# If the user did not use the arguments to specify the items to instantiate,
+# then the envvar interface is used.  Set only those that are not.
+# We use the long form for the default assignment because of an extremely
+# bizarre bug on SunOS 4.1.3.
+if $ac_need_defaults; then
+  test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files
+fi
+
+# Have a temporary directory for convenience.  Make it in the build tree
+# simply because there is no reason against having it here, and in addition,
+# creating and moving files from /tmp can sometimes cause problems.
+# Hook for its removal unless debugging.
+# Note that there is a small window in which the directory will not be cleaned:
+# after its creation but before its name has been assigned to `$tmp'.
+$debug ||
+{
+  tmp= ac_tmp=
+  trap 'exit_status=$?
+  : "${ac_tmp:=$tmp}"
+  { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status
+' 0
+  trap 'as_fn_exit 1' 1 2 13 15
+}
+# Create a (secure) tmp directory for tmp files.
+
+{
+  tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` &&
+  test -d "$tmp"
+}  ||
+{
+  tmp=./conf$$-$RANDOM
+  (umask 077 && mkdir "$tmp")
+} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5
+ac_tmp=$tmp
+
+# Set up the scripts for CONFIG_FILES section.
+# No need to generate them if there are no CONFIG_FILES.
+# This happens for instance with `./config.status config.h'.
+if test -n "$CONFIG_FILES"; then
+
+
+ac_cr=`echo X | tr X '\015'`
+# On cygwin, bash can eat \r inside `` if the user requested igncr.
+# But we know of no other shell where ac_cr would be empty at this
+# point, so we can use a bashism as a fallback.
+if test "x$ac_cr" = x; then
+  eval ac_cr=\$\'\\r\'
+fi
+ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' </dev/null 2>/dev/null`
+if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then
+  ac_cs_awk_cr='\\r'
+else
+  ac_cs_awk_cr=$ac_cr
+fi
+
+echo 'BEGIN {' >"$ac_tmp/subs1.awk" &&
+_ACEOF
+
+
+{
+  echo "cat >conf$$subs.awk <<_ACEOF" &&
+  echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' &&
+  echo "_ACEOF"
+} >conf$$subs.sh ||
+  as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'`
+ac_delim='%!_!# '
+for ac_last_try in false false false false false :; do
+  . ./conf$$subs.sh ||
+    as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+
+  ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X`
+  if test $ac_delim_n = $ac_delim_num; then
+    break
+  elif $ac_last_try; then
+    as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5
+  else
+    ac_delim="$ac_delim!$ac_delim _$ac_delim!! "
+  fi
+done
+rm -f conf$$subs.sh
+
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK &&
+_ACEOF
+sed -n '
+h
+s/^/S["/; s/!.*/"]=/
+p
+g
+s/^[^!]*!//
+:repl
+t repl
+s/'"$ac_delim"'$//
+t delim
+:nl
+h
+s/\(.\{148\}\)..*/\1/
+t more1
+s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/
+p
+n
+b repl
+:more1
+s/["\\]/\\&/g; s/^/"/; s/$/"\\/
+p
+g
+s/.\{148\}//
+t nl
+:delim
+h
+s/\(.\{148\}\)..*/\1/
+t more2
+s/["\\]/\\&/g; s/^/"/; s/$/"/
+p
+b
+:more2
+s/["\\]/\\&/g; s/^/"/; s/$/"\\/
+p
+g
+s/.\{148\}//
+t delim
+' <conf$$subs.awk | sed '
+/^[^""]/{
+  N
+  s/\n//
+}
+' >>$CONFIG_STATUS || ac_write_fail=1
+rm -f conf$$subs.awk
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+_ACAWK
+cat >>"\$ac_tmp/subs1.awk" <<_ACAWK &&
+  for (key in S) S_is_set[key] = 1
+  FS = "\a"
+
+}
+{
+  line = $ 0
+  nfields = split(line, field, "@")
+  substed = 0
+  len = length(field[1])
+  for (i = 2; i < nfields; i++) {
+    key = field[i]
+    keylen = length(key)
+    if (S_is_set[key]) {
+      value = S[key]
+      line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3)
+      len += length(value) + length(field[++i])
+      substed = 1
+    } else
+      len += 1 + keylen
+  }
+
+  print line
+}
+
+_ACAWK
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then
+  sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g"
+else
+  cat
+fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \
+  || as_fn_error $? "could not setup config files machinery" "$LINENO" 5
+_ACEOF
+
+# VPATH may cause trouble with some makes, so we remove sole $(srcdir),
+# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and
+# trailing colons and then remove the whole line if VPATH becomes empty
+# (actually we leave an empty line to preserve line numbers).
+if test "x$srcdir" = x.; then
+  ac_vpsub='/^[         ]*VPATH[        ]*=[    ]*/{
+h
+s///
+s/^/:/
+s/[     ]*$/:/
+s/:\$(srcdir):/:/g
+s/:\${srcdir}:/:/g
+s/:@srcdir@:/:/g
+s/^:*//
+s/:*$//
+x
+s/\(=[  ]*\).*/\1/
+G
+s/\n//
+s/^[^=]*=[      ]*$//
+}'
+fi
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+fi # test -n "$CONFIG_FILES"
+
+
+eval set X "  :F $CONFIG_FILES      "
+shift
+for ac_tag
+do
+  case $ac_tag in
+  :[FHLC]) ac_mode=$ac_tag; continue;;
+  esac
+  case $ac_mode$ac_tag in
+  :[FHL]*:*);;
+  :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;;
+  :[FH]-) ac_tag=-:-;;
+  :[FH]*) ac_tag=$ac_tag:$ac_tag.in;;
+  esac
+  ac_save_IFS=$IFS
+  IFS=:
+  set x $ac_tag
+  IFS=$ac_save_IFS
+  shift
+  ac_file=$1
+  shift
+
+  case $ac_mode in
+  :L) ac_source=$1;;
+  :[FH])
+    ac_file_inputs=
+    for ac_f
+    do
+      case $ac_f in
+      -) ac_f="$ac_tmp/stdin";;
+      *) # Look for the file first in the build tree, then in the source tree
+        # (if the path is not absolute).  The absolute path cannot be DOS-style,
+        # because $ac_f cannot contain `:'.
+        test -f "$ac_f" ||
+          case $ac_f in
+          [\\/$]*) false;;
+          *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";;
+          esac ||
+          as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;;
+      esac
+      case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac
+      as_fn_append ac_file_inputs " '$ac_f'"
+    done
+
+    # Let's still pretend it is `configure' which instantiates (i.e., don't
+    # use $as_me), people would be surprised to read:
+    #    /* config.h.  Generated by config.status.  */
+    configure_input='Generated from '`
+         $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g'
+       `' by configure.'
+    if test x"$ac_file" != x-; then
+      configure_input="$ac_file.  $configure_input"
+      { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5
+$as_echo "$as_me: creating $ac_file" >&6;}
+    fi
+    # Neutralize special characters interpreted by sed in replacement strings.
+    case $configure_input in #(
+    *\&* | *\|* | *\\* )
+       ac_sed_conf_input=`$as_echo "$configure_input" |
+       sed 's/[\\\\&|]/\\\\&/g'`;; #(
+    *) ac_sed_conf_input=$configure_input;;
+    esac
+
+    case $ac_tag in
+    *:-:* | *:-) cat >"$ac_tmp/stdin" \
+      || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;;
+    esac
+    ;;
+  esac
+
+  ac_dir=`$as_dirname -- "$ac_file" ||
+$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \
+        X"$ac_file" : 'X\(//\)[^/]' \| \
+        X"$ac_file" : 'X\(//\)$' \| \
+        X"$ac_file" : 'X\(/\)' \| . 2>/dev/null ||
+$as_echo X"$ac_file" |
+    sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)[^/].*/{
+           s//\1/
+           q
+         }
+         /^X\(\/\/\)$/{
+           s//\1/
+           q
+         }
+         /^X\(\/\).*/{
+           s//\1/
+           q
+         }
+         s/.*/./; q'`
+  as_dir="$ac_dir"; as_fn_mkdir_p
+  ac_builddir=.
+
+case "$ac_dir" in
+.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;;
+*)
+  ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'`
+  # A ".." for each directory in $ac_dir_suffix.
+  ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'`
+  case $ac_top_builddir_sub in
+  "") ac_top_builddir_sub=. ac_top_build_prefix= ;;
+  *)  ac_top_build_prefix=$ac_top_builddir_sub/ ;;
+  esac ;;
+esac
+ac_abs_top_builddir=$ac_pwd
+ac_abs_builddir=$ac_pwd$ac_dir_suffix
+# for backward compatibility:
+ac_top_builddir=$ac_top_build_prefix
+
+case $srcdir in
+  .)  # We are building in place.
+    ac_srcdir=.
+    ac_top_srcdir=$ac_top_builddir_sub
+    ac_abs_top_srcdir=$ac_pwd ;;
+  [\\/]* | ?:[\\/]* )  # Absolute name.
+    ac_srcdir=$srcdir$ac_dir_suffix;
+    ac_top_srcdir=$srcdir
+    ac_abs_top_srcdir=$srcdir ;;
+  *) # Relative name.
+    ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix
+    ac_top_srcdir=$ac_top_build_prefix$srcdir
+    ac_abs_top_srcdir=$ac_pwd/$srcdir ;;
+esac
+ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix
+
+
+  case $ac_mode in
+  :F)
+  #
+  # CONFIG_FILE
+  #
+
+_ACEOF
+
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+# If the template does not know about datarootdir, expand it.
+# FIXME: This hack should be removed a few years after 2.60.
+ac_datarootdir_hack=; ac_datarootdir_seen=
+ac_sed_dataroot='
+/datarootdir/ {
+  p
+  q
+}
+/@datadir@/p
+/@docdir@/p
+/@infodir@/p
+/@localedir@/p
+/@mandir@/p'
+case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in
+*datarootdir*) ac_datarootdir_seen=yes;;
+*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*)
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5
+$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;}
+_ACEOF
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+  ac_datarootdir_hack='
+  s&@datadir@&$datadir&g
+  s&@docdir@&$docdir&g
+  s&@infodir@&$infodir&g
+  s&@localedir@&$localedir&g
+  s&@mandir@&$mandir&g
+  s&\\\${datarootdir}&$datarootdir&g' ;;
+esac
+_ACEOF
+
+# Neutralize VPATH when `$srcdir' = `.'.
+# Shell code in configure.ac might set extrasub.
+# FIXME: do we really want to maintain this feature?
+cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
+ac_sed_extra="$ac_vpsub
+$extrasub
+_ACEOF
+cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
+:t
+/@[a-zA-Z_][a-zA-Z_0-9]*@/!b
+s|@configure_input@|$ac_sed_conf_input|;t t
+s&@top_builddir@&$ac_top_builddir_sub&;t t
+s&@top_build_prefix@&$ac_top_build_prefix&;t t
+s&@srcdir@&$ac_srcdir&;t t
+s&@abs_srcdir@&$ac_abs_srcdir&;t t
+s&@top_srcdir@&$ac_top_srcdir&;t t
+s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t
+s&@builddir@&$ac_builddir&;t t
+s&@abs_builddir@&$ac_abs_builddir&;t t
+s&@abs_top_builddir@&$ac_abs_top_builddir&;t t
+$ac_datarootdir_hack
+"
+eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \
+  >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+
+test -z "$ac_datarootdir_hack$ac_datarootdir_seen" &&
+  { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } &&
+  { ac_out=`sed -n '/^[         ]*datarootdir[  ]*:*=/p' \
+      "$ac_tmp/out"`; test -z "$ac_out"; } &&
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined.  Please make sure it is defined" >&5
+$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir'
+which seems to be undefined.  Please make sure it is defined" >&2;}
+
+  rm -f "$ac_tmp/stdin"
+  case $ac_file in
+  -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";;
+  *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";;
+  esac \
+  || as_fn_error $? "could not create $ac_file" "$LINENO" 5
+ ;;
+
+
+
+  esac
+
+done # for ac_tag
+
+
+as_fn_exit 0
+_ACEOF
+ac_clean_files=$ac_clean_files_save
+
+test $ac_write_fail = 0 ||
+  as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5
+
+
+# configure is writing to config.log, and then calls config.status.
+# config.status does its own redirection, appending to config.log.
+# Unfortunately, on DOS this fails, as config.log is still kept open
+# by configure, so config.status won't be able to write to it; its
+# output is simply discarded.  So we exec the FD to /dev/null,
+# effectively closing config.log, so it can be properly (re)opened and
+# appended to by config.status.  When coming back to configure, we
+# need to make the FD available again.
+if test "$no_create" != yes; then
+  ac_cs_success=:
+  ac_config_status_args=
+  test "$silent" = yes &&
+    ac_config_status_args="$ac_config_status_args --quiet"
+  exec 5>/dev/null
+  $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false
+  exec 5>>config.log
+  # Use ||, not &&, to avoid exiting from the if with $? = 1, which
+  # would make configure fail if this is the last instruction.
+  $ac_cs_success || as_fn_exit 1
+fi
+if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5
+$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;}
+fi
+
+
+# Messages
+printf '#================================================================\n'
+printf '# Ready to make.\n'
+printf '#================================================================\n'
+
+
+
+# END OF FILE
diff --git a/tcejdb/configure.in b/tcejdb/configure.in
new file mode 100644 (file)
index 0000000..a3b2679
--- /dev/null
@@ -0,0 +1,365 @@
+# Source of configuration for Tokyo Cabinet
+
+
+
+#================================================================
+# Generic Settings
+#================================================================
+
+test -n "$CFLAGS" && MYCFLAGS="$CFLAGS $MYCFLAGS"
+test -n "$CPPFLAGS" && MYCPPFLAGS="$CPPFLAGS $MYCPPFLAGS"
+test -n "$LDFLAGS" && MYLDFLAGS="$LDFLAGS $MYLDFLAGS"
+
+# Package name
+AC_INIT(tcejdb, 1.0.0)
+
+# Package information
+MYLIBVER=9
+MYLIBREV=11
+MYFORMATVER="1.0"
+
+# Targets
+MYHEADERFILES="tcutil.h tchdb.h tcbdb.h tcfdb.h tctdb.h tcadb.h ejdb.h ejdefs.h bson.h"
+MYLIBRARYFILES="libtcejdb.a"
+MYLIBOBJFILES="tcutil.o tchdb.o tcbdb.o tcfdb.o tctdb.o tcadb.o myconf.o md5.o ejdb.o bson.o timsort.o numbers.o encoding.o"
+MYCOMMANDFILES="tcutest tcumttest tcucodec tchtest tchmttest tchmgr"
+MYCOMMANDFILES="$MYCOMMANDFILES tcbtest tcbmttest tcbmgr tcftest tcfmttest tcfmgr"
+MYCOMMANDFILES="$MYCOMMANDFILES tcttest tctmttest tctmgr tcatest tcamttest tcamgr"
+MYCGIFILES="tcawmgr.cgi"
+MYMAN1FILES="tcutest.1 tcumttest.1 tcucodec.1 tchtest.1 tchmttest.1 tchmgr.1"
+MYMAN1FILES="$MYMAN1FILES tcbtest.1 tcbmttest.1 tcbmgr.1 tcftest.1 tcfmttest.1 tcfmgr.1"
+MYMAN1FILES="$MYMAN1FILES tcttest.1 tctmttest.1 tctmgr.1 tcatest.1 tcamttest.1 tcamgr.1"
+MYMAN3FILES="tokyocabinet.3 tcutil.3 tcxstr.3 tclist.3 tcmap.3 tctree.3 tcmdb.3 tcmpool.3"
+MYMAN3FILES="$MYMAN3FILES tchdb.3 tcbdb.3 tcfdb.3 tctdb.3 tcadb.3"
+#MYDOCUMENTFILES="COPYING ChangeLog doc tokyocabinet.idl"
+MYDOCUMENTFILES="COPYING ChangeLog"
+MYPCFILES="tcejdb.pc"
+
+# Building flags
+MYCFLAGS="$MYCFLAGS -std=c99 -Wall -fPIC -fsigned-char -O2"
+MYCPPFLAGS="-I. -I\$(INCLUDEDIR) -I$HOME/include -I/usr/local/include"
+MYCPPFLAGS="$MYCPPFLAGS -DNDEBUG -D_GNU_SOURCE=1 -D_REENTRANT -D__EXTENSIONS__"
+MYLDFLAGS="-L. -L\$(LIBDIR) -L$HOME/lib -L/usr/local/lib"
+MYCMDLDFLAGS=""
+MYRUNPATH="\$(LIBDIR)"
+MYLDLIBPATHENV="LD_LIBRARY_PATH"
+MYPOSTCMD="true"
+
+# Building paths
+PATH="$PATH:$HOME/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin"
+PATH="$PATH:/opt/SUNWspro/bin:/usr/ccs/bin:/usr/xpg4/bin:/usr/xpg6/bin:/usr/ucb"
+CPATH="$HOME/include:/usr/local/include:$CPATH"
+LIBRARY_PATH="$HOME/lib:/usr/local/lib:$LIBRARY_PATH"
+LD_LIBRARY_PATH="$HOME/lib:/usr/local/lib:$LD_LIBRARY_PATH"
+PKG_CONFIG_PATH="$HOME/lib/pkgconfig:/usr/local/lib/pkgconfig:$PKG_CONFIG_PATH"
+export PATH CPATH LIBRARY_PATH LD_LIBRARY_PATH PKG_CONFIG_PATH
+
+
+#================================================================
+# Options
+#================================================================
+
+
+# Internal variables
+enables=""
+
+# Debug mode
+AC_ARG_ENABLE(debug,
+  AC_HELP_STRING([--enable-debug], [build for debugging]))
+if test "$enable_debug" = "yes"
+then
+  MYCFLAGS="-std=c99 -Wall -fPIC -pedantic -fsigned-char -g -O0"
+  MYCPPFLAGS="$MYCPPFLAGS -UNDEBUG"
+  MYCMDLDFLAGS="$MYCMDLDFLAGS -static"
+  enables="$enables (debug)"
+fi
+
+# Developping mode
+AC_ARG_ENABLE(devel,
+  AC_HELP_STRING([--enable-devel], [build for development]))
+if test "$enable_devel" = "yes"
+then
+  MYCFLAGS="-std=c99 -Wall -fPIC -pedantic -fsigned-char -g -O2 -pipe"
+  MYCPPFLAGS="$MYCPPFLAGS -UNDEBUG"
+  enables="$enables (devel)"
+fi
+
+# Profiling mode
+AC_ARG_ENABLE(profile,
+  AC_HELP_STRING([--enable-profile], [build for profiling]))
+if test "$enable_profile" = "yes"
+then
+  MYCFLAGS="-std=c99 -Wall -fPIC -pedantic -fsigned-char -g -pg -O2 -pipe"
+  enables="$enables (profile)"
+fi
+
+# Static mode
+AC_ARG_ENABLE(static,
+  AC_HELP_STRING([--enable-static], [build by static linking]))
+if test "$enable_static" = "yes"
+then
+  MYCMDLDFLAGS="$MYCMDLDFLAGS -static"
+  enables="$enables (static)"
+fi
+
+# Fastest mode
+AC_ARG_ENABLE(fastest,
+  AC_HELP_STRING([--enable-fastest], [build for fastest run]))
+if test "$enable_fastest" = "yes"
+then
+  MYLIBOBJFILES="tokyocabinet_all.o"
+  MYCFLAGS="-std=c99 -Wall -fPIC -pedantic -fsigned-char -O3"
+  MYCFLAGS="$MYCFLAGS -fomit-frame-pointer -fforce-addr -minline-all-stringops"
+  MYCPPFLAGS="$MYCPPFLAGS -D_MYFASTEST"
+  enables="$enables (fastest)"
+fi
+
+# 64-bit offset mode
+AC_ARG_ENABLE(off64,
+  AC_HELP_STRING([--enable-off64], [build with 64-bit file offset on 32-bit system]))
+if test "$enable_off64" = "yes"
+then
+  MYCPPFLAGS="$MYCPPFLAGS -D_FILE_OFFSET_BITS=64"
+  enables="$enables (off64)"
+fi
+
+# Swapping byte-orders mode
+AC_ARG_ENABLE(swab,
+  AC_HELP_STRING([--enable-swab], [build for swapping byte-orders]))
+if test "$enable_swab" = "yes"
+then
+  MYCPPFLAGS="$MYCPPFLAGS -D_MYSWAB"
+  enables="$enables (swab)"
+fi
+
+# Micro yield mode
+AC_ARG_ENABLE(uyield,
+  AC_HELP_STRING([--enable-uyield], [build for detecting race conditions]))
+if test "$enable_uyield" = "yes"
+then
+  MYCPPFLAGS="$MYCPPFLAGS -D_MYMICROYIELD"
+  enables="$enables (uyield)"
+fi
+
+# Disable the unified buffer cache assumption
+AC_ARG_ENABLE(ubc,
+  AC_HELP_STRING([--disable-ubc], [build without the unified buffer cache assumption]))
+if test "$enable_ubc" = "no"
+then
+  MYCPPFLAGS="$MYCPPFLAGS -D_MYNOUBC"
+  enables="$enables (no-ubc)"
+fi
+
+# Disable ZLIB compression
+AC_ARG_ENABLE(zlib,
+  AC_HELP_STRING([--disable-zlib], [build without ZLIB compression]))
+if test "$enable_zlib" = "no"
+then
+  MYCPPFLAGS="$MYCPPFLAGS -D_MYNOZLIB"
+  enables="$enables (no-zlib)"
+fi
+
+# Disable BZIP2 compression
+AC_ARG_ENABLE(bzip,
+  AC_HELP_STRING([--disable-bzip], [build without BZIP2 compression]))
+if test "$enable_bzip" = "no"
+then
+  MYCPPFLAGS="$MYCPPFLAGS -D_MYNOBZIP"
+  enables="$enables (no-bzip)"
+fi
+
+# Disable POSIX thread
+AC_ARG_ENABLE(pthread,
+  AC_HELP_STRING([--disable-pthread], [build without POSIX thread support]))
+if test "$enable_pthread" = "no"
+then
+  MYCPPFLAGS="$MYCPPFLAGS -D_MYNOPTHREAD"
+  enables="$enables (no-pthread)"
+fi
+
+# Disable shared object
+AC_ARG_ENABLE(shared,
+  AC_HELP_STRING([--disable-shared], [avoid to build shared libraries]))
+if test "$enable_shared" = "no"
+then
+  enables="$enables (no-shared)"
+fi
+
+# Enable custom codec functions of LZMA
+AC_ARG_ENABLE(exlzma,
+  AC_HELP_STRING([--disable-exlzma], [build with the custom codec of LZMA]))
+if test "$enable_exlzma" = "yes"
+then
+  MYCPPFLAGS="$MYCPPFLAGS -D_MYEXLZMA"
+  enables="$enables (exlzma)"
+fi
+
+# Enable custom codec functions of LZO
+AC_ARG_ENABLE(exlzo,
+  AC_HELP_STRING([--disable-exlzo], [build with the custom codec of LZO]))
+if test "$enable_exlzo" = "yes"
+then
+  MYCPPFLAGS="$MYCPPFLAGS -D_MYEXLZO"
+  enables="$enables (exlzo)"
+fi
+
+# Specify the installation path of ZLIB
+AC_ARG_WITH(zlib,
+  AC_HELP_STRING([--with-zlib=DIR], [search DIR/include and DIR/lib for ZLIB]))
+if test -n "$with_zlib"
+then
+  MYCPPFLAGS="$MYCPPFLAGS -I$with_zlib/include"
+  MYLDFLAGS="$MYLDFLAGS -L$with_zlib/lib"
+  MYRUNPATH="$MYRUNPATH:$with_zlib/lib"
+  CPATH="$CPATH:$with_zlib/include"
+  LIBRARY_PATH="$LIBRARY_PATH:$with_zlib/lib"
+  LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$with_zlib/lib"
+fi
+
+# Specify the installation path of BZIP2
+AC_ARG_WITH(bzip,
+  AC_HELP_STRING([--with-bzip=DIR], [search DIR/include and DIR/lib for BZIP2]))
+if test -n "$with_bzip"
+then
+  MYCPPFLAGS="$MYCPPFLAGS -I$with_bzip/include"
+  MYLDFLAGS="$MYLDFLAGS -L$with_bzip/lib"
+  MYRUNPATH="$MYRUNPATH:$with_bzip/lib"
+  CPATH="$CPATH:$with_bzip/include"
+  LIBRARY_PATH="$LIBRARY_PATH:$with_bzip/lib"
+  LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$with_bzip/lib"
+fi
+
+# Messages
+printf '#================================================================\n'
+printf '# Configuring Tokyo Cabinet version %s%s.\n' "$PACKAGE_VERSION" "$enables"
+printf '#================================================================\n'
+
+
+
+#================================================================
+# Checking Commands and Libraries
+#================================================================
+
+# C compiler
+AC_PROG_CC
+
+# Reset variables
+if test "$GCC" != "yes"
+then
+  AC_MSG_WARN([another compiler except for GCC was detected])
+  MYCFLAGS=""
+fi
+
+# Byte order
+AC_C_BIGENDIAN(MYCPPFLAGS="$MYCPPFLAGS -D_MYBIGEND")
+
+# Underlying libraries
+AC_CHECK_LIB(c, main)
+AC_CHECK_LIB(m, main)
+if test "$enable_pthread" != "no"
+then
+  AC_CHECK_LIB(pthread, main)
+  AC_CHECK_LIB(rt, main)
+fi
+if test "$enable_zlib" != "no"
+then
+  AC_CHECK_LIB(z, main)
+fi
+if test "$enable_bzip" != "no"
+then
+  AC_CHECK_LIB(bz2, main)
+fi
+if test "$enable_exlzma" = "yes"
+then
+  AC_CHECK_LIB(lzma, main)
+fi
+if test "$enable_exlzo" = "yes"
+then
+  AC_CHECK_LIB(lzo2, main)
+fi
+AC_CHECK_LIB(tcejdb, main, AC_MSG_WARN([old version of EJDB was found]))
+
+AC_CHECK_LIB(cunit, main, true, AC_MSG_ERROR([CUnit lubrary is required]))
+
+# Necessary headers
+AC_CHECK_HEADER(stdlib.h, true, AC_MSG_ERROR([stdlib.h is required]))
+AC_CHECK_HEADER(stdint.h, true, AC_MSG_ERROR([stdint.h is required]))
+AC_CHECK_HEADER(unistd.h, true, AC_MSG_ERROR([unistd.h is required]))
+AC_CHECK_HEADER(dirent.h, true, AC_MSG_ERROR([dirent.h is required]))
+AC_CHECK_HEADER(regex.h, true, AC_MSG_ERROR([regex.h is required]))
+AC_CHECK_HEADER(glob.h, true, AC_MSG_ERROR([glob.h is required]))
+if test "$enable_pthread" != "no"
+then
+  AC_CHECK_HEADER(pthread.h, true, AC_MSG_ERROR([pthread.h is required]))
+fi
+if test "$enable_zlib" != "no"
+then
+  AC_CHECK_HEADER(zlib.h, true, AC_MSG_ERROR([zlib.h is required]))
+fi
+if test "$enable_bzip" != "no"
+then
+  AC_CHECK_HEADER(bzlib.h, true, AC_MSG_ERROR([bzlib.h is required]))
+fi
+if test "$enable_exlzma" = "yes"
+then
+  AC_CHECK_HEADER(lzmalib.h, true, AC_MSG_ERROR([lzmalib.h is required]))
+fi
+if test "$enable_exlzo" = "yes"
+then
+  AC_CHECK_HEADER(lzo/lzo1x.h, true, AC_MSG_ERROR([lzo/lzo1x.h is required]))
+fi
+
+# Shared libraries
+if test "$enable_shared" != "no" && test "$enable_profile" != "yes"
+then
+  if uname | grep Darwin >/dev/null
+  then
+    MYLIBRARYFILES="$MYLIBRARYFILES libtcejdb.$MYLIBVER.$MYLIBREV.0.dylib"
+    MYLIBRARYFILES="$MYLIBRARYFILES libtcejdb.$MYLIBVER.dylib"
+    MYLIBRARYFILES="$MYLIBRARYFILES libtcejdb.dylib"
+    MYLDLIBPATHENV="DYLD_LIBRARY_PATH"
+  else
+    MYLIBRARYFILES="$MYLIBRARYFILES libtcejdb.so.$MYLIBVER.$MYLIBREV.0"
+    MYLIBRARYFILES="$MYLIBRARYFILES libtcejdb.so.$MYLIBVER"
+    MYLIBRARYFILES="$MYLIBRARYFILES libtcejdb.so"
+  fi
+fi
+
+
+
+#================================================================
+# Generic Settings
+#================================================================
+
+# Export variables
+AC_SUBST(MYLIBVER)
+AC_SUBST(MYLIBREV)
+AC_SUBST(MYFORMATVER)
+AC_SUBST(MYHEADERFILES)
+AC_SUBST(MYLIBRARYFILES)
+AC_SUBST(MYLIBOBJFILES)
+AC_SUBST(MYCOMMANDFILES)
+AC_SUBST(MYCGIFILES)
+AC_SUBST(MYMAN1FILES)
+AC_SUBST(MYMAN3FILES)
+AC_SUBST(MYDOCUMENTFILES)
+AC_SUBST(MYPCFILES)
+AC_SUBST(MYCFLAGS)
+AC_SUBST(MYCPPFLAGS)
+AC_SUBST(MYLDFLAGS)
+AC_SUBST(MYCMDLDFLAGS)
+AC_SUBST(MYRUNPATH)
+AC_SUBST(MYLDLIBPATHENV)
+AC_SUBST(MYPOSTCMD)
+
+# Targets
+AC_OUTPUT(Makefile tcejdb.pc)
+
+# Messages
+printf '#================================================================\n'
+printf '# Ready to make.\n'
+printf '#================================================================\n'
+
+
+
+# END OF FILE
diff --git a/tcejdb/doc/benchmark.pdf b/tcejdb/doc/benchmark.pdf
new file mode 100644 (file)
index 0000000..9ad82d7
Binary files /dev/null and b/tcejdb/doc/benchmark.pdf differ
diff --git a/tcejdb/doc/common.css b/tcejdb/doc/common.css
new file mode 100644 (file)
index 0000000..b7bd5a5
--- /dev/null
@@ -0,0 +1,211 @@
+/*
+ * Style Sheets commonly used by documents of Tokyo Cabinet
+ */
+
+html {
+  margin: 0em 0em;
+  padding: 0em 0em;
+  background: #eeeeee none;
+}
+body {
+  margin: 1em 2em;
+  padding: 0em 0em;
+  background: #eeeeee none;
+  color: #111111;
+}
+
+hr {
+  margin: 2.5em 0em 1.5em 0em;
+  height: 1px;
+  border: none;
+  background: #999999 none;
+  color: #999999;
+}
+
+h1,h2,h3,h4,h5,h6 {
+  font-weight: bold;
+}
+h1 {
+  margin: 1.0em 0em 1.3em 0em;
+  padding: 0em 0em;
+  font-size: 180%;
+  color: #000000;
+}
+h2 {
+  margin: 1.0em 0em 1.0em 0.2em;
+  padding: 0.5em 0.5em;
+  width: 60%;
+  border-left: solid 0.6em #445555;
+  border-bottom: solid 1px #bbbbbb;
+  font-size: 150%;
+  color: #000011;
+}
+h3 {
+  margin: 0.8em 0em 0.5em 0.2em;
+  padding: 0em 0em;
+  font-size: 120%;
+  color: #001111;
+}
+
+p {
+  margin: 0.8em 0em;
+  line-height: 140%;
+  text-indent: 0.8em;
+}
+
+div,pre,table {
+  margin: 0.8em 1.5em;
+}
+
+div.note,div.navi {
+  text-align: right;
+  margin: 0em 0.5em;
+  color: #333333;
+}
+span.void {
+  color: #888888;
+}
+
+div.logo {
+  text-align: center;
+  margin: 3em 0em;
+}
+div.logo img {
+  border: inset 2px #ccccdd;
+}
+div.illust {
+  margin: 1em 0em;
+  text-align: center;
+}
+div.illust img {
+  border: solid 1px #ccccdd;
+}
+
+pre {
+  padding: 0.2em;
+  background-color: #ddddee;
+  border: 1px solid #bbbbcc;
+  font-size: 95%;
+}
+
+li,dt,dd {
+  line-height: 130%;
+}
+dt {
+  margin-left: 1.2em;
+}
+dd {
+  margin-left: 2.5em;
+  text-indent: -0.3em;
+}
+dl.api {
+  margin-top: -0.2em;
+}
+dl.api dd {
+  margin-left: 3.0em;
+  font-size: 95%;
+  color: #333333;
+}
+ul {
+  margin: 0.5em 2.0em;
+  padding: 0em;
+}
+ul.options {
+  list-style-type: none;
+  margin: 0.5em 1.5em;
+  font-size: 95%;
+  color: #333333;
+}
+ul ul {
+  margin-top: 0em;
+  margin-bottom: 0em;
+}
+
+table {
+  border-collapse: collapse;
+}
+td {
+  text-align: left;
+  vertical-align: top;
+  padding: 0.1em 0.5em;
+  border: solid 1px #aaaabb;
+  font-size: 95%;
+}
+td.label {
+  border: none;
+  font-size: 80%;
+  color: #333333;
+}
+td.number {
+  text-align: right;
+}
+
+a {
+  color: #0022aa;
+  text-decoration: none;
+}
+a:hover,a:focus {
+  color: #0033ee;
+  text-decoration: underline;
+}
+code,kbd {
+  font-style: normal;
+  font-weight: bold;
+  font-size: 100%;
+  color: #001111;
+}
+var {
+  padding: 0em 0.15em 0em 0em;
+  font-style: italic;
+  color: #001122;
+}
+
+@media print {
+  html,body {
+    margin: 0em 0em;
+    background-color: #ffffff;
+    color: #000000;
+  }
+  h1 {
+    padding: 8em 0em 0.5em 0em;
+    text-align: center;
+  }
+  h2 {
+    page-break-before: always;
+  }
+  div.note {
+    text-align: center;
+  }
+  div.navi,div.logo {
+    display: none;
+  }
+  hr {
+    display: none;
+  }
+  pre {
+    margin: 0.8em 0.8em;
+    background-color: #ffffff;
+    border: 1px solid #aaaaaa;
+    font-size: 90%;
+  }
+  a,code,kbd {
+    color: #000000;
+    text-decoration: none;
+  }
+  h1,h2,h3 {
+    font-family: sans-serif;
+  }
+  p,div,li,dt,dd {
+    font-family: serif;
+  }
+  pre,code,kbd {
+    font-family: monospace;
+  }
+  dd {
+    font-size: 90%;
+  }
+}
+
+
+
+/* END OF FILE */
diff --git a/tcejdb/doc/icon16.png b/tcejdb/doc/icon16.png
new file mode 100644 (file)
index 0000000..5af2298
Binary files /dev/null and b/tcejdb/doc/icon16.png differ
diff --git a/tcejdb/doc/index.html b/tcejdb/doc/index.html
new file mode 100644 (file)
index 0000000..1440a08
--- /dev/null
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+
+<head>
+<meta http-equiv="Content-Language" content="en" />
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<meta http-equiv="Content-Style-Type" content="text/css" />
+<meta http-equiv="Content-Script-Type" content="text/javascript" />
+<meta name="author" content="FAL Labs" />
+<meta name="keywords" content="Tokyo Cabinet, tokyocabinet, database, DBM" />
+<meta name="description" content="homepage of Tokyo Cabinet" />
+<link rel="contents" href="./" />
+<link rel="alternate" href="index.ja.html" hreflang="ja" title="the Japanese version" />
+<link rel="stylesheet" href="common.css" />
+<link rel="icon" href="icon16.png" />
+<link rev="made" href="mailto:info@fallabs.com" />
+<title>Tokyo Cabinet: a modern implementation of DBM</title>
+<script type="text/javascript">function startup(){
+  var elem = document.getElementById("headline");
+  if(elem){
+    var now = new Date();
+    if((now.getFullYear() + now.getMonth() + now.getDate() + now.getHours()) % 4 == 0){
+      var label;
+      switch((now.getMonth() + now.getDay() + now.getHours() + now.getMinutes()) % 24){
+      default: label = "Tokyo Cabinet"; break;
+      case 1: label = "Shibuya Cabinet"; break;
+      case 2: label = "Harajuku Cabinet"; break;
+      case 4: label = "Aoyama Cabinet"; break;
+      case 5: label = "Gakudai Cabinet"; break;
+      case 7: label = "Shinjuku Cabinet"; break;
+      case 8: label = "Ikebukuro Cabinet"; break;
+      case 10: label = "Akihabara Cabinet"; break;
+      case 11: label = "Ueno Cabinet"; break;
+      case 13: label = "Sugamo Cabinet"; break;
+      case 14: label = "Akasaka Cabinet"; break;
+      case 16: label = "Roppongi Cabinet"; break;
+      case 17: label = "Yokohama Cabinet"; break;
+      case 19: label = "Saitama Cabinet"; break;
+      case 20: label = "Tokorozawa Cabinet"; break;
+      case 22: label = "Kyoto Cabinet"; break;
+      case 23: label = "Nagoya Cabinet"; break;
+      }
+      var text;
+      switch((now.getMonth() + now.getDate() + now.getMinutes()) % 12){
+      default: text = "super hyper ultra database manager"; break;
+      case 1: text = "much quicker database manager"; break;
+      case 3: text = "the ultimate database manager"; break;
+      case 5: text = "the supreme database manager"; break;
+      case 7: text = "the lightning database manager"; break;
+      case 9: text = "the mighty unbeatable invincible database manager"; break;
+      case 11: text = "the dinosaur wing of database managers"; break;
+      }
+      elem.firstChild.nodeValue = label + ": " + text;
+    }
+  }
+}
+</script>
+</head>
+
+<body onload="startup();">
+
+<h1 id="headline">Tokyo Cabinet: a modern implementation of DBM</h1>
+
+<div class="note">Copyright (C) 2006-2012 FAL Labs</div>
+<div class="note">Last Update: Sat, 18 Aug 2012 11:05:00 +0900</div>
+<div class="navi">[<span class="void">English</span>/<a href="index.ja.html" hreflang="ja">Japanese</a>]</div>
+
+<div class="logo"><img src="logo.png" id="logo" alt="Tokyo Cabinet" width="300" height="110" /></div>
+
+<hr />
+
+<h2 id="overview">Overview</h2>
+
+<p>Tokyo Cabinet is a library of routines for managing a database.  The database is a simple data file containing records, each is a pair of a key and a value.  Every key and value is serial bytes with variable length.  Both binary data and character string can be used as a key and a value.  There is neither concept of data tables nor data types.  Records are organized in hash table, B+ tree, or fixed-length array.</p>
+
+<p>Tokyo Cabinet is developed as the successor of GDBM and QDBM on the following purposes.  They are achieved and Tokyo Cabinet replaces conventional DBM products.</p>
+
+<ul>
+<li>improves space efficiency : smaller size of database file.</li>
+<li>improves time efficiency : faster processing speed.</li>
+<li>improves parallelism : higher performance in multi-thread environment.</li>
+<li>improves usability : simplified API.</li>
+<li>improves robustness : database file is not corrupted even under catastrophic situation.</li>
+<li>supports 64-bit architecture : enormous memory space and database file are available.</li>
+</ul>
+
+<p>Tokyo Cabinet is written in the C language, and provided as API of C, Perl, Ruby, Java, and Lua.  Tokyo Cabinet is available on platforms which have API conforming to C99 and POSIX.  Tokyo Cabinet is a free software licensed under the GNU Lesser General Public License.</p>
+
+<hr />
+
+<h2 id="documents">Documents</h2>
+
+<p>The following are documents of Tokyo Cabinet.  They are contained also in the source package.</p>
+
+<ul>
+<li><a href="spex-en.html">Fundamental Specifications</a></li>
+</ul>
+
+<ul>
+<li><a href="perldoc/">Specifications of Perl API</a></li>
+<li><a href="rubydoc/">Specifications of Ruby API</a></li>
+<li><a href="javadoc/">Specifications of Java API</a></li>
+<li><a href="luadoc/">Specifications of Lua API</a></li>
+</ul>
+
+<ul>
+<li><a href="tokyoproducts.pdf">Presentation</a></li>
+<li><a href="benchmark.pdf">Report of a Benchmark Test</a></li>
+</ul>
+
+<hr />
+
+<h2 id="packages">Packages</h2>
+
+<p>The following are the source packages of Tokyo Cabinet.  As for binary packages, see the site of each distributor.</p>
+
+<ul>
+<li><a href="pkg/">Source Packages of the core library</a></li>
+</ul>
+
+<ul>
+<li><a href="perlpkg/">API for Perl</a></li>
+<li><a href="rubypkg/">API for Ruby</a></li>
+<li><a href="javapkg/">API for Java</a></li>
+<li><a href="luapkg/">API for Lua</a></li>
+</ul>
+
+<ul>
+<li><a href="misc/">Related Packages</a></li>
+</ul>
+
+<hr />
+
+<h2 id="information">Information</h2>
+
+<p>Tokyo Cabinet was written and is maintained by <a href="http://fallabs.com/">FAL Labs</a>.  You can contact the author by e-mail to `info@fallabs.com'.</p>
+
+<p>The following are sibling projects of Tokyo Cabinet.</p>
+
+<ul>
+<li><a href="http://fallabs.com/tokyotyrant/">Remote Service (Tokyo Tyrant)</a></li>
+<li><a href="http://fallabs.com/tokyodystopia/">Full-text Search System (Tokyo Dystopia)</a></li>
+<li><a href="http://fallabs.com/tokyopromenade/">Content Management System (Tokyo Promenade)</a></li>
+</ul>
+
+<ul>
+<li><a href="http://fallabs.com/kyotocabinet/">Straightforward Implementation of DBM (Kyoto Cabinet)</a></li>
+</ul>
+
+<hr />
+
+</body>
+
+</html>
+
+<!-- END OF FILE -->
diff --git a/tcejdb/doc/index.ja.html b/tcejdb/doc/index.ja.html
new file mode 100644 (file)
index 0000000..60f2863
--- /dev/null
@@ -0,0 +1,200 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
+
+<head>
+<meta http-equiv="Content-Language" content="ja" />
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<meta http-equiv="Content-Style-Type" content="text/css" />
+<meta http-equiv="Content-Script-Type" content="text/javascript" />
+<meta name="author" content="FAL Labs" />
+<meta name="keywords" content="Tokyo Cabinet, tokyocabinet, database, DBM" />
+<meta name="description" content="homepage of Tokyo Cabinet" />
+<link rel="contents" href="./" />
+<link rel="alternate" href="index.html" hreflang="en" title="the English version" />
+<link rel="stylesheet" href="common.css" />
+<link rel="icon" href="icon16.png" />
+<link rev="made" href="mailto:info@fallabs.com" />
+<title>データベースマネージャ Tokyo Cabinet</title>
+<script type="text/javascript">function startup(){
+  var elem = document.getElementById("headline");
+  if(elem){
+    var now = new Date();
+    if((now.getFullYear() + now.getMonth() + now.getDate() + now.getHours()) % 4 == 0){
+      var label;
+      switch((now.getMonth() + now.getDay() + now.getHours() + now.getMinutes()) % 24){
+      default: label = "東京収納棚"; break;
+      case 1: label = "Shibuya Cabinet"; break;
+      case 2: label = "Harajuku Cabinet"; break;
+      case 4: label = "Aoyama Cabinet"; break;
+      case 5: label = "Gakudai Cabinet"; break;
+      case 7: label = "Shinjuku Cabinet"; break;
+      case 8: label = "Ikebukuro Cabinet"; break;
+      case 10: label = "Akihabara Cabinet"; break;
+      case 11: label = "Ueno Cabinet"; break;
+      case 13: label = "Sugamo Cabinet"; break;
+      case 14: label = "Akasaka Cabinet"; break;
+      case 16: label = "Roppongi Cabinet"; break;
+      case 17: label = "Yokohama Cabinet"; break;
+      case 19: label = "Saitama Cabinet"; break;
+      case 20: label = "Tokorozawa Cabinet"; break;
+      case 22: label = "Kyoto Cabinet"; break;
+      case 23: label = "Nagoya Cabinet"; break;
+      }
+      var text;
+      switch((now.getMonth() + now.getDate() + now.getMinutes()) % 48){
+      default:
+        text = "世界最強絶対無敵電光石火疾風迅雷資料基盤管理器";
+        switch(now.getSeconds()){
+        case 0:
+          label = "東京ラブストーリー";
+          text = "そんなこと東京の女の子は全然気にしないよ…";
+          break;
+        case 20:
+          label = "東京タワー";
+          text = "〜オカンとボクと、時々、オトン〜";
+          break;
+        case 40:
+          label = "東京青春朝焼物語";
+          text = "今日から俺、東京の人になる";
+          break;
+        }
+        break;
+      case 1: text = "NDBMとは違うのだよ、NDBMとは!"; break;
+      case 2: text = "Tokyo Cabinetは伊達じゃないっ!"; break;
+      case 3: text = "こいつ…動くぞ…?"; break;
+      case 5: text = "敢えて言おう、高速であると!"; break;
+      case 6: text = "しかし、私もDBMのはずだ!"; break;
+      case 7: text = "SQLで世界を変えれるって…おかしいんだよ!! "; break;
+      case 9: text = "今の私はTokyo Cabinetだ。それ以上でもそれ以下でもない。"; break;
+      case 10: text = "私はBerkeleyとは関係ない。私はいつも一人のDBMだった。"; break;
+      case 11: text = "逆立ちしたってDBMはRDBにはなれないからな。"; break;
+      case 13: text = "見せてもらおうか!Berkeleyのデータベースの性能とやらを!"; break;
+      case 14: text = "悲しいけどこれ、DBMなのよね。"; break;
+      case 15: text = "そのDBMの性能で勝ったということを忘れるな!!"; break;
+      case 17: text = "新しい時代を創るのはSQLではない!!"; break;
+      case 18: text = "えぇぃ、マイナーチェンジのくせにっ!"; break;
+      case 19: text = "あんなの飾りです!偉い人にはそれが分からんのですよ!"; break;
+      case 21: text = "よくもこんなくたびれたDBMが現役でいられるものだ。"; break;
+      case 22: text = "見える!私にもレコードが見える!"; break;
+      case 23: text = "私の愛馬は凶暴です。"; break;
+      case 25: text = "そんなRDBみたいな口の利き方、おやめなさい!"; break;
+      case 26: text = "悪い人だ。GDBMをいじめる悪い人だ。"; break;
+      case 27: text = "作戦は一刻を争う!"; break;
+      case 29: text = "僕が一番トランザクションをうまく使えるんだ。"; break;
+      case 30: text = "実は容量が足らんのです。"; break;
+      case 31: text = "おれはDBMだ! DBMでたくさんだっ!"; break;
+      case 33: text = "遊びでやってるんじゃないんだよっ!!"; break;
+      case 34: text = "あれはいいものだ!"; break;
+      case 35: text = "人は時間さえも支配できる…。"; break;
+      case 37: text = "この風!この肌触りこそDBMよ! "; break;
+      case 38: text = "さすがハッシュだ、なんともないぜ。"; break;
+      case 39: text = "プロセッサの性能差が戦力の決定的な差でないことを見せてやる!"; break;
+      case 41: text = "君は、生き延びる事ができるか…? "; break;
+      }
+      elem.firstChild.nodeValue = label + ": " + text;
+    }
+  }
+}
+</script>
+</head>
+
+<body onload="startup();">
+
+<h1 id="headline">Tokyo Cabinet: DBMの現代的な壱実装</h1>
+
+<div class="note">Copyright (C) 2006-2012 FAL Labs</div>
+<div class="note">Last Update: Sat, 18 Aug 2012 11:05:00 +0900</div>
+<div class="navi">[<a href="index.html" hreflang="en">English</a>/<span class="void">Japanese</span>]</div>
+
+<div class="logo"><img src="logo-ja.png" id="logo" alt="Tokyo Cabinet" width="300" height="110" /></div>
+
+<hr />
+
+<h2 id="overview">概要</h2>
+
+<p>Tokyo Cabinetはデータベースを扱うルーチン群のライブラリです。データベースといっても単純なもので、キーと値のペアからなるレコード群を格納したデータファイルです。キーと値は任意の長さを持つ一連のバイト列であり、文字列でもバイナリでも扱うことができます。テーブルやデータ型の概念はありません。レコードはハッシュ表かB+木か固定長配列で編成されます。</p>
+
+<p>Tokyo CabinetはGDBMやQDBMの後継として次の点を目標として開発されました。これらの目標は達成されており、Tokyo Cabinetは従来のDBMを置き換える製品だと言えます。</p>
+
+<ul>
+<li>空間効率の向上 : データベースファイルがより小さい</li>
+<li>時間効率の向上 : 処理がより高速である</li>
+<li>並列性の向上 : マルチスレッド環境での同時実行性能の向上</li>
+<li>利便性の向上 : APIがより単純である</li>
+<li>堅牢性の向上 : 不慮の事態でもデータベースファイルが壊れにくい</li>
+<li>64ビット対応 : 巨大なメモリ空間とデータベースファイルを扱える</li>
+</ul>
+
+<p>Tokyo CabinetはC言語で記述され、CとPerlとRubyとJavaとLuaのAPIとして提供されます。Tokyo CabinetはC99およびPOSIX準拠のAPIを備えるプラットフォームで利用できます。Tokyo CabinetはGNU Lesser General Public Licenseに基づくフリーソフトウェアです。</p>
+
+<hr />
+
+<h2 id="documents">文書</h2>
+
+<p>以下の文書を読んでください。ソースパッケージにも同じものが含まれています。</p>
+
+<ul>
+<li><a href="spex-ja.html">基本仕様書</a></li>
+</ul>
+
+<ul>
+<li><a href="perldoc/">Perl用API仕様書</a></li>
+<li><a href="rubydoc/">Ruby用API仕様書</a></li>
+<li><a href="javadoc/">Java用API仕様書</a></li>
+<li><a href="luadoc/">Lua用API仕様書</a></li>
+</ul>
+
+<ul>
+<li><a href="tokyoproducts.pdf">プレゼンテーション</a></li>
+<li><a href="benchmark.pdf">ベンチマークテストのレポート</a></li>
+</ul>
+
+<hr />
+
+<h2 id="packages">ダウンロード</h2>
+
+<p>以下のソースパッケージをダウンロードしてください。バイナリパッケージについては、各ディストリビュータのサイトをご覧ください。</p>
+
+<ul>
+<li><a href="pkg/">コアライブラリのソースパッケージ</a></li>
+</ul>
+
+<ul>
+<li><a href="perlpkg/">Perl用API</a></li>
+<li><a href="rubypkg/">Ruby用API</a></li>
+<li><a href="javapkg/">Java用API</a></li>
+<li><a href="luapkg/">Lua用API</a></li>
+</ul>
+
+<ul>
+<li><a href="misc/">関連するパッケージ</a></li>
+</ul>
+
+<hr />
+
+<h2 id="information">その他の情報</h2>
+
+<p>Tokyo Cabinetは<a href="http://fallabs.com/">FAL Labs</a>が作成しました。作者と連絡をとるには、`info@fallabs.com' 宛に電子メールを送ってください。</p>
+
+<p>Tokyo Cabinetの兄弟プロジェクトもあります。</p>
+
+<ul>
+<li><a href="http://fallabs.com/tokyotyrant/">リモートサービス(Tokyo Tyrant)</a></li>
+<li><a href="http://fallabs.com/tokyodystopia/">全文検索システム(Tokyo Dystopia)</a></li>
+<li><a href="http://fallabs.com/tokyopromenade/">コンテンツ管理システム(Tokyo Promenade)</a></li>
+</ul>
+
+<ul>
+<li><a href="http://fallabs.com/kyotocabinet/">DBMの率直な実装(Kyoto Cabinet)</a></li>
+</ul>
+
+<hr />
+
+</body>
+
+</html>
+
+<!-- END OF FILE -->
diff --git a/tcejdb/doc/logo-ja.png b/tcejdb/doc/logo-ja.png
new file mode 100644 (file)
index 0000000..7c80a1d
Binary files /dev/null and b/tcejdb/doc/logo-ja.png differ
diff --git a/tcejdb/doc/logo.png b/tcejdb/doc/logo.png
new file mode 100644 (file)
index 0000000..18b4c9b
Binary files /dev/null and b/tcejdb/doc/logo.png differ
diff --git a/tcejdb/doc/spex-en.html b/tcejdb/doc/spex-en.html
new file mode 100644 (file)
index 0000000..dd4645e
--- /dev/null
@@ -0,0 +1,7145 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+
+<head>
+<meta http-equiv="Content-Language" content="en" />
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<meta http-equiv="Content-Style-Type" content="text/css" />
+<meta name="author" content="FAL Labs" />
+<meta name="keywords" content="Tokyo Cabinet, tokyocabinet, database, DBM" />
+<meta name="description" content="Specifications of Tokyo Cabinet" />
+<link rel="contents" href="./" />
+<link rel="alternate" href="spex-ja.html" hreflang="ja" title="the Japanese version" />
+<link rel="stylesheet" href="common.css" />
+<link rel="icon" href="icon16.png" />
+<link rev="made" href="mailto:info@fallabs.com" />
+<title>Fundamental Specifications of Tokyo Cabinet Version 1</title>
+</head>
+
+<body>
+
+<h1 id="headline">Fundamental Specifications of Tokyo Cabinet Version 1</h1>
+
+<div class="note">Copyright (C) 2006-2012 FAL Labs</div>
+<div class="note">Last Update: Sat, 18 Aug 2012 11:05:00 +0900</div>
+<div class="navi">[<span class="void">English</span>/<a href="spex-ja.html" hreflang="ja">Japanese</a>] [<a href="index.html">HOME</a>]</div>
+
+<hr />
+
+<h2 id="contents">Table of Contents</h2>
+
+<ol>
+<li><a href="#introduction">Introduction</a></li>
+<li><a href="#features">Features</a></li>
+<li><a href="#installation">Installation</a></li>
+<li><a href="#tcutilapi">The Utility API</a></li>
+<li><a href="#tchdbapi">The Hash Database API</a></li>
+<li><a href="#tcbdbapi">The B+ Tree Database API</a></li>
+<li><a href="#tcfdbapi">The Fixed-length Database API</a></li>
+<li><a href="#tctdbapi">The Table Database API</a></li>
+<li><a href="#tcadbapi">The Abstract Database API</a></li>
+<li><a href="#fileformat">File Format</a></li>
+<li><a href="#license">License</a></li>
+</ol>
+
+<hr />
+
+<h2 id="introduction">Introduction</h2>
+
+<p>Tokyo Cabinet is a library of routines for managing a database.  The database is a simple data file containing records, each is a pair of a key and a value.  Every key and value is serial bytes with variable length.  Both binary data and character string can be used as a key and a value.  There is neither concept of data tables nor data types.  Records are organized in hash table, B+ tree, or fixed-length array.</p>
+
+<p>As for database of hash table, each key must be unique within a database, so it is impossible to store two or more records with a key overlaps.  The following access methods are provided to the database: storing a record with a key and a value, deleting a record by a key, retrieving a record by a key.  Moreover, traversal access to every key are provided, although the order is arbitrary.  These access methods are similar to ones of DBM (or its followers: NDBM and GDBM) library defined in the UNIX standard.  Tokyo Cabinet is an alternative for DBM because of its higher performance.</p>
+
+<p>As for database of B+ tree, records whose keys are duplicated can be stored.  Access methods of storing, deleting, and retrieving are provided as with the database of hash table.  Records are stored in order by a comparison function assigned by a user.  It is possible to access each record with the cursor in ascending or descending order.  According to this mechanism, forward matching search for strings and range search for integers are realized.</p>
+
+<p>As for database of fixed-length array, records are stored with unique natural numbers.  It is impossible to store two or more records with a key overlaps.  Moreover, the length of each record is limited by the specified length.  Provided operations are the same as ones of hash database.</p>
+
+<p>Table database is also provided as a variant of hash database.  Each record is identified by the primary key and has a set of named columns.  Although there is no concept of data schema, it is possible to search for records with complex conditions efficiently by using indices of arbitrary columns.</p>
+
+<p>Tokyo Cabinet is written in the C language, and provided as API of C, Perl, Ruby, Java, and Lua.  Tokyo Cabinet is available on platforms which have API conforming to C99 and POSIX.  Tokyo Cabinet is a free software licensed under the GNU Lesser General Public License.</p>
+
+<hr />
+
+<h2 id="features">Features</h2>
+
+<p>Tokyo Cabinet is the successor of QDBM and improves time and space efficiency.  This section describes the features of Tokyo Cabinet.</p>
+
+<h3 id="features_dinosaur">The Dinosaur Wing of the DBM Forks</h3>
+
+<p>Tokyo Cabinet is developed as the successor of GDBM and QDBM on the following purposes.  They are achieved and Tokyo Cabinet replaces conventional DBM products.</p>
+
+<ul>
+<li>improves <strong>space efficiency</strong> : smaller size of database file.</li>
+<li>improves <strong>time efficiency</strong> : faster processing speed.</li>
+<li>improves <strong>parallelism</strong> : higher performance in multi-thread environment.</li>
+<li>improves <strong>usability</strong> : simplified API.</li>
+<li>improves <strong>robustness</strong> : database file is not corrupted even under catastrophic situation.</li>
+<li>supports <strong>64-bit architecture</strong> : enormous memory space and database file are available.</li>
+</ul>
+
+<p>As with QDBM, the following three restrictions of traditional DBM: a process can handle only one database, the size of a key and a value is bounded, a database file is sparse, are cleared.  Moreover, the following three restrictions of QDBM: the size of a database file is limited to 2GB, environments with different byte orders can not share a database file, only one thread can search a database at the same time, are cleared.</p>
+
+<p>Tokyo Cabinet runs very fast.  For example, elapsed time to store 1 million records is 0.7 seconds for hash database, and 1.6 seconds for B+ tree database.  Moreover, the size of database of Tokyo Cabinet is very small.  For example, overhead for a record is 16 bytes for hash database, and 5 bytes for B+ tree database.  Furthermore, scalability of Tokyo Cabinet is great.  The database size can be up to 8EB (9.22e18 bytes).</p>
+
+<h3 id="features_tchdb">Effective Implementation of Hash Database</h3>
+
+<p>Tokyo Cabinet uses hash algorithm to retrieve records.  If a bucket array has sufficient number of elements, the time complexity of retrieval is "O(1)".  That is, time required for retrieving a record is constant, regardless of the scale of a database.  It is also the same about storing and deleting.  Collision of hash values is managed by separate chaining.  Data structure of the chains is binary search tree.  Even if a bucket array has unusually scarce elements, the time complexity of retrieval is "O(log n)".</p>
+
+<p>Tokyo Cabinet attains improvement in retrieval by loading RAM with the whole of a bucket array.  If a bucket array is on RAM, it is possible to access a region of a target record by about one path of file operations.  A bucket array saved in a file is not read into RAM with the `read' call but directly mapped to RAM with the `mmap' call.  Therefore, preparation time on connecting to a database is very short, and two or more processes can share the same memory map.</p>
+
+<p>If the number of elements of a bucket array is about half of records stored within a database, although it depends on characteristic of the input, the probability of collision of hash values is about 56.7% (36.8% if the same, 21.3% if twice, 11.5% if four times, 6.0% if eight times).  In such case, it is possible to retrieve a record by two or less paths of file operations.  If it is made into a performance index, in order to handle a database containing one million of records, a bucket array with half a million of elements is needed.  The size of each element is 4 bytes.  That is, if 2M bytes of RAM is available, a database containing one million records can be handled.</p>
+
+<p>Traditional DBM provides two modes of the storing operations: "insert" and "replace".  In the case a key overlaps an existing record, the insert mode keeps the existing value, while the replace mode transposes it to the specified value.  In addition to the two modes, Tokyo Cabinet provides "concatenate" mode.  In the mode, the specified value is concatenated at the end of the existing value and stored.  This feature is useful when adding an element to a value as an array.</p>
+
+<p>Generally speaking, while succession of updating, fragmentation of available regions occurs, and the size of a database grows rapidly.  Tokyo Cabinet deal with this problem by coalescence of dispensable regions and reuse of them.  When overwriting a record with a value whose size is greater than the existing one, it is necessary to remove the region to another position of the file.  Because the time complexity of the operation depends on the size of the region of a record, extending values successively is inefficient.  However, Tokyo Cabinet deal with this problem by alignment.  If increment can be put in padding, it is not necessary to remove the region.</p>
+
+<p>The "free block pool" to reuse dispensable regions efficiently is also implemented.  It keeps a list of dispensable regions and reuse the "best fit" region, that is the smallest region in the list, when a new block is requested.  Because fragmentation is inevitable even then, two kinds of optimization (defragmentation) mechanisms are implemented.  The first is called static optimization which deploys all records into another file and then writes them back to the original file at once.  The second is called dynamic optimization which gathers up dispensable regions by replacing the locations of records and dispensable regions gradually.</p>
+
+<h3 id="features_tcbdb">Useful Implementation of B+ Tree Database</h3>
+
+<p>Although B+ tree database is slower than hash database, it features ordering access to each record.  The order can be assigned by users.  Records of B+ tree are sorted and arranged in logical pages.  Sparse index organized in B tree that is multiway balanced tree are maintained for each page.  Thus, the time complexity of retrieval and so on is "O(log n)".  Cursor is provided to access each record in order.  The cursor can jump to a position specified by a key and can step forward or backward from the current position.  Because each page is arranged as double linked list, the time complexity of stepping cursor is "O(1)".</p>
+
+<p>B+ tree database is implemented, based on the above hash database.  Because each page of B+ tree is stored as each record of hash database, B+ tree database inherits efficiency of storage management of hash database.  Because the header of each record is smaller and alignment of each page is adjusted according to the page size, in most cases, the size of database file is cut by half compared to one of hash database.  Although operation of many pages are required to update B+ tree, Tokyo Cabinet expedites the process by caching pages and reducing file operations.  In most cases, because whole of the sparse index is cached on memory, it is possible to retrieve a record by one or less path of file operations.</p>
+
+<p>Each pages of B+ tree can be stored with compressed.  Two compression method; Deflate of ZLIB and Block Sorting of BZIP2, are supported.  Because each record in a page has similar patterns, high efficiency of compression is expected due to the Lempel-Ziv or the BWT algorithms.  In case handling text data, the size of a database is reduced to about 25%.  If the scale of a database is large and disk I/O is the bottleneck, featuring compression makes the processing speed improved to a large extent.</p>
+
+<h3 id="features_tcfdb">Naive Implementation of Fixed-length Database</h3>
+
+<p>Fixed-length database has restrictions that each key should be a natural number and that the length of each value is limited.  However, time efficiency and space efficiency are higher than the other data structures as long as the use case is within the restriction.</p>
+
+<p>Because the whole region of the database is mapped on memory by the `mmap' call and referred as a multidimensional array, the overhead related to the file I/O is minimized.  Due to this simple structure, fixed-length database works faster than hash database, and its concurrency in multi-thread environment is prominent.</p>
+
+<p>The size of the database is proportional to the range of keys and the limit size of each value.  That is, the smaller the range of keys is or the smaller the length of each value is, the higher the space efficiency is.  For example, if the maximum key is 1000000 and the limit size of the value is 100 bytes, the size of the database will be about 100MB.  Because regions around referred records are only loaded on the RAM, you can increase the size of the database to the size of the virtual memory.</p>
+
+<h3 id="features_tctdb">Flexible Implementation of Table Database</h3>
+
+<p>Table database does not express simple key/value structure but expresses a structure like a table of relational database.  Each record is identified by the primary key and has a set of multiple columns named with arbitrary strings.  For example, a stuff in your company can be expressed by a record identified by the primary key of the employee ID number and structured by columns of his name, division, salary, and so on.  Unlike relational database, table database does not need to define any data schema and can contain records of various structures different from each other.</p>
+
+<p>Table database supports query functions with not only the primary key but also with conditions about arbitrary columns.  Each column condition is composed of the name of a column and a condition expression.  Operators of full matching, forward matching, regular expression matching, and so on are provided for the string type.  Operators of full matching, range matching and so on are provided for the number type.  Operators for tag search and full-text search are also provided.  A query can contain multiple conditions for logical intersection.  Search by multiple queries for logical union is also available.  The order of the result set can be specified as the ascending or descending order of strings or numbers.</p>
+
+<p>You can create indices for arbitrary columns to improve performance of search and sorting.  Although columns do not have data types, indices have types for strings or numbers.  Inverted indices for space separated tokens and character N-gram tokens are also supported.  The query optimizer uses indices in suitable way according to each query.  Indices are implemented as different files of B+ tree database.</p>
+
+<h3 id="features_practical">Practical Functionality</h3>
+
+<p>Databases on the filesystem feature transaction mechanisms.  It is possible to commit a series of operations between the beginning and the end of the transaction in a lump, or to abort the transaction and perform rollback to the state before the transaction.  Two isolation levels are supported; serializable and read uncommitted.  Durability is secured by write ahead logging and shadow paging.</p>
+
+<p>Tokyo Cabinet provides two modes to connect to a database: "reader" and "writer".  A reader can perform retrieving but neither storing nor deleting.  A writer can perform all access methods.  Exclusion control between processes is performed when connecting to a database by file locking.  While a writer is connected to a database, neither readers nor writers can be connected.  While a reader is connected to a database, other readers can be connect, but writers can not.  According to this mechanism, data consistency is guaranteed with simultaneous connections in multitasking environment.</p>
+
+<p>Functions of API of Tokyo cabinet are reentrant and available in multi-thread environment.  Discrete database object can be operated in parallel entirely.  For simultaneous operations of the same database object, read-write lock is used for exclusion control.  That is, while a writing thread is operating the database, other reading threads and writing threads are blocked.  However, while a reading thread is operating the database, reading threads are not blocked.  The locking granularity of hash database and fixed-length database is per record, and that of the other databases is per file.</p>
+
+<h3 id="features_simple">Simple but Various Interfaces</h3>
+
+<p>Tokyo Cabinet provides simple API based on the object oriented design.  Every operation for database is encapsulated and published as lucid methods as `open' (connect), `close' (disconnect), `put' (insert), `out' (remove), `get' (retrieve), and so on.  Because the three of hash, B+ tree, and fixed-length array database APIs are very similar with each other, porting an application from one to the other is easy.  Moreover, the abstract API is provided to handle these databases with the same interface.  Applications of the abstract API can determine the type of the database in runtime.</p>
+
+<p>The utility API is also provided.  Such fundamental data structure as list and map are included.  And, some useful features; memory pool, string processing, encoding, are also included.</p>
+
+<p>Six kinds of API; the utility API, the hash database API, the B+ tree database API, the fixed-length database API, the table database API, and the abstract database API, are provided for the C language.  Command line interfaces are also provided corresponding to each API.  They are useful for prototyping, test, and debugging.  Except for C, Tokyo Cabinet provides APIs for Perl, Ruby, Java, and Lua.  APIs for other languages will hopefully be provided by third party.</p>
+
+<p>In cases that multiple processes access a database at the same time or some processes access a database on a remote host, the remote service is useful.  The remote service is composed of a database server and its access library.  Applications can access the database server by using the remote database API.  The server implements HTTP and the memcached protocol partly so that client programs on almost all platforms can access the server easily.</p>
+
+<hr />
+
+<h2 id="installation">Installation</h2>
+
+<p>This section describes how to install Tokyo Cabinet with the source package.  As for a binary package, see its installation manual.</p>
+
+<h3 id="installation_preparation">Preparation</h3>
+
+<p>Tokyo Cabinet is available on UNIX-like systems.  At least, the following environments are supported.</p>
+
+<ul>
+<li>Linux 2.4 and later (x86-32/x86-64/PowerPC/Alpha/SPARC)</li>
+<li>Mac OS X 10.3 and later (x86-32/x86-64/PowerPC)</li>
+</ul>
+
+<p><code>gcc</code> 3.1 or later and <code>make</code> are required to install Tokyo Cabinet with the source package.  They are installed by default on Linux, FreeBSD and so on.</p>
+
+<p>As Tokyo Cabinet depends on the following libraries, install them beforehand.</p>
+
+<ul>
+<li><a href="http://www.zlib.net/">zlib</a> : for loss-less data compression.  1.2.3 or later is suggested.</li>
+<li><a href="http://www.bzip.org/">bzip2</a> : for loss-less data compression.  1.0.5 or later is suggested.</li>
+</ul>
+
+<h3 id="installation_installation">Installation</h3>
+
+<p>When an archive file of Tokyo Cabinet is extracted, change the current working directory to the generated directory and perform installation.</p>
+
+<p>Run the configuration script.</p>
+
+<pre>./configure
+</pre>
+
+<p>Build programs.</p>
+
+<pre>make
+</pre>
+
+<p>Perform self-diagnostic test.</p>
+
+<pre>make check
+</pre>
+
+<p>Install programs.  This operation must be carried out by the <code>root</code> user.</p>
+
+<pre>make install
+</pre>
+
+<h3 id="installation_result">Result</h3>
+
+<p>When a series of work finishes, the following files will be installed.</p>
+
+<pre>/usr/local/include/tcutil.h
+/usr/local/include/tchdb.h
+/usr/local/include/tcbdb.h
+/usr/local/include/tcfdb.h
+/usr/local/include/tctdb.h
+/usr/local/include/tcadb.h
+/usr/local/lib/libtokyocabinet.a
+/usr/local/lib/libtokyocabinet.so.x.y.z
+/usr/local/lib/libtokyocabinet.so.x
+/usr/local/lib/libtokyocabinet.so
+/usr/local/lib/pkgconfig/tokyocabinet.pc
+/usr/local/bin/tcutest
+/usr/local/bin/tcumttest
+/usr/local/bin/tcucodec
+/usr/local/bin/tchtest
+/usr/local/bin/tchmttest
+/usr/local/bin/tchmgr
+/usr/local/bin/tcbmgr
+/usr/local/bin/tcbtest
+/usr/local/bin/tcbmttest
+/usr/local/bin/tcftest
+/usr/local/bin/tcfmttest
+/usr/local/bin/tcfmgr
+/usr/local/bin/tcttest
+/usr/local/bin/tctmttest
+/usr/local/bin/tctmgr
+/usr/local/bin/tcatest
+/usr/local/bin/tcamttest
+/usr/local/bin/tcamgr
+/usr/local/libexec/tcawmgr.cgi
+/usr/local/share/tokyocabinet/...
+/usr/local/man/man1/...
+/usr/local/man/man3/...
+</pre>
+
+<h3 id="installation_option">Options of Configure</h3>
+
+<p>The following options can be specified with `<code>./configure</code>'.</p>
+
+<ul class="options">
+<li><code>--enable-debug</code> : build for debugging.  Enable debugging symbols, do not perform optimization, and perform static linking.</li>
+<li><code>--enable-devel</code> : build for development.  Enable debugging symbols, perform optimization, and perform dynamic linking.</li>
+<li><code>--enable-profile</code> : build for profiling.  Enable profiling symbols, perform optimization, and perform dynamic linking.</li>
+<li><code>--enable-static</code> : build by static linking.</li>
+<li><code>--enable-fastest</code> : build for fastest run.</li>
+<li><code>--enable-off64</code> : build with 64-bit file offset on 32-bit system.</li>
+<li><code>--enable-swab</code> : build for swapping byte-orders.</li>
+<li><code>--enable-uyield</code> : build for detecting race conditions.</li>
+<li><code>--disable-zlib</code> : build without ZLIB compression.</li>
+<li><code>--disable-bzip</code> : build without BZIP2 compression.</li>
+<li><code>--disable-pthread</code> : build without POSIX thread support.</li>
+<li><code>--disable-shared</code> :  avoid to build shared libraries.</li>
+</ul>
+
+<p>`<code>--prefix</code>' and other options are also available as with usual UNIX software packages.  If you want to install Tokyo Cabinet under `<code>/usr</code>' not `<code>/usr/local</code>', specify `<code>--prefix=/usr</code>'.  As well, the library search path does not include `<code>/usr/local/lib</code>', it is necessary to set the environment variable `<code>LD_LIBRARY_PATH</code>' to include `<code>/usr/local/lib</code>' before running applications of Tokyo Cabinet.</p>
+
+<h3 id="installation_library">How to Use the Library</h3>
+
+<p>Tokyo Cabinet provides API of the C language and it is available by programs conforming to the C89 (ANSI C) standard or the C99 standard.  As the header files of Tokyo Cabinet are provided as `<code>tcutil.h</code>', `<code>tchdb.h</code>', `<code>tcbdb.h</code>', and `<code>tcadb.h</code>', applications should include one or more of them accordingly to use the API.  As the library is provided as `<code>libtokyocabinet.a</code>' and `<code>libtokyocabinet.so</code>' and they depend on `<code>libz.so</code>', `<code>libbz2.so</code>', `<code>librt.so</code>', `<code>libpthread.so</code>', `<code>libm.so</code>', and `<code>libc.so</code>', linker options corresponding to them are required by the build command.  The typical build command is the following.</p>
+
+<pre>gcc -I/usr/local/include tc_example.c -o tc_example \
+  -L/usr/local/lib -ltokyocabinet -lz -lbz2 -lrt -lpthread -lm -lc
+</pre>
+
+<p>You can also use Tokyo Cabinet in programs written in C++.  Because each header is wrapped in C linkage (`<code>extern "C"</code>' block), you can simply include them into your C++ programs.</p>
+
+<hr />
+
+<h2 id="tcutilapi">The Utility API</h2>
+
+<p>The utility API is a set of routines to handle records on memory easily.  Especially, extensible string, array list, hash map, and ordered tree are useful.  See `<code>tcutil.h</code>' for the entire specification.</p>
+
+<h3 id="tcutilapi_description">Description</h3>
+
+<p>To use the utility API, include `<code>tcutil.h</code>' and related standard header files.  Usually, write the following description near the front of a source file.</p>
+
+<dl>
+<dt><code>#include &lt;tcutil.h&gt;</code></dt>
+<dt><code>#include &lt;stdlib.h&gt;</code></dt>
+<dt><code>#include &lt;stdbool.h&gt;</code></dt>
+<dt><code>#include &lt;stdint.h&gt;</code></dt>
+</dl>
+
+<p>Objects whose type is pointer to `<code>TCXSTR</code>' are used for extensible string.  An extensible string object is created with the function `<code>tcxstrnew</code>' and is deleted with the function `<code>tcxstrdel</code>'.  Objects whose type is pointer to `<code>TCLIST</code>' are used for array list.  A list object is created with the function `<code>tclistnew</code>' and is deleted with the function `<code>tclistdel</code>'.  Objects whose type is pointer to `<code>TCMAP</code>' are used for hash map.  A map object is created with the function `<code>tcmapnew</code>' and is deleted with the function `<code>tcmapdel</code>'.  Objects whose type is pointer to `<code>TCTREE</code>' are used for ordered tree.  A tree object is created with the function `<code>tctreenew</code>' and is deleted with the function `<code>tctreedel</code>'.  To avoid memory leak, it is important to delete every object when it is no longer in use.</p>
+
+<h3 id="tcutilapi_basicapi">API of Basic Utilities</h3>
+
+<p>The constant `tcversion' is the string containing the version information.</p>
+
+<dl class="api">
+<dt><code>extern const char *tcversion;</code></dt>
+</dl>
+
+<p>The variable `tcfatalfunc' is the pointer to the call back function for handling a fatal error.</p>
+
+<dl class="api">
+<dt><code>extern void (*tcfatalfunc)(const char *);</code></dt>
+<dd>The argument specifies the error message.</dd>
+<dd>The initial value of this variable is `NULL'.  If the value is `NULL', the default function is called when a fatal error occurs.  A fatal error occurs when memory allocation is failed.</dd>
+</dl>
+
+<p>The function `tcmalloc' is used in order to allocate a region on memory.</p>
+
+<dl class="api">
+<dt><code>void *tcmalloc(size_t <var>size</var>);</code></dt>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>The return value is the pointer to the allocated region.</dd>
+<dd>This function handles failure of memory allocation implicitly.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tccalloc' is used in order to allocate a nullified region on memory.</p>
+
+<dl class="api">
+<dt><code>void *tccalloc(size_t <var>nmemb</var>, size_t <var>size</var>);</code></dt>
+<dd>`<var>nmemb</var>' specifies the number of elements.</dd>
+<dd>`<var>size</var>' specifies the size of each element.</dd>
+<dd>The return value is the pointer to the allocated nullified region.</dd>
+<dd>This function handles failure of memory allocation implicitly.  Because the region of the return value is allocated with the `calloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcrealloc' is used in order to re-allocate a region on memory.</p>
+
+<dl class="api">
+<dt><code>void *tcrealloc(void *<var>ptr</var>, size_t <var>size</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>The return value is the pointer to the re-allocated region.</dd>
+<dd>This function handles failure of memory allocation implicitly.  Because the region of the return value is allocated with the `realloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcmemdup' is used in order to duplicate a region on memory.</p>
+
+<dl class="api">
+<dt><code>void *tcmemdup(const void *<var>ptr</var>, size_t <var>size</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>The return value is the pointer to the allocated region of the duplicate.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcstrdup' is used in order to duplicate a string on memory.</p>
+
+<dl class="api">
+<dt><code>char *tcstrdup(const void *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string.</dd>
+<dd>The return value is the allocated string equivalent to the specified string.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcfree' is used in order to free a region on memory.</p>
+
+<dl class="api">
+<dt><code>void tcfree(void *<var>ptr</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.  If it is `NULL', this function has no effect.</dd>
+<dd>Although this function is just a wrapper of `free' call, this is useful in applications using another package of the `malloc' series.</dd>
+</dl>
+
+<h3 id="tcutilapi_xstrapi">API of Extensible String</h3>
+
+<p>The function `tcxstrnew' is used in order to create an extensible string object.</p>
+
+<dl class="api">
+<dt><code>TCXSTR *tcxstrnew(void);</code></dt>
+<dd>The return value is the new extensible string object.</dd>
+</dl>
+
+<p>The function `tcxstrnew2' is used in order to create an extensible string object from a character string.</p>
+
+<dl class="api">
+<dt><code>TCXSTR *tcxstrnew2(const char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string of the initial content.</dd>
+<dd>The return value is the new extensible string object containing the specified string.</dd>
+</dl>
+
+<p>The function `tcxstrnew3' is used in order to create an extensible string object with the initial allocation size.</p>
+
+<dl class="api">
+<dt><code>TCXSTR *tcxstrnew3(int <var>asiz</var>);</code></dt>
+<dd>`<var>asiz</var>' specifies the initial allocation size.</dd>
+<dd>The return value is the new extensible string object.</dd>
+</dl>
+
+<p>The function `tcxstrdup' is used in order to copy an extensible string object.</p>
+
+<dl class="api">
+<dt><code>TCXSTR *tcxstrdup(const TCXSTR *<var>xstr</var>);</code></dt>
+<dd>`<var>xstr</var>' specifies the extensible string object.</dd>
+<dd>The return value is the new extensible string object equivalent to the specified object.</dd>
+</dl>
+
+<p>The function `tcxstrdel' is used in order to delete an extensible string object.</p>
+
+<dl class="api">
+<dt><code>void tcxstrdel(TCXSTR *<var>xstr</var>);</code></dt>
+<dd>`<var>xstr</var>' specifies the extensible string object.</dd>
+<dd>Note that the deleted object and its derivatives can not be used anymore.</dd>
+</dl>
+
+<p>The function `tcxstrcat' is used in order to concatenate a region to the end of an extensible string object.</p>
+
+<dl class="api">
+<dt><code>void tcxstrcat(TCXSTR *<var>xstr</var>, const void *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>xstr</var>' specifies the extensible string object.</dd>
+<dd>`<var>ptr</var>' specifies the pointer to the region to be appended.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+</dl>
+
+<p>The function `tcxstrcat2' is used in order to concatenate a character string to the end of an extensible string object.</p>
+
+<dl class="api">
+<dt><code>void tcxstrcat2(TCXSTR *<var>xstr</var>, const char *<var>str</var>);</code></dt>
+<dd>`<var>xstr</var>' specifies the extensible string object.</dd>
+<dd>`<var>str</var>' specifies the string to be appended.</dd>
+</dl>
+
+<p>The function `tcxstrptr' is used in order to get the pointer of the region of an extensible string object.</p>
+
+<dl class="api">
+<dt><code>const void *tcxstrptr(const TCXSTR *<var>xstr</var>);</code></dt>
+<dd>`<var>xstr</var>' specifies the extensible string object.</dd>
+<dd>The return value is the pointer of the region of the object.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.</dd>
+</dl>
+
+<p>The function `tcxstrsize' is used in order to get the size of the region of an extensible string object.</p>
+
+<dl class="api">
+<dt><code>int tcxstrsize(const TCXSTR *<var>xstr</var>);</code></dt>
+<dd>`<var>xstr</var>' specifies the extensible string object.</dd>
+<dd>The return value is the size of the region of the object.</dd>
+</dl>
+
+<p>The function `tcxstrclear' is used in order to clear an extensible string object.</p>
+
+<dl class="api">
+<dt><code>void tcxstrclear(TCXSTR *<var>xstr</var>);</code></dt>
+<dd>`<var>xstr</var>' specifies the extensible string object.</dd>
+<dd>The internal buffer of the object is cleared and the size is set zero.</dd>
+</dl>
+
+<p>The function `tcxstrprintf' is used in order to perform formatted output into an extensible string object.</p>
+
+<dl class="api">
+<dt><code>void tcxstrprintf(TCXSTR *<var>xstr</var>, const char *<var>format</var>, ...);</code></dt>
+<dd>`<var>xstr</var>' specifies the extensible string object.</dd>
+<dd>`<var>format</var>' specifies the printf-like format string.  The conversion character `%' can be used with such flag characters as `s', `d', `o', `u', `x', `X', `c', `e', `E', `f', `g', `G', `@', `?', `b', and `%'.  `@' works as with `s' but escapes meta characters of XML.  `?' works as with `s' but escapes meta characters of URL.  `b' converts an integer to the string as binary numbers.  The other conversion character work as with each original.</dd>
+<dd>The other arguments are used according to the format string.</dd>
+</dl>
+
+<p>The function `tcsprintf' is used in order to allocate a formatted string on memory.</p>
+
+<dl class="api">
+<dt><code>char *tcsprintf(const char *<var>format</var>, ...);</code></dt>
+<dd>`<var>format</var>' specifies the printf-like format string.  The conversion character `%' can be used with such flag characters as `s', `d', `o', `u', `x', `X', `c', `e', `E', `f', `g', `G', `@', `?', `b', and `%'.  `@' works as with `s' but escapes meta characters of XML.  `?' works as with `s' but escapes meta characters of URL.  `b' converts an integer to the string as binary numbers.  The other conversion character work as with each original.</dd>
+<dd>The other arguments are used according to the format string.</dd>
+<dd>The return value is the pointer to the region of the result string.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<h3 id="tcutilapi_listapi">API of Array List</h3>
+
+<p>The function `tclistnew' is used in order to create a list object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tclistnew(void);</code></dt>
+<dd>The return value is the new list object.</dd>
+</dl>
+
+<p>The function `tclistnew2' is used in order to create a list object with expecting the number of elements.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tclistnew2(int <var>anum</var>);</code></dt>
+<dd>`<var>anum</var>' specifies the number of elements expected to be stored in the list.</dd>
+<dd>The return value is the new list object.</dd>
+</dl>
+
+<p>The function `tclistnew3' is used in order to create a list object with initial string elements.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tclistnew3(const char *<var>str</var>, ...);</code></dt>
+<dd>`<var>str</var>' specifies the string of the first element.</dd>
+<dd>The other arguments are other elements.  They should be trailed by a `NULL' argument.</dd>
+<dd>The return value is the new list object.</dd>
+</dl>
+
+<p>The function `tclistdup' is used in order to copy a list object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tclistdup(const TCLIST *<var>list</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>The return value is the new list object equivalent to the specified object.</dd>
+</dl>
+
+<p>The function `tclistdel' is used in order to delete a list object.</p>
+
+<dl class="api">
+<dt><code>void tclistdel(TCLIST *<var>list</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>Note that the deleted object and its derivatives can not be used anymore.</dd>
+</dl>
+
+<p>The function `tclistnum' is used in order to get the number of elements of a list object.</p>
+
+<dl class="api">
+<dt><code>int tclistnum(const TCLIST *<var>list</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>The return value is the number of elements of the list.</dd>
+</dl>
+
+<p>The function `tclistval' is used in order to get the pointer to the region of an element of a list object.</p>
+
+<dl class="api">
+<dt><code>const void *tclistval(const TCLIST *<var>list</var>, int <var>index</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>index</var>' specifies the index of the element.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>The return value is the pointer to the region of the value.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  If `index' is equal to or more than the number of elements, the return value is `NULL'.</dd>
+</dl>
+
+<p>The function `tclistval2' is used in order to get the string of an element of a list object.</p>
+
+<dl class="api">
+<dt><code>const char *tclistval2(const TCLIST *<var>list</var>, int <var>index</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>index</var>' specifies the index of the element.</dd>
+<dd>The return value is the string of the value.</dd>
+<dd>If `index' is equal to or more than the number of elements, the return value is `NULL'.</dd>
+</dl>
+
+<p>The function `tclistpush' is used in order to add an element at the end of a list object.</p>
+
+<dl class="api">
+<dt><code>void tclistpush(TCLIST *<var>list</var>, const void *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>ptr</var>' specifies the pointer to the region of the new element.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+</dl>
+
+<p>The function `tclistpush2' is used in order to add a string element at the end of a list object.</p>
+
+<dl class="api">
+<dt><code>void tclistpush2(TCLIST *<var>list</var>, const char *<var>str</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>str</var>' specifies the string of the new element.</dd>
+</dl>
+
+<p>The function `tclistpop' is used in order to remove an element of the end of a list object.</p>
+
+<dl class="api">
+<dt><code>void *tclistpop(TCLIST *<var>list</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>The return value is the pointer to the region of the removed element.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  If the list is empty, the return value is `NULL'.</dd>
+</dl>
+
+<p>The function `tclistpop2' is used in order to remove a string element of the end of a list object.</p>
+
+<dl class="api">
+<dt><code>char *tclistpop2(TCLIST *<var>list</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>The return value is the string of the removed element.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  If the list is empty, the return value is `NULL'.</dd>
+</dl>
+
+<p>The function `tclistunshift' is used in order to add an element at the top of a list object.</p>
+
+<dl class="api">
+<dt><code>void tclistunshift(TCLIST *<var>list</var>, const void *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>ptr</var>' specifies the pointer to the region of the new element.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+</dl>
+
+<p>The function `tclistunshift2' is used in order to add a string element at the top of a list object.</p>
+
+<dl class="api">
+<dt><code>void tclistunshift2(TCLIST *<var>list</var>, const char *<var>str</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>str</var>' specifies the string of the new element.</dd>
+</dl>
+
+<p>The function `tclistshift' is used in order to remove an element of the top of a list object.</p>
+
+<dl class="api">
+<dt><code>void *tclistshift(TCLIST *<var>list</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>The return value is the pointer to the region of the removed element.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  If the list is empty, the return value is `NULL'.</dd>
+</dl>
+
+<p>The function `tclistshift2' is used in order to remove a string element of the top of a list object.</p>
+
+<dl class="api">
+<dt><code>char *tclistshift2(TCLIST *<var>list</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>The return value is the string of the removed element.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  If the list is empty, the return value is `NULL'.</dd>
+</dl>
+
+<p>The function `tclistinsert' is used in order to add an element at the specified location of a list object.</p>
+
+<dl class="api">
+<dt><code>void tclistinsert(TCLIST *<var>list</var>, int <var>index</var>, const void *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>index</var>' specifies the index of the new element.</dd>
+<dd>`<var>ptr</var>' specifies the pointer to the region of the new element.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>If `index' is equal to or more than the number of elements, this function has no effect.</dd>
+</dl>
+
+<p>The function `tclistinsert2' is used in order to add a string element at the specified location of a list object.</p>
+
+<dl class="api">
+<dt><code>void tclistinsert2(TCLIST *<var>list</var>, int <var>index</var>, const char *<var>str</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>index</var>' specifies the index of the new element.</dd>
+<dd>`<var>str</var>' specifies the string of the new element.</dd>
+<dd>If `index' is equal to or more than the number of elements, this function has no effect.</dd>
+</dl>
+
+<p>The function `tclistremove' is used in order to remove an element at the specified location of a list object.</p>
+
+<dl class="api">
+<dt><code>void *tclistremove(TCLIST *<var>list</var>, int <var>index</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>index</var>' specifies the index of the element to be removed.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>The return value is the pointer to the region of the removed element.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  If `index' is equal to or more than the number of elements, no element is removed and the return value is `NULL'.</dd>
+</dl>
+
+<p>The function `tclistremove2' is used in order to remove a string element at the specified location of a list object.</p>
+
+<dl class="api">
+<dt><code>char *tclistremove2(TCLIST *<var>list</var>, int <var>index</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>index</var>' specifies the index of the element to be removed.</dd>
+<dd>The return value is the string of the removed element.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  If `index' is equal to or more than the number of elements, no element is removed and the return value is `NULL'.</dd>
+</dl>
+
+<p>The function `tclistover' is used in order to overwrite an element at the specified location of a list object.</p>
+
+<dl class="api">
+<dt><code>void tclistover(TCLIST *<var>list</var>, int <var>index</var>, const void *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>index</var>' specifies the index of the element to be overwritten.</dd>
+<dd>`<var>ptr</var>' specifies the pointer to the region of the new content.</dd>
+<dd>`<var>size</var>' specifies the size of the new content.</dd>
+<dd>If `index' is equal to or more than the number of elements, this function has no effect.</dd>
+</dl>
+
+<p>The function `tclistover2' is used in order to overwrite a string element at the specified location of a list object.</p>
+
+<dl class="api">
+<dt><code>void tclistover2(TCLIST *<var>list</var>, int <var>index</var>, const char *<var>str</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>index</var>' specifies the index of the element to be overwritten.</dd>
+<dd>`<var>str</var>' specifies the string of the new content.</dd>
+<dd>If `index' is equal to or more than the number of elements, this function has no effect.</dd>
+</dl>
+
+<p>The function `tclistsort' is used in order to sort elements of a list object in lexical order.</p>
+
+<dl class="api">
+<dt><code>void tclistsort(TCLIST *<var>list</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+</dl>
+
+<p>The function `tclistlsearch' is used in order to search a list object for an element using liner search.</p>
+
+<dl class="api">
+<dt><code>int tclistlsearch(const TCLIST *<var>list</var>, const void *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>ptr</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>The return value is the index of a corresponding element or -1 if there is no corresponding element.</dd>
+<dd>If two or more elements correspond, the former returns.</dd>
+</dl>
+
+<p>The function `tclistbsearch' is used in order to search a list object for an element using binary search.</p>
+
+<dl class="api">
+<dt><code>int tclistbsearch(const TCLIST *<var>list</var>, const void *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.  It should be sorted in lexical order.</dd>
+<dd>`<var>ptr</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>The return value is the index of a corresponding element or -1 if there is no corresponding element.</dd>
+<dd>If two or more elements correspond, which returns is not defined.</dd>
+</dl>
+
+<p>The function `tclistclear' is used in order to clear a list object.</p>
+
+<dl class="api">
+<dt><code>void tclistclear(TCLIST *<var>list</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>All elements are removed.</dd>
+</dl>
+
+<p>The function `tclistdump' is used in order to serialize a list object into a byte array.</p>
+
+<dl class="api">
+<dt><code>void *tclistdump(const TCLIST *<var>list</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>The return value is the pointer to the region of the result serial region.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tclistload' is used in order to create a list object from a serialized byte array.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tclistload(const void *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region of serialized byte array.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>The return value is a new list object.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<h3 id="tcutilapi_mapapi">API of Hash Map</h3>
+
+<p>The function `tcmapnew' is used in order to create a map object.</p>
+
+<dl class="api">
+<dt><code>TCMAP *tcmapnew(void);</code></dt>
+<dd>The return value is the new map object.</dd>
+</dl>
+
+<p>The function `tcmapnew2' is used in order to create a map object with specifying the number of the buckets.</p>
+
+<dl class="api">
+<dt><code>TCMAP *tcmapnew2(uint32_t <var>bnum</var>);</code></dt>
+<dd>`<var>bnum</var>' specifies the number of the buckets.</dd>
+<dd>The return value is the new map object.</dd>
+</dl>
+
+<p>The function `tcmapnew3' is used in order to create a map object with initial string elements.</p>
+
+<dl class="api">
+<dt><code>TCMAP *tcmapnew3(const char *<var>str</var>, ...);</code></dt>
+<dd>`<var>str</var>' specifies the string of the first element.</dd>
+<dd>The other arguments are other elements.  They should be trailed by a `NULL' argument.</dd>
+<dd>The return value is the new map object.</dd>
+<dd>The key and the value of each record are situated one after the other.</dd>
+</dl>
+
+<p>The function `tcmapdup' is used in order to copy a map object.</p>
+
+<dl class="api">
+<dt><code>TCMAP *tcmapdup(const TCMAP *<var>map</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>The return value is the new map object equivalent to the specified object.</dd>
+</dl>
+
+<p>The function `tcmapdel' is used in order to delete a map object.</p>
+
+<dl class="api">
+<dt><code>void tcmapdel(TCMAP *<var>map</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>Note that the deleted object and its derivatives can not be used anymore.</dd>
+</dl>
+
+<p>The function `tcmapput' is used in order to store a record into a map object.</p>
+
+<dl class="api">
+<dt><code>void tcmapput(TCMAP *<var>map</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If a record with the same key exists in the map, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcmapput2' is used in order to store a string record into a map object.</p>
+
+<dl class="api">
+<dt><code>void tcmapput2(TCMAP *<var>map</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If a record with the same key exists in the map, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcmapputkeep' is used in order to store a new record into a map object.</p>
+
+<dl class="api">
+<dt><code>bool tcmapputkeep(TCMAP *<var>map</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the map, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcmapputkeep2' is used in order to store a new string record into a map object.</p>
+
+<dl class="api">
+<dt><code>bool tcmapputkeep2(TCMAP *<var>map</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the map, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcmapputcat' is used in order to concatenate a value at the end of the value of the existing record in a map object.</p>
+
+<dl class="api">
+<dt><code>void tcmapputcat(TCMAP *<var>map</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcmapputcat2' is used in order to concatenate a string value at the end of the value of the existing record in a map object.</p>
+
+<dl class="api">
+<dt><code>void tcmapputcat2(TCMAP *<var>map</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcmapout' is used in order to remove a record of a map object.</p>
+
+<dl class="api">
+<dt><code>bool tcmapout(TCMAP *<var>map</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is true.  False is returned when no record corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `tcmapout2' is used in order to remove a string record of a map object.</p>
+
+<dl class="api">
+<dt><code>bool tcmapout2(TCMAP *<var>map</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is true.  False is returned when no record corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `tcmapget' is used in order to retrieve a record in a map object.</p>
+
+<dl class="api">
+<dt><code>const void *tcmapget(const TCMAP *<var>map</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned when no record corresponds.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.</dd>
+</dl>
+
+<p>The function `tcmapget2' is used in order to retrieve a string record in a map object.</p>
+
+<dl class="api">
+<dt><code>const char *tcmapget2(const TCMAP *<var>map</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the string of the value of the corresponding record.  `NULL' is returned when no record corresponds.</dd>
+</dl>
+
+<p>The function `tcmapmove' is used in order to move a record to the edge of a map object.</p>
+
+<dl class="api">
+<dt><code>bool tcmapmove(TCMAP *<var>map</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, bool <var>head</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of a key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>head</var>' specifies the destination which is the head if it is true or the tail if else.</dd>
+<dd>If successful, the return value is true.  False is returned when no record corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `tcmapmove2' is used in order to move a string record to the edge of a map object.</p>
+
+<dl class="api">
+<dt><code>bool tcmapmove2(TCMAP *<var>map</var>, const char *<var>kstr</var>, bool <var>head</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kstr</var>' specifies the string of a key.</dd>
+<dd>`<var>head</var>' specifies the destination which is the head if it is true or the tail if else.</dd>
+<dd>If successful, the return value is true.  False is returned when no record corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `tcmapiterinit' is used in order to initialize the iterator of a map object.</p>
+
+<dl class="api">
+<dt><code>void tcmapiterinit(TCMAP *<var>map</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>The iterator is used in order to access the key of every record stored in the map object.</dd>
+</dl>
+
+<p>The function `tcmapiternext' is used in order to get the next key of the iterator of a map object.</p>
+
+<dl class="api">
+<dt><code>const void *tcmapiternext(TCMAP *<var>map</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record can be fetched from the iterator.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  The order of iteration is assured to be the same as the stored order.</dd>
+</dl>
+
+<p>The function `tcmapiternext2' is used in order to get the next key string of the iterator of a map object.</p>
+
+<dl class="api">
+<dt><code>const char *tcmapiternext2(TCMAP *<var>map</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record can be fetched from the iterator.</dd>
+<dd>The order of iteration is assured to be the same as the stored order.</dd>
+</dl>
+
+<p>The function `tcmaprnum' is used in order to get the number of records stored in a map object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcmaprnum(const TCMAP *<var>map</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>The return value is the number of the records stored in the map object.</dd>
+</dl>
+
+<p>The function `tcmapmsiz' is used in order to get the total size of memory used in a map object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcmapmsiz(const TCMAP *<var>map</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>The return value is the total size of memory used in a map object.</dd>
+</dl>
+
+<p>The function `tcmapkeys' is used in order to create a list object containing all keys in a map object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcmapkeys(const TCMAP *<var>map</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>The return value is the new list object containing all keys in the map object.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcmapvals' is used in order to create a list object containing all values in a map object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcmapvals(const TCMAP *<var>map</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>The return value is the new list object containing all values in the map object.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcmapaddint' is used in order to add an integer to a record in a map object.</p>
+
+<dl class="api">
+<dt><code>int tcmapaddint(TCMAP *<var>map</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int <var>num</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>The return value is the summation value.</dd>
+<dd>If the corresponding record exists, the value is treated as an integer and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tcmapadddouble' is used in order to add a real number to a record in a map object.</p>
+
+<dl class="api">
+<dt><code>double tcmapadddouble(TCMAP *<var>map</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, double <var>num</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>The return value is the summation value.</dd>
+<dd>If the corresponding record exists, the value is treated as a real number and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tcmapclear' is used in order to clear a map object.</p>
+
+<dl class="api">
+<dt><code>void tcmapclear(TCMAP *<var>map</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>All records are removed.</dd>
+</dl>
+
+<p>The function `tcmapcutfront' is used in order to remove front records of a map object.</p>
+
+<dl class="api">
+<dt><code>void tcmapcutfront(TCMAP *<var>map</var>, int <var>num</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>num</var>' specifies the number of records to be removed.</dd>
+</dl>
+
+<p>The function `tcmapdump' is used in order to serialize a map object into a byte array.</p>
+
+<dl class="api">
+<dt><code>void *tcmapdump(const TCMAP *<var>map</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>The return value is the pointer to the region of the result serial region.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcmapload' is used in order to create a map object from a serialized byte array.</p>
+
+<dl class="api">
+<dt><code>TCMAP *tcmapload(const void *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region of serialized byte array.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>The return value is a new map object.</dd>
+<dd>Because the object of the return value is created with the function `tcmapnew', it should be deleted with the function `tcmapdel' when it is no longer in use.</dd>
+</dl>
+
+<h3 id="tcutilapi_treeapi">API of Ordered Tree</h3>
+
+<p>The function `tctreenew' is used in order to create a tree object.</p>
+
+<dl class="api">
+<dt><code>TCTREE *tctreenew(void);</code></dt>
+<dd>The return value is the new tree object.</dd>
+</dl>
+
+<p>The function `tctreenew2' is used in order to create a tree object with specifying the custom comparison function.</p>
+
+<dl class="api">
+<dt><code>TCTREE *tctreenew2(TCCMP <var>cmp</var>, void *<var>cmpop</var>);</code></dt>
+<dd>`<var>cmp</var>' specifies the pointer to the custom comparison function.  It receives five parameters.  The first parameter is the pointer to the region of one key.  The second parameter is the size of the region of one key.  The third parameter is the pointer to the region of the other key.  The fourth parameter is the size of the region of the other key.  The fifth parameter is the pointer to the optional opaque object.  It returns positive if the former is big, negative if the latter is big, 0 if both are equivalent.</dd>
+<dd>`<var>cmpop</var>' specifies an arbitrary pointer to be given as a parameter of the comparison function.  If it is not needed, `NULL' can be specified.</dd>
+<dd>The return value is the new tree object.</dd>
+<dd>The default comparison function compares keys of two records by lexical order.  The functions `tccmplexical' (dafault), `tccmpdecimal', `tccmpint32', and `tccmpint64' are built-in.</dd>
+</dl>
+
+<p>The function `tctreedup' is used in order to copy a tree object.</p>
+
+<dl class="api">
+<dt><code>TCTREE *tctreedup(const TCTREE *<var>tree</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>The return value is the new tree object equivalent to the specified object.</dd>
+</dl>
+
+<p>The function `tctreedel' is used in order to delete a tree object.</p>
+
+<dl class="api">
+<dt><code>void tctreedel(TCTREE *<var>tree</var>);</code></dt>
+<dd>`tree' specifies the tree object.</dd>
+<dd>Note that the deleted object and its derivatives can not be used anymore.</dd>
+</dl>
+
+<p>The function `tctreeput' is used in order to store a record into a tree object.</p>
+
+<dl class="api">
+<dt><code>void tctreeput(TCTREE *<var>tree</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If a record with the same key exists in the tree, it is overwritten.</dd>
+</dl>
+
+<p>The function `tctreeput2' is used in order to store a string record into a tree object.</p>
+
+<dl class="api">
+<dt><code>void tctreeput2(TCTREE *<var>tree</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If a record with the same key exists in the tree, it is overwritten.</dd>
+</dl>
+
+<p>The function `tctreeputkeep' is used in order to store a new record into a tree object.</p>
+
+<dl class="api">
+<dt><code>bool tctreeputkeep(TCTREE *<var>tree</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the tree, this function has no effect.</dd>
+</dl>
+
+<p>The function `tctreeputkeep2' is used in order to store a new string record into a tree object.</p>
+
+<dl class="api">
+<dt><code>bool tctreeputkeep2(TCTREE *<var>tree</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the tree, this function has no effect.</dd>
+</dl>
+
+<p>The function `tctreeputcat' is used in order to concatenate a value at the end of the value of the existing record in a tree object.</p>
+
+<dl class="api">
+<dt><code>void tctreeputcat(TCTREE *<var>tree</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tctreeputcat2' is used in order to concatenate a string value at the end of the value of the existing record in a tree object.</p>
+
+<dl class="api">
+<dt><code>void tctreeputcat2(TCTREE *<var>tree</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tctreeout' is used in order to remove a record of a tree object.</p>
+
+<dl class="api">
+<dt><code>bool tctreeout(TCTREE *<var>tree</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is true.  False is returned when no record corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `tctreeout2' is used in order to remove a string record of a tree object.</p>
+
+<dl class="api">
+<dt><code>bool tctreeout2(TCTREE *<var>tree</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is true.  False is returned when no record corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `tctreeget' is used in order to retrieve a record in a tree object.</p>
+
+<dl class="api">
+<dt><code>const void *tctreeget(TCTREE *<var>tree</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned when no record corresponds.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.</dd>
+</dl>
+
+<p>The function `tctreeget2' is used in order to retrieve a string record in a tree object.</p>
+
+<dl class="api">
+<dt><code>const char *tctreeget2(TCTREE *<var>tree</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the string of the value of the corresponding record.  `NULL' is returned when no record corresponds.</dd>
+</dl>
+
+<p>The function `tctreeiterinit' is used in order to initialize the iterator of a tree object.</p>
+
+<dl class="api">
+<dt><code>void tctreeiterinit(TCTREE *<var>tree</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>The iterator is used in order to access the key of every record stored in the tree object.</dd>
+</dl>
+
+<p>The function `tctreeiternext' is used in order to get the next key of the iterator of a tree object.</p>
+
+<dl class="api">
+<dt><code>const void *tctreeiternext(TCTREE *<var>tree</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record can be fetched from the iterator.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  The order of iteration is assured to be ascending of the keys.</dd>
+</dl>
+
+<p>The function `tctreeiternext2' is used in order to get the next key string of the iterator of a tree object.</p>
+
+<dl class="api">
+<dt><code>const char *tctreeiternext2(TCTREE *<var>tree</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record can be fetched from the iterator.</dd>
+<dd>The order of iteration is assured to be ascending of the keys.</dd>
+</dl>
+
+<p>The function `tctreernum' is used in order to get the number of records stored in a tree object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tctreernum(const TCTREE *<var>tree</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>The return value is the number of the records stored in the tree object.</dd>
+</dl>
+
+<p>The function `tctreemsiz' is used in order to get the total size of memory used in a tree object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tctreemsiz(const TCTREE *<var>tree</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>The return value is the total size of memory used in a tree object.</dd>
+</dl>
+
+<p>The function `tctreekeys' is used in order to create a list object containing all keys in a tree object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tctreekeys(const TCTREE *<var>tree</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>The return value is the new list object containing all keys in the tree object.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tctreevals' is used in order to create a list object containing all values in a tree object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tctreevals(const TCTREE *<var>tree</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>The return value is the new list object containing all values in the tree object.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tctreeaddint' is used in order to add an integer to a record in a tree object.</p>
+
+<dl class="api">
+<dt><code>int tctreeaddint(TCTREE *<var>tree</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int <var>num</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>The return value is the summation value.</dd>
+<dd>If the corresponding record exists, the value is treated as an integer and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tctreeadddouble' is used in order to add a real number to a record in a tree object.</p>
+
+<dl class="api">
+<dt><code>double tctreeadddouble(TCTREE *<var>tree</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, double <var>num</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>The return value is the summation value.</dd>
+<dd>If the corresponding record exists, the value is treated as a real number and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tctreeclear' is used in order to clear a tree object.</p>
+
+<dl class="api">
+<dt><code>void tctreeclear(TCTREE *<var>tree</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>All records are removed.</dd>
+</dl>
+
+<p>The function `tctreecutfringe' is used in order to remove fringe records of a tree object.</p>
+
+<dl class="api">
+<dt><code>void tctreecutfringe(TCTREE *<var>tree</var>, int <var>num</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>num</var>' specifies the number of records to be removed.</dd>
+</dl>
+
+<p>The function `tctreedump' is used in order to serialize a tree object into a byte array.</p>
+
+<dl class="api">
+<dt><code>void *tctreedump(const TCTREE *<var>tree</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>The return value is the pointer to the region of the result serial region.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tctreeload' is used in order to create a tree object from a serialized byte array.</p>
+
+<dl class="api">
+<dt><code>TCTREE *tctreeload(const void *<var>ptr</var>, int <var>size</var>, TCCMP <var>cmp</var>, void *<var>cmpop</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region of serialized byte array.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>cmp</var>' specifies the pointer to the custom comparison function.</dd>
+<dd>`<var>cmpop</var>' specifies an arbitrary pointer to be given as a parameter of the comparison function.</dd>
+<dd>If it is not needed, `NULL' can be specified.</dd>
+<dd>The return value is a new tree object.</dd>
+<dd>Because the object of the return value is created with the function `tctreenew', it should be deleted with the function `tctreedel' when it is no longer in use.</dd>
+</dl>
+
+<h3 id="tcutilapi_mdbapi">API of On-memory Hash Database</h3>
+
+<p>The function `tcmdbnew' is used in order to create an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>TCMDB *tcmdbnew(void);</code></dt>
+<dd>The return value is the new on-memory hash database object.</dd>
+<dd>The object can be shared by plural threads because of the internal mutex.</dd>
+</dl>
+
+<p>The function `tcmdbnew2' is used in order to create an on-memory hash database object with specifying the number of the buckets.</p>
+
+<dl class="api">
+<dt><code>TCMDB *tcmdbnew2(uint32_t <var>bnum</var>);</code></dt>
+<dd>`<var>bnum</var>' specifies the number of the buckets.</dd>
+<dd>The return value is the new on-memory hash database object.</dd>
+<dd>The object can be shared by plural threads because of the internal mutex.</dd>
+</dl>
+
+<p>The function `tcmdbdel' is used in order to delete an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>void tcmdbdel(TCMDB *<var>mdb</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+</dl>
+
+<p>The function `tcmdbput' is used in order to store a record into an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>void tcmdbput(TCMDB *<var>mdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcmdbput2' is used in order to store a string record into an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>void tcmdbput2(TCMDB *<var>mdb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcmdbputkeep' is used in order to store a new record into an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tcmdbputkeep(TCMDB *<var>mdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcmdbputkeep2' is used in order to store a new string record into an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tcmdbputkeep2(TCMDB *<var>mdb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcmdbputcat' is used in order to concatenate a value at the end of the existing record in an on-memory hash database.</p>
+
+<dl class="api">
+<dt><code>void tcmdbputcat(TCMDB *<var>mdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcmdbputcat2' is used in order to concatenate a string at the end of the existing record in an on-memory hash database.</p>
+
+<dl class="api">
+<dt><code>void tcmdbputcat2(TCMDB *<var>mdb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcmdbout' is used in order to remove a record of an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tcmdbout(TCMDB *<var>mdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is true.  False is returned when no record corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `tcmdbout2' is used in order to remove a string record of an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tcmdbout2(TCMDB *<var>mdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is true.  False is returned when no record corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `tcmdbget' is used in order to retrieve a record in an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>void *tcmdbget(TCMDB *<var>mdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned when no record corresponds.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcmdbget2' is used in order to retrieve a string record in an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>char *tcmdbget2(TCMDB *<var>mdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the string of the value of the corresponding record.  `NULL' is returned when no record corresponds.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcmdbvsiz' is used in order to get the size of the value of a record in an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>int tcmdbvsiz(TCMDB *<var>mdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tcmdbvsiz2' is used in order to get the size of the value of a string record in an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>int tcmdbvsiz2(TCMDB *<var>mdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tcmdbiterinit' is used in order to initialize the iterator of an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>void tcmdbiterinit(TCMDB *<var>mdb</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>The iterator is used in order to access the key of every record stored in the on-memory hash database.</dd>
+</dl>
+
+<p>The function `tcmdbiternext' is used in order to get the next key of the iterator of an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>void *tcmdbiternext(TCMDB *<var>mdb</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return</dd>
+<dd>value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record can be fetched from the iterator.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  The order of iteration is assured to be the same as the stored order.</dd>
+</dl>
+
+<p>The function `tcmdbiternext2' is used in order to get the next key string of the iterator of an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>char *tcmdbiternext2(TCMDB *<var>mdb</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record can be fetched from the iterator.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  The order of iteration is assured to be the same as the stored order.</dd>
+</dl>
+
+<p>The function `tcmdbfwmkeys' is used in order to get forward matching keys in an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcmdbfwmkeys(TCMDB *<var>mdb</var>, const void *<var>pbuf</var>, int <var>psiz</var>, int <var>max</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>pbuf</var>' specifies the pointer to the region of the prefix.</dd>
+<dd>`<var>psiz</var>' specifies the size of the region of the prefix.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.</dd>
+</dl>
+
+<p>The function `tcmdbfwmkeys2' is used in order to get forward matching string keys in an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcmdbfwmkeys2(TCMDB *<var>mdb</var>, const char *<var>pstr</var>, int <var>max</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>pstr</var>' specifies the string of the prefix.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.</dd>
+</dl>
+
+<p>The function `tcmdbrnum' is used in order to get the number of records stored in an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcmdbrnum(TCMDB *<var>mdb</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>The return value is the number of the records stored in the database.</dd>
+</dl>
+
+<p>The function `tcmdbmsiz' is used in order to get the total size of memory used in an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcmdbmsiz(TCMDB *<var>mdb</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>The return value is the total size of memory used in the database.</dd>
+</dl>
+
+<p>The function `tcmdbaddint' is used in order to add an integer to a record in an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>int tcmdbaddint(TCMDB *<var>mdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int <var>num</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>The return value is the summation value.</dd>
+<dd>If the corresponding record exists, the value is treated as an integer and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tcmdbadddouble' is used in order to add a real number to a record in an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>double tcmdbadddouble(TCMDB *<var>mdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, double <var>num</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>The return value is the summation value.</dd>
+<dd>If the corresponding record exists, the value is treated as a real number and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tcmdbvanish' is used in order to clear an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>void tcmdbvanish(TCMDB *<var>mdb</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>All records are removed.</dd>
+</dl>
+
+<p>The function `tcmdbcutfront' is used in order to remove front records of an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>void tcmdbcutfront(TCMDB *<var>mdb</var>, int <var>num</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>num</var>' specifies the number of records to be removed.</dd>
+</dl>
+
+<h3 id="tcutilapi_ndbapi">API of On-memory Tree Database</h3>
+
+<p>The function `tcndbnew' is used in order to create an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>TCNDB *tcndbnew(void);</code></dt>
+<dd>The return value is the new on-memory tree database object.</dd>
+<dd>The object can be shared by plural threads because of the internal mutex.</dd>
+</dl>
+
+<p>The function `tcndbnew2' is used in order to create an on-memory tree database object with specifying the custom comparison function.</p>
+
+<dl class="api">
+<dt><code>TCNDB *tcndbnew2(TCCMP <var>cmp</var>, void *<var>cmpop</var>);</code></dt>
+<dd>`<var>cmp</var>' specifies the pointer to the custom comparison function.</dd>
+<dd>`<var>cmpop</var>' specifies an arbitrary pointer to be given as a parameter of the comparison function.  If it is not needed, `NULL' can be specified.</dd>
+<dd>The return value is the new on-memory tree database object.</dd>
+<dd>The default comparison function compares keys of two records by lexical order.  The functions `tccmplexical' (dafault), `tccmpdecimal', `tccmpint32', and `tccmpint64' are built-in.  The object can be shared by plural threads because of the internal mutex.</dd>
+</dl>
+
+<p>The function `tcndbdel' is used in order to delete an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>void tcndbdel(TCNDB *<var>ndb</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+</dl>
+
+<p>The function `tcndbput' is used in order to store a record into an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>void tcndbput(TCNDB *<var>ndb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcndbput2' is used in order to store a string record into an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>void tcndbput2(TCNDB *<var>ndb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcndbputkeep' is used in order to store a new record into an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcndbputkeep(TCNDB *<var>ndb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcndbputkeep2' is used in order to store a new string record into an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcndbputkeep2(TCNDB *<var>ndb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcndbputcat' is used in order to concatenate a value at the end of the existing record in an on-memory tree database.</p>
+
+<dl class="api">
+<dt><code>void tcndbputcat(TCNDB *<var>ndb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcndbputcat2' is used in order to concatenate a string at the end of the existing record in an on-memory tree database.</p>
+
+<dl class="api">
+<dt><code>void tcndbputcat2(TCNDB *<var>ndb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcndbout' is used in order to remove a record of an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcndbout(TCNDB *<var>ndb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is true.  False is returned when no record corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `tcndbout2' is used in order to remove a string record of an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcndbout2(TCNDB *<var>ndb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is true.  False is returned when no record corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `tcndbget' is used in order to retrieve a record in an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>void *tcndbget(TCNDB *<var>ndb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned when no record corresponds.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcndbget2' is used in order to retrieve a string record in an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>char *tcndbget2(TCNDB *<var>ndb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the string of the value of the corresponding record.  `NULL' is returned when no record corresponds.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcndbvsiz' is used in order to get the size of the value of a record in an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>int tcndbvsiz(TCNDB *<var>ndb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tcndbvsiz2' is used in order to get the size of the value of a string record in an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>int tcndbvsiz2(TCNDB *<var>ndb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tcndbiterinit' is used in order to initialize the iterator of an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>void tcndbiterinit(TCNDB *<var>ndb</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>The iterator is used in order to access the key of every record stored in the on-memory database.</dd>
+</dl>
+
+<p>The function `tcndbiternext' is used in order to get the next key of the iterator of an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>void *tcndbiternext(TCNDB *<var>ndb</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record can be fetched from the iterator.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  The order of iteration is assured to be the same as the stored order.</dd>
+</dl>
+
+<p>The function `tcndbiternext2' is used in order to get the next key string of the iterator of an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>char *tcndbiternext2(TCNDB *<var>ndb</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record can be fetched from the iterator.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  The order of iteration is assured to be the same as the stored order.</dd>
+</dl>
+
+<p>The function `tcndbfwmkeys' is used in order to get forward matching keys in an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcndbfwmkeys(TCNDB *<var>ndb</var>, const void *<var>pbuf</var>, int <var>psiz</var>, int <var>max</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>pbuf</var>' specifies the pointer to the region of the prefix.</dd>
+<dd>`<var>psiz</var>' specifies the size of the region of the prefix.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcndbfwmkeys2' is used in order to get forward matching string keys in an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcndbfwmkeys2(TCNDB *<var>ndb</var>, const char *<var>pstr</var>, int <var>max</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>pstr</var>' specifies the string of the prefix.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcndbrnum' is used in order to get the number of records stored in an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcndbrnum(TCNDB *<var>ndb</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>The return value is the number of the records stored in the database.</dd>
+</dl>
+
+<p>The function `tcndbmsiz' is used in order to get the total size of memory used in an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcndbmsiz(TCNDB *<var>ndb</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>The return value is the total size of memory used in the database.</dd>
+</dl>
+
+<p>The function `tcndbaddint' is used in order to add an integer to a record in an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>int tcndbaddint(TCNDB *<var>ndb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int <var>num</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>The return value is the summation value.</dd>
+<dd>If the corresponding record exists, the value is treated as an integer and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tcndbadddouble' is used in order to add a real number to a record in an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>double tcndbadddouble(TCNDB *<var>ndb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, double <var>num</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>The return value is the summation value.</dd>
+<dd>If the corresponding record exists, the value is treated as a real number and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tcndbvanish' is used in order to clear an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>void tcndbvanish(TCNDB *<var>ndb</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>All records are removed.</dd>
+</dl>
+
+<p>The function `tcndbcutfringe' is used in order to remove fringe records of an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>void tcndbcutfringe(TCNDB *<var>ndb</var>, int <var>num</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>num</var>' specifies the number of records to be removed.</dd>
+</dl>
+
+<h3 id="tcutilapi_mpoolapi">API of Memory Pool</h3>
+
+<p>The function `tcmpoolnew' is used in order to create a memory pool object.</p>
+
+<dl class="api">
+<dt><code>TCMPOOL *tcmpoolnew(void);</code></dt>
+<dd>The return value is the new memory pool object.</dd>
+</dl>
+
+<p>The function `tcmpooldel' is used in order to delete a memory pool object.</p>
+
+<dl class="api">
+<dt><code>void tcmpooldel(TCMPOOL *<var>mpool</var>);</code></dt>
+<dd>`<var>mpool</var>' specifies the memory pool object.</dd>
+<dd>Note that the deleted object and its derivatives can not be used anymore.</dd>
+</dl>
+
+<p>The function `tcmpoolpush' is used in order to relegate an arbitrary object to a memory pool object.</p>
+
+<dl class="api">
+<dt><code>void *tcmpoolpush(TCMPOOL *<var>mpool</var>, void *<var>ptr</var>, void (*<var>del</var>)(void *));</code></dt>
+<dd>`<var>mpool</var>' specifies the memory pool object.</dd>
+<dd>`<var>ptr</var>' specifies the pointer to the object to be relegated.  If it is `NULL', this function has no effect.</dd>
+<dd>`<var>del</var>' specifies the pointer to the function to delete the object.</dd>
+<dd>The return value is the pointer to the given object.</dd>
+<dd>This function assures that the specified object is deleted when the memory pool object is deleted.</dd>
+</dl>
+
+<p>The function `tcmpoolpushptr' is used in order to relegate an allocated region to a memory pool object.</p>
+
+<dl class="api">
+<dt><code>void *tcmpoolpushptr(TCMPOOL *<var>mpool</var>, void *<var>ptr</var>);</code></dt>
+<dd>`<var>mpool</var>' specifies the memory pool object.</dd>
+<dd>`<var>ptr</var>' specifies the pointer to the region to be relegated.  If it is `NULL', this function has no effect.</dd>
+<dd>The return value is the pointer to the given object.</dd>
+<dd>This function assures that the specified region is released when the memory pool object is deleted.</dd>
+</dl>
+
+<p>The function `tcmpoolpushxstr' is used in order to relegate an extensible string object to a memory pool object.</p>
+
+<dl class="api">
+<dt><code>TCXSTR *tcmpoolpushxstr(TCMPOOL *<var>mpool</var>, TCXSTR *<var>xstr</var>);</code></dt>
+<dd>`<var>mpool</var>' specifies the memory pool object.</dd>
+<dd>`<var>xstr</var>' specifies the extensible string object.  If it is `NULL', this function has no effect.</dd>
+<dd>The return value is the pointer to the given object.</dd>
+<dd>This function assures that the specified object is deleted when the memory pool object is deleted.</dd>
+</dl>
+
+<p>The function `tcmpoolpushlist' is used in order to relegate a list object to a memory pool object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcmpoolpushlist(TCMPOOL *<var>mpool</var>, TCLIST *<var>list</var>);</code></dt>
+<dd>`<var>mpool</var>' specifies the memory pool object.</dd>
+<dd>`<var>list</var>' specifies the list object.  If it is `NULL', this function has no effect.</dd>
+<dd>The return value is the pointer to the given object.</dd>
+<dd>This function assures that the specified object is deleted when the memory pool object is deleted.</dd>
+</dl>
+
+<p>The function `tcmpoolpushmap' is used in order to relegate a map object to a memory pool object.</p>
+
+<dl class="api">
+<dt><code>TCMAP *tcmpoolpushmap(TCMPOOL *<var>mpool</var>, TCMAP *<var>map</var>);</code></dt>
+<dd>`<var>mpool</var>' specifies the memory pool object.</dd>
+<dd>`<var>map</var>' specifies the map object.  If it is `NULL', this function has no effect.</dd>
+<dd>The return value is the pointer to the given object.</dd>
+<dd>This function assures that the specified object is deleted when the memory pool object is deleted.</dd>
+</dl>
+
+<p>The function `tcmpoolpushtree' is used in order to relegate a tree object to a memory pool object.</p>
+
+<dl class="api">
+<dt><code>TCTREE *tcmpoolpushtree(TCMPOOL *<var>mpool</var>, TCTREE *<var>tree</var>);</code></dt>
+<dd>`<var>mpool</var>' specifies the memory pool object.</dd>
+<dd>`<var>tree</var>' specifies the tree object.  If it is `NULL', this function has no effect.</dd>
+<dd>The return value is the pointer to the given object.</dd>
+<dd>This function assures that the specified object is deleted when the memory pool object is deleted.</dd>
+</dl>
+
+<p>The function `tcmpoolmalloc' is used in order to allocate a region relegated to a memory pool object.</p>
+
+<dl class="api">
+<dt><code>void *tcmpoolmalloc(TCMPOOL *<var>mpool</var>, size_t <var>size</var>);</code></dt>
+<dd>`<var>mpool</var>' specifies the memory pool object.</dd>
+<dd>The return value is the pointer to the allocated region under the memory pool.</dd>
+</dl>
+
+<p>The function `tcmpoolxstrnew' is used in order to create an extensible string object relegated to a memory pool object.</p>
+
+<dl class="api">
+<dt><code>TCXSTR *tcmpoolxstrnew(TCMPOOL *<var>mpool</var>);</code></dt>
+<dd>The return value is the new extensible string object under the memory pool.</dd>
+</dl>
+
+<p>The function `tcmpoollistnew' is used in order to create a list object relegated to a memory pool object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcmpoollistnew(TCMPOOL *<var>mpool</var>);</code></dt>
+<dd>The return value is the new list object under the memory pool.</dd>
+</dl>
+
+<p>The function `tcmpoolmapnew' is used in order to create a map object relegated to a memory pool object.</p>
+
+<dl class="api">
+<dt><code>TCMAP *tcmpoolmapnew(TCMPOOL *<var>mpool</var>);</code></dt>
+<dd>The return value is the new map object under the memory pool.</dd>
+</dl>
+
+<p>The function `tcmpooltreenew' is used in order to create a tree object relegated to a memory pool object.</p>
+
+<dl class="api">
+<dt><code>TCTREE *tcmpooltreenew(TCMPOOL *<var>mpool</var>);</code></dt>
+<dd>The return value is the new tree object under the memory pool.</dd>
+</dl>
+
+<p>The function `tcmpoolpop' is used in order to remove the most recently installed cleanup handler of a memory pool object.</p>
+
+<dl class="api">
+<dt><code>void tcmpoolpop(TCMPOOL *<var>mpool</var>, bool <var>exe</var>);</code></dt>
+<dd>`<var>mpool</var>' specifies the memory pool object.</dd>
+<dd>`<var>exe</var>' specifies whether to execute the destructor of the removed handler.</dd>
+</dl>
+
+<p>The function `tcmpoolclear' is used in order to remove all cleanup handler of a memory pool object.</p>
+
+<dl class="api">
+<dt><code>void tcmpoolclear(TCMPOOL *<var>mpool</var>, bool <var>exe</var>);</code></dt>
+<dd>`<var>mpool</var>' specifies the memory pool object.</dd>
+<dd>`<var>exe</var>' specifies whether to execute the destructors of the removed handlers.</dd>
+</dl>
+
+<p>The function `tcmpoolglobal' is used in order to get the global memory pool object.</p>
+
+<dl class="api">
+<dt><code>TCMPOOL *tcmpoolglobal(void);</code></dt>
+<dd>The return value is the global memory pool object.</dd>
+<dd>The global memory pool object is a singleton and assured to be deleted when the process is terminating normally.</dd>
+</dl>
+
+<h3 id="tcutilapi_miscapi">API of Miscellaneous Utilities</h3>
+
+<p>The function `tclmax' is used in order to get the larger value of two integers.</p>
+
+<dl class="api">
+<dt><code>long tclmax(long <var>a</var>, long <var>b</var>);</code></dt>
+<dd>`<var>a</var>' specifies an integer.</dd>
+<dd>`<var>b</var>' specifies the other integer.</dd>
+<dd>The return value is the larger value of the two.</dd>
+</dl>
+
+<p>The function `tclmin' is used in order to get the lesser value of two integers.</p>
+
+<dl class="api">
+<dt><code>long tclmin(long <var>a</var>, long <var>b</var>);</code></dt>
+<dd>`<var>a</var>' specifies an integer.</dd>
+<dd>`<var>b</var>' specifies the other integer.</dd>
+<dd>The return value is the lesser value of the two.</dd>
+</dl>
+
+<p>The function `tclrand' is used in order to get a random number as long integer based on uniform distribution.</p>
+
+<dl class="api">
+<dt><code>unsigned long tclrand(void);</code></dt>
+<dd>The return value is the random number between 0 and `ULONG_MAX'.</dd>
+<dd>This function uses the random number source device and generates a real random number if possible.</dd>
+</dl>
+
+<p>The function `tcdrand' is used in order to get a random number as double decimal based on uniform distribution.</p>
+
+<dl class="api">
+<dt><code>double tcdrand(void);</code></dt>
+<dd>The return value is the random number equal to or greater than 0, and less than 1.0.</dd>
+<dd>This function uses the random number source device and generates a real random number if possible.</dd>
+</dl>
+
+<p>The function `tcdrandnd' is used in order to get a random number as double decimal based on normal distribution.</p>
+
+<dl class="api">
+<dt><code>double tcdrandnd(double <var>avg</var>, double <var>sd</var>);</code></dt>
+<dd>`<var>avg</var>' specifies the average.</dd>
+<dd>`<var>sd</var>' specifies the standard deviation.</dd>
+<dd>The return value is the random number.</dd>
+<dd>This function uses the random number source device and generates a real random number if possible.</dd>
+</dl>
+
+<p>The function `tcstricmp' is used in order to compare two strings with case insensitive evaluation.</p>
+
+<dl class="api">
+<dt><code>int tcstricmp(const char *<var>astr</var>, const char *<var>bstr</var>);</code></dt>
+<dd>`<var>astr</var>' specifies a string.</dd>
+<dd>`<var>bstr</var>' specifies of the other string.</dd>
+<dd>The return value is positive if the former is big, negative if the latter is big, 0 if both are equivalent.</dd>
+</dl>
+
+<p>The function `tcstrfwm' is used in order to check whether a string begins with a key.</p>
+
+<dl class="api">
+<dt><code>bool tcstrfwm(const char *<var>str</var>, const char *<var>key</var>);</code></dt>
+<dd>`<var>str</var>' specifies the target string.</dd>
+<dd>`<var>key</var>' specifies the forward matching key string.</dd>
+<dd>The return value is true if the target string begins with the key, else, it is false.</dd>
+</dl>
+
+<p>The function `tcstrifwm' is used in order to check whether a string begins with a key with case insensitive evaluation.</p>
+
+<dl class="api">
+<dt><code>bool tcstrifwm(const char *<var>str</var>, const char *<var>key</var>);</code></dt>
+<dd>`<var>str</var>' specifies the target string.</dd>
+<dd>`<var>key</var>' specifies the forward matching key string.</dd>
+<dd>The return value is true if the target string begins with the key, else, it is false.</dd>
+</dl>
+
+<p>The function `tcstrbwm' is used in order to check whether a string ends with a key.</p>
+
+<dl class="api">
+<dt><code>bool tcstrbwm(const char *<var>str</var>, const char *<var>key</var>);</code></dt>
+<dd>`<var>str</var>' specifies the target string.</dd>
+<dd>`<var>key</var>' specifies the backward matching key string.</dd>
+<dd>The return value is true if the target string ends with the key, else, it is false.</dd>
+</dl>
+
+<p>The function `tcstribwm' is used in order to check whether a string ends with a key with case insensitive evaluation.</p>
+
+<dl class="api">
+<dt><code>bool tcstribwm(const char *<var>str</var>, const char *<var>key</var>);</code></dt>
+<dd>`<var>str</var>' specifies the target string.</dd>
+<dd>`<var>key</var>' specifies the backward matching key string.</dd>
+<dd>The return value is true if the target string ends with the key, else, it is false.</dd>
+</dl>
+
+<p>The function `tcstrdist' is used in order to calculate the edit distance of two strings.</p>
+
+<dl class="api">
+<dt><code>int tcstrdist(const char *<var>astr</var>, const char *<var>bstr</var>);</code></dt>
+<dd>`<var>astr</var>' specifies a string.</dd>
+<dd>`<var>bstr</var>' specifies of the other string.</dd>
+<dd>The return value is the edit distance which is known as the Levenshtein distance.  The cost is calculated by byte.</dd>
+</dl>
+
+<p>The function `tcstrdistutf' is used in order to calculate the edit distance of two UTF-8 strings.</p>
+
+<dl class="api">
+<dt><code>int tcstrdistutf(const char *<var>astr</var>, const char *<var>bstr</var>);</code></dt>
+<dd>`<var>astr</var>' specifies a string.</dd>
+<dd>`<var>bstr</var>' specifies of the other string.</dd>
+<dd>The return value is the edit distance which is known as the Levenshtein distance.  The cost is calculated by Unicode character.</dd>
+</dl>
+
+<p>The function `tcstrtoupper' is used in order to convert the letters of a string into upper case.</p>
+
+<dl class="api">
+<dt><code>char *tcstrtoupper(char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string to be converted.</dd>
+<dd>The return value is the string itself.</dd>
+</dl>
+
+<p>The function `tcstrtolower' is used in order to convert the letters of a string into lower case.</p>
+
+<dl class="api">
+<dt><code>char *tcstrtolower(char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string to be converted.</dd>
+<dd>The return value is the string itself.</dd>
+</dl>
+
+<p>The function `tcstrtrim' is used in order to cut space characters at head or tail of a string.</p>
+
+<dl class="api">
+<dt><code>char *tcstrtrim(char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string to be converted.</dd>
+<dd>The return value is the string itself.</dd>
+</dl>
+
+<p>The function `tcstrsqzspc' is used in order to squeeze space characters in a string and trim it.</p>
+
+<dl class="api">
+<dt><code>char *tcstrsqzspc(char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string to be converted.</dd>
+<dd>The return value is the string itself.</dd>
+</dl>
+
+<p>The function `tcstrsubchr' is used in order to substitute characters in a string.</p>
+
+<dl class="api">
+<dt><code>char *tcstrsubchr(char *<var>str</var>, const char *<var>rstr</var>, const char *<var>sstr</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string to be converted.</dd>
+<dd>`<var>rstr</var>' specifies the string containing characters to be replaced.</dd>
+<dd>`<var>sstr</var>' specifies the string containing characters to be substituted.</dd>
+<dd>If the substitute string is shorter then the replacement string, corresponding characters are removed.</dd>
+</dl>
+
+<p>The function `tcstrcntutf' is used in order to count the number of characters in a string of UTF-8.</p>
+
+<dl class="api">
+<dt><code>int tcstrcntutf(const char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string of UTF-8.</dd>
+<dd>The return value is the number of characters in the string.</dd>
+</dl>
+
+<p>The function `tcstrcututf' is used in order to cut a string of UTF-8 at the specified number of characters.</p>
+
+<dl class="api">
+<dt><code>char *tcstrcututf(char *<var>str</var>, int <var>num</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string of UTF-8.</dd>
+<dd>`<var>num</var>' specifies the number of characters to be kept.</dd>
+<dd>The return value is the string itself.</dd>
+</dl>
+
+<p>The function `tcstrutftoucs' is used in order to convert a UTF-8 string into a UCS-2 array.</p>
+
+<dl class="api">
+<dt><code>void tcstrutftoucs(const char *<var>str</var>, uint16_t *<var>ary</var>, int *<var>np</var>);</code></dt>
+<dd>`<var>str</var>' specifies the UTF-8 string.</dd>
+<dd>`<var>ary</var>' specifies the pointer to the region into which the result UCS-2 codes are written.  The size of the buffer should be sufficient.</dd>
+<dd>`<var>np</var>' specifies the pointer to a variable into which the number of elements of the result array is assigned.</dd>
+</dl>
+
+<p>The function `tcstrucstoutf' is used in order to convert a UCS-2 array into a UTF-8 string.</p>
+
+<dl class="api">
+<dt><code>int tcstrucstoutf(const uint16_t *<var>ary</var>, int <var>num</var>, char *<var>str</var>);</code></dt>
+<dd>`<var>ary</var>' specifies the array of UCS-2 codes.</dd>
+<dd>`<var>num</var>' specifies the number of the array.</dd>
+<dd>`<var>str</var>' specifies the pointer to the region into which the result UTF-8 string is written.  The size of the buffer should be sufficient.</dd>
+<dd>The return value is the length of the result string.</dd>
+</dl>
+
+<p>The function `tcstrsplit' is used in order to create a list object by splitting a string.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcstrsplit(const char *<var>str</var>, const char *<var>delims</var>);</code></dt>
+<dd>`<var>str</var>' specifies the source string.</dd>
+<dd>`<var>delims</var>' specifies a string containing delimiting characters.</dd>
+<dd>The return value is a list object of the split elements.</dd>
+<dd>If two delimiters are successive, it is assumed that an empty element is between the two.  Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcstrjoin' is used in order to create a string by joining all elements of a list object.</p>
+
+<dl class="api">
+<dt><code>char *tcstrjoin(const TCLIST *<var>list</var>, char <var>delim</var>);</code></dt>
+<dd>`<var>list</var>' specifies a list object.</dd>
+<dd>`<var>delim</var>' specifies a delimiting character.</dd>
+<dd>The return value is the result string.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcatoi' is used in order to convert a string to an integer.</p>
+
+<dl class="api">
+<dt><code>int64_t tcatoi(const char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string.</dd>
+<dd>The return value is the integer.  If the string does not contain numeric expression, 0 is returned.</dd>
+<dd>This function is equivalent to `atoll' except that it does not depend on the locale.</dd>
+</dl>
+
+<p>The function `tcatoix' is used in order to convert a string with a metric prefix to an integer.</p>
+
+<dl class="api">
+<dt><code>int64_t tcatoix(const char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string, which can be trailed by a binary metric prefix.  "K", "M", "G", "T", "P", and "E" are supported.  They are case-insensitive.</dd>
+<dd>The return value is the integer.  If the string does not contain numeric expression, 0 is returned.  If the integer overflows the domain, `INT64_MAX' or `INT64_MIN' is returned according to the sign.</dd>
+</dl>
+
+<p>The function `tcatof' is used in order to convert a string to a real number.</p>
+
+<dl class="api">
+<dt><code>double tcatof(const char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string.</dd>
+<dd>The return value is the real number.  If the string does not contain numeric expression, 0.0 is returned.</dd>
+<dd>This function is equivalent to `atof' except that it does not depend on the locale.</dd>
+</dl>
+
+<p>The function `tcregexmatch' is used in order to check whether a string matches a regular expression.</p>
+
+<dl class="api">
+<dt><code>bool tcregexmatch(const char *<var>str</var>, const char *<var>regex</var>);</code></dt>
+<dd>`<var>str</var>' specifies the target string.</dd>
+<dd>`<var>regex</var>' specifies the regular expression string.  If it begins with `*', the trailing substring is used as a case-insensitive regular expression.</dd>
+<dd>The return value is true if matching is success, else, it is false.</dd>
+</dl>
+
+<p>The function `tcregexreplace' is used in order to replace each substring matching a regular expression string.</p>
+
+<dl class="api">
+<dt><code>char *tcregexreplace(const char *<var>str</var>, const char *<var>regex</var>, const char *<var>alt</var>);</code></dt>
+<dd>`<var>str</var>' specifies the target string.</dd>
+<dd>`<var>regex</var>' specifies the regular expression string for substrings.  If it begins with `*', the trailing substring is used as a case-insensitive regular expression.</dd>
+<dd>`<var>alt</var>' specifies the alternative string with which each substrings is replaced.  Each `&amp;' in the string is replaced with the matched substring.  Each `\' in the string escapes the following character.  Special escapes "\1" through "\9" referring to the corresponding matching sub-expressions in the regular expression string are supported.</dd>
+<dd>The return value is a new converted string.  Even if the regular expression is invalid, a copy of the original string is returned.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcmd5hash' is used in order to get the MD5 hash value of a serial object.</p>
+
+<dl class="api">
+<dt><code>void tcmd5hash(const void *<var>ptr</var>, int <var>size</var>, char *<var>buf</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>buf</var>' specifies the pointer to the region into which the result string is written.  The size of the buffer should be equal to or more than 48 bytes.</dd>
+</dl>
+
+<p>The function `tcarccipher' is used in order to cipher or decipher a serial object with the Arcfour stream cipher.</p>
+
+<dl class="api">
+<dt><code>void tcarccipher(const void *<var>ptr</var>, int <var>size</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, void *<var>obuf</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the cipher key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the cipher key.</dd>
+<dd>`<var>obuf</var>' specifies the pointer to the region into which the result data is written.  The size of the buffer should be equal to or more than the input region.</dd>
+</dl>
+
+<p>The function `tctime' is used in order to get the time of day in seconds.</p>
+
+<dl class="api">
+<dt><code>double tctime(void);</code></dt>
+<dd>The return value is the time of day in seconds.  The accuracy is in microseconds.</dd>
+</dl>
+
+<p>The function `tccalendar' is used in order to get the Gregorian calendar of a time.</p>
+
+<dl class="api">
+<dt><code>void tccalendar(int64_t <var>t</var>, int <var>jl</var>, int *<var>yearp</var>, int *<var>monp</var>, int *<var>dayp</var>, int *<var>hourp</var>, int *<var>minp</var>, int *<var>secp</var>);</code></dt>
+<dd>`<var>t</var>' specifies the source time in seconds from the epoch.  If it is `INT64_MAX', the current time is specified.</dd>
+<dd>`<var>jl</var>' specifies the jet lag of a location in seconds.  If it is `INT_MAX', the local jet lag is specified.</dd>
+<dd>`<var>yearp</var>' specifies the pointer to a variable to which the year is assigned.  If it is `NULL', it is not used.</dd>
+<dd>`<var>monp</var>' specifies the pointer to a variable to which the month is assigned.  If it is `NULL', it is not used.  1 means January and 12 means December.</dd>
+<dd>`<var>dayp</var>' specifies the pointer to a variable to which the day of the month is assigned.  If it is `NULL', it is not used.</dd>
+<dd>`<var>hourp</var>' specifies the pointer to a variable to which the hours is assigned.  If it is `NULL', it is not used.</dd>
+<dd>`<var>minp</var>' specifies the pointer to a variable to which the minutes is assigned.  If it is `NULL', it is not used.</dd>
+<dd>`<var>secp</var>' specifies the pointer to a variable to which the seconds is assigned.  If it is `NULL', it is not used.</dd>
+</dl>
+
+<p>The function `tcdatestrwww' is used in order to format a date as a string in W3CDTF.</p>
+
+<dl class="api">
+<dt><code>void tcdatestrwww(int64_t <var>t</var>, int <var>jl</var>, char *<var>buf</var>);</code></dt>
+<dd>`<var>t</var>' specifies the source time in seconds from the epoch.  If it is `INT64_MAX', the current time is specified.</dd>
+<dd>`<var>jl</var>' specifies the jet lag of a location in seconds.  If it is `INT_MAX', the local jet lag is specified.</dd>
+<dd>`<var>buf</var>' specifies the pointer to the region into which the result string is written.  The size of the buffer should be equal to or more than 48 bytes.</dd>
+<dd>W3CDTF represents a date as "YYYY-MM-DDThh:mm:ddTZD".</dd>
+</dl>
+
+<p>The function `tcdatestrhttp' is used in order to format a date as a string in RFC 1123 format.</p>
+
+<dl class="api">
+<dt><code>void tcdatestrhttp(int64_t <var>t</var>, int <var>jl</var>, char *<var>buf</var>);</code></dt>
+<dd>`<var>t</var>' specifies the source time in seconds from the epoch.  If it is `INT64_MAX', the current time is specified.</dd>
+<dd>`<var>jl</var>' specifies the jet lag of a location in seconds.  If it is `INT_MAX', the local jet lag is specified.</dd>
+<dd>`<var>buf</var>' specifies the pointer to the region into which the result string is written.  The size of the buffer should be equal to or more than 48 bytes.</dd>
+<dd>RFC 1123 format represents a date as "Wdy, DD-Mon-YYYY hh:mm:dd TZD".</dd>
+</dl>
+
+<p>The function `tcstrmktime' is used in order to get the time value of a date string.</p>
+
+<dl class="api">
+<dt><code>int64_t tcstrmktime(const char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the date string in decimal, hexadecimal, W3CDTF, or RFC 822 (1123).  Decimal can be trailed by "s" for in seconds, "m" for in minutes, "h" for in hours, and "d" for in days.</dd>
+<dd>The return value is the time value of the date or `INT64_MIN' if the format is invalid.</dd>
+</dl>
+
+<p>The function `tcjetlag' is used in order to get the jet lag of the local time.</p>
+
+<dl class="api">
+<dt><code>int tcjetlag(void);</code></dt>
+<dd>The return value is the jet lag of the local time in seconds.</dd>
+</dl>
+
+<p>The function `tcdayofweek' is used in order to get the day of week of a date.</p>
+
+<dl class="api">
+<dt><code>int tcdayofweek(int <var>year</var>, int <var>mon</var>, int <var>day</var>);</code></dt>
+<dd>`<var>year</var>' specifies the year of a date.</dd>
+<dd>`<var>mon</var>' specifies the month of the date.</dd>
+<dd>`<var>day</var>' specifies the day of the date.</dd>
+<dd>The return value is the day of week of the date.  0 means Sunday and 6 means Saturday.</dd>
+</dl>
+
+<h3 id="tcutilapi_fsapi">API of Filesystem Utilities</h3>
+
+<p>The function `tcrealpath' is used in order to get the canonicalized absolute path of a file.</p>
+
+<dl class="api">
+<dt><code>char *tcrealpath(const char *<var>path</var>);</code></dt>
+<dd>`<var>path</var>' specifies the path of the file.</dd>
+<dd>The return value is the canonicalized absolute path of a file, or `NULL' if the path is invalid.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcstatfile' is used in order to get the status information of a file.</p>
+
+<dl class="api">
+<dt><code>bool tcstatfile(const char *<var>path</var>, bool *<var>isdirp</var>, int64_t *<var>sizep</var>, int64_t *<var>mtimep</var>);</code></dt>
+<dd>`<var>path</var>' specifies the path of the file.</dd>
+<dd>`<var>isdirp</var>' specifies the pointer to a variable into which whether the file is a directory is assigned.  If it is `NULL', it is ignored.</dd>
+<dd>`<var>sizep</var>' specifies the pointer to a variable into which the size of the file is assigned.  If it is `NULL', it is ignored.</dd>
+<dd>`<var>ntimep</var>' specifies the pointer to a variable into which the size of the file is assigned.  If it is `NULL', it is ignored.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcreadfile' is used in order to read whole data of a file.</p>
+
+<dl class="api">
+<dt><code>void *tcreadfile(const char *<var>path</var>, int <var>limit</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>path</var>' specifies the path of the file.  If it is `NULL', the standard input is specified.</dd>
+<dd>`<var>limit</var>' specifies the limiting size of reading data.  If it is not more than 0, the limitation is not specified.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.  If it is `NULL', it is not used.</dd>
+<dd>The return value is the pointer to the allocated region of the read data, or `NULL' if the file could not be opened.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when when is no longer in use.</dd>
+</dl>
+
+<p>The function `tcreadfilelines' is used in order to read every line of a file.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcreadfilelines(const char *<var>path</var>);</code></dt>
+<dd>`<var>path</var>' specifies the path of the file.  If it is `NULL', the standard input is specified.</dd>
+<dd>The return value is a list object of every lines if successful, else it is `NULL'.</dd>
+<dd>Line separators are cut out.  Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcwritefile' is used in order to write data into a file.</p>
+
+<dl class="api">
+<dt><code>bool tcwritefile(const char *<var>path</var>, const void *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>path</var>' specifies the path of the file.  If it is `NULL', the standard output is specified.</dd>
+<dd>`<var>ptr</var>' specifies the pointer to the data region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tccopyfile' is used in order to copy a file.</p>
+
+<dl class="api">
+<dt><code>bool tccopyfile(const char *<var>src</var>, const char *<var>dest</var>);</code></dt>
+<dd>`<var>src</var>' specifies the path of the source file.</dd>
+<dd>`<var>dest</var>' specifies the path of the destination file.</dd>
+<dd>The return value is true if successful, else, it is false.</dd>
+<dd>If the destination file exists, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcreaddir' is used in order to read names of files in a directory.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcreaddir(const char *<var>path</var>);</code></dt>
+<dd>`<var>path</var>' specifies the path of the directory.</dd>
+<dd>The return value is a list object of names if successful, else it is `NULL'.</dd>
+<dd>Links to the directory itself and to the parent directory are ignored.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcglobpat' is used in order to expand a pattern into a list of matched paths.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcglobpat(const char *<var>pattern</var>);</code></dt>
+<dd>`<var>pattern</var>' specifies the matching pattern.</dd>
+<dd>The return value is a list object of matched paths.  If no path is matched, an empty list is returned.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcremovelink' is used in order to remove a file or a directory and its sub ones recursively.</p>
+
+<dl class="api">
+<dt><code>bool tcremovelink(const char *<var>path</var>);</code></dt>
+<dd>`<var>path</var>' specifies the path of the link.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned when the link does not exist or the permission is denied.</dd>
+</dl>
+
+<p>The function `tcwrite' is used in order to write data into a file.</p>
+
+<dl class="api">
+<dt><code>bool tcwrite(int <var>fd</var>, const void *<var>buf</var>, size_t <var>size</var>);</code></dt>
+<dd>`<var>fd</var>' specifies the file descriptor.</dd>
+<dd>`<var>buf</var>' specifies the buffer to be written.</dd>
+<dd>`<var>size</var>' specifies the size of the buffer.</dd>
+<dd>The return value is true if successful, else, it is false.</dd>
+</dl>
+
+<p>The function `tcread' is used in order to read data from a file.</p>
+
+<dl class="api">
+<dt><code>bool tcread(int <var>fd</var>, void *<var>buf</var>, size_t <var>size</var>);</code></dt>
+<dd>`<var>fd</var>' specifies the file descriptor.</dd>
+<dd>`<var>buf</var>' specifies the buffer to store into.</dd>
+<dd>`<var>size</var>' specifies the size of the buffer.</dd>
+<dd>The return value is true if successful, else, it is false.</dd>
+</dl>
+
+<p>The function `tclock' is used in order to lock a file.</p>
+
+<dl class="api">
+<dt><code>bool tclock(int <var>fd</var>, bool <var>ex</var>, bool <var>nb</var>);</code></dt>
+<dd>`<var>fd</var>' specifies the file descriptor.</dd>
+<dd>`<var>ex</var>' specifies whether an exclusive lock or a shared lock is performed.</dd>
+<dd>`<var>nb</var>' specifies whether to request with non-blocking.</dd>
+<dd>The return value is true if successful, else, it is false.</dd>
+</dl>
+
+<p>The function `tcunlock' is used in order to unlock a file.</p>
+
+<dl class="api">
+<dt><code>bool tcunlock(int <var>fd</var>);</code></dt>
+<dd>`<var>fd</var>' specifies the file descriptor.</dd>
+<dd>The return value is true if successful, else, it is false.</dd>
+</dl>
+
+<p>The function `tcsystem' is used in order to execute a shell command.</p>
+
+<dl class="api">
+<dt><code>int tcsystem(const char **<var>args</var>, int <var>anum</var>);</code></dt>
+<dd>`<var>args</var>' specifies an array of the command name and its arguments.</dd>
+<dd>`<var>anum</var>' specifies the number of elements of the array.</dd>
+<dd>The return value is the exit code of the command or `INT_MAX' on failure.</dd>
+<dd>The command name and the arguments are quoted and meta characters are escaped.</dd>
+</dl>
+
+<h3 id="tcutilapi_encapi">API of Encoding Utilities</h3>
+
+<p>The function `tcurlencode' is used in order to encode a serial object with URL encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcurlencode(const char *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>The return value is the result string.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if when is no longer in use.</dd>
+</dl>
+
+<p>The function `tcurldecode' is used in order to decode a string encoded with URL encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcurldecode(const char *<var>str</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>str</var>' specifies the encoded string.</dd>
+<dd>`<var>sp</var>' specifies the pointer to a variable into which the size of the region of the return value is assigned.</dd>
+<dd>The return value is the pointer to the region of the result.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcurlbreak' is used in order to break up a URL into elements.</p>
+
+<dl class="api">
+<dt><code>TCMAP *tcurlbreak(const char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the URL string.</dd>
+<dd>The return value is the map object whose keys are the name of elements.  The key "self" indicates the URL itself.  The key "scheme" indicates the scheme.  The key "host" indicates the host of the server.  The key "port" indicates the port number of the server.  The key "authority" indicates the authority information.  The key "path" indicates the path of the resource.  The key "file" indicates the file name without the directory section.  The key "query" indicates the query string.  The key "fragment" indicates the fragment string.</dd>
+<dd>Supported schema are HTTP, HTTPS, FTP, and FILE.  Absolute URL and relative URL are supported.  Because the object of the return value is created with the function `tcmapnew', it should be deleted with the function `tcmapdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcurlresolve' is used in order to resolve a relative URL with an absolute URL.</p>
+
+<dl class="api">
+<dt><code>char *tcurlresolve(const char *<var>base</var>, const char *<var>target</var>);</code></dt>
+<dd>`<var>base</var>' specifies the absolute URL of the base location.</dd>
+<dd>`<var>target</var>' specifies the URL to be resolved.</dd>
+<dd>The return value is the resolved URL.  If the target URL is relative, a new URL of relative location from the base location is returned.  Else, a copy of the target URL is returned.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbaseencode' is used in order to encode a serial object with Base64 encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcbaseencode(const char *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>The return value is the result string.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if when is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbasedecode' is used in order to decode a string encoded with Base64 encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcbasedecode(const char *<var>str</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>str</var>' specifies the encoded string.</dd>
+<dd>`<var>sp</var>' specifies the pointer to a variable into which the size of the region of the return value is assigned.</dd>
+<dd>The return value is the pointer to the region of the result.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcquoteencode' is used in order to encode a serial object with Quoted-printable encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcquoteencode(const char *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>The return value is the result string.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if when is no longer in use.</dd>
+</dl>
+
+<p>The function `tcquotedecode' is used in order to decode a string encoded with Quoted-printable encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcquotedecode(const char *<var>str</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>str</var>' specifies the encoded string.</dd>
+<dd>`<var>sp</var>' specifies the pointer to a variable into which the size of the region of the return value is assigned.</dd>
+<dd>The return value is the pointer to the region of the result.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcmimeencode' is used in order to encode a string with MIME encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcmimeencode(const char *<var>str</var>, const char *<var>encname</var>, bool <var>base</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string.</dd>
+<dd>`<var>encname</var>' specifies the string of the name of the character encoding.</dd>
+<dd>`<var>base</var>' specifies whether to use Base64 encoding.  If it is false, Quoted-printable is used.</dd>
+<dd>The return value is the result string.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcmimedecode' is used in order to decode a string encoded with MIME encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcmimedecode(const char *<var>str</var>, char *<var>enp</var>);</code></dt>
+<dd>`<var>str</var>' specifies the encoded string.</dd>
+<dd>`<var>enp</var>' specifies the pointer to the region into which the name of encoding is written.  If it is `NULL', it is not used.  The size of the buffer should be equal to or more than 32 bytes.</dd>
+<dd>The return value is the result string.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcmimebreak' is used in order to split a string of MIME into headers and the body.</p>
+
+<dl class="api">
+<dt><code>char *tcmimebreak(const char *<var>ptr</var>, int <var>size</var>, TCMAP *<var>headers</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region of MIME data.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>headers</var>' specifies a map object to store headers.  If it is `NULL', it is not used.  Each key of the map is an uncapitalized header name.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>The return value is the pointer to the region of the body data.</dd>
+<dd>If the content type is defined, the header map has the key "TYPE" specifying the type.  If the character encoding is defined, the key "CHARSET" indicates the encoding name.  If the boundary string of multipart is defined, the key "BOUNDARY" indicates the string.  If the content disposition is defined, the key "DISPOSITION" indicates the direction.  If the file name is defined, the key "FILENAME" indicates the name.  If the attribute name is defined, the key "NAME" indicates the name.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcmimeparts' is used in order to split multipart data of MIME into its parts.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcmimeparts(const char *<var>ptr</var>, int <var>size</var>, const char *<var>boundary</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region of multipart data of MIME.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>boundary</var>' specifies the boundary string.</dd>
+<dd>The return value is a list object.  Each element of the list is the data of a part.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tchexencode' is used in order to encode a serial object with hexadecimal encoding.</p>
+
+<dl class="api">
+<dt><code>char *tchexencode(const char *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>The return value is the result string.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if when is no longer in use.</dd>
+</dl>
+
+<p>The function `tchexdecode' is used in order to decode a string encoded with hexadecimal encoding.</p>
+
+<dl class="api">
+<dt><code>char *tchexdecode(const char *<var>str</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>str</var>' specifies the encoded string.</dd>
+<dd>`<var>sp</var>' specifies the pointer to a variable into which the size of the region of the return</dd>
+<dd>value is assigned.</dd>
+<dd>The return value is the pointer to the region of the result.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcpackencode' is used in order to compress a serial object with Packbits encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcpackencode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the result object, else, it is `NULL'.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcpackdecode' is used in order to decompress a serial object compressed with Packbits encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcpackdecode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>sp</var>' specifies the pointer to a variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the result object, else, it is `NULL'.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbsencode' is used in order to compress a serial object with TCBS encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcbsencode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the result object, else, it is `NULL'.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbsdecode' is used in order to decompress a serial object compressed with TCBS encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcbsdecode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>sp</var>' specifies the pointer to a variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the result object, else, it is `NULL'.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcdeflate' is used in order to compress a serial object with Deflate encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcdeflate(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the result object, else, it is `NULL'.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcinflate' is used in order to decompress a serial object compressed with Deflate encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcinflate(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>sp</var>' specifies the pointer to a variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the result object, else, it is `NULL'.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcgzipencode' is used in order to compress a serial object with GZIP encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcgzipencode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the result object, else, it is `NULL'.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcgzipdecode' is used in order to decompress a serial object compressed with GZIP encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcgzipdecode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>sp</var>' specifies the pointer to a variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the result object, else, it is `NULL'.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcgetcrc' is used in order to get the CRC32 checksum of a serial object.</p>
+
+<dl class="api">
+<dt><code>unsigned int tcgetcrc(const char *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>The return value is the CRC32 checksum of the object.</dd>
+</dl>
+
+<p>The function `tcbzipencode' is used in order to compress a serial object with BZIP2 encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcbzipencode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the result object, else, it is `NULL'.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbzipdecode' is used in order to decompress a serial object compressed with BZIP2 encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcbzipdecode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>sp</var>' specifies the pointer to a variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the result object, else, it is `NULL'.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcberencode' is used in order to encode an array of nonnegative integers with BER encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcberencode(const unsigned int *<var>ary</var>, int <var>anum</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ary</var>' specifies the pointer to the array of nonnegative integers.</dd>
+<dd>`<var>anum</var>' specifies the size of the array.</dd>
+<dd>`<var>sp</var>' specifies the pointer to a variable into which the size of the region of the return value is assigned.</dd>
+<dd>The return value is the pointer to the region of the result.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if when is no longer in use.</dd>
+</dl>
+
+<p>The function `tcberdecode' is used in order to decode a serial object encoded with BER encoding.</p>
+
+<dl class="api">
+<dt><code>unsigned int *tcberdecode(const char *<var>ptr</var>, int <var>size</var>, int *<var>np</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>np</var>' specifies the pointer to a variable into which the number of elements of the return value is assigned.</dd>
+<dd>The return value is the pointer to the array of the result.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if when is no longer in use.</dd>
+</dl>
+
+<p>The function `tcxmlescape' is used in order to escape meta characters in a string with the entity references of XML.</p>
+
+<dl class="api">
+<dt><code>char *tcxmlescape(const char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string.</dd>
+<dd>The return value is the pointer to the escaped string.</dd>
+<dd>This function escapes only `&amp;', `&lt;', `&gt;', and `&quot;'.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcxmlunescape' is used in order to unescape entity references in a string of XML.</p>
+
+<dl class="api">
+<dt><code>char *tcxmlunescape(const char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string.</dd>
+<dd>The return value is the unescaped string.</dd>
+<dd>This function restores only `&amp;amp;', `&amp;lt;', `&amp;gt;', and `&amp;quot;'.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<h3 id="tcutilapi_example">Example Code</h3>
+
+<p>The following code is an example using extensible string, array list, and hash map.</p>
+
+<pre>#include &lt;tcutil.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdbool.h&gt;
+#include &lt;stdint.h&gt;
+#include &lt;stdio.h&gt;
+
+int main(int argc, char **argv){
+
+  { /* example to use an extensible string object */
+    TCXSTR *xstr;
+    /* create the object */
+    xstr = tcxstrnew();
+    /* concatenate strings */
+    tcxstrcat2(xstr, "hop");
+    tcxstrcat2(xstr, "step");
+    tcxstrcat2(xstr, "jump");
+    /* print the size and the content */
+    printf("%d:%s\n", tcxstrsize(xstr), (char *)tcxstrptr(xstr));
+    /* delete the object */
+    tcxstrdel(xstr);
+  }
+
+  { /* example to use a list object */
+    TCLIST *list;
+    int i;
+    /* create the object */
+    list = tclistnew();
+    /* add strings to the tail */
+    tclistpush2(list, "hop");
+    tclistpush2(list, "step");
+    tclistpush2(list, "jump");
+    /* print all elements */
+    for(i = 0; i &lt; tclistnum(list); i++){
+      printf("%d:%s\n", i, tclistval2(list, i));
+    }
+    /* delete the object */
+    tclistdel(list);
+  }
+
+  { /* example to use a map object */
+    TCMAP *map;
+    const char *key;
+    /* create the object */
+    map = tcmapnew();
+    /* add records */
+    tcmapput2(map, "foo", "hop");
+    tcmapput2(map, "bar", "step");
+    tcmapput2(map, "baz", "jump");
+    /* print all records */
+    tcmapiterinit(map);
+    while((key = tcmapiternext2(map)) != NULL){
+      printf("%s:%s\n", key, tcmapget2(map, key));
+    }
+    /* delete the object */
+    tcmapdel(map);
+  }
+
+  { /* example to use a tree object */
+    TCTREE *tree;
+    const char *key;
+    /* create the object */
+    tree = tctreenew();
+    /* add records */
+    tctreeput2(tree, "foo", "hop");
+    tctreeput2(tree, "bar", "step");
+    tctreeput2(tree, "baz", "jump");
+    /* print all records */
+    tctreeiterinit(tree);
+    while((key = tctreeiternext2(tree)) != NULL){
+      printf("%s:%s\n", key, tctreeget2(tree, key));
+    }
+    /* delete the object */
+    tctreedel(tree);
+  }
+
+  return 0;
+}
+</pre>
+
+<h3 id="tcutilapi_cli">CLI</h3>
+
+<p>To use the utility API easily, the commands `<code>tcutest</code>', `<code>tcumttest</code>', and `<code>tcucodec</code>' are provided.</p>
+
+<p>The command `<code>tcutest</code>' is a utility for facility test and performance test.  This command is used in the following format.  `<var>rnum</var>' specifies the number of iterations.  `<var>anum</var>' specifies the initial number of elements of array.  `<var>bnum</var>' specifies the number of buckets.</p>
+
+<dl class="api">
+<dt><code>tcutest xstr <var>rnum</var></code></dt>
+<dd>Perform test of extensible string.</dd>
+<dt><code>tcutest list [-rd] <var>rnum</var> [<var>anum</var>]</code></dt>
+<dd>Perform test of array list.</dd>
+<dt><code>tcutest map [-rd] [-tr] [-rnd] [-dk|-dc|-dai|-dad|-dpr] <var>rnum</var> [<var>bnum</var>]</code></dt>
+<dd>Perform test of hash map.</dd>
+<dt><code>tcutest tree [-rd] [-tr] [-rnd] [-dk|-dc|-dai|-dad|-dpr] <var>rnum</var></code></dt>
+<dd>Perform test of ordered tree.</dd>
+<dt><code>tcutest mdb [-rd] [-tr] [-rnd] [-dk|-dc|-dai|-dad|-dpr] <var>rnum</var> [<var>bnum</var>]</code></dt>
+<dd>Perform test of on-memory hash database.</dd>
+<dt><code>tcutest ndb [-rd] [-tr] [-rnd] [-dk|-dc|-dai|-dad|-dpr] <var>rnum</var></code></dt>
+<dd>Perform test of on-memory tree database.</dd>
+<dt><code>tcutest misc <var>rnum</var></code></dt>
+<dd>Perform test of miscellaneous routines.</dd>
+<dt><code>tcutest wicked <var>rnum</var></code></dt>
+<dd>Perform updating operations of list and map selected at random.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="options">
+<li><code>-rd</code> : perform the reading test also.</li>
+<li><code>-tr</code> : perform the iterator test also.</li>
+<li><code>-rnd</code> : select keys at random.</li>
+<li><code>-dk</code> : use the function `tcxxxputkeep' instead of `tcxxxput'.</li>
+<li><code>-dc</code> : use the function `tcxxxputcat' instead of `tcxxxput'.</li>
+<li><code>-dai</code> : use the function `tcxxxaddint' instead of `tcxxxput'.</li>
+<li><code>-dad</code> : use the function `tcxxxadddouble' instead of `tcxxxput'.</li>
+<li><code>-dpr</code> : use the function `tcxxxputproc' instead of `tcxxxput'.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<p>The command `<code>tcumttest</code>' is a utility for facility test under multi-thread situation.  This command is used in the following format.  `<var>tnum</var>' specifies the number of running threads.  `<var>rnum</var>' specifies the number of iterations.  `<var>bnum</var>' specifies the number of buckets.</p>
+
+<dl class="api">
+<dt><code>tcumttest combo [-rnd] <var>tnum</var> <var>rnum</var> [<var>bnum</var>]</code></dt>
+<dd>Peform storing, retrieving, and removing in turn.</dd>
+<dt><code>tcumttest typical [-nc] [-rr <var>num</var>] <var>tnum</var> <var>rnum</var> [<var>bnum</var>]</code></dt>
+<dd>Perform typical operations selected at random.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="options">
+<li><code>-rnd</code> : select keys at random.</li>
+<li><code>-nc</code> : omit the comparison test.</li>
+<li><code>-rr <var>num</var></code> : specify the ratio of reading operation by percentage.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<p>The command `<code>tcucodec</code>' is a tool to use encoding and decoding features.  This command is used in the following format.  `<var>file</var>' specifies a input file.  If it is omitted, the standard input is read.</p>
+
+<dl class="api">
+<dt><code>tcucodec url [-d] [-br] [-rs <var>base</var>] [<var>file</var>]</code></dt>
+<dd>Perform URL encoding and its decoding.</dd>
+<dt><code>tcucodec base [-d] [<var>file</var>]</code></dt>
+<dd>Perform Base64 encoding and its decoding.</dd>
+<dt><code>tcucodec quote [-d] [<var>file</var>]</code></dt>
+<dd>Perform quoted-printable encoding and its decoding.</dd>
+<dt><code>tcucodec mime [-d] [-en <var>name</var>] [-q] [-on] [-hd] [-bd] [-part <var>num</var>] [<var>file</var>]</code></dt>
+<dd>Perform MIME encoding and its decoding.</dd>
+<dt><code>tcucodec hex [-d] [<var>file</var>]</code></dt>
+<dd>Perform hexadecimal encoding and its decoding.</dd>
+<dt><code>tcucodec pack [-d] [-bwt] [<var>file</var>]</code></dt>
+<dd>Perform Packbits encoding and its decoding.</dd>
+<dt><code>tcucodec tcbs [-d] [<var>file</var>]</code></dt>
+<dd>Perform TCBS encoding and its decoding.</dd>
+<dt><code>tcucodec zlib [-d] [-gz] [<var>file</var>]</code></dt>
+<dd>Perform ZLIB encoding and its decoding.</dd>
+<dt><code>tcucodec bzip [-d] [<var>file</var>]</code></dt>
+<dd>Perform BZIP2 encoding and its decoding.</dd>
+<dt><code>tcucodec xml [-d] [-br] [<var>file</var>]</code></dt>
+<dd>Process XML.  By default, escape meta characters.</dd>
+<dt><code>tcucodec cstr [-d] [-js] [<var>file</var>]</code></dt>
+<dd>Perform C-string escaping and its unescaping.</dd>
+<dt><code>tcucodec ucs [-d] [-un] [-kw <var>str</var>] [<var>file</var>]</code></dt>
+<dd>Convert UTF-8 string into UCS-2 array.</dd>
+<dt><code>tcucodec hash [-crc] [-ch <var>num</var>] [<var>file</var>]</code></dt>
+<dd>Calculate the hash value.  By default, use MD5 function.</dd>
+<dt><code>tcucodec cipher [-key <var>str</var>] [<var>file</var>]</code></dt>
+<dd>Perform stream cipher and its decipher.</dd>
+<dt><code>tcucodec date [-ds <var>str</var>] [-jl <var>num</var>] [-wf] [-rf]</code></dt>
+<dd>Process date string.  By default, print the current UNIX time.</dd>
+<dt><code>tcucodec tmpl [-var <var>name</var> <var>value</var>] [<var>file</var>]</code></dt>
+<dd>Perform template serialization.</dd>
+<dt><code>tcucodec conf [-v|-i|-l|-p]</code></dt>
+<dd>Print some configurations.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="options">
+<li><code>-d</code> : perform decoding (unescaping), not encoding (escaping).</li>
+<li><code>-br</code> : break up URL or XML into elements.</li>
+<li><code>-rs <var>base</var></code> : specify the base URL and resolve the relative URL.</li>
+<li><code>-en <var>name</var></code> : specify the input encoding, which is UTF-8 by default.</li>
+<li><code>-q</code> : use quoted-printable encoding, which is Base64 by default.</li>
+<li><code>-on</code> : output the charset name when decoding.</li>
+<li><code>-bd</code> : perform MIME parsing and output the body.</li>
+<li><code>-hd</code> : perform MIME parsing and output the headers.</li>
+<li><code>-part <var>num</var></code> : perform MIME parsing and output the specified part.</li>
+<li><code>-bwt</code> : convert by BWT as preprocessing.</li>
+<li><code>-gz</code> : use GZIP format.</li>
+<li><code>-crc</code> : use CRC32 function.</li>
+<li><code>-js</code> : use JSON compatible format.</li>
+<li><code>-un</code> : perform UCS normalization.</li>
+<li><code>-kw <var>str</var></code> : generate KWIC string.</li>
+<li><code>-ch <var>num</var></code> : use consistent hashing function.</li>
+<li><code>-key <var>str</var></code> : specify the cipher key.</li>
+<li><code>-ds <var>str</var></code> : specify the time.</li>
+<li><code>-jl <var>num</var></code> : specify the jet lag.</li>
+<li><code>-wf</code> : format the output in W3CDTF.</li>
+<li><code>-rf</code> : format the output in RFC 1123 format.</li>
+<li><code>-var <var>name</var> <var>value</var></code> : specify a template variable.</li>
+<li><code>-v</code> : show the version number of Tokyo Cabinet.</li>
+<li><code>-i</code> : show options to include the headers of Tokyo Cabinet.</li>
+<li><code>-l</code> : show options to link the library of Tokyo Cabinet.</li>
+<li><code>-p</code> : show the directory path of the commands of Tokyo Cabinet.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<hr />
+
+<h2 id="tchdbapi">The Hash Database API</h2>
+
+<p>Hash database is a file containing a hash table and is handled with the hash database API.  See `<code>tchdb.h</code>' for the entire specification.</p>
+
+<h3 id="tchdbapi_description">Description</h3>
+
+<p>To use the hash database API, include `<code>tcutil.h</code>', `<code>tchdb.h</code>', and related standard header files.  Usually, write the following description near the front of a source file.</p>
+
+<dl>
+<dt><code>#include &lt;tcutil.h&gt;</code></dt>
+<dt><code>#include &lt;tchdb.h&gt;</code></dt>
+<dt><code>#include &lt;stdlib.h&gt;</code></dt>
+<dt><code>#include &lt;stdbool.h&gt;</code></dt>
+<dt><code>#include &lt;stdint.h&gt;</code></dt>
+</dl>
+
+<p>Objects whose type is pointer to `<code>TCHDB</code>' are used to handle hash databases.  A hash database object is created with the function `<code>tchdbnew</code>' and is deleted with the function `<code>tchdbdel</code>'.  To avoid memory leak, it is important to delete every object when it is no longer in use.</p>
+
+<p>Before operations to store or retrieve records, it is necessary to open a database file and connect the hash database object to it.  The function `<code>tchdbopen</code>' is used to open a database file and the function `<code>tchdbclose</code>' is used to close the database file.  To avoid data missing or corruption, it is important to close every database file when it is no longer in use.  It is forbidden for multible database objects in a process to open the same database at the same time.</p>
+
+<h3 id="tchdbapi_api">API</h3>
+
+<p>The function `tchdberrmsg' is used in order to get the message string corresponding to an error code.</p>
+
+<dl class="api">
+<dt><code>const char *tchdberrmsg(int <var>ecode</var>);</code></dt>
+<dd>`<var>ecode</var>' specifies the error code.</dd>
+<dd>The return value is the message string of the error code.</dd>
+</dl>
+
+<p>The function `tchdbnew' is used in order to create a hash database object.</p>
+
+<dl class="api">
+<dt><code>TCHDB *tchdbnew(void);</code></dt>
+<dd>The return value is the new hash database object.</dd>
+</dl>
+
+<p>The function `tchdbdel' is used in order to delete a hash database object.</p>
+
+<dl class="api">
+<dt><code>void tchdbdel(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>If the database is not closed, it is closed implicitly.  Note that the deleted object and its derivatives can not be used anymore.</dd>
+</dl>
+
+<p>The function `tchdbecode' is used in order to get the last happened error code of a hash database object.</p>
+
+<dl class="api">
+<dt><code>int tchdbecode(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>The return value is the last happened error code.</dd>
+<dd>The following error codes are defined: `TCESUCCESS' for success, `TCETHREAD' for threading error, `TCEINVALID' for invalid operation, `TCENOFILE' for file not found, `TCENOPERM' for no permission, `TCEMETA' for invalid meta data, `TCERHEAD' for invalid record header, `TCEOPEN' for open error, `TCECLOSE' for close error, `TCETRUNC' for trunc error, `TCESYNC' for sync error, `TCESTAT' for stat error, `TCESEEK' for seek error, `TCEREAD' for read error, `TCEWRITE' for write error, `TCEMMAP' for mmap error, `TCELOCK' for lock error, `TCEUNLINK' for unlink error, `TCERENAME' for rename error, `TCEMKDIR' for mkdir error, `TCERMDIR' for rmdir error, `TCEKEEP' for existing record, `TCENOREC' for no record found, and `TCEMISC' for miscellaneous error.</dd>
+</dl>
+
+<p>The function `tchdbsetmutex' is used in order to set mutual exclusion control of a hash database object for threading.</p>
+
+<dl class="api">
+<dt><code>bool tchdbsetmutex(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object which is not opened.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the mutual exclusion control of the database should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tchdbtune' is used in order to set the tuning parameters of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbtune(TCHDB *<var>hdb</var>, int64_t <var>bnum</var>, int8_t <var>apow</var>, int8_t <var>fpow</var>, uint8_t <var>opts</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object which is not opened.</dd>
+<dd>`<var>bnum</var>' specifies the number of elements of the bucket array.  If it is not more than 0, the default value is specified.  The default value is 131071.  Suggested size of the bucket array is about from 0.5 to 4 times of the number of all records to be stored.</dd>
+<dd>`<var>apow</var>' specifies the size of record alignment by power of 2.  If it is negative, the default value is specified.  The default value is 4 standing for 2^4=16.</dd>
+<dd>`<var>fpow</var>' specifies the maximum number of elements of the free block pool by power of 2.  If it is negative, the default value is specified.  The default value is 10 standing for 2^10=1024.</dd>
+<dd>`<var>opts</var>' specifies options by bitwise-or: `HDBTLARGE' specifies that the size of the database can be larger than 2GB by using 64-bit bucket array, `HDBTDEFLATE' specifies that each record is compressed with Deflate encoding, `HDBTBZIP' specifies that each record is compressed with BZIP2 encoding, `HDBTTCBS' specifies that each record is compressed with TCBS encoding.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the tuning parameters should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tchdbsetcache' is used in order to set the caching parameters of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbsetcache(TCHDB *<var>hdb</var>, int32_t <var>rcnum</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object which is not opened.</dd>
+<dd>`<var>rcnum</var>' specifies the maximum number of records to be cached.  If it is not more than 0, the record cache is disabled.  It is disabled by default.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the caching parameters should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tchdbsetxmsiz' is used in order to set the size of the extra mapped memory of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbsetxmsiz(TCHDB *<var>hdb</var>, int64_t <var>xmsiz</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object which is not opened.</dd>
+<dd>`<var>xmsiz</var>' specifies the size of the extra mapped memory.  If it is not more than 0, the extra mapped memory is disabled.  The default size is 67108864.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the mapping parameters should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tchdbsetdfunit' is used in order to set the unit step number of auto defragmentation of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbsetdfunit(TCHDB *<var>hdb</var>, int32_t <var>dfunit</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object which is not opened.</dd>
+<dd>`<var>dfunit</var>' specifie the unit step number.  If it is not more than 0, the auto defragmentation is disabled.  It is disabled by default.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the defragmentation parameters should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tchdbopen' is used in order to open a database file and connect a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbopen(TCHDB *<var>hdb</var>, const char *<var>path</var>, int <var>omode</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object which is not opened.</dd>
+<dd>`<var>path</var>' specifies the path of the database file.</dd>
+<dd>`<var>omode</var>' specifies the connection mode: `HDBOWRITER' as a writer, `HDBOREADER' as a reader.  If the mode is `HDBOWRITER', the following may be added by bitwise-or: `HDBOCREAT', which means it creates a new database if not exist, `HDBOTRUNC', which means it creates a new database regardless if one exists, `HDBOTSYNC', which means every transaction synchronizes updated contents with the device.  Both of `HDBOREADER' and `HDBOWRITER' can be added to by bitwise-or: `HDBONOLCK', which means it opens the database file without file locking, or `HDBOLCKNB', which means locking is performed without blocking.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tchdbclose' is used in order to close a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbclose(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update of a database is assured to be written when the database is closed.  If a writer opens a database but does not close it appropriately, the database will be broken.</dd>
+</dl>
+
+<p>The function `tchdbput' is used in order to store a record into a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbput(TCHDB *<var>hdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tchdbput2' is used in order to store a string record into a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbput2(TCHDB *<var>hdb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tchdbputkeep' is used in order to store a new record into a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbputkeep(TCHDB *<var>hdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tchdbputkeep2' is used in order to store a new string record into a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbputkeep2(TCHDB *<var>hdb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tchdbputcat' is used in order to concatenate a value at the end of the existing record in a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbputcat(TCHDB *<var>hdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tchdbputcat2' is used in order to concatenate a string value at the end of the existing record in a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbputcat2(TCHDB *<var>hdb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tchdbputasync' is used in order to store a record into a hash database object in asynchronous fashion.</p>
+
+<dl class="api">
+<dt><code>bool tchdbputasync(TCHDB *<var>hdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.  Records passed to this function are accumulated into the inner buffer and wrote into the file at a blast.</dd>
+</dl>
+
+<p>The function `tchdbputasync2' is used in order to store a string record into a hash database object in asynchronous fashion.</p>
+
+<dl class="api">
+<dt><code>bool tchdbputasync2(TCHDB *<var>hdb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.  Records passed to this function are accumulated into the inner buffer and wrote into the file at a blast.</dd>
+</dl>
+
+<p>The function `tchdbout' is used in order to remove a record of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbout(TCHDB *<var>hdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tchdbout2' is used in order to remove a string record of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbout2(TCHDB *<var>hdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tchdbget' is used in order to retrieve a record in a hash database object.</p>
+
+<dl class="api">
+<dt><code>void *tchdbget(TCHDB *<var>hdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tchdbget2' is used in order to retrieve a string record in a hash database object.</p>
+
+<dl class="api">
+<dt><code>char *tchdbget2(TCHDB *<var>hdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the string of the value of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tchdbget3' is used in order to retrieve a record in a hash database object and write the value into a buffer.</p>
+
+<dl class="api">
+<dt><code>int tchdbget3(TCHDB *<var>hdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, void *<var>vbuf</var>, int <var>max</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the buffer into which the value of the corresponding record is written.</dd>
+<dd>`<var>max</var>' specifies the size of the buffer.</dd>
+<dd>If successful, the return value is the size of the written data, else, it is -1.  -1 is returned if no record corresponds to the specified key.</dd>
+<dd>Note that an additional zero code is not appended at the end of the region of the writing buffer.</dd>
+</dl>
+
+<p>The function `tchdbvsiz' is used in order to get the size of the value of a record in a hash database object.</p>
+
+<dl class="api">
+<dt><code>int tchdbvsiz(TCHDB *<var>hdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tchdbvsiz2' is used in order to get the size of the value of a string record in a hash database object.</p>
+
+<dl class="api">
+<dt><code>int tchdbvsiz2(TCHDB *<var>hdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tchdbiterinit' is used in order to initialize the iterator of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbiterinit(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>The iterator is used in order to access the key of every record stored in a database.</dd>
+</dl>
+
+<p>The function `tchdbiternext' is used in order to get the next key of the iterator of a hash database object.</p>
+
+<dl class="api">
+<dt><code>void *tchdbiternext(TCHDB *<var>hdb</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  It is possible to access every record by iteration of calling this function.  It is allowed to update or remove records whose keys are fetched while the iteration.  However, it is not assured if updating the database is occurred while the iteration.  Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of storing matches the one of the traversal access.</dd>
+</dl>
+
+<p>The function `tchdbiternext2' is used in order to get the next key string of the iterator of a hash database object.</p>
+
+<dl class="api">
+<dt><code>char *tchdbiternext2(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>If successful, the return value is the string of the next key, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  It is possible to access every record by iteration of calling this function.  However, it is not assured if updating the database is occurred while the iteration.  Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of storing matches the one of the traversal access.</dd>
+</dl>
+
+<p>The function `tchdbiternext3' is used in order to get the next extensible objects of the iterator of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbiternext3(TCHDB *<var>hdb</var>, TCXSTR *<var>kxstr</var>, TCXSTR *<var>vxstr</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>`<var>kxstr</var>' specifies the object into which the next key is wrote down.</dd>
+<dd>`<var>vxstr</var>' specifies the object into which the next value is wrote down.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned when no record is to be get out of the iterator.</dd>
+</dl>
+
+<p>The function `tchdbfwmkeys' is used in order to get forward matching keys in a hash database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tchdbfwmkeys(TCHDB *<var>hdb</var>, const void *<var>pbuf</var>, int <var>psiz</var>, int <var>max</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>`<var>pbuf</var>' specifies the pointer to the region of the prefix.</dd>
+<dd>`<var>psiz</var>' specifies the size of the region of the prefix.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.</dd>
+</dl>
+
+<p>The function `tchdbfwmkeys2' is used in order to get forward matching string keys in a hash database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tchdbfwmkeys2(TCHDB *<var>hdb</var>, const char *<var>pstr</var>, int <var>max</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>`<var>pstr</var>' specifies the string of the prefix.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.</dd>
+</dl>
+
+<p>The function `tchdbaddint' is used in order to add an integer to a record in a hash database object.</p>
+
+<dl class="api">
+<dt><code>int tchdbaddint(TCHDB *<var>hdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int <var>num</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>If successful, the return value is the summation value, else, it is `INT_MIN'.</dd>
+<dd>If the corresponding record exists, the value is treated as an integer and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tchdbdbadddouble' is used in order to add a real number to a record in a hash database object.</p>
+
+<dl class="api">
+<dt><code>double tchdbadddouble(TCHDB *<var>hdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, double <var>num</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>If successful, the return value is the summation value, else, it is Not-a-Number.</dd>
+<dd>If the corresponding record exists, the value is treated as a real number and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tchdbsync' is used in order to synchronize updated contents of a hash database object with the file and the device.</p>
+
+<dl class="api">
+<dt><code>bool tchdbsync(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>This function is useful when another process connects to the same database file.</dd>
+</dl>
+
+<p>The function `tchdboptimize' is used in order to optimize the file of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdboptimize(TCHDB *<var>hdb</var>, int64_t <var>bnum</var>, int8_t <var>apow</var>, int8_t <var>fpow</var>, uint8_t <var>opts</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>bnum</var>' specifies the number of elements of the bucket array.  If it is not more than 0, the default value is specified.  The default value is two times of the number of records.</dd>
+<dd>`<var>apow</var>' specifies the size of record alignment by power of 2.  If it is negative, the current setting is not changed.</dd>
+<dd>`<var>fpow</var>' specifies the maximum number of elements of the free block pool by power of 2.  If it is negative, the current setting is not changed.</dd>
+<dd>`<var>opts</var>' specifies options by bitwise-or: `HDBTLARGE' specifies that the size of the database can be larger than 2GB by using 64-bit bucket array, `HDBTDEFLATE' specifies that each record is compressed with Deflate encoding, `HDBTBZIP' specifies that each record is compressed with BZIP2 encoding, `HDBTTCBS' specifies that each record is compressed with TCBS encoding.  If it is `UINT8_MAX', the current setting is not changed.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>This function is useful to reduce the size of the database file with data fragmentation by successive updating.</dd>
+</dl>
+
+<p>The function `tchdbvanish' is used in order to remove all records of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbvanish(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tchdbcopy' is used in order to copy the database file of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbcopy(TCHDB *<var>hdb</var>, const char *<var>path</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>`<var>path</var>' specifies the path of the destination file.  If it begins with `@', the trailing substring is executed as a command line.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned if the executed command returns non-zero code.</dd>
+<dd>The database file is assured to be kept synchronized and not modified while the copying or executing operation is in progress.  So, this function is useful to create a backup file of the database file.</dd>
+</dl>
+
+<p>The function `tchdbtranbegin' is used in order to begin the transaction of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbtranbegin(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>The database is locked by the thread while the transaction so that only one transaction can be activated with a database object at the same time.  Thus, the serializable isolation level is assumed if every database operation is performed in the transaction.  All updated regions are kept track of by write ahead logging while the transaction.  If the database is closed during transaction, the transaction is aborted implicitly.</dd>
+</dl>
+
+<p>The function `tchdbtrancommit' is used in order to commit the transaction of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbtrancommit(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update in the transaction is fixed when it is committed successfully.</dd>
+</dl>
+
+<p>The function `tchdbtranabort' is used in order to abort the transaction of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbtranabort(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update in the transaction is discarded when it is aborted.  The state of the database is rollbacked to before transaction.</dd>
+</dl>
+
+<p>The function `tchdbpath' is used in order to get the file path of a hash database object.</p>
+
+<dl class="api">
+<dt><code>const char *tchdbpath(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>The return value is the path of the database file or `NULL' if the object does not connect to any database file.</dd>
+</dl>
+
+<p>The function `tchdbrnum' is used in order to get the number of records of a hash database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tchdbrnum(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>The return value is the number of records or 0 if the object does not connect to any database file.</dd>
+</dl>
+
+<p>The function `tchdbfsiz' is used in order to get the size of the database file of a hash database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tchdbfsiz(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>The return value is the size of the database file or 0 if the object does not connect to any database file.</dd>
+</dl>
+
+<h3 id="tchdbapi_example">Example Code</h3>
+
+<p>The following code is an example to use a hash database.</p>
+
+<pre>#include &lt;tcutil.h&gt;
+#include &lt;tchdb.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdbool.h&gt;
+#include &lt;stdint.h&gt;
+
+int main(int argc, char **argv){
+  TCHDB *hdb;
+  int ecode;
+  char *key, *value;
+
+  /* create the object */
+  hdb = tchdbnew();
+
+  /* open the database */
+  if(!tchdbopen(hdb, "casket.tch", HDBOWRITER | HDBOCREAT)){
+    ecode = tchdbecode(hdb);
+    fprintf(stderr, "open error: %s\n", tchdberrmsg(ecode));
+  }
+
+  /* store records */
+  if(!tchdbput2(hdb, "foo", "hop") ||
+     !tchdbput2(hdb, "bar", "step") ||
+     !tchdbput2(hdb, "baz", "jump")){
+    ecode = tchdbecode(hdb);
+    fprintf(stderr, "put error: %s\n", tchdberrmsg(ecode));
+  }
+
+  /* retrieve records */
+  value = tchdbget2(hdb, "foo");
+  if(value){
+    printf("%s\n", value);
+    free(value);
+  } else {
+    ecode = tchdbecode(hdb);
+    fprintf(stderr, "get error: %s\n", tchdberrmsg(ecode));
+  }
+
+  /* traverse records */
+  tchdbiterinit(hdb);
+  while((key = tchdbiternext2(hdb)) != NULL){
+    value = tchdbget2(hdb, key);
+    if(value){
+      printf("%s:%s\n", key, value);
+      free(value);
+    }
+    free(key);
+  }
+
+  /* close the database */
+  if(!tchdbclose(hdb)){
+    ecode = tchdbecode(hdb);
+    fprintf(stderr, "close error: %s\n", tchdberrmsg(ecode));
+  }
+
+  /* delete the object */
+  tchdbdel(hdb);
+
+  return 0;
+}
+</pre>
+
+<h3 id="tchdbapi_cli">CLI</h3>
+
+<p>To use the hash database API easily, the commands `<code>tchtest</code>', `<code>tchmttest</code>', and `<code>tchmgr</code>' are provided.</p>
+
+<p>The command `<code>tchtest</code>' is a utility for facility test and performance test.  This command is used in the following format.  `<var>path</var>' specifies the path of a database file.  `<var>rnum</var>' specifies the number of iterations.  `<var>bnum</var>' specifies the number of buckets.  `<var>apow</var>' specifies the power of the alignment.  `<var>fpow</var>' specifies the power of the free block pool.</p>
+
+<dl class="api">
+<dt><code>tchtest write [-mt] [-tl] [-td|-tb|-tt|-tx] [-rc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-as] [-rnd] <var>path</var> <var>rnum</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]</code></dt>
+<dd>Store records with keys of 8 bytes.  They change as `00000001', `00000002'...</dd>
+<dt><code>tchtest read [-mt] [-rc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-wb] [-rnd] <var>path</var></code></dt>
+<dd>Retrieve all records of the database above.</dd>
+<dt><code>tchtest remove [-mt] [-rc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-rnd] <var>path</var></code></dt>
+<dd>Remove all records of the database above.</dd>
+<dt><code>tchtest rcat [-mt] [-tl] [-td|-tb|-tt|-tx] [-rc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-pn <var>num</var>] [-dai|-dad|-rl|-ru] <var>path</var> <var>rnum</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]</code></dt>
+<dd>Store records with partway duplicated keys using concatenate mode.</dd>
+<dt><code>tchtest misc [-mt] [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] <var>path</var> <var>rnum</var></code></dt>
+<dd>Perform miscellaneous test of various operations.</dd>
+<dt><code>tchtest wicked [-mt] [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] <var>path</var> <var>rnum</var></code></dt>
+<dd>Perform updating operations selected at random.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="options">
+<li><code>-mt</code> : call the function `tchdbsetmutex'.</li>
+<li><code>-tl</code> : enable the option `HDBTLARGE'.</li>
+<li><code>-td</code> : enable the option `HDBTDEFLATE'.</li>
+<li><code>-tb</code> : enable the option `HDBTBZIP'.</li>
+<li><code>-tt</code> : enable the option `HDBTTCBS'.</li>
+<li><code>-tx</code> : enable the option `HDBTEXCODEC'.</li>
+<li><code>-rc <var>num</var></code> : specify the number of cached records.</li>
+<li><code>-xm <var>num</var></code> : specify the size of the extra mapped memory.</li>
+<li><code>-df <var>num</var></code> : specify the unit step number of auto defragmentation.</li>
+<li><code>-nl</code> : enable the option `HDBNOLCK'.</li>
+<li><code>-nb</code> : enable the option `HDBLCKNB'.</li>
+<li><code>-as</code> : use the function `tchdbputasync' instead of `tchdbput'.</li>
+<li><code>-rnd</code> : select keys at random.</li>
+<li><code>-wb</code> : use the function `tchdbget3' instead of `tchdbget'.</li>
+<li><code>-pn <var>num</var></code> : specify the number of patterns.</li>
+<li><code>-dai</code> : use the function `tchdbaddint' instead of `tchdbputcat'.</li>
+<li><code>-dad</code> : use the function `tchdbadddouble' instead of `tchdbputcat'.</li>
+<li><code>-rl</code> : set the length of values at random.</li>
+<li><code>-ru</code> : select update operations at random.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<p>The command `<code>tchmttest</code>' is a utility for facility test under multi-thread situation.  This command is used in the following format.  `<var>path</var>' specifies the path of a database file.  `<var>tnum</var>' specifies the number of running threads.  `<var>rnum</var>' specifies the number of iterations.  `<var>bnum</var>' specifies the number of buckets.  `<var>apow</var>' specifies the power of the alignment.  `<var>fpow</var>' specifies the power of the free block pool.</p>
+
+<dl class="api">
+<dt><code>tchmttest write [-tl] [-td|-tb|-tt|-tx] [-rc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-as] [-rnd] <var>path</var> <var>tnum</var> <var>rnum</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]</code></dt>
+<dd>Store records with keys of 8 bytes.  They change as `00000001', `00000002'...</dd>
+<dt><code>tchmttest read [-rc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-wb] [-rnd] <var>path</var> <var>tnum</var></code></dt>
+<dd>Retrieve all records of the database above.</dd>
+<dt><code>tchmttest remove [-rc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-rnd] <var>path</var> <var>tnum</var></code></dt>
+<dd>Remove all records of the database above.</dd>
+<dt><code>tchmttest wicked [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] [-nc] <var>path</var> <var>tnum</var> <var>rnum</var></code></dt>
+<dd>Perform updating operations selected at random.</dd>
+<dt><code>tchmttest typical [-tl] [-td|-tb|-tt|-tx] [-rc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-nc] [-rr <var>num</var>] <var>path</var> <var>tnum</var> <var>rnum</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]</code></dt>
+<dd>Perform typical operations selected at random.</dd>
+<dt><code>tchmttest race [-tl] [-td|-tb|-tt|-tx] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] <var>path</var> <var>tnum</var> <var>rnum</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]</code></dt>
+<dd>Perform race condition test.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="options">
+<li><code>-tl</code> : enable the option `HDBTLARGE'.</li>
+<li><code>-td</code> : enable the option `HDBTDEFLATE'.</li>
+<li><code>-tb</code> : enable the option `HDBTBZIP'.</li>
+<li><code>-tt</code> : enable the option `HDBTTCBS'.</li>
+<li><code>-tx</code> : enable the option `HDBTEXCODEC'.</li>
+<li><code>-rc <var>num</var></code> : specify the number of cached records.</li>
+<li><code>-xm <var>num</var></code> : specify the size of the extra mapped memory.</li>
+<li><code>-df <var>num</var></code> : specify the unit step number of auto defragmentation.</li>
+<li><code>-nl</code> : enable the option `HDBNOLCK'.</li>
+<li><code>-nb</code> : enable the option `HDBLCKNB'.</li>
+<li><code>-as</code> : use the function `tchdbputasync' instead of `tchdbput'.</li>
+<li><code>-rnd</code> : select keys at random.</li>
+<li><code>-wb</code> : use the function `tchdbget3' instead of `tchdbget'.</li>
+<li><code>-nc</code> : omit the comparison test.</li>
+<li><code>-rr <var>num</var></code> : specify the ratio of reading operation by percentage.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<p>The command `<code>tchmgr</code>' is a utility for test and debugging of the hash database API and its applications.  `<var>path</var>' specifies the path of a database file.  `<var>bnum</var>' specifies the number of buckets.  `<var>apow</var>' specifies the power of the alignment.  `<var>fpow</var>' specifies the power of the free block pool.  `<var>key</var>' specifies the key of a record.  `<var>value</var>' specifies the value of a record.  `<var>file</var>' specifies the input file.</p>
+
+<dl class="api">
+<dt><code>tchmgr create [-tl] [-td|-tb|-tt|-tx] <var>path</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]</code></dt>
+<dd>Create a database file.</dd>
+<dt><code>tchmgr inform [-nl|-nb] <var>path</var></code></dt>
+<dd>Print miscellaneous information to the standard output.</dd>
+<dt><code>tchmgr put [-nl|-nb] [-sx] [-dk|-dc|-dai|-dad] <var>path</var> <var>key</var> <var>value</var></code></dt>
+<dd>Store a record.</dd>
+<dt><code>tchmgr out [-nl|-nb] [-sx] <var>path</var> <var>key</var></code></dt>
+<dd>Remove a record.</dd>
+<dt><code>tchmgr get [-nl|-nb] [-sx] [-px] [-pz] <var>path</var> <var>key</var></code></dt>
+<dd>Print the value of a record.</dd>
+<dt><code>tchmgr list [-nl|-nb] [-m <var>num</var>] [-pv] [-px] [-fm <var>str</var>] <var>path</var></code></dt>
+<dd>Print keys of all records, separated by line feeds.</dd>
+<dt><code>tchmgr optimize [-tl] [-td|-tb|-tt|-tx] [-tz] [-nl|-nb] [-df] <var>path</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]</code></dt>
+<dd>Optimize a database file.</dd>
+<dt><code>tchmgr importtsv [-nl|-nb] [-sc] <var>path</var> [<var>file</var>]</code></dt>
+<dd>Store records of TSV in each line of a file.</dd>
+<dt><code>tchmgr version</code></dt>
+<dd>Print the version information of Tokyo Cabinet.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="options">
+<li><code>-tl</code> : enable the option `HDBTLARGE'.</li>
+<li><code>-td</code> : enable the option `HDBTDEFLATE'.</li>
+<li><code>-tb</code> : enable the option `HDBTBZIP'.</li>
+<li><code>-tt</code> : enable the option `HDBTTCBS'.</li>
+<li><code>-tx</code> : enable the option `HDBTEXCODEC'.</li>
+<li><code>-nl</code> : enable the option `HDBNOLCK'.</li>
+<li><code>-nb</code> : enable the option `HDBLCKNB'.</li>
+<li><code>-sx</code> : the input data is evaluated as a hexadecimal data string.</li>
+<li><code>-dk</code> : use the function `tchdbputkeep' instead of `tchdbput'.</li>
+<li><code>-dc</code> : use the function `tchdbputcat' instead of `tchdbput'.</li>
+<li><code>-dai</code> : use the function `tchdbaddint' instead of `tchdbput'.</li>
+<li><code>-dad</code> : use the function `tchdbadddouble' instead of `tchdbput'.</li>
+<li><code>-px</code> : the output data is converted into a hexadecimal data string.</li>
+<li><code>-pz</code> : do not append line feed at the end of the output.</li>
+<li><code>-m <var>num</var></code> : specify the maximum number of the output.</li>
+<li><code>-pv</code> : print values of records also.</li>
+<li><code>-fm <var>str</var></code> : specify the prefix of keys.</li>
+<li><code>-tz</code> : enable the option `UINT8_MAX'.</li>
+<li><code>-df</code> : perform defragmentation only.</li>
+<li><code>-sc</code> : normalize keys as lower cases.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<hr />
+
+<h2 id="tcbdbapi">The B+ Tree Database API</h2>
+
+<p>B+ tree database is a file containing a B+ tree and is handled with the B+ tree database API.  See `<code>tcbdb.h</code>' for the entire specification.</p>
+
+<h3 id="tcbdbapi_description">Description</h3>
+
+<p>To use the B+ tree database API, include `<code>tcutil.h</code>', `<code>tcbdb.h</code>', and related standard header files.  Usually, write the following description near the front of a source file.</p>
+
+<dl>
+<dt><code>#include &lt;tcutil.h&gt;</code></dt>
+<dt><code>#include &lt;tcbdb.h&gt;</code></dt>
+<dt><code>#include &lt;stdlib.h&gt;</code></dt>
+<dt><code>#include &lt;stdbool.h&gt;</code></dt>
+<dt><code>#include &lt;stdint.h&gt;</code></dt>
+</dl>
+
+<p>Objects whose type is pointer to `<code>TCBDB</code>' are used to handle B+ tree databases.  A B+ tree database object is created with the function `<code>tcbdbnew</code>' and is deleted with the function `<code>tcbdbdel</code>'.  To avoid memory leak, it is important to delete every object when it is no longer in use.</p>
+
+<p>Before operations to store or retrieve records, it is necessary to open a database file and connect the B+ tree database object to it.  The function `<code>tcbdbopen</code>' is used to open a database file and the function `<code>tcbdbclose</code>' is used to close the database file.  To avoid data missing or corruption, it is important to close every database file when it is no longer in use.  It is forbidden for multible database objects in a process to open the same database at the same time.</p>
+
+<h3 id="tcbdbapi_api">API</h3>
+
+<p>The function `tcbdberrmsg' is used in order to get the message string corresponding to an error code.</p>
+
+<dl class="api">
+<dt><code>const char *tcbdberrmsg(int <var>ecode</var>);</code></dt>
+<dd>`<var>ecode</var>' specifies the error code.</dd>
+<dd>The return value is the message string of the error code.</dd>
+</dl>
+
+<p>The function `tcbdbnew' is used in order to create a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>TCBDB *tcbdbnew(void);</code></dt>
+<dd>The return value is the new B+ tree database object.</dd>
+</dl>
+
+<p>The function `tcbdbdel' is used in order to delete a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>void tcbdbdel(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>If the database is not closed, it is closed implicitly.  Note that the deleted object and its derivatives can not be used anymore.</dd>
+</dl>
+
+<p>The function `tcbdbecode' is used in order to get the last happened error code of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>int tcbdbecode(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>The return value is the last happened error code.</dd>
+<dd>The following error codes are defined: `TCESUCCESS' for success, `TCETHREAD' for threading error, `TCEINVALID' for invalid operation, `TCENOFILE' for file not found, `TCENOPERM' for no permission, `TCEMETA' for invalid meta data, `TCERHEAD' for invalid record header, `TCEOPEN' for open error, `TCECLOSE' for close error, `TCETRUNC' for trunc error, `TCESYNC' for sync error, `TCESTAT' for stat error, `TCESEEK' for seek error, `TCEREAD' for read error, `TCEWRITE' for write error, `TCEMMAP' for mmap error, `TCELOCK' for lock error, `TCEUNLINK' for unlink error, `TCERENAME' for rename error, `TCEMKDIR' for mkdir error, `TCERMDIR' for rmdir error, `TCEKEEP' for existing record, `TCENOREC' for no record found, and `TCEMISC' for miscellaneous error.</dd>
+</dl>
+
+<p>The function `tcbdbsetmutex' is used in order to set mutual exclusion control of a B+ tree database object for threading.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbsetmutex(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object which is not opened.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the mutual exclusion control of the database should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tcbdbsetcmpfunc' is used in order to set the custom comparison function of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbsetcmpfunc(TCBDB *<var>bdb</var>, TCCMP <var>cmp</var>, void *<var>cmpop</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object which is not opened.</dd>
+<dd>`<var>cmp</var>' specifies the pointer to the custom comparison function.  It receives five parameters.  The first parameter is the pointer to the region of one key.  The second parameter is the size of the region of one key.  The third parameter is the pointer to the region of the other key.  The fourth parameter is the size of the region of the other key.  The fifth parameter is the pointer to the optional opaque object.  It returns positive if the former is big, negative if the latter is big, 0 if both are equivalent.</dd>
+<dd>`<var>cmpop</var>' specifies an arbitrary pointer to be given as a parameter of the comparison function.  If it is not needed, `NULL' can be specified.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>The default comparison function compares keys of two records by lexical order.  The functions `tccmplexical' (dafault), `tccmpdecimal', `tccmpint32', and `tccmpint64' are built-in.  Note that the comparison function should be set before the database is opened.  Moreover, user-defined comparison functions should be set every time the database is being opened.</dd>
+</dl>
+
+<p>The function `tcbdbtune' is used in order to set the tuning parameters of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbtune(TCBDB *<var>bdb</var>, int32_t <var>lmemb</var>, int32_t <var>nmemb</var>, int64_t <var>bnum</var>, int8_t <var>apow</var>, int8_t <var>fpow</var>, uint8_t <var>opts</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object which is not opened.</dd>
+<dd>`<var>lmemb</var>' specifies the number of members in each leaf page.  If it is not more than 0, the default value is specified.  The default value is 128.</dd>
+<dd>`<var>nmemb</var>' specifies the number of members in each non-leaf page.  If it is not more than 0, the default value is specified.  The default value is 256.</dd>
+<dd>`<var>bnum</var>' specifies the number of elements of the bucket array.  If it is not more than 0, the default value is specified.  The default value is 32749.  Suggested size of the bucket array is about from 1 to 4 times of the number of all pages to be stored.</dd>
+<dd>`<var>apow</var>' specifies the size of record alignment by power of 2.  If it is negative, the default value is specified.  The default value is 8 standing for 2^8=256.</dd>
+<dd>`<var>fpow</var>' specifies the maximum number of elements of the free block pool by power of 2.  If it is negative, the default value is specified.  The default value is 10 standing for 2^10=1024.</dd>
+<dd>`<var>opts</var>' specifies options by bitwise-or: `BDBTLARGE' specifies that the size of the database can be larger than 2GB by using 64-bit bucket array, `BDBTDEFLATE' specifies that each page is compressed with Deflate encoding, `BDBTBZIP' specifies that each page is compressed with BZIP2 encoding, `BDBTTCBS' specifies that each page is compressed with TCBS encoding.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the tuning parameters should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tcbdbsetcache' is used in order to set the caching parameters of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbsetcache(TCBDB *<var>bdb</var>, int32_t <var>lcnum</var>, int32_t <var>ncnum</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object which is not opened.</dd>
+<dd>`<var>lcnum</var>' specifies the maximum number of leaf nodes to be cached.  If it is not more than 0, the default value is specified.  The default value is 1024.</dd>
+<dd>`<var>ncnum</var>' specifies the maximum number of non-leaf nodes to be cached.  If it is not more than 0, the default value is specified.  The default value is 512.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the caching parameters should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tcbdbsetxmsiz' is used in order to set the size of the extra mapped memory of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbsetxmsiz(TCBDB *<var>bdb</var>, int64_t <var>xmsiz</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object which is not opened.</dd>
+<dd>`<var>xmsiz</var>' specifies the size of the extra mapped memory.  If it is not more than 0, the extra mapped memory is disabled.  It is disabled by default.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the mapping parameters should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tcbdbsetdfunit' is used in order to set the unit step number of auto defragmentation of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbsetdfunit(TCBDB *<var>bdb</var>, int32_t <var>dfunit</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object which is not opened.</dd>
+<dd>`<var>dfunit</var>' specifie the unit step number.  If it is not more than 0, the auto defragmentation is disabled.  It is disabled by default.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the defragmentation parameter should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tcbdbopen' is used in order to open a database file and connect a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbopen(TCBDB *<var>bdb</var>, const char *<var>path</var>, int <var>omode</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object which is not opened.</dd>
+<dd>`<var>path</var>' specifies the path of the database file.</dd>
+<dd>`<var>omode</var>' specifies the connection mode: `BDBOWRITER' as a writer, `BDBOREADER' as a reader.  If the mode is `BDBOWRITER', the following may be added by bitwise-or: `BDBOCREAT', which means it creates a new database if not exist, `BDBOTRUNC', which means it creates a new database regardless if one exists, `BDBOTSYNC', which means every transaction synchronizes updated contents with the device.  Both of `BDBOREADER' and `BDBOWRITER' can be added to by bitwise-or: `BDBONOLCK', which means it opens the database file without file locking, or `BDBOLCKNB', which means locking is performed without blocking.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcbdbclose' is used in order to close a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbclose(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update of a database is assured to be written when the database is closed.  If a writer opens a database but does not close it appropriately, the database will be broken.</dd>
+</dl>
+
+<p>The function `tcbdbput' is used in order to store a record into a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbput(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcbdbput2' is used in order to store a string record into a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbput2(TCBDB *<var>bdb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcbdbputkeep' is used in order to store a new record into a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbputkeep(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcbdbputkeep2' is used in order to store a new string record into a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbputkeep2(TCBDB *<var>bdb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcbdbputcat' is used in order to concatenate a value at the end of the existing record in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbputcat(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcbdbputcat2' is used in order to concatenate a string value at the end of the existing record in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbputcat2(TCBDB *<var>bdb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcbdbputdup' is used in order to store a record into a B+ tree database object with allowing duplication of keys.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbputdup(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, the new record is placed after the existing one.</dd>
+</dl>
+
+<p>The function `tcbdbputdup2' is used in order to store a string record into a B+ tree database object with allowing duplication of keys.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbputdup2(TCBDB *<var>bdb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, the new record is placed after the existing one.</dd>
+</dl>
+
+<p>The function `tcbdbputdup3' is used in order to store records into a B+ tree database object with allowing duplication of keys.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbputdup3(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const TCLIST *<var>vals</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the common key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the common key.</dd>
+<dd>`<var>vals</var>' specifies a list object containing values.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, the new records are placed after the existing one.</dd>
+</dl>
+
+<p>The function `tcbdbout' is used in order to remove a record of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbout(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If the key of duplicated records is specified, the first one is selected.</dd>
+</dl>
+
+<p>The function `tcbdbout2' is used in order to remove a string record of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbout2(TCBDB *<var>bdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If the key of duplicated records is specified, the first one is selected.</dd>
+</dl>
+
+<p>The function `tcbdbout3' is used in order to remove records of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbout3(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If the key of duplicated records is specified, all of them are removed.</dd>
+</dl>
+
+<p>The function `tcbdbget' is used in order to retrieve a record in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>void *tcbdbget(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>If the key of duplicated records is specified, the first one is selected.  Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbdbget2' is used in order to retrieve a string record in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>char *tcbdbget2(TCBDB *<var>bdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the string of the value of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>If the key of duplicated records is specified, the first one is selected.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbdbget3' is used in order to retrieve a record in a B+ tree database object as a volatile buffer.</p>
+
+<dl class="api">
+<dt><code>const void *tcbdbget3(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>If the key of duplicated records is specified, the first one is selected.  Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is volatile and it may be spoiled by another operation of the database, the data should be copied into another involatile buffer immediately.</dd>
+</dl>
+
+<p>The function `tcbdbget4' is used in order to retrieve records in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcbdbget4(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is a list object of the values of the corresponding records.  `NULL' is returned if no record corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbdbvnum' is used in order to get the number of records corresponding a key in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>int tcbdbvnum(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is the number of the corresponding records, else, it is 0.</dd>
+</dl>
+
+<p>The function `tcbdbvnum2' is used in order to get the number of records corresponding a string key in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>int tcbdbvnum2(TCBDB *<var>bdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the number of the corresponding records, else, it is 0.</dd>
+</dl>
+
+<p>The function `tcbdbvsiz' is used in order to get the size of the value of a record in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>int tcbdbvsiz(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+<dd>If the key of duplicated records is specified, the first one is selected.</dd>
+</dl>
+
+<p>The function `tcbdbvsiz2' is used in order to get the size of the value of a string record in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>int tcbdbvsiz2(TCBDB *<var>bdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+<dd>If the key of duplicated records is specified, the first one is selected.</dd>
+</dl>
+
+<p>The function `tcbdbrange' is used in order to get keys of ranged records in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcbdbrange(TCBDB *<var>bdb</var>, const void *<var>bkbuf</var>, int <var>bksiz</var>, bool <var>binc</var>, const void *<var>ekbuf</var>, int <var>eksiz</var>, bool <var>einc</var>, int <var>max</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>bkbuf</var>' specifies the pointer to the region of the key of the beginning border.  If it is `NULL', the first record is specified.</dd>
+<dd>`<var>bksiz</var>' specifies the size of the region of the beginning key.</dd>
+<dd>`<var>binc</var>' specifies whether the beginning border is inclusive or not.</dd>
+<dd>`<var>ekbuf</var>' specifies the pointer to the region of the key of the ending border.  If it is `NULL', the last record is specified.</dd>
+<dd>`<var>eksiz</var>' specifies the size of the region of the ending key.</dd>
+<dd>`<var>einc</var>' specifies whether the ending border is inclusive or not.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the keys of the corresponding records.  This function does never fail.  It returns an empty list even if no record corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbdbrange2' is used in order to get string keys of ranged records in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcbdbrange2(TCBDB *<var>bdb</var>, const char *<var>bkstr</var>, bool <var>binc</var>, const char *<var>ekstr</var>, bool <var>einc</var>, int <var>max</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>bkstr</var>' specifies the string of the key of the beginning border.  If it is `NULL', the first record is specified.</dd>
+<dd>`<var>binc</var>' specifies whether the beginning border is inclusive or not.</dd>
+<dd>`<var>ekstr</var>' specifies the string of the key of the ending border.  If it is `NULL', the last record is specified.</dd>
+<dd>`<var>einc</var>' specifies whether the ending border is inclusive or not.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the keys of the corresponding records.  This function does never fail.  It returns an empty list even if no record corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbdbfwmkeys' is used in order to get forward matching keys in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcbdbfwmkeys(TCBDB *<var>bdb</var>, const void *<var>pbuf</var>, int <var>psiz</var>, int <var>max</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>pbuf</var>' specifies the pointer to the region of the prefix.</dd>
+<dd>`<var>psiz</var>' specifies the size of the region of the prefix.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbdbfwmkeys2' is used in order to get forward matching string keys in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcbdbfwmkeys2(TCBDB *<var>bdb</var>, const char *<var>pstr</var>, int <var>max</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>pstr</var>' specifies the string of the prefix.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbdbaddint' is used in order to add an integer to a record in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>int tcbdbaddint(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int <var>num</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>If successful, the return value is the summation value, else, it is `INT_MIN'.</dd>
+<dd>If the corresponding record exists, the value is treated as an integer and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tcbdbadddouble' is used in order to add a real number to a record in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>double tcbdbadddouble(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, double <var>num</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>If successful, the return value is the summation value, else, it is Not-a-Number.</dd>
+<dd>If the corresponding record exists, the value is treated as a real number and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tcbdbsync' is used in order to synchronize updated contents of a B+ tree database object with the file and the device.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbsync(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>This function is useful when another process connects to the same database file.</dd>
+</dl>
+
+<p>The function `tcbdboptimize' is used in order to optimize the file of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdboptimize(TCBDB *<var>bdb</var>, int32_t <var>lmemb</var>, int32_t <var>nmemb</var>, int64_t <var>bnum</var>, int8_t <var>apow</var>, int8_t <var>fpow</var>, uint8_t <var>opts</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>lmemb</var>' specifies the number of members in each leaf page.  If it is not more than 0, the current setting is not changed.</dd>
+<dd>`<var>nmemb</var>' specifies the number of members in each non-leaf page.  If it is not more than 0, the current setting is not changed.</dd>
+<dd>`<var>bnum</var>' specifies the number of elements of the bucket array.  If it is not more than 0, the default value is specified.  The default value is two times of the number of pages.</dd>
+<dd>`<var>apow</var>' specifies the size of record alignment by power of 2.  If it is negative, the current setting is not changed.</dd>
+<dd>`<var>fpow</var>' specifies the maximum number of elements of the free block pool by power of 2.  If it is negative, the current setting is not changed.</dd>
+<dd>`<var>opts</var>' specifies options by bitwise-or: `BDBTLARGE' specifies that the size of the database can be larger than 2GB by using 64-bit bucket array, `BDBTDEFLATE' specifies that each record is compressed with Deflate encoding, `BDBTBZIP' specifies that each page is compressed with BZIP2 encoding, `BDBTTCBS' specifies that each page is compressed with TCBS encoding.  If it is `UINT8_MAX', the current setting is not changed.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>This function is useful to reduce the size of the database file with data fragmentation by successive updating.</dd>
+</dl>
+
+<p>The function `tcbdbvanish' is used in order to remove all records of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbvanish(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcbdbcopy' is used in order to copy the database file of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbcopy(TCBDB *<var>bdb</var>, const char *<var>path</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>path</var>' specifies the path of the destination file.  If it begins with `@', the trailing substring is executed as a command line.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned if the executed command returns non-zero code.</dd>
+<dd>The database file is assured to be kept synchronized and not modified while the copying or executing operation is in progress.  So, this function is useful to create a backup file of the database file.</dd>
+</dl>
+
+<p>The function `tcbdbtranbegin' is used in order to begin the transaction of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbtranbegin(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>The database is locked by the thread while the transaction so that only one transaction can be activated with a database object at the same time.  Thus, the serializable isolation level is assumed if every database operation is performed in the transaction.  Because all pages are cached on memory while the transaction, the amount of referred records is limited by the memory capacity.  If the database is closed during transaction, the transaction is aborted implicitly.</dd>
+</dl>
+
+<p>The function `tcbdbtrancommit' is used in order to commit the transaction of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbtrancommit(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update in the transaction is fixed when it is committed successfully.</dd>
+</dl>
+
+<p>The function `tcbdbtranabort' is used in order to abort the transaction of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbtranabort(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update in the transaction is discarded when it is aborted.  The state of the database is rollbacked to before transaction.</dd>
+</dl>
+
+<p>The function `tcbdbpath' is used in order to get the file path of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>const char *tcbdbpath(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>The return value is the path of the database file or `NULL' if the object does not connect to any database file.</dd>
+</dl>
+
+<p>The function `tcbdbrnum' is used in order to get the number of records of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcbdbrnum(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>The return value is the number of records or 0 if the object does not connect to any database file.</dd>
+</dl>
+
+<p>The function `tcbdbfsiz' is used in order to get the size of the database file of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcbdbfsiz(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>The return value is the size of the database file or 0 if the object does not connect to any database file.</dd>
+</dl>
+
+<p>The function `tcbdbcurnew' is used in order to create a cursor object.</p>
+
+<dl class="api">
+<dt><code>BDBCUR *tcbdbcurnew(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>The return value is the new cursor object.</dd>
+<dd>Note that the cursor is available only after initialization with the `tcbdbcurfirst' or the `tcbdbcurjump' functions and so on.  Moreover, the position of the cursor will be indefinite when the database is updated after the initialization of the cursor.</dd>
+</dl>
+
+<p>The function `tcbdbcurdel' is used in order to delete a cursor object.</p>
+
+<dl class="api">
+<dt><code>void tcbdbcurdel(BDBCUR *<var>cur</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+</dl>
+
+<p>The function `tcbdbcurfirst' is used in order to move a cursor object to the first record.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbcurfirst(BDBCUR *<var>cur</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned if there is no record in the database.</dd>
+</dl>
+
+<p>The function `tcbdbcurlast' is used in order to move a cursor object to the last record.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbcurlast(BDBCUR *<var>cur</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned if there is no record in the database.</dd>
+</dl>
+
+<p>The function `tcbdbcurjump' is used in order to move a cursor object to the front of records corresponding a key.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbcurjump(BDBCUR *<var>cur</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned if there is no record corresponding the condition.</dd>
+<dd>The cursor is set to the first record corresponding the key or the next substitute if completely matching record does not exist.</dd>
+</dl>
+
+<p>The function `tcbdbcurjump2' is used in order to move a cursor object to the front of records corresponding a key string.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbcurjump2(BDBCUR *<var>cur</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned if there is no record corresponding the condition.</dd>
+<dd>The cursor is set to the first record corresponding the key or the next substitute if completely matching record does not exist.</dd>
+</dl>
+
+<p>The function `tcbdbcurprev' is used in order to move a cursor object to the previous record.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbcurprev(BDBCUR *<var>cur</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned if there is no previous record.</dd>
+</dl>
+
+<p>The function `tcbdbcurnext' is used in order to move a cursor object to the next record.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbcurnext(BDBCUR *<var>cur</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned if there is no next record.</dd>
+</dl>
+
+<p>The function `tcbdbcurput' is used in order to insert a record around a cursor object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbcurput(BDBCUR *<var>cur</var>, const void *<var>vbuf</var>, int <var>vsiz</var>, int <var>cpmode</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object of writer connection.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>`<var>cpmode</var>' specifies detail adjustment: `BDBCPCURRENT', which means that the value of the current record is overwritten, `BDBCPBEFORE', which means that the new record is inserted before the current record, `BDBCPAFTER', which means that the new record is inserted after the current record.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned when the cursor is at invalid position.</dd>
+<dd>After insertion, the cursor is moved to the inserted record.</dd>
+</dl>
+
+<p>The function `tcbdbcurput2' is used in order to insert a string record around a cursor object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbcurput2(BDBCUR *<var>cur</var>, const char *<var>vstr</var>, int <var>cpmode</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object of writer connection.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>`<var>cpmode</var>' specifies detail adjustment: `BDBCPCURRENT', which means that the value of the current record is overwritten, `BDBCPBEFORE', which means that the new record is inserted before the current record, `BDBCPAFTER', which means that the new record is inserted after the current record.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned when the cursor is at invalid position.</dd>
+<dd>After insertion, the cursor is moved to the inserted record.</dd>
+</dl>
+
+<p>The function `tcbdbcurout' is used in order to remove the record where a cursor object is.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbcurout(BDBCUR *<var>cur</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object of writer connection.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned when the cursor is at invalid position.</dd>
+<dd>After deletion, the cursor is moved to the next record if possible.</dd>
+</dl>
+
+<p>The function `tcbdbcurkey' is used in order to get the key of the record where the cursor object is.</p>
+
+<dl class="api">
+<dt><code>void *tcbdbcurkey(BDBCUR *<var>cur</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the key, else, it is `NULL'.  `NULL' is returned when the cursor is at invalid position.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbdbcurkey2' is used in order to get the key string of the record where the cursor object is.</p>
+
+<dl class="api">
+<dt><code>char *tcbdbcurkey2(BDBCUR *<var>cur</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>If successful, the return value is the string of the key, else, it is `NULL'.  `NULL' is returned when the cursor is at invalid position.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbdbcurkey3' is used in order to get the key of the record where the cursor object is, as a volatile buffer.</p>
+
+<dl class="api">
+<dt><code>const void *tcbdbcurkey3(BDBCUR *<var>cur</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the key, else, it is `NULL'.  `NULL' is returned when the cursor is at invalid position.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is volatile and it may be spoiled by another operation of the database, the data should be copied into another involatile buffer immediately.</dd>
+</dl>
+
+<p>The function `tcbdbcurval' is used in order to get the value of the record where the cursor object is.</p>
+
+<dl class="api">
+<dt><code>void *tcbdbcurval(BDBCUR *<var>cur</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the value, else, it is `NULL'.  `NULL' is returned when the cursor is at invalid position.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbdbcurval2' is used in order to get the value string of the record where the cursor object is.</p>
+
+<dl class="api">
+<dt><code>char *tcbdbcurval2(BDBCUR *<var>cur</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>If successful, the return value is the string of the value, else, it is `NULL'.  `NULL' is returned when the cursor is at invalid position.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbdbcurval3' is used in order to get the value of the record where the cursor object is, as a volatile buffer.</p>
+
+<dl class="api">
+<dt><code>const void *tcbdbcurval3(BDBCUR *<var>cur</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the value, else, it is `NULL'.  `NULL' is returned when the cursor is at invalid position.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is volatile and it may be spoiled by another operation of the database, the data should be copied into another involatile buffer immediately.</dd>
+</dl>
+
+<p>The function `tcbdbcurrec' is used in order to get the key and the value of the record where the cursor object is.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbcurrec(BDBCUR *<var>cur</var>, TCXSTR *<var>kxstr</var>, TCXSTR *<var>vxstr</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>`<var>kxstr</var>' specifies the object into which the key is wrote down.</dd>
+<dd>`<var>vxstr</var>' specifies the object into which the value is wrote down.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned when the cursor is at invalid position.</dd>
+</dl>
+
+<h3 id="tcbdbapi_example">Example Code</h3>
+
+<p>The following code is an example to use a B+ tree database.</p>
+
+<pre>#include &lt;tcutil.h&gt;
+#include &lt;tcbdb.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdbool.h&gt;
+#include &lt;stdint.h&gt;
+
+int main(int argc, char **argv){
+  TCBDB *bdb;
+  BDBCUR *cur;
+  int ecode;
+  char *key, *value;
+
+  /* create the object */
+  bdb = tcbdbnew();
+
+  /* open the database */
+  if(!tcbdbopen(bdb, "casket.tcb", BDBOWRITER | BDBOCREAT)){
+    ecode = tcbdbecode(bdb);
+    fprintf(stderr, "open error: %s\n", tcbdberrmsg(ecode));
+  }
+
+  /* store records */
+  if(!tcbdbput2(bdb, "foo", "hop") ||
+     !tcbdbput2(bdb, "bar", "step") ||
+     !tcbdbput2(bdb, "baz", "jump")){
+    ecode = tcbdbecode(bdb);
+    fprintf(stderr, "put error: %s\n", tcbdberrmsg(ecode));
+  }
+
+  /* retrieve records */
+  value = tcbdbget2(bdb, "foo");
+  if(value){
+    printf("%s\n", value);
+    free(value);
+  } else {
+    ecode = tcbdbecode(bdb);
+    fprintf(stderr, "get error: %s\n", tcbdberrmsg(ecode));
+  }
+
+  /* traverse records */
+  cur = tcbdbcurnew(bdb);
+  tcbdbcurfirst(cur);
+  while((key = tcbdbcurkey2(cur)) != NULL){
+    value = tcbdbcurval2(cur);
+    if(value){
+      printf("%s:%s\n", key, value);
+      free(value);
+    }
+    free(key);
+    tcbdbcurnext(cur);
+  }
+  tcbdbcurdel(cur);
+
+  /* close the database */
+  if(!tcbdbclose(bdb)){
+    ecode = tcbdbecode(bdb);
+    fprintf(stderr, "close error: %s\n", tcbdberrmsg(ecode));
+  }
+
+  /* delete the object */
+  tcbdbdel(bdb);
+
+  return 0;
+}
+</pre>
+
+<h3 id="tcbdbapi_cli">CLI</h3>
+
+<p>To use the B+ tree database API easily, the commands `<code>tcbtest</code>', `<code>tcbmttest</code>', and `<code>tcbmgr</code>' are provided.</p>
+
+<p>The command `<code>tcbtest</code>' is a utility for facility test and performance test.  This command is used in the following format.  `<var>path</var>' specifies the path of a database file.  `<var>rnum</var>' specifies the number of iterations.  `<var>lmemb</var>' specifies the number of members in each leaf page.  `<var>nmemb</var>' specifies the number of members in each non-leaf page.  `<var>bnum</var>' specifies the number of buckets.  `<var>apow</var>' specifies the power of the alignment.  `<var>fpow</var>' specifies the power of the free block pool.</p>
+
+<dl class="api">
+<dt><code>tcbtest write [-mt] [-cd|-ci|-cj] [-tl] [-td|-tb|-tt|-tx] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-ls <var>num</var>] [-ca <var>num</var>] [-nl|-nb] [-rnd] <var>path</var> <var>rnum</var> [<var>lmemb</var> [<var>nmemb</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]]]</code></dt>
+<dd>Store records with keys of 8 bytes.  They change as `00000001', `00000002'...</dd>
+<dt><code>tcbtest read [-mt] [-cd|-ci|-cj] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-wb] [-rnd] <var>path</var></code></dt>
+<dd>Retrieve all records of the database above.</dd>
+<dt><code>tcbtest remove [-mt] [-cd|-ci|-cj] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-rnd] <var>path</var></code></dt>
+<dd>Remove all records of the database above.</dd>
+<dt><code>tcbtest rcat [-mt] [-cd|-ci|-cj] [-tl] [-td|-tb|-tt|-tx] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-ls <var>num</var>] [-ca <var>num</var>] [-nl|-nb] [-pn <var>num</var>] [-dai|-dad|-rl|-ru] <var>path</var> <var>rnum</var> [<var>lmemb</var> [<var>nmemb</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]]]</code></dt>
+<dd>Store records with partway duplicated keys using concatenate mode.</dd>
+<dt><code>tcbtest queue [-mt] [-cd|-ci|-cj] [-tl] [-td|-tb|-tt|-tx] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-ls <var>num</var>] [-ca <var>num</var>] [-nl|-nb] <var>path</var> <var>rnum</var> [<var>lmemb</var> [<var>nmemb</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]]]</code></dt>
+<dd>Perform queueing and dequeueing.</dd>
+<dt><code>tcbtest misc [-mt] [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] <var>path</var> <var>rnum</var></code></dt>
+<dd>Perform miscellaneous test of various operations.</dd>
+<dt><code>tcbtest wicked [-mt] [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] <var>path</var> <var>rnum</var></code></dt>
+<dd>Perform updating operations selected at random.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="options">
+<li><code>-mt</code> : call the function `tchdbsetmutex'.</li>
+<li><code>-cd</code> : use the comparison function `tccmpdecimal'.</li>
+<li><code>-ci</code> : use the comparison function  `tccmpint32'.</li>
+<li><code>-cj</code> : use the comparison function  `tccmpint64'.</li>
+<li><code>-tl</code> : enable the option `BDBTLARGE'.</li>
+<li><code>-td</code> : enable the option `BDBTDEFLATE'.</li>
+<li><code>-tb</code> : enable the option `BDBTBZIP'.</li>
+<li><code>-tt</code> : enable the option `BDBTTCBS'.</li>
+<li><code>-tx</code> : enable the option `BDBTEXCODEC'.</li>
+<li><code>-lc <var>num</var></code> : specify the number of cached leaf pages.</li>
+<li><code>-nc <var>num</var></code> : specify the number of cached non-leaf pages.</li>
+<li><code>-xm <var>num</var></code> : specify the size of the extra mapped memory.</li>
+<li><code>-df <var>num</var></code> : specify the unit step number of auto defragmentation.</li>
+<li><code>-ls <var>num</var></code> : specify the maximum size of each leaf page.</li>
+<li><code>-ca <var>num</var></code> : specify the capacity number of records.</li>
+<li><code>-nl</code> : enable the option `BDBNOLCK'.</li>
+<li><code>-nb</code> : enable the option `BDBLCKNB'.</li>
+<li><code>-rnd</code> : select keys at random.</li>
+<li><code>-wb</code> : use the function `tcbdbget3' instead of `tcbdbget'.</li>
+<li><code>-pn <var>num</var></code> : specify the number of patterns.</li>
+<li><code>-dai</code> : use the function `tcbdbaddint' instead of `tcbdbputcat'.</li>
+<li><code>-dad</code> : use the function `tcbdbadddouble' instead of `tcbdbputcat'.</li>
+<li><code>-rl</code> : set the length of values at random.</li>
+<li><code>-ru</code> : select update operations at random.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<p>The command `<code>tcbmttest</code>' is a utility for facility test and performance test.  This command is used in the following format.  `<var>path</var>' specifies the path of a database file.  `<var>tnum</var>' specifies the number of running threads.  `<var>rnum</var>' specifies the number of iterations.  `<var>lmemb</var>' specifies the number of members in each leaf page.  `<var>nmemb</var>' specifies the number of members in each non-leaf page.  `<var>bnum</var>' specifies the number of buckets.  `<var>apow</var>' specifies the power of the alignment.  `<var>fpow</var>' specifies the power of the free block pool.</p>
+
+<dl class="api">
+<dt><code>tcbmttest write [-tl] [-td|-tb|-tt|-tx] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-rnd] <var>path</var> <var>tnum</var> <var>rnum</var> [<var>lmemb</var> [<var>nmemb</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]]]</code></dt>
+<dd>Store records with keys of 8 bytes.  They change as `00000001', `00000002'...</dd>
+<dt><code>tcbmttest read [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-wb] [-rnd] <var>path</var> <var>tnum</var></code></dt>
+<dd>Retrieve all records of the database above.</dd>
+<dt><code>tcbmttest remove [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-rnd] <var>path</var> <var>tnum</var></code></dt>
+<dd>Remove all records of the database above.</dd>
+<dt><code>tcbmttest wicked [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] [-nc] <var>path</var> <var>tnum</var> <var>rnum</var></code></dt>
+<dd>Perform updating operations selected at random.</dd>
+<dt><code>tcbmttest typical [-tl] [-td|-tb|-tt|-tx] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-nc] [-rr <var>num</var>] <var>path</var> <var>tnum</var> <var>rnum</var> [<var>lmemb</var> [<var>nmemb</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]]]</code></dt>
+<dd>Perform typical operations selected at random.</dd>
+<dt><code>tcbmttest race [-tl] [-td|-tb|-tt|-tx] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] <var>path</var> <var>tnum</var> <var>rnum</var> [<var>lmemb</var> [<var>nmemb</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]]]</code></dt>
+<dd>Perform race condition test.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="options">
+<li><code>-tl</code> : enable the option `BDBTLARGE'.</li>
+<li><code>-td</code> : enable the option `BDBTDEFLATE'.</li>
+<li><code>-tb</code> : enable the option `BDBTBZIP'.</li>
+<li><code>-tt</code> : enable the option `BDBTTCBS'.</li>
+<li><code>-tx</code> : enable the option `BDBTEXCODEC'.</li>
+<li><code>-xm <var>num</var></code> : specify the size of the extra mapped memory.</li>
+<li><code>-df <var>num</var></code> : specify the unit step number of auto defragmentation.</li>
+<li><code>-nl</code> : enable the option `BDBNOLCK'.</li>
+<li><code>-nb</code> : enable the option `BDBLCKNB'.</li>
+<li><code>-rnd</code> : select keys at random.</li>
+<li><code>-wb</code> : use the function `tchdbget3' instead of `tchdbget'.</li>
+<li><code>-nc</code> : omit the comparison test.</li>
+<li><code>-rr <var>num</var></code> : specify the ratio of reading operation by percentage.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<p>The command `<code>tcbmgr</code>' is a utility for test and debugging of the B+ tree database API and its applications.  `<var>path</var>' specifies the path of a database file.  `<var>lmemb</var>' specifies the number of members in each leaf page.  `<var>nmemb</var>' specifies the number of members in each non-leaf page.  `<var>bnum</var>' specifies the number of buckets.  `<var>apow</var>' specifies the power of the alignment.  `<var>fpow</var>' specifies the power of the free block pool.  `<var>key</var>' specifies the key of a record.  `<var>value</var>' specifies the value of a record.  `<var>file</var>' specifies the input file.</p>
+
+<dl class="api">
+<dt><code>tcbmgr create [-cd|-ci|-cj] [-tl] [-td|-tb|-tt|-tx] <var>path</var> [<var>lmemb</var> [<var>nmemb</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]]]</code></dt>
+<dd>Create a database file.</dd>
+<dt><code>tcbmgr inform [-nl|-nb] <var>path</var></code></dt>
+<dd>Print miscellaneous information to the standard output.</dd>
+<dt><code>tcbmgr put [-cd|-ci|-cj] [-nl|-nb] [-sx] [-dk|-dc|-dd|-db|-dai|-dad] <var>path</var> <var>key</var> <var>value</var></code></dt>
+<dd>Store a record.</dd>
+<dt><code>tcbmgr out [-cd|-ci|-cj] [-nl|-nb] [-sx] <var>path</var> <var>key</var></code></dt>
+<dd>Remove a record.</dd>
+<dt><code>tcbmgr get [-cd|-ci|-cj] [-nl|-nb] [-sx] [-px] [-pz] <var>path</var> <var>key</var></code></dt>
+<dd>Print the value of a record.</dd>
+<dt><code>tcbmgr list [-cd|-ci|-cj] [-nl|-nb] [-m <var>num</var>] [-bk] [-pv] [-px] [-j <var>str</var>] [-rb <var>bkey</var> <var>ekey</var>] [-fm <var>str</var>] <var>path</var></code></dt>
+<dd>Print keys of all records, separated by line feeds.</dd>
+<dt><code>tcbmgr optimize [-cd|-ci|-cj] [-tl] [-td|-tb|-tt|-tx] [-tz] [-nl|-nb] [-df] <var>path</var> [<var>lmemb</var> [<var>nmemb</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]]]</code></dt>
+<dd>Optimize a database file.</dd>
+<dt><code>tcbmgr importtsv [-nl|-nb] [-sc] <var>path</var> [<var>file</var>]</code></dt>
+<dd>Store records of TSV in each line of a file.</dd>
+<dt><code>tcbmgr version</code></dt>
+<dd>Print the version information of Tokyo Cabinet.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="options">
+<li><code>-cd</code> : use the comparison function `tccmpdecimal'.</li>
+<li><code>-ci</code> : use the comparison function  `tccmpint32'.</li>
+<li><code>-cj</code> : use the comparison function  `tccmpint64'.</li>
+<li><code>-tl</code> : enable the option `BDBTLARGE'.</li>
+<li><code>-td</code> : enable the option `BDBTDEFLATE'.</li>
+<li><code>-tb</code> : enable the option `BDBTBZIP'.</li>
+<li><code>-tt</code> : enable the option `BDBTTCBS'.</li>
+<li><code>-tx</code> : enable the option `BDBTEXCODEC'.</li>
+<li><code>-nl</code> : enable the option `BDBNOLCK'.</li>
+<li><code>-nb</code> : enable the option `BDBLCKNB'.</li>
+<li><code>-sx</code> : the input data is evaluated as a hexadecimal data string.</li>
+<li><code>-dk</code> : use the function `tcbdbputkeep' instead of `tcbdbput'.</li>
+<li><code>-dc</code> : use the function `tcbdbputcat' instead of `tcbdbput'.</li>
+<li><code>-dd</code> : use the function `tcbdbputdup' instead of `tcbdbput'.</li>
+<li><code>-db</code> : use the function `tcbdbputdupback' instead of `tcbdbput'.</li>
+<li><code>-dai</code> : use the function `tcbdbaddint' instead of `tcbdbput'.</li>
+<li><code>-dad</code> : use the function `tcbdbadddouble' instead of `tcbdbput'.</li>
+<li><code>-px</code> : the output data is converted into a hexadecimal data string.</li>
+<li><code>-pz</code> : do not append line feed at the end of the output.</li>
+<li><code>-m <var>num</var></code> : specify the maximum number of the output.</li>
+<li><code>-bk</code> : perform backword scanning.</li>
+<li><code>-pv</code> : print values of records also.</li>
+<li><code>-j <var>str</var></code> : specify the key where the cursor jump to.</li>
+<li><code>-rb <var>bkey</var> <var>ekey</var></code> : specify the range of keys.</li>
+<li><code>-fm <var>str</var></code> : specify the prefix of keys.</li>
+<li><code>-tz</code> : enable the option `UINT8_MAX'.</li>
+<li><code>-df</code> : perform defragmentation only.</li>
+<li><code>-sc</code> : normalize keys as lower cases.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<hr />
+
+<h2 id="tcfdbapi">The Fixed-length Database API</h2>
+
+<p>Fixed-length database is a file containing an array of fixed-length elements and is handled with the fixed-length database API.  See `<code>tcfdb.h</code>' for the entire specification.</p>
+
+<h3 id="tcfdbapi_description">Description</h3>
+
+<p>To use the fixed-length database API, include `<code>tcutil.h</code>', `<code>tcfdb.h</code>', and related standard header files.  Usually, write the following description near the front of a source file.</p>
+
+<dl>
+<dt><code>#include &lt;tcutil.h&gt;</code></dt>
+<dt><code>#include &lt;tcfdb.h&gt;</code></dt>
+<dt><code>#include &lt;stdlib.h&gt;</code></dt>
+<dt><code>#include &lt;stdbool.h&gt;</code></dt>
+<dt><code>#include &lt;stdint.h&gt;</code></dt>
+</dl>
+
+<p>Objects whose type is pointer to `<code>TCFDB</code>' are used to handle fixed-length databases.  A fixed-length database object is created with the function `<code>tcfdbnew</code>' and is deleted with the function `<code>tcfdbdel</code>'.  To avoid memory leak, it is important to delete every object when it is no longer in use.</p>
+
+<p>Before operations to store or retrieve records, it is necessary to open a database file and connect the fixed-length database object to it.  The function `<code>tcfdbopen</code>' is used to open a database file and the function `<code>tcfdbclose</code>' is used to close the database file.  To avoid data missing or corruption, it is important to close every database file when it is no longer in use.  It is forbidden for multible database objects in a process to open the same database at the same time.</p>
+
+<h3 id="tcfdbapi_api">API</h3>
+
+<p>The function `tcfdberrmsg' is used in order to get the message string corresponding to an error code.</p>
+
+<dl class="api">
+<dt><code>const char *tcfdberrmsg(int <var>ecode</var>);</code></dt>
+<dd>`<var>ecode</var>' specifies the error code.</dd>
+<dd>The return value is the message string of the error code.</dd>
+</dl>
+
+<p>The function `tcfdbnew' is used in order to create a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>TCFDB *tcfdbnew(void);</code></dt>
+<dd>The return value is the new fixed-length database object.</dd>
+</dl>
+
+<p>The function `tcfdbdel' is used in order to delete a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>void tcfdbdel(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>If the database is not closed, it is closed implicitly.  Note that the deleted object and its derivatives can not be used anymore.</dd>
+</dl>
+
+<p>The function `tcfdbecode' is used in order to get the last happened error code of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>int tcfdbecode(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>The return value is the last happened error code.</dd>
+<dd>The following error codes are defined: `TCESUCCESS' for success, `TCETHREAD' for threading error, `TCEINVALID' for invalid operation, `TCENOFILE' for file not found, `TCENOPERM' for no permission, `TCEMETA' for invalid meta data, `TCERHEAD' for invalid record header, `TCEOPEN' for open error, `TCECLOSE' for close error, `TCETRUNC' for trunc error, `TCESYNC' for sync error, `TCESTAT' for stat error, `TCESEEK' for seek error, `TCEREAD' for read error, `TCEWRITE' for write error, `TCEMMAP' for mmap error, `TCELOCK' for lock error, `TCEUNLINK' for unlink error, `TCERENAME' for rename error, `TCEMKDIR' for mkdir error, `TCERMDIR' for rmdir error, `TCEKEEP' for existing record, `TCENOREC' for no record found, and `TCEMISC' for miscellaneous error.</dd>
+</dl>
+
+<p>The function `tcfdbsetmutex' is used in order to set mutual exclusion control of a fixed-length database object for threading.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbsetmutex(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object which is not opened.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the mutual exclusion control is needed if the object is shared by plural threads and this function should be called before the database is opened.</dd>
+</dl>
+
+<p>The function `tcfdbtune' is used in order to set the tuning parameters of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbtune(TCFDB *<var>fdb</var>, int32_t <var>width</var>, int64_t <var>limsiz</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object which is not opened.</dd>
+<dd>`<var>width</var>' specifies the width of the value of each record.  If it is not more than 0, the default value is specified.  The default value is 255.</dd>
+<dd>`<var>limsiz</var>' specifies the limit size of the database file.  If it is not more than 0, the default value is specified.  The default value is 268435456.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the tuning parameters should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tcfdbopen' is used in order to open a database file and connect a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbopen(TCFDB *<var>fdb</var>, const char *<var>path</var>, int <var>omode</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object which is not opened.</dd>
+<dd>`<var>path</var>' specifies the path of the database file.</dd>
+<dd>`<var>omode</var>' specifies the connection mode: `FDBOWRITER' as a writer, `FDBOREADER' as a reader.  If the mode is `FDBOWRITER', the following may be added by bitwise-or: `FDBOCREAT', which means it creates a new database if not exist, `FDBOTRUNC', which means it creates a new database regardless if one exists, `FDBOTSYNC', which means every transaction synchronizes updated contents with the device.  Both of `FDBOREADER' and `FDBOWRITER' can be added to by bitwise-or: `FDBONOLCK', which means it opens the database file without file locking, or `FDBOLCKNB', which means locking is performed without blocking.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcfdbclose' is used in order to close a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbclose(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update of a database is assured to be written when the database is closed.  If a writer opens a database but does not close it appropriately, the database will be broken.</dd>
+</dl>
+
+<p>The function `tcfdbput' is used in order to store a record into a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbput(TCFDB *<var>fdb</var>, int64_t <var>id</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>id</var>' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDPREV', the number less by one than the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.  If it is `FDBIDNEXT', the number greater by one than the maximum ID number of existing records is specified.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.  If the size of the value is greater than the width tuning parameter of the database, the size is cut down to the width.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcfdbput2' is used in order to store a record with a decimal key into a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbput2(TCFDB *<var>fdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "prev", the number less by one than the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.  If it is "next", the number greater by one than the maximum ID number of existing records is specified.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.  If the size of the value is greater than the width tuning parameter of the database, the size is cut down to the width.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcfdbput3' is used in order to store a string record with a decimal key into a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbput3(TCFDB *<var>fdb</var>, const char *<var>kstr</var>, const void *<var>vstr</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "prev", the number less by one than the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.  If it is "next", the number greater by one than the maximum ID number of existing records is specified.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcfdbputkeep' is used in order to store a new record into a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbputkeep(TCFDB *<var>fdb</var>, int64_t <var>id</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>id</var>' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDPREV', the number less by one than the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.  If it is `FDBIDNEXT', the number greater by one than the maximum ID number of existing records is specified.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.  If the size of the value is greater than the width tuning parameter of the database, the size is cut down to the width.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcfdbputkeep2' is used in order to store a new record with a decimal key into a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbputkeep2(TCFDB *<var>fdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "prev", the number less by one than the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.  If it is "next", the number greater by one than the maximum ID number of existing records is specified.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.  If the size of the value is greater than the width tuning parameter of the database, the size is cut down to the width.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcfdbputkeep3' is used in order to store a new string record with a decimal key into a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbputkeep3(TCFDB *<var>fdb</var>, const char *<var>kstr</var>, const void *<var>vstr</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "prev", the number less by one than the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.  If it is "next", the number greater by one than the maximum ID number of existing records is specified.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcfdbputcat' is used in order to concatenate a value at the end of the existing record in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbputcat(TCFDB *<var>fdb</var>, int64_t <var>id</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>id</var>' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDPREV', the number less by one than the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.  If it is `FDBIDNEXT', the number greater by one than the maximum ID number of existing records is specified.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.  If the size of the value is greater than the width tuning parameter of the database, the size is cut down to the width.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcfdbputcat2' is used in order to concatenate a value with a decimal key in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbputcat2(TCFDB *<var>fdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "prev", the number less by one than the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.  If it is "next", the number greater by one than the maximum ID number of existing records is specified.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.  If the size of the value is greater than the width tuning parameter of the database, the size is cut down to the width.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcfdbputcat3' is used in order to concatenate a string value with a decimal key in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbputcat3(TCFDB *<var>fdb</var>, const char *<var>kstr</var>, const void *<var>vstr</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "prev", the number less by one than the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.  If it is "next", the number greater by one than the maximum ID number of existing records is specified.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcfdbout' is used in order to remove a record of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbout(TCFDB *<var>fdb</var>, int64_t <var>id</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>id</var>' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcfdbout2' is used in order to remove a record with a decimal key of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbout2(TCFDB *<var>fdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcfdbout3' is used in order to remove a string record with a decimal key of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbout3(TCFDB *<var>fdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcfdbget' is used in order to retrieve a record in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>void *tcfdbget(TCFDB *<var>fdb</var>, int64_t <var>id</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>id</var>' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcfdbget2' is used in order to retrieve a record with a decimal key in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>void *tcfdbget2(TCFDB *<var>fdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcfdbget3' is used in order to retrieve a string record with a decimal key in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>char *tcfdbget3(TCFDB *<var>fdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.</dd>
+<dd>If successful, the return value is the string of the value of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcfdbget4' is used in order to retrieve a record in a fixed-length database object and write the value into a buffer.</p>
+
+<dl class="api">
+<dt><code>int tcfdbget4(TCFDB *<var>fdb</var>, int64_t <var>id</var>, void *<var>vbuf</var>, int <var>max</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>id</var>' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the buffer into which the value of the corresponding record is written.</dd>
+<dd>`<var>max</var>' specifies the size of the buffer.</dd>
+<dd>If successful, the return value is the size of the written data, else, it is -1.  -1 is returned if no record corresponds to the specified key.</dd>
+<dd>Note that an additional zero code is not appended at the end of the region of the writing buffer.</dd>
+</dl>
+
+<p>The function `tcfdbvsiz' is used in order to get the size of the value of a record in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>int tcfdbvsiz(TCFDB *<var>fdb</var>, int64_t <var>id</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>id</var>' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tcfdbvsiz2' is used in order to get the size of the value with a decimal key in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>int tcfdbvsiz2(TCFDB *<var>fdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tcfdbvsiz3' is used in order to get the size of the string value with a decimal key in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>int tcfdbvsiz3(TCFDB *<var>fdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tcfdbiterinit' is used in order to initialize the iterator of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbiterinit(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>The iterator is used in order to access the key of every record stored in a database.</dd>
+</dl>
+
+<p>The function `tcfdbiternext' is used in order to get the next ID number of the iterator of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcfdbiternext(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>If successful, the return value is the next ID number of the iterator, else, it is 0.  0 is returned when no record is to be get out of the iterator.</dd>
+<dd>It is possible to access every record by iteration of calling this function.  It is allowed to update or remove records whose keys are fetched while the iteration.  The order of this traversal access method is ascending of the ID number.</dd>
+</dl>
+
+<p>The function `tcfdbiternext2' is used in order to get the next decimay key of the iterator of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>void *tcfdbiternext2(TCFDB *<var>fdb</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the next decimal key, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  It is possible to access every record by iteration of calling this function.  It is allowed to update or remove records whose keys are fetched while the iteration.  The order of this traversal access method is ascending of the ID number.</dd>
+</dl>
+
+<p>The function `tcfdbiternext3' is used in order to get the next decimay key string of the iterator of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>char *tcfdbiternext3(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>If successful, the return value is the string of the next decimal key, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  It is possible to access every record by iteration of calling this function.  It is allowed to update or remove records whose keys are fetched while the iteration.  The order of this traversal access method is ascending of the ID number.</dd>
+</dl>
+
+<p>The function `tcfdbrange' is used in order to get range matching ID numbers in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t *tcfdbrange(TCFDB *<var>fdb</var>, int64_t <var>lower</var>, int64_t <var>upper</var>, int <var>max</var>, int *<var>np</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>lower</var>' specifies the lower limit of the range.  If it is `FDBIDMIN', the minimum ID is specified.</dd>
+<dd>`<var>upper</var>' specifies the upper limit of the range.  If it is `FDBIDMAX', the maximum ID is specified.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>`<var>np</var>' specifies the pointer to the variable into which the number of elements of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to an array of ID numbers of the corresponding records.  `NULL' is returned on failure.  This function does never fail.  It returns an empty array even if no key corresponds.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcfdbrange2' is used in order to get range matching decimal keys in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcfdbrange2(TCFDB *<var>fdb</var>, const void *<var>lbuf</var>, int <var>lsiz</var>, const void *<var>ubuf</var>, int <var>usiz</var>, int <var>max</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>lbuf</var>' specifies the pointer to the region of the lower key.  If it is "min", the minimum ID number of existing records is specified.</dd>
+<dd>`<var>lsiz</var>' specifies the size of the region of the lower key.</dd>
+<dd>`<var>ubuf</var>' specifies the pointer to the region of the upper key.  If it is "max", the maximum ID number of existing records is specified.</dd>
+<dd>`<var>usiz</var>' specifies the size of the region of the upper key.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding decimal keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.</dd>
+</dl>
+
+<p>The function `tcfdbrange3' is used in order to get range matching decimal keys with strings in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcfdbrange3(TCFDB *<var>fdb</var>, const char *<var>lstr</var>, const char *<var>ustr</var>, int <var>max</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>lstr</var>' specifies the string of the lower key.  If it is "min", the minimum ID number of existing records is specified.</dd>
+<dd>`<var>ustr</var>' specifies the string of the upper key.  If it is "max", the maximum ID number of existing records is specified.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding decimal keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.</dd>
+</dl>
+
+<p>The function `tcfdbrange4' is used in order to get keys with an interval notation in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcfdbrange4(TCFDB *<var>fdb</var>, const void *<var>ibuf</var>, int <var>isiz</var>, int <var>max</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>ibuf</var>' specifies the pointer to the region of the interval notation.</dd>
+<dd>`<var>isiz</var>' specifies the size of the region of the interval notation.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding decimal keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.</dd>
+</dl>
+
+<p>The function `tcfdbrange5' is used in order to get keys with an interval notation string in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcfdbrange5(TCFDB *<var>fdb</var>, const void *<var>istr</var>, int <var>max</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>istr</var>' specifies the pointer to the region of the interval notation string.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding decimal keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.</dd>
+</dl>
+
+<p>The function `tcfdbaddint' is used in order to add an integer to a record in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>int tcfdbaddint(TCFDB *<var>fdb</var>, int64_t <var>id</var>, int <var>num</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>id</var>' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDPREV', the number less by one than the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.  If it is `FDBIDNEXT', the number greater by one than the maximum ID number of existing records is specified.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>If successful, the return value is the summation value, else, it is `INT_MIN'.</dd>
+<dd>If the corresponding record exists, the value is treated as an integer and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tcfdbadddouble' is used in order to add a real number to a record in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>double tcfdbadddouble(TCFDB *<var>fdb</var>, int64_t <var>id</var>, double <var>num</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>id</var>' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDPREV', the number less by one than the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.  If it is `FDBIDNEXT', the number greater by one than the maximum ID number of existing records is specified.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>If successful, the return value is the summation value, else, it is Not-a-Number.</dd>
+<dd>If the corresponding record exists, the value is treated as a real number and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tcfdbsync' is used in order to synchronize updated contents of a fixed-length database object with the file and the device.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbsync(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>This function is useful when another process connects to the same database file.</dd>
+</dl>
+
+<p>The function `tcfdboptimize' is used in order to optimize the file of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdboptimize(TCFDB *<var>fdb</var>, int32_t <var>width</var>, int64_t <var>limsiz</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>width</var>' specifies the width of the value of each record.  If it is not more than 0, the current setting is not changed.</dd>
+<dd>`<var>limsiz</var>' specifies the limit size of the database file.  If it is not more than 0, the current setting is not changed.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcfdbvanish' is used in order to remove all records of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbvanish(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcfdbcopy' is used in order to copy the database file of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbcopy(TCFDB *<var>fdb</var>, const char *<var>path</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>path</var>' specifies the path of the destination file.  If it begins with `@', the trailing substring is executed as a command line.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned if the executed command returns non-zero code.</dd>
+<dd>The database file is assured to be kept synchronized and not modified while the copying or executing operation is in progress.  So, this function is useful to create a backup file of the database file.</dd>
+</dl>
+
+<p>The function `tcfdbtranbegin' is used in order to begin the transaction of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbtranbegin(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>The database is locked by the thread while the transaction so that only one transaction can be activated with a database object at the same time.  Thus, the serializable isolation level is assumed if every database operation is performed in the transaction.  All updated regions are kept track of by write ahead logging while the transaction.  If the database is closed during transaction, the transaction is aborted implicitly.</dd>
+</dl>
+
+<p>The function `tcfdbtrancommit' is used in order to commit the transaction of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbtrancommit(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update in the transaction is fixed when it is committed successfully.</dd>
+</dl>
+
+<p>The function `tcfdbtranabort' is used in order to abort the transaction of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbtranabort(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update in the transaction is discarded when it is aborted.  The state of the database is rollbacked to before transaction.</dd>
+</dl>
+
+<p>The function `tcfdbpath' is used in order to get the file path of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>const char *tcfdbpath(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>The return value is the path of the database file or `NULL' if the object does not connect to any database file.</dd>
+</dl>
+
+<p>The function `tcfdbrnum' is used in order to get the number of records of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcfdbrnum(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>The return value is the number of records or 0 if the object does not connect to any database file.</dd>
+</dl>
+
+<p>The function `tcfdbfsiz' is used in order to get the size of the database file of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcfdbfsiz(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>The return value is the size of the database file or 0 if the object does not connect to any database file.</dd>
+</dl>
+
+<h3 id="tcfdbapi_example">Example Code</h3>
+
+<p>The following code is an example to use a hash database.</p>
+
+<pre>#include &lt;tcutil.h&gt;
+#include &lt;tcfdb.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdbool.h&gt;
+#include &lt;stdint.h&gt;
+
+int main(int argc, char **argv){
+  TCFDB *fdb;
+  int ecode;
+  char *key, *value;
+
+  /* create the object */
+  fdb = tcfdbnew();
+
+  /* open the database */
+  if(!tcfdbopen(fdb, "casket.tcf", FDBOWRITER | FDBOCREAT)){
+    ecode = tcfdbecode(fdb);
+    fprintf(stderr, "open error: %s\n", tcfdberrmsg(ecode));
+  }
+
+  /* store records */
+  if(!tcfdbput3(fdb, "1", "one") ||
+     !tcfdbput3(fdb, "12", "twelve") ||
+     !tcfdbput3(fdb, "144", "one forty four")){
+    ecode = tcfdbecode(fdb);
+    fprintf(stderr, "put error: %s\n", tcfdberrmsg(ecode));
+  }
+
+  /* retrieve records */
+  value = tcfdbget3(fdb, "1");
+  if(value){
+    printf("%s\n", value);
+    free(value);
+  } else {
+    ecode = tcfdbecode(fdb);
+    fprintf(stderr, "get error: %s\n", tcfdberrmsg(ecode));
+  }
+
+  /* traverse records */
+  tcfdbiterinit(fdb);
+  while((key = tcfdbiternext3(fdb)) != NULL){
+    value = tcfdbget3(fdb, key);
+    if(value){
+      printf("%s:%s\n", key, value);
+      free(value);
+    }
+    free(key);
+  }
+
+  /* close the database */
+  if(!tcfdbclose(fdb)){
+    ecode = tcfdbecode(fdb);
+    fprintf(stderr, "close error: %s\n", tcfdberrmsg(ecode));
+  }
+
+  /* delete the object */
+  tcfdbdel(fdb);
+
+  return 0;
+}
+</pre>
+
+<h3 id="tcfdbapi_cli">CLI</h3>
+
+<p>To use the fixed-length database API easily, the commands `<code>tcftest</code>', `<code>tcfmttest</code>', and `<code>tcfmgr</code>' are provided.</p>
+
+<p>The command `<code>tcftest</code>' is a utility for facility test and performance test.  This command is used in the following format.  `<var>path</var>' specifies the path of a database file.  `<var>rnum</var>' specifies the number of iterations.  `<var>width</var>' specifies the width of the value of each record.  `<var>limsiz</var>' specifies the limit size of the database file.</p>
+
+<dl class="api">
+<dt><code>tcftest write [-mt] [-nl|-nb] [-rnd] <var>path</var> <var>rnum</var> [<var>width</var> [<var>limsiz</var>]]</code></dt>
+<dd>Store records with keys of 8 bytes.  They change as `00000001', `00000002'...</dd>
+<dt><code>tcftest read [-mt] [-nl|-nb] [-wb] [-rnd] <var>path</var></code></dt>
+<dd>Retrieve all records of the database above.</dd>
+<dt><code>tcftest remove [-mt] [-nl|-nb] [-rnd] <var>path</var></code></dt>
+<dd>Remove all records of the database above.</dd>
+<dt><code>tcftest rcat [-mt] [-nl|-nb] [-pn <var>num</var>] [-dai|-dad|-rl] <var>path</var> <var>rnum</var> [<var>limsiz</var>]]</code></dt>
+<dd>Store records with partway duplicated keys using concatenate mode.</dd>
+<dt><code>tcftest misc [-mt] [-nl|-nb] <var>path</var> <var>rnum</var></code></dt>
+<dd>Perform miscellaneous test of various operations.</dd>
+<dt><code>tcftest wicked [-mt] [-nl|-nb] <var>path</var> <var>rnum</var></code></dt>
+<dd>Perform updating operations selected at random.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="options">
+<li><code>-mt</code> : call the function `tcfdbsetmutex'.</li>
+<li><code>-nl</code> : enable the option `FDBNOLCK'.</li>
+<li><code>-nb</code> : enable the option `FDBLCKNB'.</li>
+<li><code>-rnd</code> : select keys at random.</li>
+<li><code>-wb</code> : use the function `tcfdbget4' instead of `tcfdbget2'.</li>
+<li><code>-pn <var>num</var></code> : specify the number of patterns.</li>
+<li><code>-dai</code> : use the function `tcfdbaddint' instead of `tcfdbputcat'.</li>
+<li><code>-dad</code> : use the function `tcfdbadddouble' instead of `tcfdbputcat'.</li>
+<li><code>-rl</code> : set the length of values at random.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<p>The command `<code>tcfmttest</code>' is a utility for facility test under multi-thread situation.  This command is used in the following format.  `<var>path</var>' specifies the path of a database file.  `<var>tnum</var>' specifies the number of running threads.  `<var>rnum</var>' specifies the number of iterations.  `<var>width</var>' specifies the width of the value of each record.  `<var>limsiz</var>' specifies the limit size of the database file.</p>
+
+<dl class="api">
+<dt><code>tcfmttest write [-nl|-nb] [-rnd] <var>path</var> <var>tnum</var> <var>rnum</var> [<var>width</var> [<var>limsiz</var>]]</code></dt>
+<dd>Store records with keys of 8 bytes.  They change as `00000001', `00000002'...</dd>
+<dt><code>tcfmttest read [-nl|-nb] [-wb] [-rnd] <var>path</var> <var>tnum</var></code></dt>
+<dd>Retrieve all records of the database above.</dd>
+<dt><code>tcfmttest remove [-nl|-nb] [-rnd] <var>path</var> <var>tnum</var></code></dt>
+<dd>Remove all records of the database above.</dd>
+<dt><code>tcfmttest wicked [-nl|-nb] [-nc] <var>path</var> <var>tnum</var> <var>rnum</var></code></dt>
+<dd>Perform updating operations selected at random.</dd>
+<dt><code>tcfmttest typical [-nl|-nb] [-nc] [-rr <var>num</var>] <var>path</var> <var>tnum</var> <var>rnum</var> [<var>width</var> [<var>limsiz</var>]]</code></dt>
+<dd>Perform typical operations selected at random.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="options">
+<li><code>-nl</code> : enable the option `FDBNOLCK'.</li>
+<li><code>-nb</code> : enable the option `FDBLCKNB'.</li>
+<li><code>-rnd</code> : select keys at random.</li>
+<li><code>-wb</code> : use the function `tcfdbget4' instead of `tcfdbget2'.</li>
+<li><code>-nc</code> : omit the comparison test.</li>
+<li><code>-rr <var>num</var></code> : specify the ratio of reading operation by percentage.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<p>The command `<code>tcfmgr</code>' is a utility for test and debugging of the fixed-length database API and its applications.  `<var>path</var>' specifies the path of a database file.  `<var>width</var>' specifies the width of the value of each record.  `<var>limsiz</var>' specifies the limit size of the database file.  `<var>key</var>' specifies the key of a record.  `<var>value</var>' specifies the value of a record.  `<var>file</var>' specifies the input file.</p>
+
+<dl class="api">
+<dt><code>tcfmgr create <var>path</var> [<var>width</var> [<var>limsiz</var>]]</code></dt>
+<dd>Create a database file.</dd>
+<dt><code>tcfmgr inform [-nl|-nb] <var>path</var></code></dt>
+<dd>Print miscellaneous information to the standard output.</dd>
+<dt><code>tcfmgr put [-nl|-nb] [-sx] [-dk|-dc|-dai|-dad] <var>path</var> <var>key</var> <var>value</var></code></dt>
+<dd>Store a record.</dd>
+<dt><code>tcfmgr out [-nl|-nb] [-sx] <var>path</var> <var>key</var></code></dt>
+<dd>Remove a record.</dd>
+<dt><code>tcfmgr get [-nl|-nb] [-sx] [-px] [-pz] <var>path</var> <var>key</var></code></dt>
+<dd>Print the value of a record.</dd>
+<dt><code>tcfmgr list [-nl|-nb] [-m <var>num</var>] [-pv] [-px] [-rb <var>lkey</var> <var>ukey</var>] [-ri <var>str</var>] <var>path</var></code></dt>
+<dd>Print keys of all records, separated by line feeds.</dd>
+<dt><code>tcfmgr optimize [-nl|-nb] <var>path</var> [<var>width</var> [<var>limsiz</var>]]</code></dt>
+<dd>Optimize a database file.</dd>
+<dt><code>tcfmgr importtsv [-nl|-nb] [-sc] <var>path</var> [<var>file</var>]</code></dt>
+<dd>Store records of TSV in each line of a file.</dd>
+<dt><code>tcfmgr version</code></dt>
+<dd>Print the version information of Tokyo Cabinet.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="options">
+<li><code>-nl</code> : enable the option `FDBNOLCK'.</li>
+<li><code>-nb</code> : enable the option `FDBLCKNB'.</li>
+<li><code>-sx</code> : the input data is evaluated as a hexadecimal data string.</li>
+<li><code>-dk</code> : use the function `tcfdbputkeep' instead of `tcfdbput'.</li>
+<li><code>-dc</code> : use the function `tcfdbputcat' instead of `tcfdbput'.</li>
+<li><code>-dai</code> : use the function `tcfdbaddint' instead of `tcfdbput'.</li>
+<li><code>-dad</code> : use the function `tcfdbadddouble' instead of `tcfdbput'.</li>
+<li><code>-px</code> : the output data is converted into a hexadecimal data string.</li>
+<li><code>-pz</code> : do not append line feed at the end of the output.</li>
+<li><code>-m <var>num</var></code> : specify the maximum number of the output.</li>
+<li><code>-pv</code> : print values of records also.</li>
+<li><code>-rb <var>lkey</var> <var>ukey</var></code> : specify the range of keys.</li>
+<li><code>-ri <var>str</var></code> : specify the interval notation of keys.</li>
+<li><code>-sc</code> : normalize keys as lower cases.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<hr />
+
+<h2 id="tctdbapi">The Table Database API</h2>
+
+<p>Table database is a file containing records composed of the primary keys and arbitrary columns and is handled with the table database API.  See `<code>tctdb.h</code>' for the entire specification.</p>
+
+<h3 id="tctdbapi_description">Description</h3>
+
+<p>To use the table database API, include `<code>tcutil.h</code>', `<code>tctdb.h</code>', and related standard header files.  Usually, write the following description near the front of a source file.</p>
+
+<dl>
+<dt><code>#include &lt;tcutil.h&gt;</code></dt>
+<dt><code>#include &lt;tctdb.h&gt;</code></dt>
+<dt><code>#include &lt;stdlib.h&gt;</code></dt>
+<dt><code>#include &lt;stdbool.h&gt;</code></dt>
+<dt><code>#include &lt;stdint.h&gt;</code></dt>
+</dl>
+
+<p>Objects whose type is pointer to `<code>TCTDB</code>' are used to handle table databases.  A table database object is created with the function `<code>tctdbnew</code>' and is deleted with the function `<code>tctdbdel</code>'.  To avoid memory leak, it is important to delete every object when it is no longer in use.</p>
+
+<p>Before operations to store or retrieve records, it is necessary to open a database file and connect the table database object to it.  The function `<code>tctdbopen</code>' is used to open a database file and the function `<code>tctdbclose</code>' is used to close the database file.  To avoid data missing or corruption, it is important to close every database file when it is no longer in use.  It is forbidden for multible database objects in a process to open the same database at the same time.</p>
+
+<h3 id="tctdbapi_api">API</h3>
+
+<p>The function `tctdberrmsg' is used in order to get the message string corresponding to an error code.</p>
+
+<dl class="api">
+<dt><code>const char *tctdberrmsg(int <var>ecode</var>);</code></dt>
+<dd>`<var>ecode</var>' specifies the error code.</dd>
+<dd>The return value is the message string of the error code.</dd>
+</dl>
+
+<p>The function `tctdbnew' is used in order to create a table database object.</p>
+
+<dl class="api">
+<dt><code>TCTDB *tctdbnew(void);</code></dt>
+<dd>The return value is the new table database object.</dd>
+</dl>
+
+<p>The function `tctdbdel' is used in order to delete a table database object.</p>
+
+<dl class="api">
+<dt><code>void tctdbdel(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>If the database is not closed, it is closed implicitly.  Note that the deleted object and its derivatives can not be used anymore.</dd>
+</dl>
+
+<p>The function `tctdbecode' is used in order to get the last happened error code of a table database object.</p>
+
+<dl class="api">
+<dt><code>int tctdbecode(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>The return value is the last happened error code.</dd>
+<dd>The following error codes are defined: `TCESUCCESS' for success, `TCETHREAD' for threading error, `TCEINVALID' for invalid operation, `TCENOFILE' for file not found, `TCENOPERM' for no permission, `TCEMETA' for invalid meta data, `TCERHEAD' for invalid record header, `TCEOPEN' for open error, `TCECLOSE' for close error, `TCETRUNC' for trunc error, `TCESYNC' for sync error, `TCESTAT' for stat error, `TCESEEK' for seek error, `TCEREAD' for read error, `TCEWRITE' for write error, `TCEMMAP' for mmap error, `TCELOCK' for lock error, `TCEUNLINK' for unlink error, `TCERENAME' for rename error, `TCEMKDIR' for mkdir error, `TCERMDIR' for rmdir error, `TCEKEEP' for existing record, `TCENOREC' for no record found, and `TCEMISC' for miscellaneous error.</dd>
+</dl>
+
+<p>The function `tctdbsetmutex' is used in order to set mutual exclusion control of a table database object for threading.</p>
+
+<dl class="api">
+<dt><code>bool tctdbsetmutex(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object which is not opened.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the mutual exclusion control is needed if the object is shared by plural threads and this function should be called before the database is opened.</dd>
+</dl>
+
+<p>The function `tctdbtune' is used in order to set the tuning parameters of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbtune(TCTDB *<var>tdb</var>, int64_t <var>bnum</var>, int8_t <var>apow</var>, int8_t <var>fpow</var>, uint8_t <var>opts</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object which is not opened.</dd>
+<dd>`<var>bnum</var>' specifies the number of elements of the bucket array.  If it is not more than 0, the default value is specified.  The default value is 131071.  Suggested size of the bucket array is about from 0.5 to 4 times of the number of all records to be stored.</dd>
+<dd>`<var>apow</var>' specifies the size of record alignment by power of 2.  If it is negative, the default value is specified.  The default value is 4 standing for 2^4=16.</dd>
+<dd>`<var>fpow</var>' specifies the maximum number of elements of the free block pool by power of 2.  If it is negative, the default value is specified.  The default value is 10 standing for 2^10=1024.</dd>
+<dd>`<var>opts</var>' specifies options by bitwise-or: `TDBTLARGE' specifies that the size of the database can be larger than 2GB by using 64-bit bucket array, `TDBTDEFLATE' specifies that each record is compressed with Deflate encoding, `TDBTBZIP' specifies that each record is compressed with BZIP2 encoding, `TDBTTCBS' specifies that each record is compressed with TCBS encoding.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the tuning parameters should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tctdbsetcache' is set the caching parameters of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbsetcache(TCTDB *<var>tdb</var>, int32_t <var>rcnum</var>, int32_t <var>lcnum</var>, int32_t <var>ncnum</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object which is not opened.</dd>
+<dd>`<var>rcnum</var>' specifies the maximum number of records to be cached.  If it is not more than 0, the record cache is disabled.  It is disabled by default.</dd>
+<dd>`<var>lcnum</var>' specifies the maximum number of leaf nodes to be cached.  If it is not more than 0, the default value is specified.  The default value is 4096.</dd>
+<dd>`<var>ncnum</var>' specifies the maximum number of non-leaf nodes to be cached.  If it is not more than 0, the default value is specified.  The default value is 512.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the caching parameters should be set before the database is opened.  Leaf nodes and non-leaf nodes are used in column indices.</dd>
+</dl>
+
+<p>The function `tctdbsetxmsiz' is used in order to set the size of the extra mapped memory of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbsetxmsiz(TCTDB *<var>tdb</var>, int64_t <var>xmsiz</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object which is not opened.</dd>
+<dd>`<var>xmsiz</var>' specifies the size of the extra mapped memory.  If it is not more than 0, the extra mapped memory is disabled.  The default size is 67108864.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the mapping parameters should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tctdbsetdfunit' is used in order to set the unit step number of auto defragmentation of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbsetdfunit(TCTDB *<var>tdb</var>, int32_t <var>dfunit</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object which is not opened.</dd>
+<dd>`<var>dfunit</var>' specifie the unit step number.  If it is not more than 0, the auto defragmentation is disabled.  It is disabled by default.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the defragmentation parameters should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tctdbopen' is used in order to open a database file and connect a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbopen(TCTDB *<var>tdb</var>, const char *<var>path</var>, int <var>omode</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object which is not opened.</dd>
+<dd>`<var>path</var>' specifies the path of the database file.</dd>
+<dd>`<var>omode</var>' specifies the connection mode: `TDBOWRITER' as a writer, `TDBOREADER' as a reader.  If the mode is `TDBOWRITER', the following may be added by bitwise-or: `TDBOCREAT', which means it creates a new database if not exist, `TDBOTRUNC', which means it creates a new database regardless if one exists, `TDBOTSYNC', which means every transaction synchronizes updated contents with the device.  Both of `TDBOREADER' and `TDBOWRITER' can be added to by bitwise-or: `TDBONOLCK', which means it opens the database file without file locking, or `TDBOLCKNB', which means locking is performed without blocking.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tctdbclose' is used in order to close a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbclose(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update of a database is assured to be written when the database is closed.  If a writer opens a database but does not close it appropriately, the database will be broken.</dd>
+</dl>
+
+<p>The function `tctdbput' is used in order to store a record into a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbput(TCTDB *<var>tdb</var>, const void *<var>pkbuf</var>, int <var>pksiz</var>, TCMAP *<var>cols</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>pkbuf</var>' specifies the pointer to the region of the primary key.</dd>
+<dd>`<var>pksiz</var>' specifies the size of the region of the primary key.</dd>
+<dd>`<var>cols</var>' specifies a map object containing columns.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tctdbput2' is used in order to store a string record into a table database object with a zero separated column string.</p>
+
+<dl class="api">
+<dt><code>bool tctdbput2(TCTDB *<var>tdb</var>, const void *<var>pkbuf</var>, int <var>pksiz</var>, const void *<var>cbuf</var>, int <var>csiz</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>pkbuf</var>' specifies the pointer to the region of the primary key.</dd>
+<dd>`<var>pksiz</var>' specifies the size of the region of the primary key.</dd>
+<dd>`<var>cbuf</var>' specifies the pointer to the region of the zero separated column string where the name and the value of each column are situated one after the other.</dd>
+<dd>`<var>csiz</var>' specifies the size of the region of the column string.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tctdbput3' is used in order to store a string record into a table database object with a tab separated column string.</p>
+
+<dl class="api">
+<dt><code>bool tctdbput3(TCTDB *<var>tdb</var>, const char *<var>pkstr</var>, const char *<var>cstr</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>pkstr</var>' specifies the string of the primary key.</dd>
+<dd>`<var>cstr</var>' specifies the string of the the tab separated column string where the name and the value of each column are situated one after the other.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tctdbputkeep' is used in order to store a new record into a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbputkeep(TCTDB *<var>tdb</var>, const void *<var>pkbuf</var>, int <var>pksiz</var>, TCMAP *<var>cols</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>pkbuf</var>' specifies the pointer to the region of the primary key.</dd>
+<dd>`<var>pksiz</var>' specifies the size of the region of the primary key.</dd>
+<dd>`<var>cols</var>' specifies a map object containing columns.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tctdbputkeep2' is used in order to store a new string record into a table database object with a zero separated column string.</p>
+
+<dl class="api">
+<dt><code>bool tctdbputkeep2(TCTDB *<var>tdb</var>, const void *<var>pkbuf</var>, int <var>pksiz</var>, const void *<var>cbuf</var>, int <var>csiz</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>pkbuf</var>' specifies the pointer to the region of the primary key.</dd>
+<dd>`<var>pksiz</var>' specifies the size of the region of the primary key.</dd>
+<dd>`<var>cbuf</var>' specifies the pointer to the region of the zero separated column string where the name and the value of each column are situated one after the other.</dd>
+<dd>`<var>csiz</var>' specifies the size of the region of the column string.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tctdbputkeep3' is used in order to store a new string record into a table database object with a tab separated column string.</p>
+
+<dl class="api">
+<dt><code>bool tctdbputkeep3(TCTDB *<var>tdb</var>, const char *<var>pkstr</var>, const char *<var>cstr</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>pkstr</var>' specifies the string of the primary key.</dd>
+<dd>`<var>cstr</var>' specifies the string of the the tab separated column string where the name and the value of each column are situated one after the other.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tctdbputcat' is used in order to concatenate columns of the existing record in a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbputcat(TCTDB *<var>tdb</var>, const void *<var>pkbuf</var>, int <var>pksiz</var>, TCMAP *<var>cols</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>pkbuf</var>' specifies the pointer to the region of the primary key.</dd>
+<dd>`<var>pksiz</var>' specifies the size of the region of the primary key.</dd>
+<dd>`<var>cols</var>' specifies a map object containing columns.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tctdbputcat2' is used in order to concatenate columns in a table database object with a zero separated column string.</p>
+
+<dl class="api">
+<dt><code>bool tctdbputcat2(TCTDB *<var>tdb</var>, const void *<var>pkbuf</var>, int <var>pksiz</var>, const void *<var>cbuf</var>, int <var>csiz</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>pkbuf</var>' specifies the pointer to the region of the primary key.</dd>
+<dd>`<var>pksiz</var>' specifies the size of the region of the primary key.</dd>
+<dd>`<var>cbuf</var>' specifies the pointer to the region of the zero separated column string where the name and the value of each column are situated one after the other.</dd>
+<dd>`<var>csiz</var>' specifies the size of the region of the column string.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tctdbputcat3' is used in order to concatenate columns in a table database object with with a tab separated column string.</p>
+
+<dl class="api">
+<dt><code>bool tctdbputcat3(TCTDB *<var>tdb</var>, const char *<var>pkstr</var>, const char *<var>cstr</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>pkstr</var>' specifies the string of the primary key.</dd>
+<dd>`<var>cstr</var>' specifies the string of the the tab separated column string where the name and the value of each column are situated one after the other.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tctdbout' is used in order to remove a record of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbout(TCTDB *<var>tdb</var>, const void *<var>pkbuf</var>, int <var>pksiz</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>pkbuf</var>' specifies the pointer to the region of the primary key.</dd>
+<dd>`<var>pksiz</var>' specifies the size of the region of the primary key.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tctdbout2' is used in order to remove a string record of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbout2(TCTDB *<var>tdb</var>, const char *<var>pkstr</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>pkstr</var>' specifies the string of the primary key.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tctdbget' is used in order to retrieve a record in a table database object.</p>
+
+<dl class="api">
+<dt><code>TCMAP *tctdbget(TCTDB *<var>tdb</var>, const void *<var>pkbuf</var>, int <var>pksiz</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>`<var>pkbuf</var>' specifies the pointer to the region of the primary key.</dd>
+<dd>`<var>pksiz</var>' specifies the size of the region of the primary key.</dd>
+<dd>If successful, the return value is a map object of the columns of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tcmapnew', it should be deleted with the function `tcmapdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tctdbget2' is used in order to retrieve a record in a table database object as a zero separated column string.</p>
+
+<dl class="api">
+<dt><code>char *tctdbget2(TCTDB *<var>tdb</var>, const void *<var>pkbuf</var>, int <var>pksiz</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>`<var>pkbuf</var>' specifies the pointer to the region of the primary key.</dd>
+<dd>`<var>pksiz</var>' specifies the size of the region of the primary key.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the column string of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tctdbget3' is used in order to retrieve a string record in a table database object as a tab separated column string.</p>
+
+<dl class="api">
+<dt><code>char *tctdbget3(TCTDB *<var>tdb</var>, const char *<var>pkstr</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>`<var>pkstr</var>' specifies the string of the primary key.</dd>
+<dd>If successful, the return value is the tab separated column string of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tctdbvsiz' is used in order to get the size of the value of a record in a table database object.</p>
+
+<dl class="api">
+<dt><code>int tctdbvsiz(TCTDB *<var>tdb</var>, const void *<var>pkbuf</var>, int <var>pksiz</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the primary key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the primary key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tctdbvsiz2' is used in order to get the size of the value of a string record in a table database object.</p>
+
+<dl class="api">
+<dt><code>int tctdbvsiz2(TCTDB *<var>tdb</var>, const char *<var>pkstr</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the primary key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tctdbiterinit' is used in order to initialize the iterator of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbiterinit(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>The iterator is used in order to access the primary key of every record stored in a database.</dd>
+</dl>
+
+<p>The function `tctdbiternext' is used in order to get the next primary key of the iterator of a table database object.</p>
+
+<dl class="api">
+<dt><code>void *tctdbiternext(TCTDB *<var>tdb</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the next primary key, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  It is possible to access every record by iteration of calling this function.  It is allowed to update or remove records whose keys are fetched while the iteration.  However, it is not assured if updating the database is occurred while the iteration.  Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of storing matches the one of the traversal access.</dd>
+</dl>
+
+<p>The function `tctdbiternext2' is used in order to get the next primary key string of the iterator of a table database object.</p>
+
+<dl class="api">
+<dt><code>char *tctdbiternext2(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>If successful, the return value is the string of the next primary key, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  It is possible to access every record by iteration of calling this function.  However, it is not assured if updating the database is occurred while the iteration.  Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of storing matches the one of the traversal access.</dd>
+</dl>
+
+<p>The function `tctdbiternext3' is used in order to get the columns of the next record of the iterator of a table database object.</p>
+
+<dl class="api">
+<dt><code>TCMAP *tctdbiternext3(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>If successful, the return value is a map object of the columns of the next record, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.  The primary key is added into the map as a column of an empty string key.</dd>
+<dd>Because the object of the return value is created with the function `tcmapnew', it should be deleted with the function `tcmapdel' when it is no longer in use.  It is possible to access every record by iteration of calling this function.  However, it is not assured if updating the database is occurred while the iteration.  Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of storing matches the one of the traversal access.</dd>
+</dl>
+
+<p>The function `tctdbfwmkeys' is used in order to get forward matching primary keys in a table database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tctdbfwmkeys(TCTDB *<var>tdb</var>, const void *<var>pbuf</var>, int <var>psiz</var>, int <var>max</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>`<var>pbuf</var>' specifies the pointer to the region of the prefix.</dd>
+<dd>`<var>psiz</var>' specifies the size of the region of the prefix.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.</dd>
+</dl>
+
+<p>The function `tctdbfwmkeys2' is used in order to get forward matching string primary keys in a table database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tctdbfwmkeys2(TCTDB *<var>tdb</var>, const char *<var>pstr</var>, int <var>max</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>`<var>pstr</var>' specifies the string of the prefix.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.</dd>
+</dl>
+
+<p>The function `tctdbaddint' is used in order to add an integer to a column of a record in a table database object.</p>
+
+<dl class="api">
+<dt><code>int tctdbaddint(TCTDB *<var>tdb</var>, const void *<var>pkbuf</var>, int <var>pksiz</var>, int <var>num</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the primary key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the primary key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>If successful, the return value is the summation value, else, it is `INT_MIN'.</dd>
+<dd>The additional value is stored as a decimal string value of a column whose name is "_num".  If no record corresponds, a new record with the additional value is stored.</dd>
+</dl>
+
+<p>The function `tctdbadddouble' is used in order to add a real number to a column of a record in a table database object.</p>
+
+<dl class="api">
+<dt><code>double tctdbadddouble(TCTDB *<var>tdb</var>, const void *<var>pkbuf</var>, int <var>pksiz</var>, double <var>num</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the primary key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the primary key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>If successful, the return value is the summation value, else, it is Not-a-Number.</dd>
+<dd>The additional value is stored as a decimal string value of a column whose name is "_num".  If no record corresponds, a new record with the additional value is stored.</dd>
+</dl>
+
+<p>The function `tctdbsync' is used in order to synchronize updated contents of a table database object with the file and the device.</p>
+
+<dl class="api">
+<dt><code>bool tctdbsync(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>This function is useful when another process connects to the same database file.</dd>
+</dl>
+
+<p>The function `tctdboptimize' is used in order to optimize the file of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdboptimize(TCTDB *<var>tdb</var>, int64_t <var>bnum</var>, int8_t <var>apow</var>, int8_t <var>fpow</var>, uint8_t <var>opts</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>bnum</var>' specifies the number of elements of the bucket array.  If it is not more than 0, the default value is specified.  The default value is two times of the number of records.</dd>
+<dd>`<var>apow</var>' specifies the size of record alignment by power of 2.  If it is negative, the current setting is not changed.</dd>
+<dd>`<var>fpow</var>' specifies the maximum number of elements of the free block pool by power of 2.  If it is negative, the current setting is not changed.</dd>
+<dd>`<var>opts</var>' specifies options by bitwise-or: `TDBTLARGE' specifies that the size of the database can be larger than 2GB by using 64-bit bucket array, `TDBTDEFLATE' specifies that each record is compressed with Deflate encoding, `TDBTBZIP' specifies that each record is compressed with BZIP2 encoding, `TDBTTCBS' specifies that each record is compressed with TCBS encoding.  If it is `UINT8_MAX', the current setting is not changed.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>This function is useful to reduce the size of the database file with data fragmentation by successive updating.</dd>
+</dl>
+
+<p>The function `tctdbvanish' is used in order to remove all records of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbvanish(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tctdbcopy' is used in order to copy the database file of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbcopy(TCTDB *<var>tdb</var>, const char *<var>path</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>`<var>path</var>' specifies the path of the destination file.  If it begins with `@', the trailing substring is executed as a command line.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned if the executed command returns non-zero code.</dd>
+<dd>The database file is assured to be kept synchronized and not modified while the copying or executing operation is in progress.  So, this function is useful to create a backup file of the database file.</dd>
+</dl>
+
+<p>The function `tctdbtranbegin' is used in order to begin the transaction of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbtranbegin(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>The database is locked by the thread while the transaction so that only one transaction can be activated with a database object at the same time.  Thus, the serializable isolation level is assumed if every database operation is performed in the transaction.  Because all pages are cached on memory while the transaction, the amount of referred records is limited by the memory capacity.  If the database is closed during transaction, the transaction is aborted implicitly.</dd>
+</dl>
+
+<p>The function `tctdbtrancommit' is used in order to commit the transaction of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbtrancommit(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update in the transaction is fixed when it is committed successfully.</dd>
+</dl>
+
+<p>The function `tctdbtranabort' is used in order to abort the transaction of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbtranabort(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update in the transaction is discarded when it is aborted.  The state of the database is rollbacked to before transaction.</dd>
+</dl>
+
+<p>The function `tctdbpath' is used in order to get the file path of a table database object.</p>
+
+<dl class="api">
+<dt><code>const char *tctdbpath(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>The return value is the path of the database file or `NULL' if the object does not connect to any database file.</dd>
+</dl>
+
+<p>The function `tctdbrnum' is used in order to get the number of records ccccof a table database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tctdbrnum(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>The return value is the number of records or 0 if the object does not connect to any database file.</dd>
+</dl>
+
+<p>The function `tctdbfsiz' is used in order to get the size of the database file of a table database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tctdbfsiz(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>The return value is the size of the database file or 0 if the object does not connect to any database file.</dd>
+</dl>
+
+<p>The function `tctdbsetindex' is used in order to set a column index to a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbsetindex(TCTDB *<var>tdb</var>, const char *<var>name</var>, int <var>type</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>name</var>' specifies the name of a column.  If the name of an existing index is specified, the index is rebuilt.  An empty string means the primary key.</dd>
+<dd>`<var>type</var>' specifies the index type: `TDBITLEXICAL' for lexical string, `TDBITDECIMAL' for decimal string, `TDBITTOKEN' for token inverted index, `TDBITQGRAM' for q-gram inverted index.  If it is `TDBITOPT', the index is optimized.  If it is `TDBITVOID', the index is removed.  If `TDBITKEEP' is added by bitwise-or and the index exists, this function merely returns failure.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the setting indices should be set after the database is opened.</dd>
+</dl>
+
+<p>The function `tctdbgenuid' is used in order to generate a unique ID number of a table database object.</p>
+
+<dl class="api">
+<dt><code>int64_t tctdbgenuid(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>The return value is the new unique ID number or -1 on failure.</dd>
+</dl>
+
+<p>The function `tctdbqrynew' is used in order to create a query object.</p>
+
+<dl class="api">
+<dt><code>TDBQRY *tctdbqrynew(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>The return value is the new query object.</dd>
+</dl>
+
+<p>The function `tctdbqrydel' is used in order to delete a query object.</p>
+
+<dl class="api">
+<dt><code>void tctdbqrydel(TDBQRY *<var>qry</var>);</code></dt>
+<dd>`<var>qry</var>' specifies the query object.</dd>
+</dl>
+
+<p>The function `tctdbqryaddcond' is used in order to add a narrowing condition to a query object.</p>
+
+<dl class="api">
+<dt><code>void tctdbqryaddcond(TDBQRY *<var>qry</var>, const char *<var>name</var>, int <var>op</var>, const char *<var>expr</var>);</code></dt>
+<dd>`<var>qry</var>' specifies the query object.</dd>
+<dd>`<var>name</var>' specifies the name of a column.  An empty string means the primary key.</dd>
+<dd>`<var>op</var>' specifies an operation type: `TDBQCSTREQ' for string which is equal to the expression, `TDBQCSTRINC' for string which is included in the expression, `TDBQCSTRBW' for string which begins with the expression, `TDBQCSTREW' for string which ends with the expression, `TDBQCSTRAND' for string which includes all tokens in the expression, `TDBQCSTROR' for string which includes at least one token in the expression, `TDBQCSTROREQ' for string which is equal to at least one token in the expression, `TDBQCSTRRX' for string which matches regular expressions of the expression, `TDBQCNUMEQ' for number which is equal to the expression, `TDBQCNUMGT' for number which is greater than the expression, `TDBQCNUMGE' for number which is greater than or equal to the expression, `TDBQCNUMLT' for number which is less than the expression, `TDBQCNUMLE' for number which is less than or equal to the expression, `TDBQCNUMBT' for number which is between two tokens of the expression, `TDBQCNUMOREQ' for number which is equal to at least one token in the expression, `TDBQCFTSPH' for full-text search with the phrase of the expression, `TDBQCFTSAND' for full-text search with all tokens in the expression, `TDBQCFTSOR' for full-text search with at least one token in the expression, `TDBQCFTSEX' for full-text search with the compound expression.  All operations can be flagged by bitwise-or: `TDBQCNEGATE' for negation, `TDBQCNOIDX' for using no index.</dd>
+<dd>`<var>expr</var>' specifies an operand exression.</dd>
+</dl>
+
+<p>The function `tctdbqrysetorder' is used in order to set the order of a query object.</p>
+
+<dl class="api">
+<dt><code>void tctdbqrysetorder(TDBQRY *<var>qry</var>, const char *<var>name</var>, int <var>type</var>);</code></dt>
+<dd>`<var>qry</var>' specifies the query object.</dd>
+<dd>`<var>name</var>' specifies the name of a column.  An empty string means the primary key.</dd>
+<dd>`<var>type</var>' specifies the order type: `TDBQOSTRASC' for string ascending, `TDBQOSTRDESC' for string descending, `TDBQONUMASC' for number ascending, `TDBQONUMDESC' for number descending.</dd>
+</dl>
+
+<p>The function `tctdbqrysetlimit' is used in order to set the limit number of records of the result of a query object.</p>
+
+<dl class="api">
+<dt><code>void tctdbqrysetlimit(TDBQRY *<var>qry</var>, int <var>max</var>, int <var>skip</var>);</code></dt>
+<dd>`<var>qry</var>' specifies the query object.</dd>
+<dd>`<var>max</var>' specifies the maximum number of records of the result.  If it is negative, no limit is specified.</dd>
+<dd>`<var>skip</var>' specifies the number of skipped records of the result.  If it is not more than 0, no record is skipped.</dd>
+</dl>
+
+<p>The function `tctdbqrysearch' is used in order to execute the search of a query object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tctdbqrysearch(TDBQRY *<var>qry</var>);</code></dt>
+<dd>`<var>qry</var>' specifies the query object.</dd>
+<dd>The return value is a list object of the primary keys of the corresponding records.  This function does never fail.  It returns an empty list even if no record corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tctdbqrysearchout' is used in order to remove each record corresponding to a query object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbqrysearchout(TDBQRY *<var>qry</var>);</code></dt>
+<dd>`<var>qry</var>' specifies the query object of the database connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tctdbqryproc' is used in order to process each record corresponding to a query object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbqryproc(TDBQRY *<var>qry</var>, TDBQRYPROC <var>proc</var>, void *<var>op</var>);</code></dt>
+<dd>`<var>qry</var>' specifies the query object of the database connected as a writer.</dd>
+<dd>`<var>proc</var>' specifies the pointer to the iterator function called for each record.  It receives four parameters.  The first parameter is the pointer to the region of the primary key.  The second parameter is the size of the region of the primary key.  The third parameter is a map object containing columns.  The fourth parameter is the pointer to the optional opaque object.  It returns flags of the post treatment by bitwise-or: `TDBQPPUT' to modify the record, `TDBQPOUT' to remove the record, `TDBQPSTOP' to stop the iteration.</dd>
+<dd>`<var>op</var>' specifies an arbitrary pointer to be given as a parameter of the iterator function.  If it is not needed, `NULL' can be specified.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tctdbqryhint' is used in order to get the hint string of a query object.</p>
+
+<dl class="api">
+<dt><code>const char *tctdbqryhint(TDBQRY *<var>qry</var>);</code></dt>
+<dd>`<var>qry</var>' specifies the query object.</dd>
+<dd>The return value is the hint string.</dd>
+</dl>
+
+<p>The function `tctdbmetasearch' is used in order to retrieve records with multiple query objects and get the set of the result.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tctdbmetasearch(TDBQRY **<var>qrys</var>, int <var>num</var>, int <var>type</var>);</code></dt>
+<dd>`<var>qrys</var>' specifies an array of the query objects.</dd>
+<dd>`<var>num</var>' specifies the number of elements of the array.</dd>
+<dd>`<var>type</var>' specifies a set operation type: `TDBMSUNION' for the union set, `TDBMSISECT' for the intersection set, `TDBMSDIFF' for the difference set.</dd>
+<dd>The return value is a list object of the primary keys of the corresponding records.  This function does never fail.  It returns an empty list even if no record corresponds.</dd>
+<dd>If the first query object has the order setting, the result array is sorted by the order.  Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<h3 id="tctdbapi_example">Example Code</h3>
+
+<p>The following code is an example to use a table database.</p>
+
+<pre>#include &lt;tcutil.h&gt;
+#include &lt;tctdb.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdbool.h&gt;
+#include &lt;stdint.h&gt;
+
+int main(int argc, char **argv){
+  TCTDB *tdb;
+  int ecode, pksiz, i, rsiz;
+  char pkbuf[256];
+  const char *rbuf, *name;
+  TCMAP *cols;
+  TDBQRY *qry;
+  TCLIST *res;
+
+  /* create the object */
+  tdb = tctdbnew();
+
+  /* open the database */
+  if(!tctdbopen(tdb, "casket.tct", TDBOWRITER | TDBOCREAT)){
+    ecode = tctdbecode(tdb);
+    fprintf(stderr, "open error: %s\n", tctdberrmsg(ecode));
+  }
+
+  /* store a record */
+  pksiz = sprintf(pkbuf, "%ld", (long)tctdbgenuid(tdb));
+  cols = tcmapnew3("name", "mikio", "age", "30", "lang", "ja,en,c", NULL);
+  if(!tctdbput(tdb, pkbuf, pksiz, cols)){
+    ecode = tctdbecode(tdb);
+    fprintf(stderr, "put error: %s\n", tctdberrmsg(ecode));
+  }
+  tcmapdel(cols);
+
+  /* store a record in a naive way */
+  pksiz = sprintf(pkbuf, "12345");
+  cols = tcmapnew();
+  tcmapput2(cols, "name", "falcon");
+  tcmapput2(cols, "age", "31");
+  tcmapput2(cols, "lang", "ja");
+  if(!tctdbput(tdb, pkbuf, pksiz, cols)){
+    ecode = tctdbecode(tdb);
+    fprintf(stderr, "put error: %s\n", tctdberrmsg(ecode));
+  }
+  tcmapdel(cols);
+
+  /* store a record with a TSV string */
+  if(!tctdbput3(tdb, "abcde", "name\tjoker\tage\t19\tlang\ten,es")){
+    ecode = tctdbecode(tdb);
+    fprintf(stderr, "put error: %s\n", tctdberrmsg(ecode));
+  }
+
+  /* search for records */
+  qry = tctdbqrynew(tdb);
+  tctdbqryaddcond(qry, "age", TDBQCNUMGE, "20");
+  tctdbqryaddcond(qry, "lang", TDBQCSTROR, "ja,en");
+  tctdbqrysetorder(qry, "name", TDBQOSTRASC);
+  tctdbqrysetlimit(qry, 10, 0);
+  res = tctdbqrysearch(qry);
+  for(i = 0; i &lt; tclistnum(res); i++){
+    rbuf = tclistval(res, i, &amp;rsiz);
+    cols = tctdbget(tdb, rbuf, rsiz);
+    if(cols){
+      printf("%s", rbuf);
+      tcmapiterinit(cols);
+      while((name = tcmapiternext2(cols)) != NULL){
+        printf("\t%s\t%s", name, tcmapget2(cols, name));
+      }
+      printf("\n");
+      tcmapdel(cols);
+    }
+  }
+  tclistdel(res);
+  tctdbqrydel(qry);
+
+  /* close the database */
+  if(!tctdbclose(tdb)){
+    ecode = tctdbecode(tdb);
+    fprintf(stderr, "close error: %s\n", tctdberrmsg(ecode));
+  }
+
+  /* delete the object */
+  tctdbdel(tdb);
+
+  return 0;
+}
+</pre>
+
+<h3 id="tctdbapi_cli">CLI</h3>
+
+<p>To use the table database API easily, the commands `<code>tcttest</code>', `<code>tctmttest</code>', and `<code>tctmgr</code>' are provided.</p>
+
+<p>The command `<code>tcttest</code>' is a utility for facility test and performance test.  This command is used in the following format.  `<var>path</var>' specifies the path of a database file.  `<var>rnum</var>' specifies the number of iterations.  `<var>bnum</var>' specifies the number of buckets.  `<var>apow</var>' specifies the power of the alignment.  `<var>fpow</var>' specifies the power of the free block pool.</p>
+
+<dl class="api">
+<dt><code>tcttest write [-mt] [-tl] [-td|-tb|-tt|-tx] [-rc <var>num</var>] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-ip] [-is] [-in] [-it] [-if] [-ix] [-nl|-nb] [-rnd] <var>path</var> <var>rnum</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]</code></dt>
+<dd>Store records with columns "str", "num", "type", and "flag".</dd>
+<dt><code>tcttest read [-mt] [-rc <var>num</var>] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-rnd] <var>path</var></code></dt>
+<dd>Retrieve all records of the database above.</dd>
+<dt><code>tcttest remove [-mt] [-rc <var>num</var>] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-rnd] <var>path</var></code></dt>
+<dd>Remove all records of the database above.</dd>
+<dt><code>tcttest rcat [-mt] [-tl] [-td|-tb|-tt|-tx] [-rc <var>num</var>] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-ip] [-is] [-in] [-it] [-if] [-ix] [-nl|-nb] [-pn <var>num</var>] [-dai|-dad|-rl|-ru] <var>path</var> <var>rnum</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]</code></dt>
+<dd>Store records with partway duplicated keys using concatenate mode.</dd>
+<dt><code>tcttest misc [-mt] [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] <var>path</var> <var>rnum</var></code></dt>
+<dd>Perform miscellaneous test of various operations.</dd>
+<dt><code>tcttest wicked [-mt] [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] <var>path</var> <var>rnum</var></code></dt>
+<dd>Perform updating operations selected at random.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="options">
+<li><code>-mt</code> : call the function `tctdbsetmutex'.</li>
+<li><code>-tl</code> : enable the option `TDBTLARGE'.</li>
+<li><code>-td</code> : enable the option `TDBTDEFLATE'.</li>
+<li><code>-tb</code> : enable the option `TDBTBZIP'.</li>
+<li><code>-tt</code> : enable the option `TDBTTCBS'.</li>
+<li><code>-tx</code> : enable the option `TDBTEXCODEC'.</li>
+<li><code>-rc <var>num</var></code> : specify the number of cached records.</li>
+<li><code>-lc <var>num</var></code> : specify the number of cached leaf pages.</li>
+<li><code>-nc <var>num</var></code> : specify the number of cached non-leaf pages.</li>
+<li><code>-xm <var>num</var></code> : specify the size of the extra mapped memory.</li>
+<li><code>-df <var>num</var></code> : specify the unit step number of auto defragmentation.</li>
+<li><code>-ip</code> : create the number index for the primary key.</li>
+<li><code>-is</code> : create the string index for the column "str".</li>
+<li><code>-in</code> : create the number index for the column "num".</li>
+<li><code>-it</code> : create the string index for the column "type".</li>
+<li><code>-if</code> : create the token inverted index for the column "flag".</li>
+<li><code>-ix</code> : create the q-gram inverted index for the column "text".</li>
+<li><code>-nl</code> : enable the option `TDBNOLCK'.</li>
+<li><code>-nb</code> : enable the option `TDBLCKNB'.</li>
+<li><code>-rnd</code> : select keys at random.</li>
+<li><code>-pn <var>num</var></code> : specify the number of patterns.</li>
+<li><code>-dai</code> : use the function `tctdbaddint' instead of `tctdbputcat'.</li>
+<li><code>-dad</code> : use the function `tctdbadddouble' instead of `tctdbputcat'.</li>
+<li><code>-rl</code> : set the length of values at random.</li>
+<li><code>-ru</code> : select update operations at random.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<p>The command `<code>tctmttest</code>' is a utility for facility test under multi-thread situation.  This command is used in the following format.  `<var>path</var>' specifies the path of a database file.  `<var>tnum</var>' specifies the number of running threads.  `<var>rnum</var>' specifies the number of iterations.  `<var>bnum</var>' specifies the number of buckets.  `<var>apow</var>' specifies the power of the alignment.  `<var>fpow</var>' specifies the power of the free block pool.</p>
+
+<dl class="api">
+<dt><code>tctmttest write [-tl] [-td|-tb|-tt|-tx] [-rc <var>num</var>] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-ip] [-is] [-in] [-it] [-if] [-ix] [-nl|-nb] [-rnd] <var>path</var> <var>tnum</var> <var>rnum</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]</code></dt>
+<dd>Store records with columns "str", "num", "type", and "flag".</dd>
+<dt><code>tctmttest read [-rc <var>num</var>] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-rnd] <var>path</var> <var>tnum</var></code></dt>
+<dd>Retrieve all records of the database above.</dd>
+<dt><code>tctmttest remove [-rc <var>num</var>] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-rnd] <var>path</var> <var>tnum</var></code></dt>
+<dd>Remove all records of the database above.</dd>
+<dt><code>tctmttest wicked [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] <var>path</var> <var>tnum</var> <var>rnum</var></code></dt>
+<dd>Perform updating operations selected at random.</dd>
+<dt><code>tctmttest typical [-tl] [-td|-tb|-tt|-tx] [-rc <var>num</var>] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-rr <var>num</var>] <var>path</var> <var>tnum</var> <var>rnum</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]</code></dt>
+<dd>Perform typical operations selected at random.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="options">
+<li><code>-tl</code> : enable the option `TDBTLARGE'.</li>
+<li><code>-td</code> : enable the option `TDBTDEFLATE'.</li>
+<li><code>-tb</code> : enable the option `TDBTBZIP'.</li>
+<li><code>-tt</code> : enable the option `TDBTTCBS'.</li>
+<li><code>-tx</code> : enable the option `TDBTEXCODEC'.</li>
+<li><code>-rc <var>num</var></code> : specify the number of cached records.</li>
+<li><code>-lc <var>num</var></code> : specify the number of cached leaf pages.</li>
+<li><code>-nc <var>num</var></code> : specify the number of cached non-leaf pages.</li>
+<li><code>-xm <var>num</var></code> : specify the size of the extra mapped memory.</li>
+<li><code>-df <var>num</var></code> : specify the unit step number of auto defragmentation.</li>
+<li><code>-ip</code> : create the number index for the primary key.</li>
+<li><code>-is</code> : create the string index for the column "str".</li>
+<li><code>-in</code> : create the number index for the column "num".</li>
+<li><code>-it</code> : create the string index for the column "type".</li>
+<li><code>-if</code> : create the token inverted index for the column "flag".</li>
+<li><code>-ix</code> : create the q-gram inverted index for the column "text".</li>
+<li><code>-nl</code> : enable the option `TDBNOLCK'.</li>
+<li><code>-nb</code> : enable the option `TDBLCKNB'.</li>
+<li><code>-rnd</code> : select keys at random.</li>
+<li><code>-nc</code> : omit the comparison test.</li>
+<li><code>-rr <var>num</var></code> : specify the ratio of reading operation by percentage.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<p>The command `<code>tctmgr</code>' is a utility for test and debugging of the table database API and its applications.  `<var>path</var>' specifies the path of a database file.  `<var>bnum</var>' specifies the number of buckets.  `<var>apow</var>' specifies the power of the alignment.  `<var>fpow</var>' specifies the power of the free block pool.  `<var>pkey</var>' specifies the primary key of a record.  `<var>cols</var>' specifies the names and the values of a record alternately.  `<var>name</var>' specifies the name of a column.  `<var>op</var>' specifies an operator.  `<var>expr</var>' specifies the condition expression.  `<var>file</var>' specifies the input file.</p>
+
+<dl class="api">
+<dt><code>tctmgr create [-tl] [-td|-tb|-tt|-tx] <var>path</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]</code></dt>
+<dd>Create a database file.</dd>
+<dt><code>tctmgr inform [-nl|-nb] <var>path</var></code></dt>
+<dd>Print miscellaneous information to the standard output.</dd>
+<dt><code>tctmgr put [-nl|-nb] [-sx] [-dk|-dc|-dai|-dad] <var>path</var> <var>pkey</var> [<var>cols</var> ...]</code></dt>
+<dd>Store a record.</dd>
+<dt><code>tctmgr out [-nl|-nb] [-sx] <var>path</var> <var>pkey</var></code></dt>
+<dd>Remove a record.</dd>
+<dt><code>tctmgr get [-nl|-nb] [-sx] [-px] [-pz] <var>path</var> <var>pkey</var></code></dt>
+<dd>Print the value of a record.</dd>
+<dt><code>tctmgr list [-nl|-nb] [-m <var>num</var>] [-pv] [-px] [-fm <var>str</var>] <var>path</var></code></dt>
+<dd>Print the primary keys of all records, separated by line feeds.</dd>
+<dt><code>tctmgr search [-nl|-nb] [-ord <var>name</var> <var>type</var>] [-m <var>num</var>] [-sk <var>num</var>] [-kw] [-pv] [-px] [-ph] [-bt <var>num</var>] [-rm] [-ms <var>type</var>] <var>path</var> [<var>name</var> <var>op</var> <var>expr</var> ...]</code></dt>
+<dd>Print records matching conditions, separated by line feeds.</dd>
+<dt><code>tctmgr optimize [-tl] [-td|-tb|-tt|-tx] [-tz] [-nl|-nb] [-df] <var>path</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]</code></dt>
+<dd>Optimize a database file.</dd>
+<dt><code>tctmgr setindex [-nl|-nb] [-it <var>type</var>] <var>path</var> <var>name</var></code></dt>
+<dd>Set the index of a column.</dd>
+<dt><code>tctmgr importtsv [-nl|-nb] [-sc] <var>path</var> [<var>file</var>]</code></dt>
+<dd>Store records of TSV in each line of a file.</dd>
+<dt><code>tctmgr version</code></dt>
+<dd>Print the version information of Tokyo Cabinet.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="options">
+<li><code>-tl</code> : enable the option `TDBTLARGE'.</li>
+<li><code>-td</code> : enable the option `TDBTDEFLATE'.</li>
+<li><code>-tb</code> : enable the option `TDBTBZIP'.</li>
+<li><code>-tt</code> : enable the option `TDBTTCBS'.</li>
+<li><code>-tx</code> : enable the option `TDBTEXCODEC'.</li>
+<li><code>-nl</code> : enable the option `TDBNOLCK'.</li>
+<li><code>-nb</code> : enable the option `TDBLCKNB'.</li>
+<li><code>-sx</code> : the input data is evaluated as a hexadecimal data string.</li>
+<li><code>-dk</code> : use the function `tctdbputkeep' instead of `tctdbput'.</li>
+<li><code>-dc</code> : use the function `tctdbputcat' instead of `tctdbput'.</li>
+<li><code>-dai</code> : use the function `tctdbaddint' instead of `tctdbput'.</li>
+<li><code>-dad</code> : use the function `tctdbadddouble' instead of `tctdbput'.</li>
+<li><code>-px</code> : the output data is converted into a hexadecimal data string.</li>
+<li><code>-pz</code> : do not append line feed at the end of the output.</li>
+<li><code>-m <var>num</var></code> : specify the maximum number of the output.</li>
+<li><code>-pv</code> : print values of records also.</li>
+<li><code>-fm <var>str</var></code> : specify the prefix of keys.</li>
+<li><code>-ord <var>name</var> <var>type</var></code> : specify the order of the result.</li>
+<li><code>-sk <var>num</var></code> : specify the number of skipped records.</li>
+<li><code>-kw</code> : print KWIC string.</li>
+<li><code>-ph</code> : print hint information also.</li>
+<li><code>-bt</code> : specify the number of benchmark tests.</li>
+<li><code>-rm</code> : remove every record in the result.</li>
+<li><code>-ms <var>type</var></code> : specify the set operation of meta search.</li>
+<li><code>-tz</code> : enable the option `UINT8_MAX'.</li>
+<li><code>-df</code> : perform defragmentation only.</li>
+<li><code>-it <var>type</var></code> : specify the index type among "lexical", "decimal", "token", "qgram", and "void".</li>
+<li><code>-cd</code> : create the number index instead of the string index.</li>
+<li><code>-cv</code> : remove the existing index.</li>
+<li><code>-sc</code> : normalize keys as lower cases.</li>
+</ul>
+
+<p>The operator of the `search' subcommand is one of "STREQ", "STRINC", "STRBW", "STREW", "STRAND", "STROR", "STROREQ", "STRRX", "NUMEQ", "NUMGT", "NUMGE", "NUMLT", "NUMLE", "NUMBT", "NUMOREQ", "FTSPH", "FTSAND", "FTSOR", and "FTSEX".  If "~" preposes each operator, the logical meaning is reversed.  If "+" preposes each operator, no index is used for the operator.  The type of the `-ord' option is one of "STRASC", "STRDESC", "NUMASC", and "NUMDESC".  The type of the `-ms' option is one of "UNION", "ISECT", and "DIFF".  This command returns 0 on success, another on failure.</p>
+
+<hr />
+
+<h2 id="tcadbapi">The Abstract Database API</h2>
+
+<p>Abstract database is a set of interfaces to use on-memory hash database, on-memory tree database, hash database, B+ tree database, fixed-length database, and table database with the same API.  See `<code>tcadb.h</code>' for the entire specification.</p>
+
+<h3 id="tcadbapi_description">Description</h3>
+
+<p>To use the abstract database API, include `<code>tcutil.h</code>', `<code>tcadb.h</code>', and related standard header files.  Usually, write the following description near the front of a source file.</p>
+
+<dl>
+<dt><code>#include &lt;tcutil.h&gt;</code></dt>
+<dt><code>#include &lt;tcadb.h&gt;</code></dt>
+<dt><code>#include &lt;stdlib.h&gt;</code></dt>
+<dt><code>#include &lt;stdbool.h&gt;</code></dt>
+<dt><code>#include &lt;stdint.h&gt;</code></dt>
+</dl>
+
+<p>Objects whose type is pointer to `<code>TCADB</code>' are used to handle abstract databases.  An abstract database object is created with the function `<code>tcadbnew</code>' and is deleted with the function `<code>tcadbdel</code>'.  To avoid memory leak, it is important to delete every object when it is no longer in use.</p>
+
+<p>Before operations to store or retrieve records, it is necessary to connect the abstract database object to the concrete one.  The function `<code>tcadbopen</code>' is used to open a concrete database and the function `<code>tcadbclose</code>' is used to close the database.  To avoid data missing or corruption, it is important to close every database instance when it is no longer in use.  It is forbidden for multible database objects in a process to open the same database at the same time.</p>
+
+<h3 id="tcadbapi_api">API</h3>
+
+<p>The function `tcadbnew' is used in order to create an abstract database object.</p>
+
+<dl class="api">
+<dt><code>TCADB *tcadbnew(void);</code></dt>
+<dd>The return value is the new abstract database object.</dd>
+</dl>
+
+<p>The function `tcadbdel' is used in order to delete an abstract database object.</p>
+
+<dl class="api">
+<dt><code>void tcadbdel(TCADB *<var>adb</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+</dl>
+
+<p>The function `tcadbopen' is used in order to open an abstract database.</p>
+
+<dl class="api">
+<dt><code>bool tcadbopen(TCADB *<var>adb</var>, const char *<var>name</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>name</var>' specifies the name of the database.  If it is "*", the database will be an on-memory hash database.  If it is "+", the database will be an on-memory tree database.  If its suffix is ".tch", the database will be a hash database.  If its suffix is ".tcb", the database will be a B+ tree database.  If its suffix is ".tcf", the database will be a fixed-length database.  If its suffix is ".tct", the database will be a table database.  Otherwise, this function fails.  Tuning parameters can trail the name, separated by "#".  Each parameter is composed of the name and the value, separated by "=".  On-memory hash database supports "bnum", "capnum", and "capsiz".  On-memory tree database supports "capnum" and "capsiz".  Hash database supports "mode", "bnum", "apow", "fpow", "opts", "rcnum", "xmsiz", and "dfunit".  B+ tree database supports "mode", "lmemb", "nmemb", "bnum", "apow", "fpow", "opts", "lcnum", "ncnum", "xmsiz", and "dfunit".  Fixed-length database supports "mode", "width", and "limsiz".  Table database supports "mode", "bnum", "apow", "fpow", "opts", "rcnum", "lcnum", "ncnum", "xmsiz", "dfunit", and "idx".</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>The tuning parameter "capnum" specifies the capacity number of records.  "capsiz" specifies the capacity size of using memory.  Records spilled the capacity are removed by the storing order.  "mode" can contain "w" of writer, "r" of reader, "c" of creating, "t" of truncating, "e" of no locking, and "f" of non-blocking lock.  The default mode is relevant to "wc".  "opts" can contains "l" of large option, "d" of Deflate option, "b" of BZIP2 option, and "t" of TCBS option.  "idx" specifies the column name of an index and its type separated by ":".  For example, "casket.tch#bnum=1000000#opts=ld" means that the name of the database file is "casket.tch", and the bucket number is 1000000, and the options are large and Deflate.</dd>
+</dl>
+
+<p>The function `tcadbclose' is used in order to close an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbclose(TCADB *<var>adb</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update of a database is assured to be written when the database is closed.  If a writer opens a database but does not close it appropriately, the database will be broken.</dd>
+</dl>
+
+<p>The function `tcadbput' is used in order to store a record into an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbput(TCADB *<var>adb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcadbput2' is used in order to store a string record into an abstract object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbput2(TCADB *<var>adb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcadbputkeep' is used in order to store a new record into an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbputkeep(TCADB *<var>adb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcadbputkeep2' is used in order to store a new string record into an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbputkeep2(TCADB *<var>adb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcadbputcat' is used in order to concatenate a value at the end of the existing record in an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbputcat(TCADB *<var>adb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcadbputcat2' is used in order to concatenate a string value at the end of the existing record in an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbputcat2(TCADB *<var>adb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcadbout' is used in order to remove a record of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbout(TCADB *<var>adb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcadbout2' is used in order to remove a string record of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbout2(TCADB *<var>adb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcadbget' is used in order to retrieve a record in an abstract database object.</p>
+
+<dl class="api">
+<dt><code>void *tcadbget(TCADB *<var>adb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcadbget2' is used in order to retrieve a string record in an abstract database object.</p>
+
+<dl class="api">
+<dt><code>char *tcadbget2(TCADB *<var>adb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the string of the value of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcadbvsiz' is used in order to get the size of the value of a record in an abstract database object.</p>
+
+<dl class="api">
+<dt><code>int tcadbvsiz(TCADB *<var>adb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tcadbvsiz2' is used in order to get the size of the value of a string record in an abstract database object.</p>
+
+<dl class="api">
+<dt><code>int tcadbvsiz2(TCADB *<var>adb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tcadbiterinit' is used in order to initialize the iterator of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbiterinit(TCADB *<var>adb</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>The iterator is used in order to access the key of every record stored in a database.</dd>
+</dl>
+
+<p>The function `tcadbiternext' is used in order to get the next key of the iterator of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>void *tcadbiternext(TCADB *<var>adb</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  It is possible to access every record by iteration of calling this function.  It is allowed to update or remove records whose keys are fetched while the iteration.  However, it is not assured if updating the database is occurred while the iteration.  Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of storing matches the one of the traversal access.</dd>
+</dl>
+
+<p>The function `tcadbiternext2' is used in order to get the next key string of the iterator of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>char *tcadbiternext2(TCADB *<var>adb</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>If successful, the return value is the string of the next key, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  It is possible to access every record by iteration of calling this function.  However, it is not assured if updating the database is occurred while the iteration.  Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of storing matches the one of the traversal access.</dd>
+</dl>
+
+<p>The function `tcadbfwmkeys' is used in order to get forward matching keys in an abstract database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcadbfwmkeys(TCADB *<var>adb</var>, const void *<var>pbuf</var>, int <var>psiz</var>, int <var>max</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>pbuf</var>' specifies the pointer to the region of the prefix.</dd>
+<dd>`<var>psiz</var>' specifies the size of the region of the prefix.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.</dd>
+</dl>
+
+<p>The function `tcadbfwmkeys2' is used in order to get forward matching string keys in an abstract database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcadbfwmkeys2(TCADB *<var>adb</var>, const char *<var>pstr</var>, int <var>max</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>pstr</var>' specifies the string of the prefix.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.</dd>
+</dl>
+
+<p>The function `tcadbaddint' is used in order to add an integer to a record in an abstract database object.</p>
+
+<dl class="api">
+<dt><code>int tcadbaddint(TCADB *<var>adb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int <var>num</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>If successful, the return value is the summation value, else, it is `INT_MIN'.</dd>
+<dd>If the corresponding record exists, the value is treated as an integer and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tcadbadddouble' is used in order to add a real number to a record in an abstract database object.</p>
+
+<dl class="api">
+<dt><code>double tcadbadddouble(TCADB *<var>adb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, double <var>num</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>If successful, the return value is the summation value, else, it is Not-a-Number.</dd>
+<dd>If the corresponding record exists, the value is treated as a real number and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tcadbsync' is used in order to synchronize updated contents of an abstract database object with the file and the device.</p>
+
+<dl class="api">
+<dt><code>bool tcadbsync(TCADB *<var>adb</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcadboptimize' is used in order to optimize the storage of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadboptimize(TCADB *<var>adb</var>, const char *<var>params</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>params</var>' specifies the string of the tuning parameters, which works as with the tuning of parameters the function `tcadbopen'.  If it is `NULL', it is not used.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>This function is useful to reduce the size of the database storage with data fragmentation by successive updating.</dd>
+</dl>
+
+<p>The function `tcadbvanish' is used in order to remove all records of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbvanish(TCADB *<var>adb</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcadbcopy' is used in order to copy the database file of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbcopy(TCADB *<var>adb</var>, const char *<var>path</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>path</var>' specifies the path of the destination file.  If it begins with `@', the trailing substring is executed as a command line.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned if the executed command returns non-zero code.</dd>
+<dd>The database file is assured to be kept synchronized and not modified while the copying or executing operation is in progress.  So, this function is useful to create a backup file of the database file.</dd>
+</dl>
+
+<p>The function `tcadbtranbegin' is used in order to begin the transaction of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbtranbegin(TCADB *<var>adb</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>The database is locked by the thread while the transaction so that only one transaction can be activated with a database object at the same time.  Thus, the serializable isolation level is assumed if every database operation is performed in the transaction.  All updated regions are kept track of by write ahead logging while the transaction.  If the database is closed during transaction, the transaction is aborted implicitly.</dd>
+</dl>
+
+<p>The function `tcadbtrancommit' is used in order to commit the transaction of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbtrancommit(TCADB *<var>adb</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update in the transaction is fixed when it is committed successfully.</dd>
+</dl>
+
+<p>The function `tcadbtranabort' is used in order to abort the transaction of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbtranabort(TCADB *<var>adb</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update in the transaction is discarded when it is aborted.  The state of the database is rollbacked to before transaction.</dd>
+</dl>
+
+<p>The function `tcadbpath' is used in order to get the file path of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>const char *tcadbpath(TCADB *<var>adb</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>The return value is the path of the database file or `NULL' if the object does not connect to any database.  "*" stands for on-memory hash database.  "+" stands for on-memory tree database.</dd>
+</dl>
+
+<p>The function `tcadbrnum' is used in order to get the number of records of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcadbrnum(TCADB *<var>adb</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>The return value is the number of records or 0 if the object does not connect to any database instance.</dd>
+</dl>
+
+<p>The function `tcadbsize' is used in order to get the size of the database of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcadbsize(TCADB *<var>adb</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>The return value is the size of the database or 0 if the object does not connect to any database instance.</dd>
+</dl>
+
+<p>The function `tcadbmisc' is used in order to call a versatile function for miscellaneous operations of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcadbmisc(TCADB *<var>adb</var>, const char *<var>name</var>, const TCLIST *<var>args</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>name</var>' specifies the name of the function.  All databases support "put", "out", "get", "putlist", "outlist", "getlist", and "getpart".  "put" is to store a record.  It receives a key and a value, and returns an empty list.  "out" is to remove a record.  It receives a key, and returns an empty list.  "get" is to retrieve a record.  It receives a key, and returns a list of the values.  "putlist" is to store records.  It receives keys and values one after the other, and returns an empty list.  "outlist" is to remove records.  It receives keys, and returns an empty list.  "getlist" is to retrieve records.  It receives keys, and returns keys and values of corresponding records one after the other.  "getpart" is to retrieve the partial value of a record.  It receives a key, the offset of the region, and the length of the region.</dd>
+<dd>`<var>args</var>' specifies a list object containing arguments.</dd>
+<dd>If successful, the return value is a list object of the result.  `NULL' is returned on failure.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<h3 id="tcadbapi_example">Example Code</h3>
+
+<p>The following code is an example to use an abstract database.</p>
+
+<pre>#include &lt;tcutil.h&gt;
+#include &lt;tcadb.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdbool.h&gt;
+#include &lt;stdint.h&gt;
+
+int main(int argc, char **argv){
+  TCADB *adb;
+  char *key, *value;
+
+  /* create the object */
+  adb = tcadbnew();
+
+  /* open the database */
+  if(!tcadbopen(adb, "casket.tch")){
+    fprintf(stderr, "open error\n");
+  }
+
+  /* store records */
+  if(!tcadbput2(adb, "foo", "hop") ||
+     !tcadbput2(adb, "bar", "step") ||
+     !tcadbput2(adb, "baz", "jump")){
+    fprintf(stderr, "put error\n");
+  }
+
+  /* retrieve records */
+  value = tcadbget2(adb, "foo");
+  if(value){
+    printf("%s\n", value);
+    free(value);
+  } else {
+    fprintf(stderr, "get error\n");
+  }
+
+  /* traverse records */
+  tcadbiterinit(adb);
+  while((key = tcadbiternext2(adb)) != NULL){
+    value = tcadbget2(adb, key);
+    if(value){
+      printf("%s:%s\n", key, value);
+      free(value);
+    }
+    free(key);
+  }
+
+  /* close the database */
+  if(!tcadbclose(adb)){
+    fprintf(stderr, "close error\n");
+  }
+
+  /* delete the object */
+  tcadbdel(adb);
+
+  return 0;
+}
+</pre>
+
+<h3 id="tcadbapi_cli">CLI</h3>
+
+<p>To use the abstract database API easily, the commands `<code>tcatest</code>', `<code>tcamttest</code>' and `<code>tcamgr</code>' are provided.</p>
+
+<p>The command `<code>tcatest</code>' is a utility for facility test and performance test.  This command is used in the following format.  `<var>name</var>' specifies the database name.  `<var>rnum</var>' specifies the number of iterations.  `<var>tnum</var>' specifies the number of transactions.</p>
+
+<dl class="api">
+<dt><code>tcatest write <var>name</var> <var>rnum</var></code></dt>
+<dd>Store records with keys of 8 bytes.  They change as `00000001', `00000002'...</dd>
+<dt><code>tcatest read <var>name</var></code></dt>
+<dd>Retrieve all records of the database above.</dd>
+<dt><code>tcatest remove <var>name</var></code></dt>
+<dd>Remove all records of the database above.</dd>
+<dt><code>tcatest rcat <var>name</var> <var>rnum</var></code></dt>
+<dd>Store records with partway duplicated keys using concatenate mode.</dd>
+<dt><code>tcatest misc <var>name</var> <var>rnum</var></code></dt>
+<dd>Perform miscellaneous test of various operations.</dd>
+<dt><code>tcatest wicked <var>name</var> <var>rnum</var></code></dt>
+<dd>Perform updating operations of list and map selected at random.</dd>
+<dt><code>tcatest compare <var>name</var> <var>tnum</var> <var>rnum</var></code></dt>
+<dd>Perform comparison test of database schema.</dd>
+</dl>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<p>The command `<code>tcamttest</code>' is a utility for facility test under multi-thread situation.  This command is used in the following format.  `<var>name</var>' specifies the database name.  `<var>tnum</var>' specifies the number of running threads.  `<var>rnum</var>' specifies the number of iterations.</p>
+
+<dl class="api">
+<dt><code>tcamttest write <var>name</var> <var>tnum</var> <var>rnum</var></code></dt>
+<dd>Store records with keys of 8 bytes.  They change as `00000001', `00000002'...</dd>
+<dt><code>tcamttest read <var>name</var> <var>tnum</var></code></dt>
+<dd>Retrieve all records of the database above.</dd>
+<dt><code>tcamttest remove <var>name</var> <var>tnum</var></code></dt>
+<dd>Remove all records of the database above.</dd>
+</dl>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<p>The command `<code>tcamgr</code>' is a utility for test and debugging of the abstract database API and its applications.  `<var>name</var>' specifies the name of a database.  `<var>key</var>' specifies the key of a record.  `<var>value</var>' specifies the value of a record.  `<var>params</var>' specifies the tuning parameters.  `<var>func</var>' specifies the name of a function.  `<var>arg</var>' specifies the arguments of the function.  `<var>dest</var>' specifies the path of the destination file.</p>
+
+<dl class="api">
+<dt><code>tcamgr create <var>name</var></code></dt>
+<dd>Create a database file.</dd>
+<dt><code>tcamgr inform <var>name</var></code></dt>
+<dd>Print miscellaneous information to the standard output.</dd>
+<dt><code>tcamgr put [-sx] [-sep <var>chr</var>] [-dk|-dc|-dai|-dad] <var>name</var> <var>key</var> <var>value</var></code></dt>
+<dd>Store a record.</dd>
+<dt><code>tcamgr out [-sx] [-sep <var>chr</var>] <var>name</var> <var>key</var></code></dt>
+<dd>Remove a record.</dd>
+<dt><code>tcamgr get [-sx] [-sep <var>chr</var>] [-px] [-pz] <var>name</var> <var>key</var></code></dt>
+<dd>Print the value of a record.</dd>
+<dt><code>tcamgr list [-sep <var>chr</var>] [-m <var>num</var>] [-pv] [-px] [-fm <var>str</var>] <var>name</var></code></dt>
+<dd>Print keys of all records, separated by line feeds.</dd>
+<dt><code>tcamgr optimize <var>name</var> <var>params</var></code></dt>
+<dd>Optimize a database file.</dd>
+<dt><code>tcamgr misc [-sx] [-sep <var>chr</var>] [-px] <var>name</var> <var>func</var> [<var>arg</var>...]</code></dt>
+<dd>Call a versatile function for miscellaneous operations.</dd>
+<dt><code>tcamgr map [-fm <var>str</var>] <var>name</var> <var>dest</var></code></dt>
+<dd>Map records into another B+ tree database.</dd>
+<dt><code>tcamgr version</code></dt>
+<dd>Print the version information of Tokyo Cabinet.</dd>
+</dl>
+
+<p>Options feature the following.</p>
+
+<ul class="options">
+<li><code>-sx</code> : the input data is evaluated as a hexadecimal data string.</li>
+<li><code>-sep <var>chr</var></code> : specify the separator of the input data.</li>
+<li><code>-dk</code> : use the function `tcadbputkeep' instead of `tcadbput'.</li>
+<li><code>-dc</code> : use the function `tcadbputcat' instead of `tcadbput'.</li>
+<li><code>-dai</code> : use the function `tcadbaddint' instead of `tcadbput'.</li>
+<li><code>-dad</code> : use the function `tcadbadddouble' instead of `tcadbput'.</li>
+<li><code>-px</code> : the output data is converted into a hexadecimal data string.</li>
+<li><code>-pz</code> : do not append line feed at the end of the output.</li>
+<li><code>-m <var>num</var></code> : specify the maximum number of the output.</li>
+<li><code>-pv</code> : print values of records also.</li>
+<li><code>-fm <var>str</var></code> : specify the prefix of keys.</li>
+</ul>
+
+<p>This command returns 0 on success, another on failure.</p>
+
+<h3 id="tcadbapi_cgi">CGI</h3>
+
+<p>To use the abstract database API easily, the CGI script `<code>tcawmgr.cgi</code>' is provided.</p>
+
+<p>The CGI script `<code>tcawmgr.cgi</code>' is a utility to browse and edit an abstract database by Web interface.  The database should be placed in the same directory of the CGI script and named as "<code>casket.tch</code>", "<code>casket.tcb</code>", or "<code>casket.tcf</code>".  And, its permission should allow reading and writing by the user executing the CGI script.  Install the CGI script in a public directory of your Web server then you can start to use the CGI script by accessing the assigned URL.</p>
+
+<hr />
+
+<h2 id="fileformat">File Format</h2>
+
+<p>This section describes the format of the database files of Tokyo Cabinet.</p>
+
+<h3 id="fileformat_tchdb">File Format of Hash Database</h3>
+
+<p>There are four sections in the file managed by the hash database; the header section, the bucket section, the free block pool section, and the record section.  Numeric values in the file are serialized in the little endian order or in the variable length format.  The latter format is delta encoding based on the 128-radix numbering.</p>
+
+<p>The header section is from the top of the file and its length is 256 bytes.  There are the following information.</p>
+
+<table summary="database header format">
+<tr>
+<td class="label">name</td>
+<td class="label">offset</td>
+<td class="label">length</td>
+<td class="label">feature</td>
+</tr>
+<tr>
+<td>magic number</td>
+<td class="number">0</td>
+<td class="number">32</td>
+<td>identification of the database.  Begins with "ToKyO CaBiNeT"</td>
+</tr>
+<tr>
+<td>database type</td>
+<td class="number">32</td>
+<td class="number">1</td>
+<td>hash (0x01) / B+ tree (0x02) / fixed-length (0x03) / table (0x04)</td>
+</tr>
+<tr>
+<td>additional flags</td>
+<td class="number">33</td>
+<td class="number">1</td>
+<td>logical union of open (1&lt;&lt;0) and fatal (1&lt;&lt;1)</td>
+</tr>
+<tr>
+<td>alignment power</td>
+<td class="number">34</td>
+<td class="number">1</td>
+<td>the alignment size, by power of 2</td>
+</tr>
+<tr>
+<td>free block pool power</td>
+<td class="number">35</td>
+<td class="number">1</td>
+<td>the number of elements in the free block pool, by power of 2</td>
+</tr>
+<tr>
+<td>options</td>
+<td class="number">36</td>
+<td class="number">1</td>
+<td>logical union of large (1&lt;&lt;0), Deflate (1&lt;&lt;1), BZIP2 (1&lt;&lt;2), TCBS (1&lt;&lt;3), extra codec (1&lt;&lt;4)</td>
+</tr>
+<tr>
+<td>bucket number</td>
+<td class="number">40</td>
+<td class="number">8</td>
+<td>the number of elements of the bucket array</td>
+</tr>
+<tr>
+<td>record number</td>
+<td class="number">48</td>
+<td class="number">8</td>
+<td>the number of records in the database</td>
+</tr>
+<tr>
+<td>file size</td>
+<td class="number">56</td>
+<td class="number">8</td>
+<td>the file size of the database</td>
+</tr>
+<tr>
+<td>first record</td>
+<td class="number">64</td>
+<td class="number">8</td>
+<td>the offset of the first record</td>
+</tr>
+<tr>
+<td>opaque region</td>
+<td class="number">128</td>
+<td class="number">128</td>
+<td>users can use this region arbitrarily</td>
+</tr>
+</table>
+
+<p>The bucket section trails the header section and its size is defined by the bucket number.  Each element of the bucket array indicates the offset of the first record of the hash chain.  The format of each element is the fixed length number and its size is 4 bytes in the normal mode or 8 bytes in the large mode.  The offset is recorded as the quotient by the alignment.</p>
+
+<p>The free block pool section trails the bucket section and its size is defined by the free block pool number.  Each element of the free block pool indicates the offset and the size of each free block.  The offset is recorded as the difference of the former free block and as the quotient by the alignment.  The offset and the size are serialized in the variable length format.</p>
+
+<p>The record section trails the free block pool section and occupies the rest region to the end of the file.  Each element has the following information.  The region of each record begins at the offset of the multiple of the alignment.</p>
+
+<table summary="record format">
+<tr>
+<td class="label">name</td>
+<td class="label">offset</td>
+<td class="label">length</td>
+<td class="label">feature</td>
+</tr>
+<tr>
+<td>magic number</td>
+<td class="number">0</td>
+<td class="number">1</td>
+<td>identification of record block. always 0xC8</td>
+</tr>
+<tr>
+<td>hash value</td>
+<td class="number">1</td>
+<td class="number">1</td>
+<td>the hash value to decide the path of the hash chain</td>
+</tr>
+<tr>
+<td>left chain</td>
+<td class="number">2</td>
+<td class="number">4</td>
+<td>the alignment quotient of the destination of the left chain</td>
+</tr>
+<tr>
+<td>right chain</td>
+<td class="number">6</td>
+<td class="number">4</td>
+<td>the alignment quotient of the destination of the right chain</td>
+</tr>
+<tr>
+<td>padding size</td>
+<td class="number">10</td>
+<td class="number">2</td>
+<td>the size of the padding</td>
+</tr>
+<tr>
+<td>key size</td>
+<td class="number">12</td>
+<td class="number">vary</td>
+<td>the size of the key</td>
+</tr>
+<tr>
+<td>value size</td>
+<td class="number">vary</td>
+<td class="number">vary</td>
+<td>the size of the value</td>
+</tr>
+<tr>
+<td>key</td>
+<td class="number">vary</td>
+<td class="number">vary</td>
+<td>the data of the key</td>
+</tr>
+<tr>
+<td>value</td>
+<td class="number">vary</td>
+<td class="number">vary</td>
+<td>the data of the value</td>
+</tr>
+<tr>
+<td>padding</td>
+<td class="number">vary</td>
+<td class="number">vary</td>
+<td>useless data</td>
+</tr>
+</table>
+
+<p>However, regions of free blocks contain the following information.</p>
+
+<table summary="free block format">
+<tr>
+<td class="label">name</td>
+<td class="label">offset</td>
+<td class="label">length</td>
+<td class="label">feature</td>
+</tr>
+<tr>
+<td>magic number</td>
+<td class="number">0</td>
+<td class="number">1</td>
+<td>identification of record block. always 0xB0</td>
+</tr>
+<tr>
+<td>block size</td>
+<td class="number">1</td>
+<td class="number">4</td>
+<td>size of the block</td>
+</tr>
+</table>
+
+<p>The transaction log is recorded in the file whose name is composed of the database name and the suffix ".wal".  The top eight bytes indicate the file size of the beginning of the transaction.  After that, there are the following information.</p>
+
+<table summary="transaction log format">
+<tr>
+<td class="label">name</td>
+<td class="label">offset</td>
+<td class="label">length</td>
+<td class="label">feature</td>
+</tr>
+<tr>
+<td>offset</td>
+<td class="number">0</td>
+<td class="number">8</td>
+<td>the offset of the updated region</td>
+</tr>
+<tr>
+<td>size</td>
+<td class="number">8</td>
+<td class="number">4</td>
+<td>the size of the updated region</td>
+</tr>
+<tr>
+<td>data</td>
+<td class="number">12</td>
+<td class="number">vary</td>
+<td>the data before update</td>
+</tr>
+</table>
+
+<h3 id="fileformat_tcbdb">File Format of B+ Tree Database</h3>
+
+<p>All data managed by the B+ tree database are recorded in the hash database.  Recorded data are classified into meta data and logical pages.  Logical pages are classified into leaf nodes and non-leaf nodes.  The formats of the fixed length number and the variable length number are the same as with the hash database.</p>
+
+<p>Meta data are recorded in the opaque region in the header of the hash database and have the following information.</p>
+
+<table summary="database header format">
+<tr>
+<td class="label">name</td>
+<td class="label">offset</td>
+<td class="label">length</td>
+<td class="label">feature</td>
+</tr>
+
+<tr>
+<td>comparison function</td>
+<td class="number">0</td>
+<td class="number">1</td>
+<td>tccmplexical (0x00), tccmpdecimal (0x01), tccmpint32 (0x02), tccmpint64 (0x03), other (0xff)</td>
+</tr>
+<tr>
+<td>reserved region</td>
+<td class="number">1</td>
+<td class="number">7</td>
+<td>not used</td>
+</tr>
+<tr>
+<td>record number of leaf node</td>
+<td class="number">8</td>
+<td class="number">4</td>
+<td>the maximum number of records in a leaf node</td>
+</tr>
+<tr>
+<td>index number of non-leaf node</td>
+<td class="number">12</td>
+<td class="number">4</td>
+<td>the maximum number of indices in a leaf node</td>
+</tr>
+<tr>
+<td>root node ID</td>
+<td class="number">16</td>
+<td class="number">8</td>
+<td>the page ID of the root node of B+ tree</td>
+</tr>
+<tr>
+<td>first leaf ID</td>
+<td class="number">24</td>
+<td class="number">8</td>
+<td>the page ID of the first leaf node</td>
+</tr>
+<tr>
+<td>last leaf ID</td>
+<td class="number">32</td>
+<td class="number">8</td>
+<td>the page ID of the last leaf node</td>
+</tr>
+<tr>
+<td>leaf number</td>
+<td class="number">40</td>
+<td class="number">8</td>
+<td>the number of the leaf nodes</td>
+</tr>
+<tr>
+<td>non-leaf number</td>
+<td class="number">48</td>
+<td class="number">8</td>
+<td>the number of the non-leaf nodes</td>
+</tr>
+<tr>
+<td>record number</td>
+<td class="number">56</td>
+<td class="number">8</td>
+<td>the number of records in the database</td>
+</tr>
+</table>
+
+<p>Each leaf node contains a list of records.  Each non-leaf node contains a list of indices to child nodes.  Though each record is a logical unit of user data, records with the same key are integrated into one record physically.  Each physical record has the following information.</p>
+
+<table summary="record format">
+<tr>
+<td class="label">name</td>
+<td class="label">offset</td>
+<td class="label">length</td>
+<td class="label">feature</td>
+</tr>
+<tr>
+<td>key size</td>
+<td class="number">0</td>
+<td class="number">vary</td>
+<td>the size of the key</td>
+</tr>
+<tr>
+<td>value size</td>
+<td class="number">vary</td>
+<td class="number">vary</td>
+<td>the size of the value</td>
+</tr>
+<tr>
+<td>duplication number</td>
+<td class="number">vary</td>
+<td class="number">vary</td>
+<td>the number of values with the same key</td>
+</tr>
+<tr>
+<td>key</td>
+<td class="number">vary</td>
+<td class="number">vary</td>
+<td>the data of the key</td>
+</tr>
+<tr>
+<td>value</td>
+<td class="number">vary</td>
+<td class="number">vary</td>
+<td>the data of the value</td>
+</tr>
+<tr>
+<td>duplicated records</td>
+<td class="number">vary</td>
+<td class="number">vary</td>
+<td>a list of value sizes and value data</td>
+</tr>
+</table>
+
+<p>Each leaf node is a physical unit of a set of records.  Each leaf node is identified by the sequential ID number from 1.  Each leaf node is recorded in the hash database.  The key is a string in the hexadecimal numbering.  The value has the following information.  Records are kept in the ascending order of keys.</p>
+
+<table summary="leaf node format">
+<tr>
+<td class="label">name</td>
+<td class="label">offset</td>
+<td class="label">length</td>
+<td class="label">feature</td>
+</tr>
+<tr>
+<td>previous leaf</td>
+<td class="number">0</td>
+<td class="number">vary</td>
+<td>the ID number of the previous leaf node</td>
+</tr>
+<tr>
+<td>next leaf</td>
+<td class="number">vary</td>
+<td class="number">vary</td>
+<td>the ID number of the next leaf node</td>
+</tr>
+<tr>
+<td>record list</td>
+<td class="number">vary</td>
+<td class="number">vary</td>
+<td>the serialized data of all records in the node</td>
+</tr>
+</table>
+
+<p>Each index is a logical unit of pointer to the child node.  Each index has the following information.</p>
+
+<table summary="index format">
+<tr>
+<td class="label">name</td>
+<td class="label">offset</td>
+<td class="label">length</td>
+<td class="label">feature</td>
+</tr>
+<tr>
+<td>page ID</td>
+<td class="number">0</td>
+<td class="number">vary</td>
+<td>the ID number of the referred page</td>
+</tr>
+<tr>
+<td>key size</td>
+<td class="number">vary</td>
+<td class="number">vary</td>
+<td>the size of the key</td>
+</tr>
+<tr>
+<td>key</td>
+<td class="number">vary</td>
+<td class="number">vary</td>
+<td>the data of the key</td>
+</tr>
+</table>
+
+<p>Each non-leaf node is a physical unit of a set of indices.  Each non-leaf node is identified by the sequential number from 281474976710657.  Each non-leaf node is recorded in the hash database.  The key is a string begins with "#" and is trailed by the hexadecimal number of the ID number subtracted by 281474976710657.  The value has the following information.  Indices are kept in the ascending order of keys.</p>
+
+<table summary="non-leaf format">
+<tr>
+<td class="label">name</td>
+<td class="label">offset</td>
+<td class="label">length</td>
+<td class="label">feature</td>
+</tr>
+<tr>
+<td>accession ID</td>
+<td class="number">0</td>
+<td class="number">vary</td>
+<td>the ID number of the first child node</td>
+</tr>
+<tr>
+<td>index list</td>
+<td class="number">vary</td>
+<td class="number">vary</td>
+<td>the serialized data of all indices in the node</td>
+</tr>
+</table>
+
+<h3 id="fileformat_tcfdb">File Format of Fixed-length Database</h3>
+
+<p>There are two sections in the file managed by the fixed-length database; the header section, and the record section.  Numeric values in the file are serialized in the little endian order.</p>
+
+<p>The header section is from the top of the file and its length is 256 bytes. There are the following information.</p>
+
+<table summary="database header format">
+<tr>
+<td class="label">name</td>
+<td class="label">offset</td>
+<td class="label">length</td>
+<td class="label">feature</td>
+</tr>
+<tr>
+<td>magic number</td>
+<td class="number">0</td>
+<td class="number">32</td>
+<td>identification of the database. Begins with "ToKyO CaBiNeT"</td>
+</tr>
+<tr>
+<td>database type</td>
+<td class="number">32</td>
+<td class="number">1</td>
+<td>always 0x03</td>
+</tr>
+<tr>
+<td>additional flags</td>
+<td class="number">33</td>
+<td class="number">1</td>
+<td>logical union of open (1&lt;&lt;0) and fatal (1&lt;&lt;1)</td>
+</tr>
+<tr>
+<td>record number</td>
+<td class="number">48</td>
+<td class="number">8</td>
+<td>the number of records in the database</td>
+</tr>
+<tr>
+<td>file size</td>
+<td class="number">56</td>
+<td class="number">8</td>
+<td>the file size of the database</td>
+</tr>
+<tr>
+<td>record width</td>
+<td class="number">64</td>
+<td class="number">8</td>
+<td>the width of each record</td>
+</tr>
+<tr>
+<td>limit size</td>
+<td class="number">72</td>
+<td class="number">8</td>
+<td>the limit size of the database</td>
+</tr>
+<tr>
+<td>least ID</td>
+<td class="number">80</td>
+<td class="number">8</td>
+<td>the least ID number of records</td>
+</tr>
+<tr>
+<td>greatest ID</td>
+<td class="number">88</td>
+<td class="number">8</td>
+<td>the greatest ID number of records</td>
+</tr>
+<tr>
+<td>opaque region</td>
+<td class="number">128</td>
+<td class="number">128</td>
+<td>users can use this region arbitrarily</td>
+</tr>
+</table>
+
+<p>The record section trails the header section and occupies the rest region to the end of the file. Each element has the following information.  The size region takes 1 byte if the record width is less than 256 bytes, or takes 2 bytes if the record width is less than 65536, else takes 4 bytes.  The size of each record is the summation of the size of the width region and the record width.  So, the region of each record begins at the offset generated by the ID number subtracted by 1 and multiplied by the record width and the added by 256.</p>
+
+<table summary="record format">
+<tr>
+<td class="label">name</td>
+<td class="label">offset</td>
+<td class="label">length</td>
+<td class="label">feature</td>
+</tr>
+<tr>
+<td>value size</td>
+<td class="number">0</td>
+<td class="number">vary</td>
+<td>the size of the value</td>
+</tr>
+<tr>
+<td>value</td>
+<td class="number">vary</td>
+<td class="number">vary</td>
+<td>the data of the value</td>
+</tr>
+<tr>
+<td>padding</td>
+<td class="number">vary</td>
+<td class="number">vary</td>
+<td>padding.  If the size of the value is 0, the first byte indicates whether the record exists or not</td>
+</tr>
+</table>
+
+<p>The naming convention and the file format of the transaction log file is the same as the one of the hash database.</p>
+
+<h3 id="fileformat_note">Note</h3>
+
+<p>Because database files are not sparse, you can copy them as with normal files.  Moreover, the database formats don't depend on the byte order of the running environment, you can migrate the database files between environments with different byte orders.</p>
+
+<p>If possible, set the MIME type `<code>application/x-tokyocabinet-hash</code>' when sending files of the hash database.  The suffix of the file name should be `<code>.tch</code>'.  As for the B+ tree database, `<code>application/x-tokyocabinet-btree</code>' and `<code>.tcb</code>'.  As for the fixed-length database, `<code>application/x-tokyocabinet-fixed</code>' and `<code>.tcf</code>'.  As for the table database, `<code>application/x-tokyocabinet-btree</code>' and `<code>.tct</code>'.</p>
+
+<p>To make the `<code>file</code>' command identify the database formats, append the following lines to the `<code>magic</code>' file.</p>
+
+<pre># Tokyo Cabinet magic data
+0       string    ToKyO\ CaBiNeT\n   Tokyo Cabinet
+&gt;14     string    x                  \b (%s)
+&gt;32     byte      0                  \b, Hash
+!:mime  application/x-tokyocabinet-hash
+&gt;32     byte      1                  \b, B+ tree
+!:mime  application/x-tokyocabinet-btree
+&gt;32     byte      2                  \b, Fixed-length
+!:mime  application/x-tokyocabinet-fixed
+&gt;32     byte      3                  \b, Table
+!:mime  application/x-tokyocabinet-table
+&gt;33     byte      &amp;1                 \b, [open]
+&gt;33     byte      &amp;2                 \b, [fatal]
+&gt;34     byte      x                  \b, apow=%d
+&gt;35     byte      x                  \b, fpow=%d
+&gt;36     byte      &amp;1                 \b, [large]
+&gt;36     byte      &amp;2                 \b, [deflate]
+&gt;36     byte      &amp;4                 \b, [bzip]
+&gt;36     byte      &amp;8                 \b, [tcbs]
+&gt;36     byte      &amp;16                \b, [excodec]
+&gt;40     lequad    x                  \b, bnum=%lld
+&gt;48     lequad    x                  \b, rnum=%lld
+&gt;56     lequad    x                  \b, fsiz=%lld
+</pre>
+
+<hr />
+
+<h2 id="license">License</h2>
+
+<p>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.</p>
+
+<p>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.</p>
+
+<p>You should have received a copy of the GNU Lesser General Public License along with Tokyo Cabinet (See the file `<code>COPYING</code>'); if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.</p>
+
+<p>Tokyo Cabinet was written by FAL Labs.  You can contact the author by e-mail to `<code>info@fallabs.com</code>'.</p>
+
+<hr />
+
+</body>
+
+</html>
+
+<!-- END OF FILE -->
diff --git a/tcejdb/doc/spex-ja.html b/tcejdb/doc/spex-ja.html
new file mode 100644 (file)
index 0000000..e3938d1
--- /dev/null
@@ -0,0 +1,7476 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
+
+<head>
+<meta http-equiv="Content-Language" content="ja" />
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<meta http-equiv="Content-Style-Type" content="text/css" />
+<meta name="author" content="FAL Labs" />
+<meta name="keywords" content="Tokyo Cabinet, tokyocabinet, database, DBM" />
+<meta name="description" content="Specifications of Tokyo Cabinet" />
+<link rel="contents" href="./" />
+<link rel="alternate" href="spex-en.html" hreflang="en" title="the English version" />
+<link rel="stylesheet" href="common.css" />
+<link rel="icon" href="icon16.png" />
+<link rev="made" href="mailto:info@fallabs.com" />
+<title>Fundamental Specifications of Tokyo Cabinet Version 1 (Japanese)</title>
+</head>
+
+<body>
+
+<h1 id="headline">Tokyo Cabinet第1版基本仕様書</h1>
+
+<div class="note">Copyright (C) 2006-2012 FAL Labs</div>
+<div class="note">Last Update: Sat, 18 Aug 2012 11:05:00 +0900</div>
+<div class="navi">[<a href="spex-en.html" hreflang="en">English</a>/<span class="void">Japanese</span>] [<a href="index.ja.html">HOME</a>]</div>
+
+<hr />
+
+<h2 id="contents">目次</h2>
+
+<ol>
+<li><a href="#introduction">はじめに</a></li>
+<li><a href="#features">特徴</a></li>
+<li><a href="#installation">インストール</a></li>
+<li><a href="#tcutilapi">ユーティリティAPI</a></li>
+<li><a href="#tchdbapi">ハッシュデータベースAPI</a></li>
+<li><a href="#tcbdbapi">B+木データベースAPI</a></li>
+<li><a href="#tcfdbapi">固定長データベースAPI</a></li>
+<li><a href="#tctdbapi">テーブルデータベースAPI</a></li>
+<li><a href="#tcadbapi">抽象データベースAPI</a></li>
+<li><a href="#tips">ちょっとしたコツ</a></li>
+<li><a href="#fileformat">ファイルフォーマット</a></li>
+<li><a href="#faq">よく聞かれる質問</a></li>
+<li><a href="#license">ライセンス</a></li>
+</ol>
+
+<hr />
+
+<h2 id="introduction">はじめに</h2>
+
+<p>Tokyo Cabinetはデータベースを扱うルーチン群のライブラリです。データベースといっても単純なもので、キーと値のペアからなるレコード群を格納したデータファイルです。キーと値は任意の長さを持つ一連のバイト列であり、文字列でもバイナリでも扱うことができます。テーブルやデータ型の概念はありません。レコードはハッシュ表かB+木か固定長配列で編成されます。</p>
+
+<p>ハッシュ表のデータベースでは、キーはデータベース内で一意であり、キーが重複する複数のレコードを格納することはできません。このデータベースに対しては、キーと値を指定してレコードを格納したり、キーを指定して対応するレコードを削除したり、キーを指定して対応するレコードを検索したりすることができます。また、データベースに格納してある全てのキーを順不同に一つずつ取り出すこともできます。このような操作は、UNIX標準で定義されているDBMライブラリおよびその追従であるNDBMやGDBMに類するものです。Tokyo CabinetはDBMのより良い代替として利用することができます。</p>
+
+<p>B+木のデータベースでは、キーが重複する複数のレコードを格納することができます。このデータベースに対しては、ハッシュ表のデータベースと同様に、キーを指定してレコードを格納したり取り出したり削除したりすることができます。レコードはユーザが指示した比較関数に基づいて整列されて格納されます。カーソルを用いて各レコードを昇順または降順で参照することができます。この機構によって、文字列の前方一致検索や数値の範囲検索が可能になります。</p>
+
+<p>固定長配列のデータベースでは、一意な自然数をキーとしてレコードが格納されます。キーが重複する複数のレコードを格納することはできません。また、各レコードの値の長さは一定以下に制限されます。提供される操作はハッシュデータベースとほぼ同様です。</p>
+
+<p>ハッシュ表のデータベース変種として、テーブルのデータベースも提供されます。各レコードは主キーで識別されるとともに、名前付きコラムの集合を値として持ちます。データスキーマの概念はありませんが、任意のコラムに張られたインデックスを用いることで複雑な条件に基づくレコードの検索を効率化することができます。</p>
+
+<p>Tokyo CabinetはC言語で記述され、CとPerlとRubyとJavaとLuaのAPIとして提供されます。Tokyo CabinetはC99およびPOSIX準拠のAPIを備えるプラットフォームで利用できます。Tokyo CabinetはGNU Lesser General Public Licenseに基づくフリーソフトウェアです。</p>
+
+<hr />
+
+<h2 id="features">特徴</h2>
+
+<p>Tokyo CabinetはQDBMの後継であり、空間効率と時間効率と使いやすさを向上させた製品です。この節ではTokyo Cabinetの特徴について説明します。</p>
+
+<h3 id="features_dinosaur">DBM一族の最右翼</h3>
+
+<p>Tokyo CabinetはGDBMやQDBMの後継として次の点を目標として開発されました。これらの目標は達成されており、Tokyo Cabinetは従来のDBMを置き換える製品だと言えます。</p>
+
+<ul>
+<li><strong>空間効率</strong>の向上 : データベースファイルがより小さい</li>
+<li><strong>時間効率</strong>の向上 : 処理がより高速である</li>
+<li><strong>並列性</strong>の向上 : マルチスレッド環境での同時実行性能の向上</li>
+<li><strong>利便性</strong>の向上 : APIがより単純である</li>
+<li><strong>堅牢性</strong>の向上 : 不慮の事態でもデータベースファイルが壊れにくい</li>
+<li><strong>64ビット</strong>対応 : 巨大なメモリ空間とデータベースファイルを扱える</li>
+</ul>
+
+<p>Tokyo CabinetはQDBMと同様に、伝統的なDBMが抱える三つの制限事項を回避しています。すなわち、プロセス内で複数のデータベースを扱うことができ、キーと値のサイズに制限がなく、データベースファイルがスパースではありません。さらに、QDBMが抱える三つの制限事項を回避しています。すなわち、2GB以上のデータベースファイルを扱うことができ、バイトオーダの異なる環境間でデータベースファイルを共有することができ、複数のスレッドが同時にデータベースの探索を行うことができます。</p>
+
+<p>Tokyo Cabinetは高速に動作します。例えば100万件のレコードの登録にかかる時間は、ハッシュデータベースで0.7秒ほど、B+木データベースで1.6秒ほどです。そしてTokyo Cabinetのデータベースは小さいです。例えば1レコードあたりのオーバーヘッドは、ハッシュデータベースで16バイトほど、B+木データベースで5バイトほどです。さらにTokyo Cabinetで扱えるデータの規模は莫大です。最大8EB(9.22e18バイト)までのデータベースファイルを扱うことができます。</p>
+
+<h3 id="features_tchdb">効率的なハッシュデータベースの実装</h3>
+
+<p>Tokyo Cabinetはレコードの探索にハッシュアルゴリズムを用います。バケット配列に十分な要素数があれば、レコードの探索にかかる時間計算量は O(1) です。すなわち、レコードの探索に必要な時間はデータベースの規模に関わらず一定です。追加や削除に関しても同様です。ハッシュ値の衝突はセパレートチェーン法で管理します。チェーンのデータ構造は二分探索木です。したがって、バケット配列の要素数が著しく少ない場合でも、探索等の時間計算量は O(log n) に抑えられます。</p>
+
+<p>Tokyo Cabinetはバケット配列を全てRAM上に保持することによって、処理の高速化を図ります。バケット配列がRAM上にあれば、ほぼ1パスのファイル操作でレコードに該当するファイル上の領域を参照することができます。ファイルに記録されたバケット配列は `read' コールでRAM上に読み込むのではなく、`mmap' コールでRAMに直接マッピングされます。したがって、データベースに接続する際の準備時間が極めて短く、また、複数のプロセスでメモリマップを共有することができます。</p>
+
+<p>バケット配列の要素数が格納するレコード数の半分ほどであれば、データの性質によって多少前後しますが、ハッシュ値の衝突率は56.7%ほどです(等倍だと36.8%、2倍だと21.3%、4倍だと11.5%、8倍だと6.0%ほど)。そのような場合、平均2パス以下のファイル操作でレコードを探索することができます。これを性能指標とするならば、例えば100万個のレコードを格納するためには50万要素のバケット配列が求められます。バケット配列の各要素は4バイトです。すなわち、2MバイトのRAMが利用できれば100万レコードのデータベースが構築できます。</p>
+
+<p>伝統的なDBMにはレコードの追加操作に関して「挿入」モードと「置換」モードがあります。前者では、キーが既存のレコードと重複する際に既存の値を残します。後者では、キーが既存のレコードと重複した際に新しい値に置き換えます。Tokyo Cabinetはその2つに加えて「連結」モードがあります。既存の値の末尾に指定された値を連結して格納する操作です。レコードの値を配列として扱う場合、要素を追加するには連結モードが役に立ちます。</p>
+
+<p>一般的に、データベースの更新処理を続けるとファイル内の利用可能領域の断片化(フラグメンテーション)が起き、ファイルのサイズが肥大化してしまいます。Tokyo Cabinetは隣接する不要領域を連結して再利用することによってこの問題に対処します。既存のレコードの値をより大きなサイズの値に上書きする場合、そのレコードの領域をファイル中の別の位置に移動させる必要があります。この処理の時間計算量はレコードのサイズに依存するので、値を拡張していく場合には効率が悪くなります。しかし、Tokyo Cabinetはアラインメントによってこの問題に対処します。増分がパディングに収まれば領域を移動させる必要はありません。</p>
+
+<p>不要領域を効率的に再利用するための「フリーブロックプール」も実装されています。これは不要になった領域をリストに記憶しておき、新しい領域が要求された際ににリストの中から最も小さい不要領域(ベストフィット)を選択して再利用するものです。それでも断片化は避けられないので、レコードの領域を詰め直して最適化(デフラグ)する二種類の機能も実装されています。一つめは静的な最適化で、全てのレコードを別ファイルに配置しなおしてから一気に書き戻すものです。二つめは動的な最適化で、レコードと不要領域の位置を入れ替える操作を少しずつ行って不要領域を集結させていくものです。</p>
+
+<h3 id="features_tcbdb">便利なB+木データベースの実装</h3>
+
+<p>B+木データベースはハッシュデータベースより遅いのですが、ユーザが定義した順序に基づいて各レコードを参照できることが特長です。B+木は複数のレコードを整列させた状態で論理的なページにまとめて管理します。各ページに対してはB木すなわち多進平衡木によって階層化された疎インデックスが維持されます。したがって、各レコードの探索等にかかる時間計算量は O(log n) です。各レコードを順番に参照するためにカーソルが提供されます。カーソルの場所はキーを指定して飛ばすことができ、また現在の場所から次のレコードに進めたり前のレコードに戻したりすることができます。各ページは双方向リンクリストで編成されるので、カーソルを前後に移動させる操作の時間計算量は O(1) です。</p>
+
+<p>B+木データベースは上述のハッシュデータベースを基盤として実装されます。B+木の各ページはハッシュデータベースのレコードとして記録されるので、ハッシュデータベースの記憶管理の効率性を継承しています。B+木では各レコードのヘッダが小さく、アラインメントはページの単位でとられるので、ほとんどの場合、ハッシュデータベースに較べてデータベースファイルのサイズが半減します。B+木を更新する際には多くのページを操作する必要がありますが、Tokyo Cabinetはページをキャッシュすることによってファイル操作を減らして処理を効率化します。ほとんどの場合、疎インデックス全体がメモリ上にキャッシュされるので、各レコードを参照するのに必要なファイル操作は平均1パス以下です。</p>
+
+<p>各ページを圧縮して保存する機能も提供されます。圧縮方式はZLIBのDeflateとBZIP2のブロックソーティングの2種類をサポートしています。同一ページ内の各レコードは似たようなパターンを持つため、Lempel-ZivやBWTなどのアルゴリズムを適用すると高い圧縮効率が期待できます。テキストデータを扱う場合、データベースのサイズが元の25%程度になります。データベースの規模が大きくディスクI/Oがボトルネックとなる場合は、圧縮機能を有効化すると処理速度が大幅に改善されます。</p>
+
+<h3 id="features_tcfdb">素朴な固定長データベースの実装</h3>
+
+<p>固定長データベースは、キーが自然数でなくてはならず、また値のサイズが制限されますが、その条件を受諾できる場合には最も効率的です。レコード群は固定長の要素の配列として保持され、各レコードはキーの倍数から算出されるオフセットの位置に格納されます。したがって、各レコードの探索等にかかる時間計算量は O(1) です。提供される操作群はハッシュデータベースとほぼ同じです。</p>
+
+<p>データベース全体を `mmap' コールでメモリ上にマッピングして多次元配列として参照するので、ファイルI/Oにかかるオーバーヘッドは極小化されます。構造が単純なおかげで、固定長データベースはハッシュデータベースよりもさらに高速に動作するとともに、マルチスレッド環境での並列実行性能も傑出しています。</p>
+
+<p>データベースのサイズは、キーの変域と値の制限長に比例します。すなわち、キーの変域が小さく、値のサイズが小さいほど、空間効率は向上します。例えば、キーの最大値が100万で、値の制限長が100バイトの場合、データベースのサイズは100MBほどになります。RAM上に読み込まれるのは実際に参照されたレコードの周辺の領域のみなので、データベースのサイズは仮想メモリのサイズまで大きくすることができます。</p>
+
+<h3 id="features_tctdb">柔軟なテーブルデータベースの実装</h3>
+
+<p>テーブルデータベースは、単純なキーと値の構造ではなく、リレーショナルデータベースの表のような構造を表現します。各レコードは主キーで識別されるとともに、任意の文字列で名前を付けられたコラムの集合を値として持ちます。例えば、社員番号を主キーにして、名前や部署や給与などのコラムを構造化して格納することができます。リレーショナルデータベースと違ってデータスキーマを事前に定義する必要はなく、レコード毎に異なる種類のコラムを持たせることができます。</p>
+
+<p>テーブルデータベースに対しては、主キー以外の条件でも問い合わせを行うことができます。条件はコラムの名前と条件式で構成されます。条件式の演算子としては、文字列型に関しては完全一致や前方一致や正規表現などが提供され、数値型に関しては完全一致や範囲一致が提供されます。タグ検索や全文検索の演算子も提供されます。クエリに複数の条件式を持たせることで論理積条件を指定できます。複数のクエリを使って検索を行うことで論理和条件を指定できます。検索結果の順序は文字列または数値の昇順または降順を指定することができます。</p>
+
+<p>コラムを使った検索やソートを高速化するために、コラム毎のインデックスを作成することができます。コラムには型の概念はありませんが、インデックスには文字列型もしくは数値型の区別があります。空白区切りトークンと文字N-gramトークンの転置インデックスもサポートされます。クエリオプティマイザは検索条件やソート条件に応じた最適な順序でインデックスを利用します。インデックスはB+木データベースの外部ファイルとして実装されます。</p>
+
+<h3 id="features_practical">実用的な機能性</h3>
+
+<p>ファイルシステム上のデータベースはトランザクション機構を提供します。トランザクションを開始してから終了するまでの一連の操作を一括してデータベースにコミットしたり、一連の更新操作を破棄してデータベースの状態をトランザクションの開始前の状態にロールバックしたりすることができます。トランザクションの分離レベルは2種類あります。データベースに対する全ての操作をトランザクション内で行うと直列化可能(serializable)トランザクションとなり、トランザクション外の操作を同時に行うと非コミット読み取り(read uncommitted)トランザクションとなります。耐久性はログ先行書き込みとシャドウページングによって担保されます。</p>
+
+<p>Tokyo Cabinetにはデータベースに接続するモードとして、「リーダ」と「ライタ」の二種類があります。リーダは読み込み専用で、ライタは読み書き両用です。データベースにはファイルロックによってプロセス間での排他制御が行われます。ライタが接続している間は、他のプロセスはリーダとしてもライタとしても接続できません。リーダが接続している間は、他のプロセスのリーダは接続できるが、ライタは接続できません。この機構によって、マルチタスク環境での同時接続に伴うデータの整合性が保証されます。</p>
+
+<p>Tokyo CabinetのAPIの各関数はリエントラントであり、マルチスレッド環境で安全に利用することができます。別個のデータベースオブジェクトに対しては全ての操作を完全に並列に行うことができます。同一のデータベースオブジェクトに対しては、リードライトロックで排他制御を行います。すなわち、読み込みを行うスレッド同士は並列に実行でき、書き込みを行うスレッドは他の読み込みや書き込みをブロックします。ロックの粒度は、ハッシュデータベースと固定長データベースではレコード単位、それ以外のデータベースではファイル単位です。</p>
+
+<h3 id="features_simple">単純だが多様なインタフェース群</h3>
+
+<p>Tokyo Cabinetはオブジェクト指向に基づいた簡潔なAPIを提供します。データベースに対する全ての操作はデータベースオブジェクトにカプセル化され、開く(open)、閉じる(close)、挿入する(put)、削除する(out)、取得する(get)といった関数(メソッド)を呼ぶことでプログラミングを進めていけます。ハッシュデータベースとB+木データベースと固定長データベースのAPIは互いに酷似しているので、アプリケーションを一方から他方に移植することも簡単です。さらに、それらのAPI群を全く同じインターフェイスで操作するための抽象APIも提供されます。抽象APIを用いると実行時にデータベースの種類を決定することができます。</p>
+
+<p>メモリ上でレコードを簡単に扱うために、ユーティリティAPIが提供されます。リストやマップといった基本的なデータ構造をはじめ、メモリプールや文字列処理や符号処理など、プログラミングで良く使う機能を詰め込んでいます。</p>
+
+<p>C言語のAPIには、ユーティリティAPI、ハッシュデータベースAPI、B+木データベースAPI、固定長データベースAPI、テーブルデータベースAPI、抽象データベースAPIの6種類があります。各APIに対応したコマンドラインインタフェースも用意されています。それらはプロトタイピングやテストやデバッグなどで活躍するでしょう。Tokyo CabinetはC言語の他にも、PerlとRubyとJavaとLuaのAPIを提供します。その他の言語のインターフェイスも第三者によって提供されるでしょう。</p>
+
+<p>複数のプロセスが同時にデータベースを操作したい場合やリモートホストにあるデータベースを操作したい場合には、リモートサービスを使うと便利です。リモートサービスはデータベースサーバとそのアクセスライブラリからなり、アプリケーションはリモートデータベースAPIを介してデータベースサーバを操作することができます。HTTPやmemcachedプロトコルもサポートするので、ほぼ全てのプラットフォームからデータベースサーバを簡単に操作することができます。</p>
+
+<hr />
+
+<h2 id="installation">インストール</h2>
+
+<p>Tokyo Cabinetのソースパッケージからのインストール方法を説明します。バイナリパッケージのインストール方法についてはそれぞれのパッケージの説明書をご覧ください。</p>
+
+<h3 id="installation_preparation">前提</h3>
+
+<p>Tokyo Cabinetの現在バージョンは、UNIX系のOSで利用することができます。少なくとも、以下の環境では動作するはずです。</p>
+
+<ul>
+<li>Linux 2.4以降 (x86-32/x86-64/PowerPC/Alpha/SPARC)</li>
+<li>Mac OS X 10.3以降 (x86-32/x86-64/PowerPC)</li>
+</ul>
+
+<p>ソースパッケージを用いてTokyo Cabinetをインストールするには、<code>gcc</code>のバージョン3.1以降と<code>make</code>が必要です。それらはLinuxやFreeBSDなどには標準的にインストールされています。</p>
+
+<p>Tokyo Cabinetは、以下のライブラリを利用しています。予めインストールしておいてください。</p>
+
+<ul>
+<li><a href="http://www.zlib.net/">zlib</a> : 可逆データ圧縮。バージョン1.2.3以降推奨。</li>
+<li><a href="http://www.bzip.org/">bzip2</a> : 可逆データ圧縮。バージョン1.0.5以降推奨。</li>
+</ul>
+
+<h3 id="installation_installation">ビルドとインストール</h3>
+
+<p>Tokyo Cabinetの配布用アーカイブファイルを展開したら、作成されたディレクトリに入ってインストール作業を行います。</p>
+
+<p><code>configure</code>スクリプトを実行して、ビルド環境を設定します。</p>
+
+<pre>./configure
+</pre>
+
+<p>プログラムをビルドします。</p>
+
+<pre>make
+</pre>
+
+<p>プログラムの自己診断テストを行います。</p>
+
+<pre>make check
+</pre>
+
+<p>プログラムをインストールします。作業は<code>root</code>ユーザで行います。</p>
+
+<pre>make install
+</pre>
+
+<h3 id="installation_result">結果</h3>
+
+<p>一連の作業が終ると、以下のファイルがインストールされます。</p>
+
+<pre>/usr/local/include/tcutil.h
+/usr/local/include/tchdb.h
+/usr/local/include/tcbdb.h
+/usr/local/include/tcfdb.h
+/usr/local/include/tctdb.h
+/usr/local/include/tcadb.h
+/usr/local/lib/libtokyocabinet.a
+/usr/local/lib/libtokyocabinet.so.x.y.z
+/usr/local/lib/libtokyocabinet.so.x
+/usr/local/lib/libtokyocabinet.so
+/usr/local/lib/pkgconfig/tokyocabinet.pc
+/usr/local/bin/tcutest
+/usr/local/bin/tcumttest
+/usr/local/bin/tcucodec
+/usr/local/bin/tchtest
+/usr/local/bin/tchmttest
+/usr/local/bin/tchmgr
+/usr/local/bin/tcbmgr
+/usr/local/bin/tcbtest
+/usr/local/bin/tcbmttest
+/usr/local/bin/tcftest
+/usr/local/bin/tcfmttest
+/usr/local/bin/tcfmgr
+/usr/local/bin/tcttest
+/usr/local/bin/tctmttest
+/usr/local/bin/tctmgr
+/usr/local/bin/tcatest
+/usr/local/bin/tcamttest
+/usr/local/bin/tcamgr
+/usr/local/libexec/tcawmgr.cgi
+/usr/local/share/tokyocabinet/...
+/usr/local/man/man1/...
+/usr/local/man/man3/...
+</pre>
+
+<h3 id="installation_option">configureのオプション</h3>
+
+<p>「<code>./configure</code>」を実行する際には、以下のオプションを指定することができます。</p>
+
+<ul class="options">
+<li><code>--enable-debug</code> : デバッグ用にビルドする。デバッグシンボルを有効化し、最適化を行わず、静的にリンクする。</li>
+<li><code>--enable-devel</code> : 開発用にビルドする。デバッグシンボルを有効化し、最適化を行い、動的にリンクする。</li>
+<li><code>--enable-profile</code> : プロファイル用にビルドする。プロファイルオプションを有効化し、最適化を行い、動的にリンクする。</li>
+<li><code>--enable-static</code> : 静的にリンクする。</li>
+<li><code>--enable-fastest</code> : 最高速になるように最適化を行う。</li>
+<li><code>--enable-off64</code> : 32ビット環境でも64ビットのファイルオフセットを用いる。</li>
+<li><code>--enable-swab</code> : バイトオーダの変換を強制する。</li>
+<li><code>--enable-uyield</code> : レースコンディションの検出用にビルドする。</li>
+<li><code>--disable-zlib</code> : ZLIBによるレコード圧縮を無効にする。</li>
+<li><code>--disable-bzip</code> : BZIP2によるレコード圧縮を無効にする。</li>
+<li><code>--disable-pthread</code> : POSIXスレッドのサポートを無効にする。</li>
+<li><code>--disable-shared</code> : 共有ライブラリのビルドを行わない。</li>
+</ul>
+
+<p>`<code>--prefix</code>' などのオプションも一般的なUNIXソフトウェアのパッケージと同様に利用可能です。`<code>/usr/local</code>' 以下ではなく '<code>/usr</code>' 以下にインストールしたい場合は `<code>--prefix=/usr</code>' を指定してください。なお、ライブラリ検索パスに `<code>/usr/local/lib</code>' が入っていない環境では、Tokyo Cabinetのアプリケーションを実行する際に環境変数 `<code>LD_LIBRARY_PATH</code>' の値に `<code>/usr/local/lib</code>' を含めておくようにしてください。</p>
+
+<h3 id="installation_library">ライブラリの使い方</h3>
+
+<p>Tokyo CabinetはC言語のAPIを提供し、それはC89標準(ANSI C)またはC99標準に準拠したプログラムから利用することができます。Tokyo Cabinetヘッダは `<code>tcutil.h</code>'、`<code>tchdb.h</code>'、`<code>tcbdb.h</code>'、`<code>tcadb.h</code>' として提供されますので、適宜それらをアプリケーションのソースコード中でインクルードした上で、APIの各種機能を利用してください。ライブラリは `<code>libtokyocabinet.a</code>' および `<code>libtokyocabinet.so</code>' として提供され、それらは `<code>libz.so</code>'、`<code>libbz2.so</code>', `<code>librt.so</code>', `<code>libpthread.so</code>'、`<code>libm.so</code>'、`<code>libc.so</code>' に依存しますので、アプリケーションプログラムをビルドする際にはそれらに対応するリンカオプションをつけてください。最も典型的なビルド手順は以下のようになります。</p>
+
+<pre>gcc -I/usr/local/include tc_example.c -o tc_example \
+  -L/usr/local/lib -ltokyocabinet -lz -lbz2 -lrt -lpthread -lm -lc
+</pre>
+
+<p>Tokyo CabinetはC++言語のプログラムからも利用することができます。各ヘッダは暗黙的にCリンケージ(「<code>extern "C"</code>」ブロック)で包まれているので、単にインクルードするだけで利用することができます。</p>
+
+<hr />
+
+<h2 id="tcutilapi">ユーティリティAPI</h2>
+
+<p>ユーティリティAPIは、メモリ上で簡単にレコードを扱うためのルーチン集です。特に拡張可能文字列と配列リストがハッシュマップと順序木が便利です。`<code>tcutil.h</code>' にAPIの仕様の完全な記述があります。</p>
+
+<h3 id="tcutilapi_description">概要</h3>
+
+<p>ユーティリティAPIを使うためには、`<code>tcutil.h</code>' および関連する標準ヘッダファイルをインクルードしてください。通常、ソースファイルの冒頭付近で以下の記述を行います。</p>
+
+<dl>
+<dt><code>#include &lt;tcutil.h&gt;</code></dt>
+<dt><code>#include &lt;stdlib.h&gt;</code></dt>
+<dt><code>#include &lt;stdbool.h&gt;</code></dt>
+<dt><code>#include &lt;stdint.h&gt;</code></dt>
+</dl>
+
+<p>拡張可能文字列を扱う際には、`<code>TCXSTR</code>' 型へのポインタをオブジェクトとして用います。拡張可能文字列オブジェクトは、関数 `<code>tcxstrnew</code>' で作成し、関数 `<code>tcxstrdel</code>' で破棄します。配列リストを扱う際には、`<code>TCLIST</code>' 型へのポインタをオブジェクトとして用います。リストオブジェクトは、関数 `<code>tclistnew</code>' で作成し、関数 `<code>tclistdel</code>' で破棄します。ハッシュマップを扱う際には、`<code>TCMAP</code>' 型へのポインタをオブジェクトとして用います。マップオブジェクトは、関数 `<code>tcmapopen</code>' で作成し、関数 `<code>tcmapdel</code>' で破棄します。順序木を扱う際には、`<code>TCTREE</code>' 型へのポインタをオブジェクトとして用います。ツリーオブジェクトは、関数 `<code>tctreeopen</code>' で作成し、関数 `<code>tctreedel</code>' で破棄します。作成したオブジェクトを使い終わったら必ず破棄してください。そうしないとメモリリークが発生します。</p>
+
+<h3 id="tcutilapi_basicapi">基礎的なユーティリティのAPI(英語御免)</h3>
+
+<p>The constant `tcversion' is the string containing the version information.</p>
+
+<dl class="api">
+<dt><code>extern const char *tcversion;</code></dt>
+</dl>
+
+<p>The variable `tcfatalfunc' is the pointer to the call back function for handling a fatal error.</p>
+
+<dl class="api">
+<dt><code>extern void (*tcfatalfunc)(const char *);</code></dt>
+<dd>The argument specifies the error message.</dd>
+<dd>The initial value of this variable is `NULL'.  If the value is `NULL', the default function is called when a fatal error occurs.  A fatal error occurs when memory allocation is failed.</dd>
+</dl>
+
+<p>The function `tcmalloc' is used in order to allocate a region on memory.</p>
+
+<dl class="api">
+<dt><code>void *tcmalloc(size_t <var>size</var>);</code></dt>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>The return value is the pointer to the allocated region.</dd>
+<dd>This function handles failure of memory allocation implicitly.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tccalloc' is used in order to allocate a nullified region on memory.</p>
+
+<dl class="api">
+<dt><code>void *tccalloc(size_t <var>nmemb</var>, size_t <var>size</var>);</code></dt>
+<dd>`<var>nmemb</var>' specifies the number of elements.</dd>
+<dd>`<var>size</var>' specifies the size of each element.</dd>
+<dd>The return value is the pointer to the allocated nullified region.</dd>
+<dd>This function handles failure of memory allocation implicitly.  Because the region of the return value is allocated with the `calloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcrealloc' is used in order to re-allocate a region on memory.</p>
+
+<dl class="api">
+<dt><code>void *tcrealloc(void *<var>ptr</var>, size_t <var>size</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>The return value is the pointer to the re-allocated region.</dd>
+<dd>This function handles failure of memory allocation implicitly.  Because the region of the return value is allocated with the `realloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcmemdup' is used in order to duplicate a region on memory.</p>
+
+<dl class="api">
+<dt><code>void *tcmemdup(const void *<var>ptr</var>, size_t <var>size</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>The return value is the pointer to the allocated region of the duplicate.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcstrdup' is used in order to duplicate a string on memory.</p>
+
+<dl class="api">
+<dt><code>char *tcstrdup(const void *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string.</dd>
+<dd>The return value is the allocated string equivalent to the specified string.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcfree' is used in order to free a region on memory.</p>
+
+<dl class="api">
+<dt><code>void tcfree(void *<var>ptr</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.  If it is `NULL', this function has no effect.</dd>
+<dd>Although this function is just a wrapper of `free' call, this is useful in applications using another package of the `malloc' series.</dd>
+</dl>
+
+<h3 id="tcutilapi_xstrapi">拡張可能文字列のAPI(英語御免)</h3>
+
+<p>The function `tcxstrnew' is used in order to create an extensible string object.</p>
+
+<dl class="api">
+<dt><code>TCXSTR *tcxstrnew(void);</code></dt>
+<dd>The return value is the new extensible string object.</dd>
+</dl>
+
+<p>The function `tcxstrnew2' is used in order to create an extensible string object from a character string.</p>
+
+<dl class="api">
+<dt><code>TCXSTR *tcxstrnew2(const char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string of the initial content.</dd>
+<dd>The return value is the new extensible string object containing the specified string.</dd>
+</dl>
+
+<p>The function `tcxstrnew3' is used in order to create an extensible string object with the initial allocation size.</p>
+
+<dl class="api">
+<dt><code>TCXSTR *tcxstrnew3(int <var>asiz</var>);</code></dt>
+<dd>`<var>asiz</var>' specifies the initial allocation size.</dd>
+<dd>The return value is the new extensible string object.</dd>
+</dl>
+
+<p>The function `tcxstrdup' is used in order to copy an extensible string object.</p>
+
+<dl class="api">
+<dt><code>TCXSTR *tcxstrdup(const TCXSTR *<var>xstr</var>);</code></dt>
+<dd>`<var>xstr</var>' specifies the extensible string object.</dd>
+<dd>The return value is the new extensible string object equivalent to the specified object.</dd>
+</dl>
+
+<p>The function `tcxstrdel' is used in order to delete an extensible string object.</p>
+
+<dl class="api">
+<dt><code>void tcxstrdel(TCXSTR *<var>xstr</var>);</code></dt>
+<dd>`<var>xstr</var>' specifies the extensible string object.</dd>
+<dd>Note that the deleted object and its derivatives can not be used anymore.</dd>
+</dl>
+
+<p>The function `tcxstrcat' is used in order to concatenate a region to the end of an extensible string object.</p>
+
+<dl class="api">
+<dt><code>void tcxstrcat(TCXSTR *<var>xstr</var>, const void *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>xstr</var>' specifies the extensible string object.</dd>
+<dd>`<var>ptr</var>' specifies the pointer to the region to be appended.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+</dl>
+
+<p>The function `tcxstrcat2' is used in order to concatenate a character string to the end of an extensible string object.</p>
+
+<dl class="api">
+<dt><code>void tcxstrcat2(TCXSTR *<var>xstr</var>, const char *<var>str</var>);</code></dt>
+<dd>`<var>xstr</var>' specifies the extensible string object.</dd>
+<dd>`<var>str</var>' specifies the string to be appended.</dd>
+</dl>
+
+<p>The function `tcxstrptr' is used in order to get the pointer of the region of an extensible string object.</p>
+
+<dl class="api">
+<dt><code>const void *tcxstrptr(const TCXSTR *<var>xstr</var>);</code></dt>
+<dd>`<var>xstr</var>' specifies the extensible string object.</dd>
+<dd>The return value is the pointer of the region of the object.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.</dd>
+</dl>
+
+<p>The function `tcxstrsize' is used in order to get the size of the region of an extensible string object.</p>
+
+<dl class="api">
+<dt><code>int tcxstrsize(const TCXSTR *<var>xstr</var>);</code></dt>
+<dd>`<var>xstr</var>' specifies the extensible string object.</dd>
+<dd>The return value is the size of the region of the object.</dd>
+</dl>
+
+<p>The function `tcxstrclear' is used in order to clear an extensible string object.</p>
+
+<dl class="api">
+<dt><code>void tcxstrclear(TCXSTR *<var>xstr</var>);</code></dt>
+<dd>`<var>xstr</var>' specifies the extensible string object.</dd>
+<dd>The internal buffer of the object is cleared and the size is set zero.</dd>
+</dl>
+
+<p>The function `tcxstrprintf' is used in order to perform formatted output into an extensible string object.</p>
+
+<dl class="api">
+<dt><code>void tcxstrprintf(TCXSTR *<var>xstr</var>, const char *<var>format</var>, ...);</code></dt>
+<dd>`<var>xstr</var>' specifies the extensible string object.</dd>
+<dd>`<var>format</var>' specifies the printf-like format string.  The conversion character `%' can be used with such flag characters as `s', `d', `o', `u', `x', `X', `c', `e', `E', `f', `g', `G', `@', `?', `b', and `%'.  `@' works as with `s' but escapes meta characters of XML.  `?' works as with `s' but escapes meta characters of URL.  `b' converts an integer to the string as binary numbers.  The other conversion character work as with each original.</dd>
+<dd>The other arguments are used according to the format string.</dd>
+</dl>
+
+<p>The function `tcsprintf' is used in order to allocate a formatted string on memory.</p>
+
+<dl class="api">
+<dt><code>char *tcsprintf(const char *<var>format</var>, ...);</code></dt>
+<dd>`<var>format</var>' specifies the printf-like format string.  The conversion character `%' can be used with such flag characters as `s', `d', `o', `u', `x', `X', `c', `e', `E', `f', `g', `G', `@', `?', `b', and `%'.  `@' works as with `s' but escapes meta characters of XML.  `?' works as with `s' but escapes meta characters of URL.  `b' converts an integer to the string as binary numbers.  The other conversion character work as with each original.</dd>
+<dd>The other arguments are used according to the format string.</dd>
+<dd>The return value is the pointer to the region of the result string.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<h3 id="tcutilapi_listapi">配列リストのAPI(英語御免)</h3>
+
+<p>The function `tclistnew' is used in order to create a list object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tclistnew(void);</code></dt>
+<dd>The return value is the new list object.</dd>
+</dl>
+
+<p>The function `tclistnew2' is used in order to create a list object with expecting the number of elements.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tclistnew2(int <var>anum</var>);</code></dt>
+<dd>`<var>anum</var>' specifies the number of elements expected to be stored in the list.</dd>
+<dd>The return value is the new list object.</dd>
+</dl>
+
+<p>The function `tclistnew3' is used in order to create a list object with initial string elements.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tclistnew3(const char *<var>str</var>, ...);</code></dt>
+<dd>`<var>str</var>' specifies the string of the first element.</dd>
+<dd>The other arguments are other elements.  They should be trailed by a `NULL' argument.</dd>
+<dd>The return value is the new list object.</dd>
+</dl>
+
+<p>The function `tclistdup' is used in order to copy a list object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tclistdup(const TCLIST *<var>list</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>The return value is the new list object equivalent to the specified object.</dd>
+</dl>
+
+<p>The function `tclistdel' is used in order to delete a list object.</p>
+
+<dl class="api">
+<dt><code>void tclistdel(TCLIST *<var>list</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>Note that the deleted object and its derivatives can not be used anymore.</dd>
+</dl>
+
+<p>The function `tclistnum' is used in order to get the number of elements of a list object.</p>
+
+<dl class="api">
+<dt><code>int tclistnum(const TCLIST *<var>list</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>The return value is the number of elements of the list.</dd>
+</dl>
+
+<p>The function `tclistval' is used in order to get the pointer to the region of an element of a list object.</p>
+
+<dl class="api">
+<dt><code>const void *tclistval(const TCLIST *<var>list</var>, int <var>index</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>index</var>' specifies the index of the element.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>The return value is the pointer to the region of the value.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  If `index' is equal to or more than the number of elements, the return value is `NULL'.</dd>
+</dl>
+
+<p>The function `tclistval2' is used in order to get the string of an element of a list object.</p>
+
+<dl class="api">
+<dt><code>const char *tclistval2(const TCLIST *<var>list</var>, int <var>index</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>index</var>' specifies the index of the element.</dd>
+<dd>The return value is the string of the value.</dd>
+<dd>If `index' is equal to or more than the number of elements, the return value is `NULL'.</dd>
+</dl>
+
+<p>The function `tclistpush' is used in order to add an element at the end of a list object.</p>
+
+<dl class="api">
+<dt><code>void tclistpush(TCLIST *<var>list</var>, const void *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>ptr</var>' specifies the pointer to the region of the new element.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+</dl>
+
+<p>The function `tclistpush2' is used in order to add a string element at the end of a list object.</p>
+
+<dl class="api">
+<dt><code>void tclistpush2(TCLIST *<var>list</var>, const char *<var>str</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>str</var>' specifies the string of the new element.</dd>
+</dl>
+
+<p>The function `tclistpop' is used in order to remove an element of the end of a list object.</p>
+
+<dl class="api">
+<dt><code>void *tclistpop(TCLIST *<var>list</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>The return value is the pointer to the region of the removed element.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  If the list is empty, the return value is `NULL'.</dd>
+</dl>
+
+<p>The function `tclistpop2' is used in order to remove a string element of the end of a list object.</p>
+
+<dl class="api">
+<dt><code>char *tclistpop2(TCLIST *<var>list</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>The return value is the string of the removed element.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  If the list is empty, the return value is `NULL'.</dd>
+</dl>
+
+<p>The function `tclistunshift' is used in order to add an element at the top of a list object.</p>
+
+<dl class="api">
+<dt><code>void tclistunshift(TCLIST *<var>list</var>, const void *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>ptr</var>' specifies the pointer to the region of the new element.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+</dl>
+
+<p>The function `tclistunshift2' is used in order to add a string element at the top of a list object.</p>
+
+<dl class="api">
+<dt><code>void tclistunshift2(TCLIST *<var>list</var>, const char *<var>str</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>str</var>' specifies the string of the new element.</dd>
+</dl>
+
+<p>The function `tclistshift' is used in order to remove an element of the top of a list object.</p>
+
+<dl class="api">
+<dt><code>void *tclistshift(TCLIST *<var>list</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>The return value is the pointer to the region of the removed element.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  If the list is empty, the return value is `NULL'.</dd>
+</dl>
+
+<p>The function `tclistshift2' is used in order to remove a string element of the top of a list object.</p>
+
+<dl class="api">
+<dt><code>char *tclistshift2(TCLIST *<var>list</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>The return value is the string of the removed element.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  If the list is empty, the return value is `NULL'.</dd>
+</dl>
+
+<p>The function `tclistinsert' is used in order to add an element at the specified location of a list object.</p>
+
+<dl class="api">
+<dt><code>void tclistinsert(TCLIST *<var>list</var>, int <var>index</var>, const void *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>index</var>' specifies the index of the new element.</dd>
+<dd>`<var>ptr</var>' specifies the pointer to the region of the new element.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>If `index' is equal to or more than the number of elements, this function has no effect.</dd>
+</dl>
+
+<p>The function `tclistinsert2' is used in order to add a string element at the specified location of a list object.</p>
+
+<dl class="api">
+<dt><code>void tclistinsert2(TCLIST *<var>list</var>, int <var>index</var>, const char *<var>str</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>index</var>' specifies the index of the new element.</dd>
+<dd>`<var>str</var>' specifies the string of the new element.</dd>
+<dd>If `index' is equal to or more than the number of elements, this function has no effect.</dd>
+</dl>
+
+<p>The function `tclistremove' is used in order to remove an element at the specified location of a list object.</p>
+
+<dl class="api">
+<dt><code>void *tclistremove(TCLIST *<var>list</var>, int <var>index</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>index</var>' specifies the index of the element to be removed.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>The return value is the pointer to the region of the removed element.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  If `index' is equal to or more than the number of elements, no element is removed and the return value is `NULL'.</dd>
+</dl>
+
+<p>The function `tclistremove2' is used in order to remove a string element at the specified location of a list object.</p>
+
+<dl class="api">
+<dt><code>char *tclistremove2(TCLIST *<var>list</var>, int <var>index</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>index</var>' specifies the index of the element to be removed.</dd>
+<dd>The return value is the string of the removed element.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  If `index' is equal to or more than the number of elements, no element is removed and the return value is `NULL'.</dd>
+</dl>
+
+<p>The function `tclistover' is used in order to overwrite an element at the specified location of a list object.</p>
+
+<dl class="api">
+<dt><code>void tclistover(TCLIST *<var>list</var>, int <var>index</var>, const void *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>index</var>' specifies the index of the element to be overwritten.</dd>
+<dd>`<var>ptr</var>' specifies the pointer to the region of the new content.</dd>
+<dd>`<var>size</var>' specifies the size of the new content.</dd>
+<dd>If `index' is equal to or more than the number of elements, this function has no effect.</dd>
+</dl>
+
+<p>The function `tclistover2' is used in order to overwrite a string element at the specified location of a list object.</p>
+
+<dl class="api">
+<dt><code>void tclistover2(TCLIST *<var>list</var>, int <var>index</var>, const char *<var>str</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>index</var>' specifies the index of the element to be overwritten.</dd>
+<dd>`<var>str</var>' specifies the string of the new content.</dd>
+<dd>If `index' is equal to or more than the number of elements, this function has no effect.</dd>
+</dl>
+
+<p>The function `tclistsort' is used in order to sort elements of a list object in lexical order.</p>
+
+<dl class="api">
+<dt><code>void tclistsort(TCLIST *<var>list</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+</dl>
+
+<p>The function `tclistlsearch' is used in order to search a list object for an element using liner search.</p>
+
+<dl class="api">
+<dt><code>int tclistlsearch(const TCLIST *<var>list</var>, const void *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>ptr</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>The return value is the index of a corresponding element or -1 if there is no corresponding element.</dd>
+<dd>If two or more elements correspond, the former returns.</dd>
+</dl>
+
+<p>The function `tclistbsearch' is used in order to search a list object for an element using binary search.</p>
+
+<dl class="api">
+<dt><code>int tclistbsearch(const TCLIST *<var>list</var>, const void *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.  It should be sorted in lexical order.</dd>
+<dd>`<var>ptr</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>The return value is the index of a corresponding element or -1 if there is no corresponding element.</dd>
+<dd>If two or more elements correspond, which returns is not defined.</dd>
+</dl>
+
+<p>The function `tclistclear' is used in order to clear a list object.</p>
+
+<dl class="api">
+<dt><code>void tclistclear(TCLIST *<var>list</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>All elements are removed.</dd>
+</dl>
+
+<p>The function `tclistdump' is used in order to serialize a list object into a byte array.</p>
+
+<dl class="api">
+<dt><code>void *tclistdump(const TCLIST *<var>list</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>list</var>' specifies the list object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>The return value is the pointer to the region of the result serial region.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tclistload' is used in order to create a list object from a serialized byte array.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tclistload(const void *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region of serialized byte array.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>The return value is a new list object.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<h3 id="tcutilapi_mapapi">ハッシュマップのAPI(英語御免)</h3>
+
+<p>The function `tcmapnew' is used in order to create a map object.</p>
+
+<dl class="api">
+<dt><code>TCMAP *tcmapnew(void);</code></dt>
+<dd>The return value is the new map object.</dd>
+</dl>
+
+<p>The function `tcmapnew2' is used in order to create a map object with specifying the number of the buckets.</p>
+
+<dl class="api">
+<dt><code>TCMAP *tcmapnew2(uint32_t <var>bnum</var>);</code></dt>
+<dd>`<var>bnum</var>' specifies the number of the buckets.</dd>
+<dd>The return value is the new map object.</dd>
+</dl>
+
+<p>The function `tcmapnew3' is used in order to create a map object with initial string elements.</p>
+
+<dl class="api">
+<dt><code>TCMAP *tcmapnew3(const char *<var>str</var>, ...);</code></dt>
+<dd>`<var>str</var>' specifies the string of the first element.</dd>
+<dd>The other arguments are other elements.  They should be trailed by a `NULL' argument.</dd>
+<dd>The return value is the new map object.</dd>
+<dd>The key and the value of each record are situated one after the other.</dd>
+</dl>
+
+<p>The function `tcmapdup' is used in order to copy a map object.</p>
+
+<dl class="api">
+<dt><code>TCMAP *tcmapdup(const TCMAP *<var>map</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>The return value is the new map object equivalent to the specified object.</dd>
+</dl>
+
+<p>The function `tcmapdel' is used in order to delete a map object.</p>
+
+<dl class="api">
+<dt><code>void tcmapdel(TCMAP *<var>map</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>Note that the deleted object and its derivatives can not be used anymore.</dd>
+</dl>
+
+<p>The function `tcmapput' is used in order to store a record into a map object.</p>
+
+<dl class="api">
+<dt><code>void tcmapput(TCMAP *<var>map</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If a record with the same key exists in the map, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcmapput2' is used in order to store a string record into a map object.</p>
+
+<dl class="api">
+<dt><code>void tcmapput2(TCMAP *<var>map</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If a record with the same key exists in the map, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcmapputkeep' is used in order to store a new record into a map object.</p>
+
+<dl class="api">
+<dt><code>bool tcmapputkeep(TCMAP *<var>map</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the map, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcmapputkeep2' is used in order to store a new string record into a map object.</p>
+
+<dl class="api">
+<dt><code>bool tcmapputkeep2(TCMAP *<var>map</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the map, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcmapputcat' is used in order to concatenate a value at the end of the value of the existing record in a map object.</p>
+
+<dl class="api">
+<dt><code>void tcmapputcat(TCMAP *<var>map</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcmapputcat2' is used in order to concatenate a string value at the end of the value of the existing record in a map object.</p>
+
+<dl class="api">
+<dt><code>void tcmapputcat2(TCMAP *<var>map</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcmapout' is used in order to remove a record of a map object.</p>
+
+<dl class="api">
+<dt><code>bool tcmapout(TCMAP *<var>map</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is true.  False is returned when no record corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `tcmapout2' is used in order to remove a string record of a map object.</p>
+
+<dl class="api">
+<dt><code>bool tcmapout2(TCMAP *<var>map</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is true.  False is returned when no record corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `tcmapget' is used in order to retrieve a record in a map object.</p>
+
+<dl class="api">
+<dt><code>const void *tcmapget(const TCMAP *<var>map</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned when no record corresponds.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.</dd>
+</dl>
+
+<p>The function `tcmapget2' is used in order to retrieve a string record in a map object.</p>
+
+<dl class="api">
+<dt><code>const char *tcmapget2(const TCMAP *<var>map</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the string of the value of the corresponding record.  `NULL' is returned when no record corresponds.</dd>
+</dl>
+
+<p>The function `tcmapmove' is used in order to move a record to the edge of a map object.</p>
+
+<dl class="api">
+<dt><code>bool tcmapmove(TCMAP *<var>map</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, bool <var>head</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of a key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>head</var>' specifies the destination which is the head if it is true or the tail if else.</dd>
+<dd>If successful, the return value is true.  False is returned when no record corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `tcmapmove2' is used in order to move a string record to the edge of a map object.</p>
+
+<dl class="api">
+<dt><code>bool tcmapmove2(TCMAP *<var>map</var>, const char *<var>kstr</var>, bool <var>head</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kstr</var>' specifies the string of a key.</dd>
+<dd>`<var>head</var>' specifies the destination which is the head if it is true or the tail if else.</dd>
+<dd>If successful, the return value is true.  False is returned when no record corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `tcmapiterinit' is used in order to initialize the iterator of a map object.</p>
+
+<dl class="api">
+<dt><code>void tcmapiterinit(TCMAP *<var>map</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>The iterator is used in order to access the key of every record stored in the map object.</dd>
+</dl>
+
+<p>The function `tcmapiternext' is used in order to get the next key of the iterator of a map object.</p>
+
+<dl class="api">
+<dt><code>const void *tcmapiternext(TCMAP *<var>map</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record can be fetched from the iterator.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  The order of iteration is assured to be the same as the stored order.</dd>
+</dl>
+
+<p>The function `tcmapiternext2' is used in order to get the next key string of the iterator of a map object.</p>
+
+<dl class="api">
+<dt><code>const char *tcmapiternext2(TCMAP *<var>map</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record can be fetched from the iterator.</dd>
+<dd>The order of iteration is assured to be the same as the stored order.</dd>
+</dl>
+
+<p>The function `tcmaprnum' is used in order to get the number of records stored in a map object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcmaprnum(const TCMAP *<var>map</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>The return value is the number of the records stored in the map object.</dd>
+</dl>
+
+<p>The function `tcmapmsiz' is used in order to get the total size of memory used in a map object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcmapmsiz(const TCMAP *<var>map</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>The return value is the total size of memory used in a map object.</dd>
+</dl>
+
+<p>The function `tcmapkeys' is used in order to create a list object containing all keys in a map object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcmapkeys(const TCMAP *<var>map</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>The return value is the new list object containing all keys in the map object.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcmapvals' is used in order to create a list object containing all values in a map object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcmapvals(const TCMAP *<var>map</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>The return value is the new list object containing all values in the map object.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcmapaddint' is used in order to add an integer to a record in a map object.</p>
+
+<dl class="api">
+<dt><code>int tcmapaddint(TCMAP *<var>map</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int <var>num</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>The return value is the summation value.</dd>
+<dd>If the corresponding record exists, the value is treated as an integer and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tcmapadddouble' is used in order to add a real number to a record in a map object.</p>
+
+<dl class="api">
+<dt><code>double tcmapadddouble(TCMAP *<var>map</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, double <var>num</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>The return value is the summation value.</dd>
+<dd>If the corresponding record exists, the value is treated as a real number and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tcmapclear' is used in order to clear a map object.</p>
+
+<dl class="api">
+<dt><code>void tcmapclear(TCMAP *<var>map</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>All records are removed.</dd>
+</dl>
+
+<p>The function `tcmapcutfront' is used in order to remove front records of a map object.</p>
+
+<dl class="api">
+<dt><code>void tcmapcutfront(TCMAP *<var>map</var>, int <var>num</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>num</var>' specifies the number of records to be removed.</dd>
+</dl>
+
+<p>The function `tcmapdump' is used in order to serialize a map object into a byte array.</p>
+
+<dl class="api">
+<dt><code>void *tcmapdump(const TCMAP *<var>map</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>map</var>' specifies the map object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>The return value is the pointer to the region of the result serial region.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcmapload' is used in order to create a map object from a serialized byte array.</p>
+
+<dl class="api">
+<dt><code>TCMAP *tcmapload(const void *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region of serialized byte array.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>The return value is a new map object.</dd>
+<dd>Because the object of the return value is created with the function `tcmapnew', it should be deleted with the function `tcmapdel' when it is no longer in use.</dd>
+</dl>
+
+<h3 id="tcutilapi_treeapi">順序木のAPI(英語御免)</h3>
+
+<p>The function `tctreenew' is used in order to create a tree object.</p>
+
+<dl class="api">
+<dt><code>TCTREE *tctreenew(void);</code></dt>
+<dd>The return value is the new tree object.</dd>
+</dl>
+
+<p>The function `tctreenew2' is used in order to create a tree object with specifying the custom comparison function.</p>
+
+<dl class="api">
+<dt><code>TCTREE *tctreenew2(TCCMP <var>cmp</var>, void *<var>cmpop</var>);</code></dt>
+<dd>`<var>cmp</var>' specifies the pointer to the custom comparison function.  It receives five parameters.  The first parameter is the pointer to the region of one key.  The second parameter is the size of the region of one key.  The third parameter is the pointer to the region of the other key.  The fourth parameter is the size of the region of the other key.  The fifth parameter is the pointer to the optional opaque object.  It returns positive if the former is big, negative if the latter is big, 0 if both are equivalent.</dd>
+<dd>`<var>cmpop</var>' specifies an arbitrary pointer to be given as a parameter of the comparison function.  If it is not needed, `NULL' can be specified.</dd>
+<dd>The return value is the new tree object.</dd>
+<dd>The default comparison function compares keys of two records by lexical order.  The functions `tccmplexical' (dafault), `tccmpdecimal', `tccmpint32', and `tccmpint64' are built-in.</dd>
+</dl>
+
+<p>The function `tctreedup' is used in order to copy a tree object.</p>
+
+<dl class="api">
+<dt><code>TCTREE *tctreedup(const TCTREE *<var>tree</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>The return value is the new tree object equivalent to the specified object.</dd>
+</dl>
+
+<p>The function `tctreedel' is used in order to delete a tree object.</p>
+
+<dl class="api">
+<dt><code>void tctreedel(TCTREE *<var>tree</var>);</code></dt>
+<dd>`tree' specifies the tree object.</dd>
+<dd>Note that the deleted object and its derivatives can not be used anymore.</dd>
+</dl>
+
+<p>The function `tctreeput' is used in order to store a record into a tree object.</p>
+
+<dl class="api">
+<dt><code>void tctreeput(TCTREE *<var>tree</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If a record with the same key exists in the tree, it is overwritten.</dd>
+</dl>
+
+<p>The function `tctreeput2' is used in order to store a string record into a tree object.</p>
+
+<dl class="api">
+<dt><code>void tctreeput2(TCTREE *<var>tree</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If a record with the same key exists in the tree, it is overwritten.</dd>
+</dl>
+
+<p>The function `tctreeputkeep' is used in order to store a new record into a tree object.</p>
+
+<dl class="api">
+<dt><code>bool tctreeputkeep(TCTREE *<var>tree</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the tree, this function has no effect.</dd>
+</dl>
+
+<p>The function `tctreeputkeep2' is used in order to store a new string record into a tree object.</p>
+
+<dl class="api">
+<dt><code>bool tctreeputkeep2(TCTREE *<var>tree</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the tree, this function has no effect.</dd>
+</dl>
+
+<p>The function `tctreeputcat' is used in order to concatenate a value at the end of the value of the existing record in a tree object.</p>
+
+<dl class="api">
+<dt><code>void tctreeputcat(TCTREE *<var>tree</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tctreeputcat2' is used in order to concatenate a string value at the end of the value of the existing record in a tree object.</p>
+
+<dl class="api">
+<dt><code>void tctreeputcat2(TCTREE *<var>tree</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tctreeout' is used in order to remove a record of a tree object.</p>
+
+<dl class="api">
+<dt><code>bool tctreeout(TCTREE *<var>tree</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is true.  False is returned when no record corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `tctreeout2' is used in order to remove a string record of a tree object.</p>
+
+<dl class="api">
+<dt><code>bool tctreeout2(TCTREE *<var>tree</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is true.  False is returned when no record corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `tctreeget' is used in order to retrieve a record in a tree object.</p>
+
+<dl class="api">
+<dt><code>const void *tctreeget(TCTREE *<var>tree</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned when no record corresponds.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.</dd>
+</dl>
+
+<p>The function `tctreeget2' is used in order to retrieve a string record in a tree object.</p>
+
+<dl class="api">
+<dt><code>const char *tctreeget2(TCTREE *<var>tree</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the string of the value of the corresponding record.  `NULL' is returned when no record corresponds.</dd>
+</dl>
+
+<p>The function `tctreeiterinit' is used in order to initialize the iterator of a tree object.</p>
+
+<dl class="api">
+<dt><code>void tctreeiterinit(TCTREE *<var>tree</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>The iterator is used in order to access the key of every record stored in the tree object.</dd>
+</dl>
+
+<p>The function `tctreeiternext' is used in order to get the next key of the iterator of a tree object.</p>
+
+<dl class="api">
+<dt><code>const void *tctreeiternext(TCTREE *<var>tree</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record can be fetched from the iterator.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  The order of iteration is assured to be ascending of the keys.</dd>
+</dl>
+
+<p>The function `tctreeiternext2' is used in order to get the next key string of the iterator of a tree object.</p>
+
+<dl class="api">
+<dt><code>const char *tctreeiternext2(TCTREE *<var>tree</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record can be fetched from the iterator.</dd>
+<dd>The order of iteration is assured to be ascending of the keys.</dd>
+</dl>
+
+<p>The function `tctreernum' is used in order to get the number of records stored in a tree object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tctreernum(const TCTREE *<var>tree</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>The return value is the number of the records stored in the tree object.</dd>
+</dl>
+
+<p>The function `tctreemsiz' is used in order to get the total size of memory used in a tree object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tctreemsiz(const TCTREE *<var>tree</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>The return value is the total size of memory used in a tree object.</dd>
+</dl>
+
+<p>The function `tctreekeys' is used in order to create a list object containing all keys in a tree object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tctreekeys(const TCTREE *<var>tree</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>The return value is the new list object containing all keys in the tree object.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tctreevals' is used in order to create a list object containing all values in a tree object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tctreevals(const TCTREE *<var>tree</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>The return value is the new list object containing all values in the tree object.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tctreeaddint' is used in order to add an integer to a record in a tree object.</p>
+
+<dl class="api">
+<dt><code>int tctreeaddint(TCTREE *<var>tree</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int <var>num</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>The return value is the summation value.</dd>
+<dd>If the corresponding record exists, the value is treated as an integer and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tctreeadddouble' is used in order to add a real number to a record in a tree object.</p>
+
+<dl class="api">
+<dt><code>double tctreeadddouble(TCTREE *<var>tree</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, double <var>num</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>The return value is the summation value.</dd>
+<dd>If the corresponding record exists, the value is treated as a real number and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tctreeclear' is used in order to clear a tree object.</p>
+
+<dl class="api">
+<dt><code>void tctreeclear(TCTREE *<var>tree</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>All records are removed.</dd>
+</dl>
+
+<p>The function `tctreecutfringe' is used in order to remove fringe records of a tree object.</p>
+
+<dl class="api">
+<dt><code>void tctreecutfringe(TCTREE *<var>tree</var>, int <var>num</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>num</var>' specifies the number of records to be removed.</dd>
+</dl>
+
+<p>The function `tctreedump' is used in order to serialize a tree object into a byte array.</p>
+
+<dl class="api">
+<dt><code>void *tctreedump(const TCTREE *<var>tree</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>tree</var>' specifies the tree object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>The return value is the pointer to the region of the result serial region.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tctreeload' is used in order to create a tree object from a serialized byte array.</p>
+
+<dl class="api">
+<dt><code>TCTREE *tctreeload(const void *<var>ptr</var>, int <var>size</var>, TCCMP <var>cmp</var>, void *<var>cmpop</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region of serialized byte array.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>cmp</var>' specifies the pointer to the custom comparison function.</dd>
+<dd>`<var>cmpop</var>' specifies an arbitrary pointer to be given as a parameter of the comparison function.</dd>
+<dd>If it is not needed, `NULL' can be specified.</dd>
+<dd>The return value is a new tree object.</dd>
+<dd>Because the object of the return value is created with the function `tctreenew', it should be deleted with the function `tctreedel' when it is no longer in use.</dd>
+</dl>
+
+<h3 id="tcutilapi_mdbapi">オンメモリハッシュデータベースのAPI(英語御免)</h3>
+
+<p>The function `tcmdbnew' is used in order to create an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>TCMDB *tcmdbnew(void);</code></dt>
+<dd>The return value is the new on-memory hash database object.</dd>
+<dd>The object can be shared by plural threads because of the internal mutex.</dd>
+</dl>
+
+<p>The function `tcmdbnew2' is used in order to create an on-memory hash database object with specifying the number of the buckets.</p>
+
+<dl class="api">
+<dt><code>TCMDB *tcmdbnew2(uint32_t <var>bnum</var>);</code></dt>
+<dd>`<var>bnum</var>' specifies the number of the buckets.</dd>
+<dd>The return value is the new on-memory hash database object.</dd>
+<dd>The object can be shared by plural threads because of the internal mutex.</dd>
+</dl>
+
+<p>The function `tcmdbdel' is used in order to delete an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>void tcmdbdel(TCMDB *<var>mdb</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+</dl>
+
+<p>The function `tcmdbput' is used in order to store a record into an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>void tcmdbput(TCMDB *<var>mdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcmdbput2' is used in order to store a string record into an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>void tcmdbput2(TCMDB *<var>mdb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcmdbputkeep' is used in order to store a new record into an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tcmdbputkeep(TCMDB *<var>mdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcmdbputkeep2' is used in order to store a new string record into an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tcmdbputkeep2(TCMDB *<var>mdb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcmdbputcat' is used in order to concatenate a value at the end of the existing record in an on-memory hash database.</p>
+
+<dl class="api">
+<dt><code>void tcmdbputcat(TCMDB *<var>mdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcmdbputcat2' is used in order to concatenate a string at the end of the existing record in an on-memory hash database.</p>
+
+<dl class="api">
+<dt><code>void tcmdbputcat2(TCMDB *<var>mdb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcmdbout' is used in order to remove a record of an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tcmdbout(TCMDB *<var>mdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is true.  False is returned when no record corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `tcmdbout2' is used in order to remove a string record of an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tcmdbout2(TCMDB *<var>mdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is true.  False is returned when no record corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `tcmdbget' is used in order to retrieve a record in an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>void *tcmdbget(TCMDB *<var>mdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned when no record corresponds.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcmdbget2' is used in order to retrieve a string record in an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>char *tcmdbget2(TCMDB *<var>mdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the string of the value of the corresponding record.  `NULL' is returned when no record corresponds.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcmdbvsiz' is used in order to get the size of the value of a record in an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>int tcmdbvsiz(TCMDB *<var>mdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tcmdbvsiz2' is used in order to get the size of the value of a string record in an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>int tcmdbvsiz2(TCMDB *<var>mdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tcmdbiterinit' is used in order to initialize the iterator of an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>void tcmdbiterinit(TCMDB *<var>mdb</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>The iterator is used in order to access the key of every record stored in the on-memory hash database.</dd>
+</dl>
+
+<p>The function `tcmdbiternext' is used in order to get the next key of the iterator of an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>void *tcmdbiternext(TCMDB *<var>mdb</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return</dd>
+<dd>value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record can be fetched from the iterator.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  The order of iteration is assured to be the same as the stored order.</dd>
+</dl>
+
+<p>The function `tcmdbiternext2' is used in order to get the next key string of the iterator of an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>char *tcmdbiternext2(TCMDB *<var>mdb</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record can be fetched from the iterator.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  The order of iteration is assured to be the same as the stored order.</dd>
+</dl>
+
+<p>The function `tcmdbfwmkeys' is used in order to get forward matching keys in an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcmdbfwmkeys(TCMDB *<var>mdb</var>, const void *<var>pbuf</var>, int <var>psiz</var>, int <var>max</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>pbuf</var>' specifies the pointer to the region of the prefix.</dd>
+<dd>`<var>psiz</var>' specifies the size of the region of the prefix.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.</dd>
+</dl>
+
+<p>The function `tcmdbfwmkeys2' is used in order to get forward matching string keys in an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcmdbfwmkeys2(TCMDB *<var>mdb</var>, const char *<var>pstr</var>, int <var>max</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>pstr</var>' specifies the string of the prefix.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.</dd>
+</dl>
+
+<p>The function `tcmdbrnum' is used in order to get the number of records stored in an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcmdbrnum(TCMDB *<var>mdb</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>The return value is the number of the records stored in the database.</dd>
+</dl>
+
+<p>The function `tcmdbmsiz' is used in order to get the total size of memory used in an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcmdbmsiz(TCMDB *<var>mdb</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>The return value is the total size of memory used in the database.</dd>
+</dl>
+
+<p>The function `tcmdbaddint' is used in order to add an integer to a record in an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>int tcmdbaddint(TCMDB *<var>mdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int <var>num</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>The return value is the summation value.</dd>
+<dd>If the corresponding record exists, the value is treated as an integer and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tcmdbadddouble' is used in order to add a real number to a record in an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>double tcmdbadddouble(TCMDB *<var>mdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, double <var>num</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>The return value is the summation value.</dd>
+<dd>If the corresponding record exists, the value is treated as a real number and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tcmdbvanish' is used in order to clear an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>void tcmdbvanish(TCMDB *<var>mdb</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>All records are removed.</dd>
+</dl>
+
+<p>The function `tcmdbcutfront' is used in order to remove front records of an on-memory hash database object.</p>
+
+<dl class="api">
+<dt><code>void tcmdbcutfront(TCMDB *<var>mdb</var>, int <var>num</var>);</code></dt>
+<dd>`<var>mdb</var>' specifies the on-memory hash database object.</dd>
+<dd>`<var>num</var>' specifies the number of records to be removed.</dd>
+</dl>
+
+<h3 id="tcutilapi_ndbapi">オンメモリツリーデータベースのAPI(英語御免)</h3>
+
+<p>The function `tcndbnew' is used in order to create an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>TCNDB *tcndbnew(void);</code></dt>
+<dd>The return value is the new on-memory tree database object.</dd>
+<dd>The object can be shared by plural threads because of the internal mutex.</dd>
+</dl>
+
+<p>The function `tcndbnew2' is used in order to create an on-memory tree database object with specifying the custom comparison function.</p>
+
+<dl class="api">
+<dt><code>TCNDB *tcndbnew2(TCCMP <var>cmp</var>, void *<var>cmpop</var>);</code></dt>
+<dd>`<var>cmp</var>' specifies the pointer to the custom comparison function.</dd>
+<dd>`<var>cmpop</var>' specifies an arbitrary pointer to be given as a parameter of the comparison function.  If it is not needed, `NULL' can be specified.</dd>
+<dd>The return value is the new on-memory tree database object.</dd>
+<dd>The default comparison function compares keys of two records by lexical order.  The functions `tccmplexical' (dafault), `tccmpdecimal', `tccmpint32', and `tccmpint64' are built-in.  The object can be shared by plural threads because of the internal mutex.</dd>
+</dl>
+
+<p>The function `tcndbdel' is used in order to delete an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>void tcndbdel(TCNDB *<var>ndb</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+</dl>
+
+<p>The function `tcndbput' is used in order to store a record into an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>void tcndbput(TCNDB *<var>ndb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcndbput2' is used in order to store a string record into an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>void tcndbput2(TCNDB *<var>ndb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcndbputkeep' is used in order to store a new record into an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcndbputkeep(TCNDB *<var>ndb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcndbputkeep2' is used in order to store a new string record into an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcndbputkeep2(TCNDB *<var>ndb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcndbputcat' is used in order to concatenate a value at the end of the existing record in an on-memory tree database.</p>
+
+<dl class="api">
+<dt><code>void tcndbputcat(TCNDB *<var>ndb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcndbputcat2' is used in order to concatenate a string at the end of the existing record in an on-memory tree database.</p>
+
+<dl class="api">
+<dt><code>void tcndbputcat2(TCNDB *<var>ndb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcndbout' is used in order to remove a record of an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcndbout(TCNDB *<var>ndb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is true.  False is returned when no record corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `tcndbout2' is used in order to remove a string record of an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcndbout2(TCNDB *<var>ndb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is true.  False is returned when no record corresponds to the specified key.</dd>
+</dl>
+
+<p>The function `tcndbget' is used in order to retrieve a record in an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>void *tcndbget(TCNDB *<var>ndb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned when no record corresponds.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcndbget2' is used in order to retrieve a string record in an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>char *tcndbget2(TCNDB *<var>ndb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the string of the value of the corresponding record.  `NULL' is returned when no record corresponds.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcndbvsiz' is used in order to get the size of the value of a record in an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>int tcndbvsiz(TCNDB *<var>ndb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tcndbvsiz2' is used in order to get the size of the value of a string record in an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>int tcndbvsiz2(TCNDB *<var>ndb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tcndbiterinit' is used in order to initialize the iterator of an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>void tcndbiterinit(TCNDB *<var>ndb</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>The iterator is used in order to access the key of every record stored in the on-memory database.</dd>
+</dl>
+
+<p>The function `tcndbiternext' is used in order to get the next key of the iterator of an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>void *tcndbiternext(TCNDB *<var>ndb</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record can be fetched from the iterator.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  The order of iteration is assured to be the same as the stored order.</dd>
+</dl>
+
+<p>The function `tcndbiternext2' is used in order to get the next key string of the iterator of an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>char *tcndbiternext2(TCNDB *<var>ndb</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record can be fetched from the iterator.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  The order of iteration is assured to be the same as the stored order.</dd>
+</dl>
+
+<p>The function `tcndbfwmkeys' is used in order to get forward matching keys in an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcndbfwmkeys(TCNDB *<var>ndb</var>, const void *<var>pbuf</var>, int <var>psiz</var>, int <var>max</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>pbuf</var>' specifies the pointer to the region of the prefix.</dd>
+<dd>`<var>psiz</var>' specifies the size of the region of the prefix.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcndbfwmkeys2' is used in order to get forward matching string keys in an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcndbfwmkeys2(TCNDB *<var>ndb</var>, const char *<var>pstr</var>, int <var>max</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>pstr</var>' specifies the string of the prefix.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcndbrnum' is used in order to get the number of records stored in an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcndbrnum(TCNDB *<var>ndb</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>The return value is the number of the records stored in the database.</dd>
+</dl>
+
+<p>The function `tcndbmsiz' is used in order to get the total size of memory used in an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcndbmsiz(TCNDB *<var>ndb</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>The return value is the total size of memory used in the database.</dd>
+</dl>
+
+<p>The function `tcndbaddint' is used in order to add an integer to a record in an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>int tcndbaddint(TCNDB *<var>ndb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int <var>num</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>The return value is the summation value.</dd>
+<dd>If the corresponding record exists, the value is treated as an integer and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tcndbadddouble' is used in order to add a real number to a record in an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>double tcndbadddouble(TCNDB *<var>ndb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, double <var>num</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>The return value is the summation value.</dd>
+<dd>If the corresponding record exists, the value is treated as a real number and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tcndbvanish' is used in order to clear an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>void tcndbvanish(TCNDB *<var>ndb</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>All records are removed.</dd>
+</dl>
+
+<p>The function `tcndbcutfringe' is used in order to remove fringe records of an on-memory tree database object.</p>
+
+<dl class="api">
+<dt><code>void tcndbcutfringe(TCNDB *<var>ndb</var>, int <var>num</var>);</code></dt>
+<dd>`<var>ndb</var>' specifies the on-memory tree database object.</dd>
+<dd>`<var>num</var>' specifies the number of records to be removed.</dd>
+</dl>
+
+<h3 id="tcutilapi_mpoolapi">メモリプールのAPI(英語御免)</h3>
+
+<p>The function `tcmpoolnew' is used in order to create a memory pool object.</p>
+
+<dl class="api">
+<dt><code>TCMPOOL *tcmpoolnew(void);</code></dt>
+<dd>The return value is the new memory pool object.</dd>
+</dl>
+
+<p>The function `tcmpooldel' is used in order to delete a memory pool object.</p>
+
+<dl class="api">
+<dt><code>void tcmpooldel(TCMPOOL *<var>mpool</var>);</code></dt>
+<dd>`<var>mpool</var>' specifies the memory pool object.</dd>
+<dd>Note that the deleted object and its derivatives can not be used anymore.</dd>
+</dl>
+
+<p>The function `tcmpoolpush' is used in order to relegate an arbitrary object to a memory pool object.</p>
+
+<dl class="api">
+<dt><code>void *tcmpoolpush(TCMPOOL *<var>mpool</var>, void *<var>ptr</var>, void (*<var>del</var>)(void *));</code></dt>
+<dd>`<var>mpool</var>' specifies the memory pool object.</dd>
+<dd>`<var>ptr</var>' specifies the pointer to the object to be relegated.  If it is `NULL', this function has no effect.</dd>
+<dd>`<var>del</var>' specifies the pointer to the function to delete the object.</dd>
+<dd>The return value is the pointer to the given object.</dd>
+<dd>This function assures that the specified object is deleted when the memory pool object is deleted.</dd>
+</dl>
+
+<p>The function `tcmpoolpushptr' is used in order to relegate an allocated region to a memory pool object.</p>
+
+<dl class="api">
+<dt><code>void *tcmpoolpushptr(TCMPOOL *<var>mpool</var>, void *<var>ptr</var>);</code></dt>
+<dd>`<var>mpool</var>' specifies the memory pool object.</dd>
+<dd>`<var>ptr</var>' specifies the pointer to the region to be relegated.  If it is `NULL', this function has no effect.</dd>
+<dd>The return value is the pointer to the given object.</dd>
+<dd>This function assures that the specified region is released when the memory pool object is deleted.</dd>
+</dl>
+
+<p>The function `tcmpoolpushxstr' is used in order to relegate an extensible string object to a memory pool object.</p>
+
+<dl class="api">
+<dt><code>TCXSTR *tcmpoolpushxstr(TCMPOOL *<var>mpool</var>, TCXSTR *<var>xstr</var>);</code></dt>
+<dd>`<var>mpool</var>' specifies the memory pool object.</dd>
+<dd>`<var>xstr</var>' specifies the extensible string object.  If it is `NULL', this function has no effect.</dd>
+<dd>The return value is the pointer to the given object.</dd>
+<dd>This function assures that the specified object is deleted when the memory pool object is deleted.</dd>
+</dl>
+
+<p>The function `tcmpoolpushlist' is used in order to relegate a list object to a memory pool object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcmpoolpushlist(TCMPOOL *<var>mpool</var>, TCLIST *<var>list</var>);</code></dt>
+<dd>`<var>mpool</var>' specifies the memory pool object.</dd>
+<dd>`<var>list</var>' specifies the list object.  If it is `NULL', this function has no effect.</dd>
+<dd>The return value is the pointer to the given object.</dd>
+<dd>This function assures that the specified object is deleted when the memory pool object is deleted.</dd>
+</dl>
+
+<p>The function `tcmpoolpushmap' is used in order to relegate a map object to a memory pool object.</p>
+
+<dl class="api">
+<dt><code>TCMAP *tcmpoolpushmap(TCMPOOL *<var>mpool</var>, TCMAP *<var>map</var>);</code></dt>
+<dd>`<var>mpool</var>' specifies the memory pool object.</dd>
+<dd>`<var>map</var>' specifies the map object.  If it is `NULL', this function has no effect.</dd>
+<dd>The return value is the pointer to the given object.</dd>
+<dd>This function assures that the specified object is deleted when the memory pool object is deleted.</dd>
+</dl>
+
+<p>The function `tcmpoolpushtree' is used in order to relegate a tree object to a memory pool object.</p>
+
+<dl class="api">
+<dt><code>TCTREE *tcmpoolpushtree(TCMPOOL *<var>mpool</var>, TCTREE *<var>tree</var>);</code></dt>
+<dd>`<var>mpool</var>' specifies the memory pool object.</dd>
+<dd>`<var>tree</var>' specifies the tree object.  If it is `NULL', this function has no effect.</dd>
+<dd>The return value is the pointer to the given object.</dd>
+<dd>This function assures that the specified object is deleted when the memory pool object is deleted.</dd>
+</dl>
+
+<p>The function `tcmpoolmalloc' is used in order to allocate a region relegated to a memory pool object.</p>
+
+<dl class="api">
+<dt><code>void *tcmpoolmalloc(TCMPOOL *<var>mpool</var>, size_t <var>size</var>);</code></dt>
+<dd>`<var>mpool</var>' specifies the memory pool object.</dd>
+<dd>The return value is the pointer to the allocated region under the memory pool.</dd>
+</dl>
+
+<p>The function `tcmpoolxstrnew' is used in order to create an extensible string object relegated to a memory pool object.</p>
+
+<dl class="api">
+<dt><code>TCXSTR *tcmpoolxstrnew(TCMPOOL *<var>mpool</var>);</code></dt>
+<dd>The return value is the new extensible string object under the memory pool.</dd>
+</dl>
+
+<p>The function `tcmpoollistnew' is used in order to create a list object relegated to a memory pool object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcmpoollistnew(TCMPOOL *<var>mpool</var>);</code></dt>
+<dd>The return value is the new list object under the memory pool.</dd>
+</dl>
+
+<p>The function `tcmpoolmapnew' is used in order to create a map object relegated to a memory pool object.</p>
+
+<dl class="api">
+<dt><code>TCMAP *tcmpoolmapnew(TCMPOOL *<var>mpool</var>);</code></dt>
+<dd>The return value is the new map object under the memory pool.</dd>
+</dl>
+
+<p>The function `tcmpooltreenew' is used in order to create a tree object relegated to a memory pool object.</p>
+
+<dl class="api">
+<dt><code>TCTREE *tcmpooltreenew(TCMPOOL *<var>mpool</var>);</code></dt>
+<dd>The return value is the new tree object under the memory pool.</dd>
+</dl>
+
+<p>The function `tcmpoolpop' is used in order to remove the most recently installed cleanup handler of a memory pool object.</p>
+
+<dl class="api">
+<dt><code>void tcmpoolpop(TCMPOOL *<var>mpool</var>, bool <var>exe</var>);</code></dt>
+<dd>`<var>mpool</var>' specifies the memory pool object.</dd>
+<dd>`<var>exe</var>' specifies whether to execute the destructor of the removed handler.</dd>
+</dl>
+
+<p>The function `tcmpoolclear' is used in order to remove all cleanup handler of a memory pool object.</p>
+
+<dl class="api">
+<dt><code>void tcmpoolclear(TCMPOOL *<var>mpool</var>, bool <var>exe</var>);</code></dt>
+<dd>`<var>mpool</var>' specifies the memory pool object.</dd>
+<dd>`<var>exe</var>' specifies whether to execute the destructors of the removed handlers.</dd>
+</dl>
+
+<p>The function `tcmpoolglobal' is used in order to get the global memory pool object.</p>
+
+<dl class="api">
+<dt><code>TCMPOOL *tcmpoolglobal(void);</code></dt>
+<dd>The return value is the global memory pool object.</dd>
+<dd>The global memory pool object is a singleton and assured to be deleted when the process is terminating normally.</dd>
+</dl>
+
+<h3 id="tcutilapi_miscapi">雑多なユーティリティのAPI(英語御免)</h3>
+
+<p>The function `tclmax' is used in order to get the larger value of two integers.</p>
+
+<dl class="api">
+<dt><code>long tclmax(long <var>a</var>, long <var>b</var>);</code></dt>
+<dd>`<var>a</var>' specifies an integer.</dd>
+<dd>`<var>b</var>' specifies the other integer.</dd>
+<dd>The return value is the larger value of the two.</dd>
+</dl>
+
+<p>The function `tclmin' is used in order to get the lesser value of two integers.</p>
+
+<dl class="api">
+<dt><code>long tclmin(long <var>a</var>, long <var>b</var>);</code></dt>
+<dd>`<var>a</var>' specifies an integer.</dd>
+<dd>`<var>b</var>' specifies the other integer.</dd>
+<dd>The return value is the lesser value of the two.</dd>
+</dl>
+
+<p>The function `tclrand' is used in order to get a random number as long integer based on uniform distribution.</p>
+
+<dl class="api">
+<dt><code>unsigned long tclrand(void);</code></dt>
+<dd>The return value is the random number between 0 and `ULONG_MAX'.</dd>
+<dd>This function uses the random number source device and generates a real random number if possible.</dd>
+</dl>
+
+<p>The function `tcdrand' is used in order to get a random number as double decimal based on uniform distribution.</p>
+
+<dl class="api">
+<dt><code>double tcdrand(void);</code></dt>
+<dd>The return value is the random number equal to or greater than 0, and less than 1.0.</dd>
+<dd>This function uses the random number source device and generates a real random number if possible.</dd>
+</dl>
+
+<p>The function `tcdrandnd' is used in order to get a random number as double decimal based on normal distribution.</p>
+
+<dl class="api">
+<dt><code>double tcdrandnd(double <var>avg</var>, double <var>sd</var>);</code></dt>
+<dd>`<var>avg</var>' specifies the average.</dd>
+<dd>`<var>sd</var>' specifies the standard deviation.</dd>
+<dd>The return value is the random number.</dd>
+<dd>This function uses the random number source device and generates a real random number if possible.</dd>
+</dl>
+
+<p>The function `tcstricmp' is used in order to compare two strings with case insensitive evaluation.</p>
+
+<dl class="api">
+<dt><code>int tcstricmp(const char *<var>astr</var>, const char *<var>bstr</var>);</code></dt>
+<dd>`<var>astr</var>' specifies a string.</dd>
+<dd>`<var>bstr</var>' specifies of the other string.</dd>
+<dd>The return value is positive if the former is big, negative if the latter is big, 0 if both are equivalent.</dd>
+</dl>
+
+<p>The function `tcstrfwm' is used in order to check whether a string begins with a key.</p>
+
+<dl class="api">
+<dt><code>bool tcstrfwm(const char *<var>str</var>, const char *<var>key</var>);</code></dt>
+<dd>`<var>str</var>' specifies the target string.</dd>
+<dd>`<var>key</var>' specifies the forward matching key string.</dd>
+<dd>The return value is true if the target string begins with the key, else, it is false.</dd>
+</dl>
+
+<p>The function `tcstrifwm' is used in order to check whether a string begins with a key with case insensitive evaluation.</p>
+
+<dl class="api">
+<dt><code>bool tcstrifwm(const char *<var>str</var>, const char *<var>key</var>);</code></dt>
+<dd>`<var>str</var>' specifies the target string.</dd>
+<dd>`<var>key</var>' specifies the forward matching key string.</dd>
+<dd>The return value is true if the target string begins with the key, else, it is false.</dd>
+</dl>
+
+<p>The function `tcstrbwm' is used in order to check whether a string ends with a key.</p>
+
+<dl class="api">
+<dt><code>bool tcstrbwm(const char *<var>str</var>, const char *<var>key</var>);</code></dt>
+<dd>`<var>str</var>' specifies the target string.</dd>
+<dd>`<var>key</var>' specifies the backward matching key string.</dd>
+<dd>The return value is true if the target string ends with the key, else, it is false.</dd>
+</dl>
+
+<p>The function `tcstribwm' is used in order to check whether a string ends with a key with case insensitive evaluation.</p>
+
+<dl class="api">
+<dt><code>bool tcstribwm(const char *<var>str</var>, const char *<var>key</var>);</code></dt>
+<dd>`<var>str</var>' specifies the target string.</dd>
+<dd>`<var>key</var>' specifies the backward matching key string.</dd>
+<dd>The return value is true if the target string ends with the key, else, it is false.</dd>
+</dl>
+
+<p>The function `tcstrdist' is used in order to calculate the edit distance of two strings.</p>
+
+<dl class="api">
+<dt><code>int tcstrdist(const char *<var>astr</var>, const char *<var>bstr</var>);</code></dt>
+<dd>`<var>astr</var>' specifies a string.</dd>
+<dd>`<var>bstr</var>' specifies of the other string.</dd>
+<dd>The return value is the edit distance which is known as the Levenshtein distance.  The cost is calculated by byte.</dd>
+</dl>
+
+<p>The function `tcstrdistutf' is used in order to calculate the edit distance of two UTF-8 strings.</p>
+
+<dl class="api">
+<dt><code>int tcstrdistutf(const char *<var>astr</var>, const char *<var>bstr</var>);</code></dt>
+<dd>`<var>astr</var>' specifies a string.</dd>
+<dd>`<var>bstr</var>' specifies of the other string.</dd>
+<dd>The return value is the edit distance which is known as the Levenshtein distance.  The cost is calculated by Unicode character.</dd>
+</dl>
+
+<p>The function `tcstrtoupper' is used in order to convert the letters of a string into upper case.</p>
+
+<dl class="api">
+<dt><code>char *tcstrtoupper(char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string to be converted.</dd>
+<dd>The return value is the string itself.</dd>
+</dl>
+
+<p>The function `tcstrtolower' is used in order to convert the letters of a string into lower case.</p>
+
+<dl class="api">
+<dt><code>char *tcstrtolower(char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string to be converted.</dd>
+<dd>The return value is the string itself.</dd>
+</dl>
+
+<p>The function `tcstrtrim' is used in order to cut space characters at head or tail of a string.</p>
+
+<dl class="api">
+<dt><code>char *tcstrtrim(char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string to be converted.</dd>
+<dd>The return value is the string itself.</dd>
+</dl>
+
+<p>The function `tcstrsqzspc' is used in order to squeeze space characters in a string and trim it.</p>
+
+<dl class="api">
+<dt><code>char *tcstrsqzspc(char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string to be converted.</dd>
+<dd>The return value is the string itself.</dd>
+</dl>
+
+<p>The function `tcstrsubchr' is used in order to substitute characters in a string.</p>
+
+<dl class="api">
+<dt><code>char *tcstrsubchr(char *<var>str</var>, const char *<var>rstr</var>, const char *<var>sstr</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string to be converted.</dd>
+<dd>`<var>rstr</var>' specifies the string containing characters to be replaced.</dd>
+<dd>`<var>sstr</var>' specifies the string containing characters to be substituted.</dd>
+<dd>If the substitute string is shorter then the replacement string, corresponding characters are removed.</dd>
+</dl>
+
+<p>The function `tcstrcntutf' is used in order to count the number of characters in a string of UTF-8.</p>
+
+<dl class="api">
+<dt><code>int tcstrcntutf(const char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string of UTF-8.</dd>
+<dd>The return value is the number of characters in the string.</dd>
+</dl>
+
+<p>The function `tcstrcututf' is used in order to cut a string of UTF-8 at the specified number of characters.</p>
+
+<dl class="api">
+<dt><code>char *tcstrcututf(char *<var>str</var>, int <var>num</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string of UTF-8.</dd>
+<dd>`<var>num</var>' specifies the number of characters to be kept.</dd>
+<dd>The return value is the string itself.</dd>
+</dl>
+
+<p>The function `tcstrutftoucs' is used in order to convert a UTF-8 string into a UCS-2 array.</p>
+
+<dl class="api">
+<dt><code>void tcstrutftoucs(const char *<var>str</var>, uint16_t *<var>ary</var>, int *<var>np</var>);</code></dt>
+<dd>`<var>str</var>' specifies the UTF-8 string.</dd>
+<dd>`<var>ary</var>' specifies the pointer to the region into which the result UCS-2 codes are written.  The size of the buffer should be sufficient.</dd>
+<dd>`<var>np</var>' specifies the pointer to a variable into which the number of elements of the result array is assigned.</dd>
+</dl>
+
+<p>The function `tcstrucstoutf' is used in order to convert a UCS-2 array into a UTF-8 string.</p>
+
+<dl class="api">
+<dt><code>int tcstrucstoutf(const uint16_t *<var>ary</var>, int <var>num</var>, char *<var>str</var>);</code></dt>
+<dd>`<var>ary</var>' specifies the array of UCS-2 codes.</dd>
+<dd>`<var>num</var>' specifies the number of the array.</dd>
+<dd>`<var>str</var>' specifies the pointer to the region into which the result UTF-8 string is written.  The size of the buffer should be sufficient.</dd>
+<dd>The return value is the length of the result string.</dd>
+</dl>
+
+<p>The function `tcstrsplit' is used in order to create a list object by splitting a string.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcstrsplit(const char *<var>str</var>, const char *<var>delims</var>);</code></dt>
+<dd>`<var>str</var>' specifies the source string.</dd>
+<dd>`<var>delims</var>' specifies a string containing delimiting characters.</dd>
+<dd>The return value is a list object of the split elements.</dd>
+<dd>If two delimiters are successive, it is assumed that an empty element is between the two.  Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcstrjoin' is used in order to create a string by joining all elements of a list object.</p>
+
+<dl class="api">
+<dt><code>char *tcstrjoin(const TCLIST *<var>list</var>, char <var>delim</var>);</code></dt>
+<dd>`<var>list</var>' specifies a list object.</dd>
+<dd>`<var>delim</var>' specifies a delimiting character.</dd>
+<dd>The return value is the result string.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcatoi' is used in order to convert a string to an integer.</p>
+
+<dl class="api">
+<dt><code>int64_t tcatoi(const char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string.</dd>
+<dd>The return value is the integer.  If the string does not contain numeric expression, 0 is returned.</dd>
+<dd>This function is equivalent to `atoll' except that it does not depend on the locale.</dd>
+</dl>
+
+<p>The function `tcatoix' is used in order to convert a string with a metric prefix to an integer.</p>
+
+<dl class="api">
+<dt><code>int64_t tcatoix(const char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string, which can be trailed by a binary metric prefix.  "K", "M", "G", "T", "P", and "E" are supported.  They are case-insensitive.</dd>
+<dd>The return value is the integer.  If the string does not contain numeric expression, 0 is returned.  If the integer overflows the domain, `INT64_MAX' or `INT64_MIN' is returned according to the sign.</dd>
+</dl>
+
+<p>The function `tcatof' is used in order to convert a string to a real number.</p>
+
+<dl class="api">
+<dt><code>double tcatof(const char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string.</dd>
+<dd>The return value is the real number.  If the string does not contain numeric expression, 0.0 is returned.</dd>
+<dd>This function is equivalent to `atof' except that it does not depend on the locale.</dd>
+</dl>
+
+<p>The function `tcregexmatch' is used in order to check whether a string matches a regular expression.</p>
+
+<dl class="api">
+<dt><code>bool tcregexmatch(const char *<var>str</var>, const char *<var>regex</var>);</code></dt>
+<dd>`<var>str</var>' specifies the target string.</dd>
+<dd>`<var>regex</var>' specifies the regular expression string.  If it begins with `*', the trailing substring is used as a case-insensitive regular expression.</dd>
+<dd>The return value is true if matching is success, else, it is false.</dd>
+</dl>
+
+<p>The function `tcregexreplace' is used in order to replace each substring matching a regular expression string.</p>
+
+<dl class="api">
+<dt><code>char *tcregexreplace(const char *<var>str</var>, const char *<var>regex</var>, const char *<var>alt</var>);</code></dt>
+<dd>`<var>str</var>' specifies the target string.</dd>
+<dd>`<var>regex</var>' specifies the regular expression string for substrings.  If it begins with `*', the trailing substring is used as a case-insensitive regular expression.</dd>
+<dd>`<var>alt</var>' specifies the alternative string with which each substrings is replaced.  Each `&amp;' in the string is replaced with the matched substring.  Each `\' in the string escapes the following character.  Special escapes "\1" through "\9" referring to the corresponding matching sub-expressions in the regular expression string are supported.</dd>
+<dd>The return value is a new converted string.  Even if the regular expression is invalid, a copy of the original string is returned.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcmd5hash' is used in order to get the MD5 hash value of a serial object.</p>
+
+<dl class="api">
+<dt><code>void tcmd5hash(const void *<var>ptr</var>, int <var>size</var>, char *<var>buf</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>buf</var>' specifies the pointer to the region into which the result string is written.  The size of the buffer should be equal to or more than 48 bytes.</dd>
+</dl>
+
+<p>The function `tcarccipher' is used in order to cipher or decipher a serial object with the Arcfour stream cipher.</p>
+
+<dl class="api">
+<dt><code>void tcarccipher(const void *<var>ptr</var>, int <var>size</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, void *<var>obuf</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the cipher key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the cipher key.</dd>
+<dd>`<var>obuf</var>' specifies the pointer to the region into which the result data is written.  The size of the buffer should be equal to or more than the input region.</dd>
+</dl>
+
+<p>The function `tctime' is used in order to get the time of day in seconds.</p>
+
+<dl class="api">
+<dt><code>double tctime(void);</code></dt>
+<dd>The return value is the time of day in seconds.  The accuracy is in microseconds.</dd>
+</dl>
+
+<p>The function `tccalendar' is used in order to get the Gregorian calendar of a time.</p>
+
+<dl class="api">
+<dt><code>void tccalendar(int64_t <var>t</var>, int <var>jl</var>, int *<var>yearp</var>, int *<var>monp</var>, int *<var>dayp</var>, int *<var>hourp</var>, int *<var>minp</var>, int *<var>secp</var>);</code></dt>
+<dd>`<var>t</var>' specifies the source time in seconds from the epoch.  If it is `INT64_MAX', the current time is specified.</dd>
+<dd>`<var>jl</var>' specifies the jet lag of a location in seconds.  If it is `INT_MAX', the local jet lag is specified.</dd>
+<dd>`<var>yearp</var>' specifies the pointer to a variable to which the year is assigned.  If it is `NULL', it is not used.</dd>
+<dd>`<var>monp</var>' specifies the pointer to a variable to which the month is assigned.  If it is `NULL', it is not used.  1 means January and 12 means December.</dd>
+<dd>`<var>dayp</var>' specifies the pointer to a variable to which the day of the month is assigned.  If it is `NULL', it is not used.</dd>
+<dd>`<var>hourp</var>' specifies the pointer to a variable to which the hours is assigned.  If it is `NULL', it is not used.</dd>
+<dd>`<var>minp</var>' specifies the pointer to a variable to which the minutes is assigned.  If it is `NULL', it is not used.</dd>
+<dd>`<var>secp</var>' specifies the pointer to a variable to which the seconds is assigned.  If it is `NULL', it is not used.</dd>
+</dl>
+
+<p>The function `tcdatestrwww' is used in order to format a date as a string in W3CDTF.</p>
+
+<dl class="api">
+<dt><code>void tcdatestrwww(int64_t <var>t</var>, int <var>jl</var>, char *<var>buf</var>);</code></dt>
+<dd>`<var>t</var>' specifies the source time in seconds from the epoch.  If it is `INT64_MAX', the current time is specified.</dd>
+<dd>`<var>jl</var>' specifies the jet lag of a location in seconds.  If it is `INT_MAX', the local jet lag is specified.</dd>
+<dd>`<var>buf</var>' specifies the pointer to the region into which the result string is written.  The size of the buffer should be equal to or more than 48 bytes.</dd>
+<dd>W3CDTF represents a date as "YYYY-MM-DDThh:mm:ddTZD".</dd>
+</dl>
+
+<p>The function `tcdatestrhttp' is used in order to format a date as a string in RFC 1123 format.</p>
+
+<dl class="api">
+<dt><code>void tcdatestrhttp(int64_t <var>t</var>, int <var>jl</var>, char *<var>buf</var>);</code></dt>
+<dd>`<var>t</var>' specifies the source time in seconds from the epoch.  If it is `INT64_MAX', the current time is specified.</dd>
+<dd>`<var>jl</var>' specifies the jet lag of a location in seconds.  If it is `INT_MAX', the local jet lag is specified.</dd>
+<dd>`<var>buf</var>' specifies the pointer to the region into which the result string is written.  The size of the buffer should be equal to or more than 48 bytes.</dd>
+<dd>RFC 1123 format represents a date as "Wdy, DD-Mon-YYYY hh:mm:dd TZD".</dd>
+</dl>
+
+<p>The function `tcstrmktime' is used in order to get the time value of a date string.</p>
+
+<dl class="api">
+<dt><code>int64_t tcstrmktime(const char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the date string in decimal, hexadecimal, W3CDTF, or RFC 822 (1123).  Decimal can be trailed by "s" for in seconds, "m" for in minutes, "h" for in hours, and "d" for in days.</dd>
+<dd>The return value is the time value of the date or `INT64_MIN' if the format is invalid.</dd>
+</dl>
+
+<p>The function `tcjetlag' is used in order to get the jet lag of the local time.</p>
+
+<dl class="api">
+<dt><code>int tcjetlag(void);</code></dt>
+<dd>The return value is the jet lag of the local time in seconds.</dd>
+</dl>
+
+<p>The function `tcdayofweek' is used in order to get the day of week of a date.</p>
+
+<dl class="api">
+<dt><code>int tcdayofweek(int <var>year</var>, int <var>mon</var>, int <var>day</var>);</code></dt>
+<dd>`<var>year</var>' specifies the year of a date.</dd>
+<dd>`<var>mon</var>' specifies the month of the date.</dd>
+<dd>`<var>day</var>' specifies the day of the date.</dd>
+<dd>The return value is the day of week of the date.  0 means Sunday and 6 means Saturday.</dd>
+</dl>
+
+<h3 id="tcutilapi_fsapi">ファイルシステム関連ユーティリティのAPI(英語御免)</h3>
+
+<p>The function `tcrealpath' is used in order to get the canonicalized absolute path of a file.</p>
+
+<dl class="api">
+<dt><code>char *tcrealpath(const char *<var>path</var>);</code></dt>
+<dd>`<var>path</var>' specifies the path of the file.</dd>
+<dd>The return value is the canonicalized absolute path of a file, or `NULL' if the path is invalid.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcstatfile' is used in order to get the status information of a file.</p>
+
+<dl class="api">
+<dt><code>bool tcstatfile(const char *<var>path</var>, bool *<var>isdirp</var>, int64_t *<var>sizep</var>, int64_t *<var>mtimep</var>);</code></dt>
+<dd>`<var>path</var>' specifies the path of the file.</dd>
+<dd>`<var>isdirp</var>' specifies the pointer to a variable into which whether the file is a directory is assigned.  If it is `NULL', it is ignored.</dd>
+<dd>`<var>sizep</var>' specifies the pointer to a variable into which the size of the file is assigned.  If it is `NULL', it is ignored.</dd>
+<dd>`<var>ntimep</var>' specifies the pointer to a variable into which the size of the file is assigned.  If it is `NULL', it is ignored.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcreadfile' is used in order to read whole data of a file.</p>
+
+<dl class="api">
+<dt><code>void *tcreadfile(const char *<var>path</var>, int <var>limit</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>path</var>' specifies the path of the file.  If it is `NULL', the standard input is specified.</dd>
+<dd>`<var>limit</var>' specifies the limiting size of reading data.  If it is not more than 0, the limitation is not specified.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.  If it is `NULL', it is not used.</dd>
+<dd>The return value is the pointer to the allocated region of the read data, or `NULL' if the file could not be opened.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when when is no longer in use.</dd>
+</dl>
+
+<p>The function `tcreadfilelines' is used in order to read every line of a file.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcreadfilelines(const char *<var>path</var>);</code></dt>
+<dd>`<var>path</var>' specifies the path of the file.  If it is `NULL', the standard input is specified.</dd>
+<dd>The return value is a list object of every lines if successful, else it is `NULL'.</dd>
+<dd>Line separators are cut out.  Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcwritefile' is used in order to write data into a file.</p>
+
+<dl class="api">
+<dt><code>bool tcwritefile(const char *<var>path</var>, const void *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>path</var>' specifies the path of the file.  If it is `NULL', the standard output is specified.</dd>
+<dd>`<var>ptr</var>' specifies the pointer to the data region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tccopyfile' is used in order to copy a file.</p>
+
+<dl class="api">
+<dt><code>bool tccopyfile(const char *<var>src</var>, const char *<var>dest</var>);</code></dt>
+<dd>`<var>src</var>' specifies the path of the source file.</dd>
+<dd>`<var>dest</var>' specifies the path of the destination file.</dd>
+<dd>The return value is true if successful, else, it is false.</dd>
+<dd>If the destination file exists, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcreaddir' is used in order to read names of files in a directory.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcreaddir(const char *<var>path</var>);</code></dt>
+<dd>`<var>path</var>' specifies the path of the directory.</dd>
+<dd>The return value is a list object of names if successful, else it is `NULL'.</dd>
+<dd>Links to the directory itself and to the parent directory are ignored.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcglobpat' is used in order to expand a pattern into a list of matched paths.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcglobpat(const char *<var>pattern</var>);</code></dt>
+<dd>`<var>pattern</var>' specifies the matching pattern.</dd>
+<dd>The return value is a list object of matched paths.  If no path is matched, an empty list is returned.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcremovelink' is used in order to remove a file or a directory and its sub ones recursively.</p>
+
+<dl class="api">
+<dt><code>bool tcremovelink(const char *<var>path</var>);</code></dt>
+<dd>`<var>path</var>' specifies the path of the link.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned when the link does not exist or the permission is denied.</dd>
+</dl>
+
+<p>The function `tcwrite' is used in order to write data into a file.</p>
+
+<dl class="api">
+<dt><code>bool tcwrite(int <var>fd</var>, const void *<var>buf</var>, size_t <var>size</var>);</code></dt>
+<dd>`<var>fd</var>' specifies the file descriptor.</dd>
+<dd>`<var>buf</var>' specifies the buffer to be written.</dd>
+<dd>`<var>size</var>' specifies the size of the buffer.</dd>
+<dd>The return value is true if successful, else, it is false.</dd>
+</dl>
+
+<p>The function `tcread' is used in order to read data from a file.</p>
+
+<dl class="api">
+<dt><code>bool tcread(int <var>fd</var>, void *<var>buf</var>, size_t <var>size</var>);</code></dt>
+<dd>`<var>fd</var>' specifies the file descriptor.</dd>
+<dd>`<var>buf</var>' specifies the buffer to store into.</dd>
+<dd>`<var>size</var>' specifies the size of the buffer.</dd>
+<dd>The return value is true if successful, else, it is false.</dd>
+</dl>
+
+<p>The function `tclock' is used in order to lock a file.</p>
+
+<dl class="api">
+<dt><code>bool tclock(int <var>fd</var>, bool <var>ex</var>, bool <var>nb</var>);</code></dt>
+<dd>`<var>fd</var>' specifies the file descriptor.</dd>
+<dd>`<var>ex</var>' specifies whether an exclusive lock or a shared lock is performed.</dd>
+<dd>`<var>nb</var>' specifies whether to request with non-blocking.</dd>
+<dd>The return value is true if successful, else, it is false.</dd>
+</dl>
+
+<p>The function `tcunlock' is used in order to unlock a file.</p>
+
+<dl class="api">
+<dt><code>bool tcunlock(int <var>fd</var>);</code></dt>
+<dd>`<var>fd</var>' specifies the file descriptor.</dd>
+<dd>The return value is true if successful, else, it is false.</dd>
+</dl>
+
+<p>The function `tcsystem' is used in order to execute a shell command.</p>
+
+<dl class="api">
+<dt><code>int tcsystem(const char **<var>args</var>, int <var>anum</var>);</code></dt>
+<dd>`<var>args</var>' specifies an array of the command name and its arguments.</dd>
+<dd>`<var>anum</var>' specifies the number of elements of the array.</dd>
+<dd>The return value is the exit code of the command or `INT_MAX' on failure.</dd>
+<dd>The command name and the arguments are quoted and meta characters are escaped.</dd>
+</dl>
+
+<h3 id="tcutilapi_encapi">エンコーディング関連ユーティリティののAPI(英語御免)</h3>
+
+<p>The function `tcurlencode' is used in order to encode a serial object with URL encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcurlencode(const char *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>The return value is the result string.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if when is no longer in use.</dd>
+</dl>
+
+<p>The function `tcurldecode' is used in order to decode a string encoded with URL encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcurldecode(const char *<var>str</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>str</var>' specifies the encoded string.</dd>
+<dd>`<var>sp</var>' specifies the pointer to a variable into which the size of the region of the return value is assigned.</dd>
+<dd>The return value is the pointer to the region of the result.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcurlbreak' is used in order to break up a URL into elements.</p>
+
+<dl class="api">
+<dt><code>TCMAP *tcurlbreak(const char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the URL string.</dd>
+<dd>The return value is the map object whose keys are the name of elements.  The key "self" indicates the URL itself.  The key "scheme" indicates the scheme.  The key "host" indicates the host of the server.  The key "port" indicates the port number of the server.  The key "authority" indicates the authority information.  The key "path" indicates the path of the resource.  The key "file" indicates the file name without the directory section.  The key "query" indicates the query string.  The key "fragment" indicates the fragment string.</dd>
+<dd>Supported schema are HTTP, HTTPS, FTP, and FILE.  Absolute URL and relative URL are supported.  Because the object of the return value is created with the function `tcmapnew', it should be deleted with the function `tcmapdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcurlresolve' is used in order to resolve a relative URL with an absolute URL.</p>
+
+<dl class="api">
+<dt><code>char *tcurlresolve(const char *<var>base</var>, const char *<var>target</var>);</code></dt>
+<dd>`<var>base</var>' specifies the absolute URL of the base location.</dd>
+<dd>`<var>target</var>' specifies the URL to be resolved.</dd>
+<dd>The return value is the resolved URL.  If the target URL is relative, a new URL of relative location from the base location is returned.  Else, a copy of the target URL is returned.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbaseencode' is used in order to encode a serial object with Base64 encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcbaseencode(const char *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>The return value is the result string.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if when is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbasedecode' is used in order to decode a string encoded with Base64 encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcbasedecode(const char *<var>str</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>str</var>' specifies the encoded string.</dd>
+<dd>`<var>sp</var>' specifies the pointer to a variable into which the size of the region of the return value is assigned.</dd>
+<dd>The return value is the pointer to the region of the result.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcquoteencode' is used in order to encode a serial object with Quoted-printable encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcquoteencode(const char *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>The return value is the result string.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if when is no longer in use.</dd>
+</dl>
+
+<p>The function `tcquotedecode' is used in order to decode a string encoded with Quoted-printable encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcquotedecode(const char *<var>str</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>str</var>' specifies the encoded string.</dd>
+<dd>`<var>sp</var>' specifies the pointer to a variable into which the size of the region of the return value is assigned.</dd>
+<dd>The return value is the pointer to the region of the result.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcmimeencode' is used in order to encode a string with MIME encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcmimeencode(const char *<var>str</var>, const char *<var>encname</var>, bool <var>base</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string.</dd>
+<dd>`<var>encname</var>' specifies the string of the name of the character encoding.</dd>
+<dd>`<var>base</var>' specifies whether to use Base64 encoding.  If it is false, Quoted-printable is used.</dd>
+<dd>The return value is the result string.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcmimedecode' is used in order to decode a string encoded with MIME encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcmimedecode(const char *<var>str</var>, char *<var>enp</var>);</code></dt>
+<dd>`<var>str</var>' specifies the encoded string.</dd>
+<dd>`<var>enp</var>' specifies the pointer to the region into which the name of encoding is written.  If it is `NULL', it is not used.  The size of the buffer should be equal to or more than 32 bytes.</dd>
+<dd>The return value is the result string.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcmimebreak' is used in order to split a string of MIME into headers and the body.</p>
+
+<dl class="api">
+<dt><code>char *tcmimebreak(const char *<var>ptr</var>, int <var>size</var>, TCMAP *<var>headers</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region of MIME data.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>headers</var>' specifies a map object to store headers.  If it is `NULL', it is not used.  Each key of the map is an uncapitalized header name.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>The return value is the pointer to the region of the body data.</dd>
+<dd>If the content type is defined, the header map has the key "TYPE" specifying the type.  If the character encoding is defined, the key "CHARSET" indicates the encoding name.  If the boundary string of multipart is defined, the key "BOUNDARY" indicates the string.  If the content disposition is defined, the key "DISPOSITION" indicates the direction.  If the file name is defined, the key "FILENAME" indicates the name.  If the attribute name is defined, the key "NAME" indicates the name.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcmimeparts' is used in order to split multipart data of MIME into its parts.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcmimeparts(const char *<var>ptr</var>, int <var>size</var>, const char *<var>boundary</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region of multipart data of MIME.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>boundary</var>' specifies the boundary string.</dd>
+<dd>The return value is a list object.  Each element of the list is the data of a part.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tchexencode' is used in order to encode a serial object with hexadecimal encoding.</p>
+
+<dl class="api">
+<dt><code>char *tchexencode(const char *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>The return value is the result string.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if when is no longer in use.</dd>
+</dl>
+
+<p>The function `tchexdecode' is used in order to decode a string encoded with hexadecimal encoding.</p>
+
+<dl class="api">
+<dt><code>char *tchexdecode(const char *<var>str</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>str</var>' specifies the encoded string.</dd>
+<dd>`<var>sp</var>' specifies the pointer to a variable into which the size of the region of the return</dd>
+<dd>value is assigned.</dd>
+<dd>The return value is the pointer to the region of the result.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcpackencode' is used in order to compress a serial object with Packbits encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcpackencode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the result object, else, it is `NULL'.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcpackdecode' is used in order to decompress a serial object compressed with Packbits encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcpackdecode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>sp</var>' specifies the pointer to a variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the result object, else, it is `NULL'.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbsencode' is used in order to compress a serial object with TCBS encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcbsencode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the result object, else, it is `NULL'.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbsdecode' is used in order to decompress a serial object compressed with TCBS encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcbsdecode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>sp</var>' specifies the pointer to a variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the result object, else, it is `NULL'.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcdeflate' is used in order to compress a serial object with Deflate encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcdeflate(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the result object, else, it is `NULL'.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcinflate' is used in order to decompress a serial object compressed with Deflate encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcinflate(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>sp</var>' specifies the pointer to a variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the result object, else, it is `NULL'.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcgzipencode' is used in order to compress a serial object with GZIP encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcgzipencode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the result object, else, it is `NULL'.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcgzipdecode' is used in order to decompress a serial object compressed with GZIP encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcgzipdecode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>sp</var>' specifies the pointer to a variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the result object, else, it is `NULL'.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcgetcrc' is used in order to get the CRC32 checksum of a serial object.</p>
+
+<dl class="api">
+<dt><code>unsigned int tcgetcrc(const char *<var>ptr</var>, int <var>size</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>The return value is the CRC32 checksum of the object.</dd>
+</dl>
+
+<p>The function `tcbzipencode' is used in order to compress a serial object with BZIP2 encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcbzipencode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the result object, else, it is `NULL'.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbzipdecode' is used in order to decompress a serial object compressed with BZIP2 encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcbzipdecode(const char *<var>ptr</var>, int <var>size</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>sp</var>' specifies the pointer to a variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the result object, else, it is `NULL'.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcberencode' is used in order to encode an array of nonnegative integers with BER encoding.</p>
+
+<dl class="api">
+<dt><code>char *tcberencode(const unsigned int *<var>ary</var>, int <var>anum</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>ary</var>' specifies the pointer to the array of nonnegative integers.</dd>
+<dd>`<var>anum</var>' specifies the size of the array.</dd>
+<dd>`<var>sp</var>' specifies the pointer to a variable into which the size of the region of the return value is assigned.</dd>
+<dd>The return value is the pointer to the region of the result.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if when is no longer in use.</dd>
+</dl>
+
+<p>The function `tcberdecode' is used in order to decode a serial object encoded with BER encoding.</p>
+
+<dl class="api">
+<dt><code>unsigned int *tcberdecode(const char *<var>ptr</var>, int <var>size</var>, int *<var>np</var>);</code></dt>
+<dd>`<var>ptr</var>' specifies the pointer to the region.</dd>
+<dd>`<var>size</var>' specifies the size of the region.</dd>
+<dd>`<var>np</var>' specifies the pointer to a variable into which the number of elements of the return value is assigned.</dd>
+<dd>The return value is the pointer to the array of the result.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if when is no longer in use.</dd>
+</dl>
+
+<p>The function `tcxmlescape' is used in order to escape meta characters in a string with the entity references of XML.</p>
+
+<dl class="api">
+<dt><code>char *tcxmlescape(const char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string.</dd>
+<dd>The return value is the pointer to the escaped string.</dd>
+<dd>This function escapes only `&amp;', `&lt;', `&gt;', and `&quot;'.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcxmlunescape' is used in order to unescape entity references in a string of XML.</p>
+
+<dl class="api">
+<dt><code>char *tcxmlunescape(const char *<var>str</var>);</code></dt>
+<dd>`<var>str</var>' specifies the string.</dd>
+<dd>The return value is the unescaped string.</dd>
+<dd>This function restores only `&amp;amp;', `&amp;lt;', `&amp;gt;', and `&amp;quot;'.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<h3 id="tcutilapi_example">コード例</h3>
+
+<p>拡張可能文字列と配列リストとハッシュマップを使ったコード例を以下に示します。</p>
+
+<pre>#include &lt;tcutil.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdbool.h&gt;
+#include &lt;stdint.h&gt;
+#include &lt;stdio.h&gt;
+
+int main(int argc, char **argv){
+
+  { /* 拡張可能文字列オブジェクトの使用例 */
+    TCXSTR *xstr;
+    /* オブジェクトを作成する */
+    xstr = tcxstrnew();
+    /* 文字列を連結する */
+    tcxstrcat2(xstr, "hop");
+    tcxstrcat2(xstr, "step");
+    tcxstrcat2(xstr, "jump");
+    /* サイズと内容を印字する */
+    printf("%d:%s\n", tcxstrsize(xstr), (char *)tcxstrptr(xstr));
+    /* オブジェクトを破棄する */
+    tcxstrdel(xstr);
+  }
+
+  { /* リストオブジェクトの使用例 */
+    TCLIST *list;
+    int i;
+    /* オブジェクトを作成する */
+    list = tclistnew();
+    /* 末尾に文字列を追加する */
+    tclistpush2(list, "hop");
+    tclistpush2(list, "step");
+    tclistpush2(list, "jump");
+    /* 全ての要素を印字する */
+    for(i = 0; i &lt; tclistnum(list); i++){
+      printf("%d:%s\n", i, tclistval2(list, i));
+    }
+    /* オブジェクトを破棄する */
+    tclistdel(list);
+  }
+
+  { /* マップオブジェクトの使用例 */
+    TCMAP *map;
+    const char *key;
+    /* オブジェクトを作成する */
+    map = tcmapnew();
+    /* レコードを追加する */
+    tcmapput2(map, "foo", "hop");
+    tcmapput2(map, "bar", "step");
+    tcmapput2(map, "baz", "jump");
+    /* 全てのレコードを印字する */
+    tcmapiterinit(map);
+    while((key = tcmapiternext2(map)) != NULL){
+      printf("%s:%s\n", key, tcmapget2(map, key));
+    }
+    /* オブジェクトを破棄する */
+    tcmapdel(map);
+  }
+
+  { /* マップオブジェクトの使用例 */
+    TCTREE *tree;
+    const char *key;
+    /* オブジェクトを作成する */
+    tree = tctreenew();
+    /* レコードを追加する */
+    tctreeput2(tree, "foo", "hop");
+    tctreeput2(tree, "bar", "step");
+    tctreeput2(tree, "baz", "jump");
+    /* 全てのレコードを印字する */
+    tctreeiterinit(tree);
+    while((key = tctreeiternext2(tree)) != NULL){
+      printf("%s:%s\n", key, tctreeget2(tree, key));
+    }
+    /* オブジェクトを破棄する */
+    tctreedel(tree);
+  }
+
+  return 0;
+}
+</pre>
+
+<h3 id="tcutilapi_cli">CLI</h3>
+
+<p>ユーティリティAPIを簡単に利用するために、コマンドラインインターフェイスとして `<code>tcutest</code>' と `<code>tcumttest</code>'  と `<code>tcucodec</code>' が提供されます。</p>
+
+<p>コマンド `<code>tcutest</code>' は、ユーティリティAPIの機能テストや性能テストに用いるツールです。以下の書式で用います。`<var>rnum</var>' は試行回数を指定し、`<var>anum</var>' は配列の初期容量を指定し、`<var>bnum</var>' はバケット数を指定します。</p>
+
+<dl class="api">
+<dt><code>tcutest xstr <var>rnum</var></code></dt>
+<dd>拡張可能文字列に文字列を連結するテストを行います。</dd>
+<dt><code>tcutest list [-rd] <var>rnum</var> [<var>anum</var>]</code></dt>
+<dd>配列リストに要素を追加するテストを行います。</dd>
+<dt><code>tcutest map [-rd] [-tr] [-rnd] [-dk|-dc|-dai|-dad|-dpr] <var>rnum</var> [<var>bnum</var>]</code></dt>
+<dd>ハッシュマップにレコードを追加するテストを行います。</dd>
+<dt><code>tcutest tree [-rd] [-tr] [-rnd] [-dk|-dc|-dai|-dad|-dpr] <var>rnum</var></code></dt>
+<dd>順序木にレコードを追加するテストを行います。</dd>
+<dt><code>tcutest mdb [-rd] [-tr] [-rnd] [-dk|-dc|-dai|-dad|-dpr] <var>rnum</var> [<var>bnum</var>]</code></dt>
+<dd>オンメモリハッシュデータベースにレコードを追加するテストを行います。</dd>
+<dt><code>tcutest ndb [-rd] [-tr] [-rnd] [-dk|-dc|-dai|-dad|-dpr] <var>rnum</var></code></dt>
+<dd>オンメモリツリーデータベースにレコードを追加するテストを行います。</dd>
+<dt><code>tcutest misc <var>rnum</var></code></dt>
+<dd>その他の雑多なテストを行います。</dd>
+<dt><code>tcutest wicked <var>rnum</var></code></dt>
+<dd>配列リストとハッシュマップの各種更新操作を無作為に選択して実行するテストを行います。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持ちます。</p>
+
+<ul class="options">
+<li><code>-rd</code> : 取得のテストも行う。</li>
+<li><code>-tr</code> : イテレータのテストも行う。</li>
+<li><code>-rnd</code> : キーを無作為に選択する。</li>
+<li><code>-dk</code> : 関数 `tcxxxput' の代わりに関数 `tcxxxputkeep' を用いる。</li>
+<li><code>-dc</code> : 関数 `tcxxxput' の代わりに関数 `tcxxxputcat' を用いる。</li>
+<li><code>-dai</code> : 関数 `tcxxxput' の代わりに関数 `tcxxxaddint' を用いる。</li>
+<li><code>-dad</code> : 関数 `tcxxxput' の代わりに関数 `tcxxxadddouble' を用いる。</li>
+<li><code>-dpr</code> : 関数 `tcxxxput' の代わりに関数 `tcxxxputproc' を用いる。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了します。</p>
+
+<p>コマンド `<code>tcumttest</code>' は、オンメモリハッシュデータベースAPIとオンメモリツリーデータベースAPIの機能テストをマルチスレッドで行うツールです。以下の書式で用います。`<var>tnum</var>' はスレッド数を指定し、`<var>rnum</var>' は試行回数を指定し、`<var>bnum</var>' はバケット数を指定します。</p>
+
+<dl class="api">
+<dt><code>tcumttest combo [-rnd] <var>tnum</var> <var>rnum</var> [<var>bnum</var>]</code></dt>
+<dd>レコードの格納と検索と削除を順に実行する。</dd>
+<dt><code>tcumttest typical [-nc] [-rr <var>num</var>] <var>tnum</var> <var>rnum</var> [<var>bnum</var>]</code></dt>
+<dd>典型的な操作を無作為に選択して実行する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持ちます</p>
+
+<ul class="options">
+<li><code>-rnd</code> : キーを無作為に選択する。</li>
+<li><code>-nc</code> : 比較テストを行わない。</li>
+<li><code>-rr <var>num</var></code> : 読み込み操作の割合を百分率で指定する。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了します。</p>
+
+<p>コマンド `<code>tcucodec</code>' は、ユーティリティAPIが提供するエンコードおよびデコードの機能を利用するツールです。以下の書式で用います。`<var>file</var>' は入力ファイルを指定しますが、省略されれば標準入力を読み込みます。</p>
+
+<dl class="api">
+<dt><code>tcucodec url [-d] [-br] [-rs <var>base</var>] [<var>file</var>]</code></dt>
+<dd>URLエンコードとそのデコードを行う。</dd>
+<dt><code>tcucodec base [-d] [<var>file</var>]</code></dt>
+<dd>Base64エンコードとそのデコードを行う。</dd>
+<dt><code>tcucodec quote [-d] [<var>file</var>]</code></dt>
+<dd>Quoted-printableエンコードとそのデコードを行う。</dd>
+<dt><code>tcucodec mime [-d] [-en <var>name</var>] [-q] [-on] [-hd] [-bd] [-part <var>num</var>] [<var>file</var>]</code></dt>
+<dd>MIMEエンコードとそのデコードを行う。</dd>
+<dt><code>tcucodec hex [-d] [<var>file</var>]</code></dt>
+<dd>16進数エンコードとそのデコードを行う。</dd>
+<dt><code>tcucodec pack [-d] [-bwt] [<var>file</var>]</code></dt>
+<dd>Packbitsの圧縮とその伸長を行う。</dd>
+<dt><code>tcucodec tcbs [-d] [<var>file</var>]</code></dt>
+<dd>TCBSの圧縮とその伸長を行う。</dd>
+<dt><code>tcucodec zlib [-d] [-gz] [<var>file</var>]</code></dt>
+<dd>ZLIBの圧縮とその伸長を行う。</dd>
+<dt><code>tcucodec bzip [-d] [<var>file</var>]</code></dt>
+<dd>BZIP2の圧縮とその伸長を行う。</dd>
+<dt><code>tcucodec xml [-d] [-br] [<var>file</var>]</code></dt>
+<dd>XMLの処理を行う。デフォルトではメタ文字のエスケープを行う。</dd>
+<dt><code>tcucodec cstr [-d] [-js] [<var>file</var>]</code></dt>
+<dd>C文字列のエスケープとそのアンエスケープを行う。</dd>
+<dt><code>tcucodec ucs [-d] [-un] [-kw <var>str</var>] [<var>file</var>]</code></dt>
+<dd>UTF-8の文字列をUCS-2の配列に変換する。</dd>
+<dt><code>tcucodec hash [-crc] [-ch <var>num</var>] [<var>file</var>]</code></dt>
+<dd>ハッシュ値を算出する。デフォルトではMD5関数を用いる。</dd>
+<dt><code>tcucodec cipher [-key <var>str</var>] [<var>file</var>]</code></dt>
+<dd>ストリーム暗号化とその復号を行う。</dd>
+<dt><code>tcucodec date [-ds <var>str</var>] [-jl <var>num</var>] [-wf] [-rf]</code></dt>
+<dd>時刻の書式変換を行う。デフォルトでは現在のUNIX時間を出力する。</dd>
+<dt><code>tcucodec tmpl [-var <var>name</var> <var>val</var>] [<var>file</var>]</code></dt>
+<dd>テンプレートの直列化を行う。</dd>
+<dt><code>tcucodec conf [-v|-i|-l|-p]</code></dt>
+<dd>各種の設定情報を出力する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持ちます。</p>
+
+<ul class="options">
+<li><code>-d</code> : エンコード(エスケープ)ではなく、デコード(アンエスケープ)を行う。</li>
+<li><code>-br</code> : URLやXMLを構成要素に分解する。</li>
+<li><code>-rs <var>base</var></code> : ベースURLを指定して、相対URLを解決する。</li>
+<li><code>-en <var>name</var></code> : 入力の文字コードを指定する。デフォルトはUTF-8である。</li>
+<li><code>-q</code> : Quoted-printableエンコードを用いる。デフォルトはBase64である。</li>
+<li><code>-on</code> : デコード時に結果でなく文字コード名を出力する。</li>
+<li><code>-bd</code> : MIME解析を行ってボディを出力する。</li>
+<li><code>-hd</code> : MIME解析を行ってヘッダを出力する。</li>
+<li><code>-part <var>num</var></code> : MIME解析を行ってマルチパートの指定されたパートを出力する。</li>
+<li><code>-bwt</code> : 前処理としてBWTを用いる。</li>
+<li><code>-gz</code> : GZIP形式を用いる。</li>
+<li><code>-crc</code> : CRC32関数を用いる。</li>
+<li><code>-js</code> : JSON互換形式を用いる。</li>
+<li><code>-un</code> : UCSの正規化を行う。</li>
+<li><code>-kw <var>str</var></code> : KWIC文字列を生成する。</li>
+<li><code>-ch <var>num</var></code> : コンシステントハッシュ関数を用いる。</li>
+<li><code>-key <var>str</var></code> : 暗号鍵を指定する。</li>
+<li><code>-ds <var>str</var></code> : 時刻を指定する。</li>
+<li><code>-jl <var>num</var></code> : 時差を指定する。</li>
+<li><code>-wf</code> : 出力をW3CDTF形式にする。</li>
+<li><code>-rf</code> : 出力をRFC 1123形式にする。</li>
+<li><code>-var <var>name</var> <var>value</var></code> : テンプレート変数を指定する。</li>
+<li><code>-v</code> : Tokyo Cabinetのバージョン番号を表示する。</li>
+<li><code>-i</code> : Tokyo Cabinetのヘッダのインクルードオプションを表示する。</li>
+<li><code>-l</code> : Tokyo Cabinetのライブラリのリンクオプションを表示する。</li>
+<li><code>-p</code> : Tokyo Cabinetのコマンドのあるディレクトリを表示する。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了します。</p>
+
+<hr />
+
+<h2 id="tchdbapi">ハッシュデータベースAPI</h2>
+
+<p>ハッシュデータベースは、ハッシュ表を単一のファイルに記録したデータベースです。それを扱うのがハッシュデータベースAPIです。`<code>tchdb.h</code>' にAPIの仕様の完全な記述があります。</p>
+
+<h3 id="tchdbapi_description">概要</h3>
+
+<p>ハッシュデータベースAPIを使うためには、`<code>tcutil.h</code>'、`<code>tchdb.h</code>' および関連する標準ヘッダファイルをインクルードしてください。通常、ソースファイルの冒頭付近で以下の記述を行います。</p>
+
+<dl>
+<dt><code>#include &lt;tcutil.h&gt;</code></dt>
+<dt><code>#include &lt;tchdb.h&gt;</code></dt>
+<dt><code>#include &lt;stdlib.h&gt;</code></dt>
+<dt><code>#include &lt;stdbool.h&gt;</code></dt>
+<dt><code>#include &lt;stdint.h&gt;</code></dt>
+</dl>
+
+<p>ハッシュデータベースを扱う際には、`<code>TCHDB</code>' 型へのポインタをオブジェクトとして用います。ハッシュデータベースオブジェクトは、関数 `<code>tchdbnew</code>' で作成し、関数 `<code>tchdbdel</code>' で破棄します。作成したオブジェクトを使い終わったら必ず破棄してください。そうしないとメモリリークが発生します。</p>
+
+<p>レコードの格納や探索を行う前提として、ハッシューデータベースオブジェクトをデータベースファイルと接続させる必要があります。データベースファイルを開いて接続するには関数 `<code>tchdbopen</code>' を用い、接続の解除してファイルを閉じるには関数 `<code>tchdbclose</code>' を用います。開いたデータベースファイルは必ず閉じてください。そうしないとデータベースファイルが壊れたり格納したデータが失われたりする可能性があります。単一のプロセス内で複数のデータベースオブジェクトが同じデータベースファイルを同時に開くことはできません。</p>
+
+<h3 id="tchdbapi_api">API(英語ゴメン)</h3>
+
+<p>The function `tchdberrmsg' is used in order to get the message string corresponding to an error code.</p>
+
+<dl class="api">
+<dt><code>const char *tchdberrmsg(int <var>ecode</var>);</code></dt>
+<dd>`<var>ecode</var>' specifies the error code.</dd>
+<dd>The return value is the message string of the error code.</dd>
+</dl>
+
+<p>The function `tchdbnew' is used in order to create a hash database object.</p>
+
+<dl class="api">
+<dt><code>TCHDB *tchdbnew(void);</code></dt>
+<dd>The return value is the new hash database object.</dd>
+</dl>
+
+<p>The function `tchdbdel' is used in order to delete a hash database object.</p>
+
+<dl class="api">
+<dt><code>void tchdbdel(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>If the database is not closed, it is closed implicitly.  Note that the deleted object and its derivatives can not be used anymore.</dd>
+</dl>
+
+<p>The function `tchdbecode' is used in order to get the last happened error code of a hash database object.</p>
+
+<dl class="api">
+<dt><code>int tchdbecode(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>The return value is the last happened error code.</dd>
+<dd>The following error codes are defined: `TCESUCCESS' for success, `TCETHREAD' for threading error, `TCEINVALID' for invalid operation, `TCENOFILE' for file not found, `TCENOPERM' for no permission, `TCEMETA' for invalid meta data, `TCERHEAD' for invalid record header, `TCEOPEN' for open error, `TCECLOSE' for close error, `TCETRUNC' for trunc error, `TCESYNC' for sync error, `TCESTAT' for stat error, `TCESEEK' for seek error, `TCEREAD' for read error, `TCEWRITE' for write error, `TCEMMAP' for mmap error, `TCELOCK' for lock error, `TCEUNLINK' for unlink error, `TCERENAME' for rename error, `TCEMKDIR' for mkdir error, `TCERMDIR' for rmdir error, `TCEKEEP' for existing record, `TCENOREC' for no record found, and `TCEMISC' for miscellaneous error.</dd>
+</dl>
+
+<p>The function `tchdbsetmutex' is used in order to set mutual exclusion control of a hash database object for threading.</p>
+
+<dl class="api">
+<dt><code>bool tchdbsetmutex(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object which is not opened.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the mutual exclusion control is needed if the object is shared by plural threads and this function should be called before the database is opened.</dd>
+</dl>
+
+<p>The function `tchdbtune' is used in order to set the tuning parameters of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbtune(TCHDB *<var>hdb</var>, int64_t <var>bnum</var>, int8_t <var>apow</var>, int8_t <var>fpow</var>, uint8_t <var>opts</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object which is not opened.</dd>
+<dd>`<var>bnum</var>' specifies the number of elements of the bucket array.  If it is not more than 0, the default value is specified.  The default value is 131071.  Suggested size of the bucket array is about from 0.5 to 4 times of the number of all records to be stored.</dd>
+<dd>`<var>apow</var>' specifies the size of record alignment by power of 2.  If it is negative, the default value is specified.  The default value is 4 standing for 2^4=16.</dd>
+<dd>`<var>fpow</var>' specifies the maximum number of elements of the free block pool by power of 2.  If it is negative, the default value is specified.  The default value is 10 standing for 2^10=1024.</dd>
+<dd>`<var>opts</var>' specifies options by bitwise-or: `HDBTLARGE' specifies that the size of the database can be larger than 2GB by using 64-bit bucket array, `HDBTDEFLATE' specifies that each record is compressed with Deflate encoding, `HDBTBZIP' specifies that each record is compressed with BZIP2 encoding, `HDBTTCBS' specifies that each record is compressed with TCBS encoding.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the tuning parameters should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tchdbsetcache' is used in order to set the caching parameters of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbsetcache(TCHDB *<var>hdb</var>, int32_t <var>rcnum</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object which is not opened.</dd>
+<dd>`<var>rcnum</var>' specifies the maximum number of records to be cached.  If it is not more than 0, the record cache is disabled.  It is disabled by default.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the caching parameters should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tchdbsetxmsiz' is used in order to set the size of the extra mapped memory of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbsetxmsiz(TCHDB *<var>hdb</var>, int64_t <var>xmsiz</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object which is not opened.</dd>
+<dd>`<var>xmsiz</var>' specifies the size of the extra mapped memory.  If it is not more than 0, the extra mapped memory is disabled.  The default size is 67108864.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the mapping parameters should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tchdbsetdfunit' is used in order to set the unit step number of auto defragmentation of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbsetdfunit(TCHDB *<var>hdb</var>, int32_t <var>dfunit</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object which is not opened.</dd>
+<dd>`<var>dfunit</var>' specifie the unit step number.  If it is not more than 0, the auto defragmentation is disabled.  It is disabled by default.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the defragmentation parameters should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tchdbopen' is used in order to open a database file and connect a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbopen(TCHDB *<var>hdb</var>, const char *<var>path</var>, int <var>omode</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object which is not opened.</dd>
+<dd>`<var>path</var>' specifies the path of the database file.</dd>
+<dd>`<var>omode</var>' specifies the connection mode: `HDBOWRITER' as a writer, `HDBOREADER' as a reader.  If the mode is `HDBOWRITER', the following may be added by bitwise-or: `HDBOCREAT', which means it creates a new database if not exist, `HDBOTRUNC', which means it creates a new database regardless if one exists, `HDBOTSYNC', which means every transaction synchronizes updated contents with the device.  Both of `HDBOREADER' and `HDBOWRITER' can be added to by bitwise-or: `HDBONOLCK', which means it opens the database file without file locking, or `HDBOLCKNB', which means locking is performed without blocking.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tchdbclose' is used in order to close a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbclose(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update of a database is assured to be written when the database is closed.  If a writer opens a database but does not close it appropriately, the database will be broken.</dd>
+</dl>
+
+<p>The function `tchdbput' is used in order to store a record into a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbput(TCHDB *<var>hdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tchdbput2' is used in order to store a string record into a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbput2(TCHDB *<var>hdb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tchdbputkeep' is used in order to store a new record into a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbputkeep(TCHDB *<var>hdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tchdbputkeep2' is used in order to store a new string record into a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbputkeep2(TCHDB *<var>hdb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tchdbputcat' is used in order to concatenate a value at the end of the existing record in a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbputcat(TCHDB *<var>hdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tchdbputcat2' is used in order to concatenate a string value at the end of the existing record in a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbputcat2(TCHDB *<var>hdb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tchdbputasync' is used in order to store a record into a hash database object in asynchronous fashion.</p>
+
+<dl class="api">
+<dt><code>bool tchdbputasync(TCHDB *<var>hdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.  Records passed to this function are accumulated into the inner buffer and wrote into the file at a blast.</dd>
+</dl>
+
+<p>The function `tchdbputasync2' is used in order to store a string record into a hash database object in asynchronous fashion.</p>
+
+<dl class="api">
+<dt><code>bool tchdbputasync2(TCHDB *<var>hdb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.  Records passed to this function are accumulated into the inner buffer and wrote into the file at a blast.</dd>
+</dl>
+
+<p>The function `tchdbout' is used in order to remove a record of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbout(TCHDB *<var>hdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tchdbout2' is used in order to remove a string record of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbout2(TCHDB *<var>hdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tchdbget' is used in order to retrieve a record in a hash database object.</p>
+
+<dl class="api">
+<dt><code>void *tchdbget(TCHDB *<var>hdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tchdbget2' is used in order to retrieve a string record in a hash database object.</p>
+
+<dl class="api">
+<dt><code>char *tchdbget2(TCHDB *<var>hdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the string of the value of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tchdbget3' is used in order to retrieve a record in a hash database object and write the value into a buffer.</p>
+
+<dl class="api">
+<dt><code>int tchdbget3(TCHDB *<var>hdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, void *<var>vbuf</var>, int <var>max</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the buffer into which the value of the corresponding record is written.</dd>
+<dd>`<var>max</var>' specifies the size of the buffer.</dd>
+<dd>If successful, the return value is the size of the written data, else, it is -1.  -1 is returned if no record corresponds to the specified key.</dd>
+<dd>Note that an additional zero code is not appended at the end of the region of the writing buffer.</dd>
+</dl>
+
+<p>The function `tchdbvsiz' is used in order to get the size of the value of a record in a hash database object.</p>
+
+<dl class="api">
+<dt><code>int tchdbvsiz(TCHDB *<var>hdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tchdbvsiz2' is used in order to get the size of the value of a string record in a hash database object.</p>
+
+<dl class="api">
+<dt><code>int tchdbvsiz2(TCHDB *<var>hdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tchdbiterinit' is used in order to initialize the iterator of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbiterinit(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>The iterator is used in order to access the key of every record stored in a database.</dd>
+</dl>
+
+<p>The function `tchdbiternext' is used in order to get the next key of the iterator of a hash database object.</p>
+
+<dl class="api">
+<dt><code>void *tchdbiternext(TCHDB *<var>hdb</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  It is possible to access every record by iteration of calling this function.  It is allowed to update or remove records whose keys are fetched while the iteration.  However, it is not assured if updating the database is occurred while the iteration.  Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of storing matches the one of the traversal access.</dd>
+</dl>
+
+<p>The function `tchdbiternext2' is used in order to get the next key string of the iterator of a hash database object.</p>
+
+<dl class="api">
+<dt><code>char *tchdbiternext2(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>If successful, the return value is the string of the next key, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  It is possible to access every record by iteration of calling this function.  However, it is not assured if updating the database is occurred while the iteration.  Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of storing matches the one of the traversal access.</dd>
+</dl>
+
+<p>The function `tchdbiternext3' is used in order to get the next extensible objects of the iterator of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbiternext3(TCHDB *<var>hdb</var>, TCXSTR *<var>kxstr</var>, TCXSTR *<var>vxstr</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>`<var>kxstr</var>' specifies the object into which the next key is wrote down.</dd>
+<dd>`<var>vxstr</var>' specifies the object into which the next value is wrote down.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned when no record is to be get out of the iterator.</dd>
+</dl>
+
+<p>The function `tchdbfwmkeys' is used in order to get forward matching keys in a hash database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tchdbfwmkeys(TCHDB *<var>hdb</var>, const void *<var>pbuf</var>, int <var>psiz</var>, int <var>max</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>`<var>pbuf</var>' specifies the pointer to the region of the prefix.</dd>
+<dd>`<var>psiz</var>' specifies the size of the region of the prefix.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.</dd>
+</dl>
+
+<p>The function `tchdbfwmkeys2' is used in order to get forward matching string keys in a hash database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tchdbfwmkeys2(TCHDB *<var>hdb</var>, const char *<var>pstr</var>, int <var>max</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>`<var>pstr</var>' specifies the string of the prefix.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.</dd>
+</dl>
+
+<p>The function `tchdbaddint' is used in order to add an integer to a record in a hash database object.</p>
+
+<dl class="api">
+<dt><code>int tchdbaddint(TCHDB *<var>hdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int <var>num</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>If successful, the return value is the summation value, else, it is `INT_MIN'.</dd>
+<dd>If the corresponding record exists, the value is treated as an integer and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tchdbdbadddouble' is used in order to add a real number to a record in a hash database object.</p>
+
+<dl class="api">
+<dt><code>double tchdbadddouble(TCHDB *<var>hdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, double <var>num</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>If successful, the return value is the summation value, else, it is Not-a-Number.</dd>
+<dd>If the corresponding record exists, the value is treated as a real number and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tchdbsync' is used in order to synchronize updated contents of a hash database object with the file and the device.</p>
+
+<dl class="api">
+<dt><code>bool tchdbsync(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>This function is useful when another process connects to the same database file.</dd>
+</dl>
+
+<p>The function `tchdboptimize' is used in order to optimize the file of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdboptimize(TCHDB *<var>hdb</var>, int64_t <var>bnum</var>, int8_t <var>apow</var>, int8_t <var>fpow</var>, uint8_t <var>opts</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>`<var>bnum</var>' specifies the number of elements of the bucket array.  If it is not more than 0, the default value is specified.  The default value is two times of the number of records.</dd>
+<dd>`<var>apow</var>' specifies the size of record alignment by power of 2.  If it is negative, the current setting is not changed.</dd>
+<dd>`<var>fpow</var>' specifies the maximum number of elements of the free block pool by power of 2.  If it is negative, the current setting is not changed.</dd>
+<dd>`<var>opts</var>' specifies options by bitwise-or: `HDBTLARGE' specifies that the size of the database can be larger than 2GB by using 64-bit bucket array, `HDBTDEFLATE' specifies that each record is compressed with Deflate encoding, `HDBTTCBS' specifies that each record is compressed with TCBS encoding.  If it is `UINT8_MAX', the current setting is not changed.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>This function is useful to reduce the size of the database file with data fragmentation by successive updating.</dd>
+</dl>
+
+<p>The function `tchdbvanish' is used in order to remove all records of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbvanish(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tchdbcopy' is used in order to copy the database file of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbcopy(TCHDB *<var>hdb</var>, const char *<var>path</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>`<var>path</var>' specifies the path of the destination file.  If it begins with `@', the trailing substring is executed as a command line.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned if the executed command returns non-zero code.</dd>
+<dd>The database file is assured to be kept synchronized and not modified while the copying or executing operation is in progress.  So, this function is useful to create a backup file of the database file.</dd>
+</dl>
+
+<p>The function `tchdbtranbegin' is used in order to begin the transaction of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbtranbegin(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>The database is locked by the thread while the transaction so that only one transaction can be activated with a database object at the same time.  Thus, the serializable isolation level is assumed if every database operation is performed in the transaction.  All updated regions are kept track of by write ahead logging while the transaction.  If the database is closed during transaction, the transaction is aborted implicitly.</dd>
+</dl>
+
+<p>The function `tchdbtrancommit' is used in order to commit the transaction of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbtrancommit(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update in the transaction is fixed when it is committed successfully.</dd>
+</dl>
+
+<p>The function `tchdbtranabort' is used in order to abort the transaction of a hash database object.</p>
+
+<dl class="api">
+<dt><code>bool tchdbtranabort(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update in the transaction is discarded when it is aborted.  The state of the database is rollbacked to before transaction.</dd>
+</dl>
+
+<p>The function `tchdbpath' is used in order to get the file path of a hash database object.</p>
+
+<dl class="api">
+<dt><code>const char *tchdbpath(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>The return value is the path of the database file or `NULL' if the object does not connect to any database file.</dd>
+</dl>
+
+<p>The function `tchdbrnum' is used in order to get the number of records of a hash database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tchdbrnum(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>The return value is the number of records or 0 if the object does not connect to any database file.</dd>
+</dl>
+
+<p>The function `tchdbfsiz' is used in order to get the size of the database file of a hash database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tchdbfsiz(TCHDB *<var>hdb</var>);</code></dt>
+<dd>`<var>hdb</var>' specifies the hash database object.</dd>
+<dd>The return value is the size of the database file or 0 if the object does not connect to any database file.</dd>
+</dl>
+
+<h3 id="tchdbapi_example">コード例</h3>
+
+<p>ハッシュデータベースを使ったコード例を以下に示します。</p>
+
+<pre>#include &lt;tcutil.h&gt;
+#include &lt;tchdb.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdbool.h&gt;
+#include &lt;stdint.h&gt;
+
+int main(int argc, char **argv){
+  TCHDB *hdb;
+  int ecode;
+  char *key, *value;
+
+  /* オブジェクトを作成する */
+  hdb = tchdbnew();
+
+  /* データベースを開く */
+  if(!tchdbopen(hdb, "casket.tch", HDBOWRITER | HDBOCREAT)){
+    ecode = tchdbecode(hdb);
+    fprintf(stderr, "open error: %s\n", tchdberrmsg(ecode));
+  }
+
+  /* レコードを格納する */
+  if(!tchdbput2(hdb, "foo", "hop") ||
+     !tchdbput2(hdb, "bar", "step") ||
+     !tchdbput2(hdb, "baz", "jump")){
+    ecode = tchdbecode(hdb);
+    fprintf(stderr, "put error: %s\n", tchdberrmsg(ecode));
+  }
+
+  /* レコードを取得する */
+  value = tchdbget2(hdb, "foo");
+  if(value){
+    printf("%s\n", value);
+    free(value);
+  } else {
+    ecode = tchdbecode(hdb);
+    fprintf(stderr, "get error: %s\n", tchdberrmsg(ecode));
+  }
+
+  /* 横断的にレコードを参照する */
+  tchdbiterinit(hdb);
+  while((key = tchdbiternext2(hdb)) != NULL){
+    value = tchdbget2(hdb, key);
+    if(value){
+      printf("%s:%s\n", key, value);
+      free(value);
+    }
+    free(key);
+  }
+
+  /* データベースを閉じる */
+  if(!tchdbclose(hdb)){
+    ecode = tchdbecode(hdb);
+    fprintf(stderr, "close error: %s\n", tchdberrmsg(ecode));
+  }
+
+  /* オブジェクトを破棄する */
+  tchdbdel(hdb);
+
+  return 0;
+}
+</pre>
+
+<h3 id="tchdbapi_cli">CLI</h3>
+
+<p>ハッシュデータベースAPIを簡単に利用するために、コマンドラインインターフェイスとして `<code>tchtest</code>' と `<code>tchmttest</code>' と `<code>tchmgr</code>' が提供されます。</p>
+
+<p>コマンド `<code>tchtest</code>' は、ハッシュデータベースAPIの機能テストや性能テストに用いるツールです。以下の書式で用います。`<var>path</var>' はデータベースファイルのパスを指定し、`<var>rnum</var>' は試行回数を指定し、`<var>bnum</var>' はバケット数を指定し、`<var>apow</var>' はアラインメント力を指定し、`<var>fpow</var>' はフリーブロックプール力を指定します。</p>
+
+<dl class="api">
+<dt><code>tchtest write [-mt] [-tl] [-td|-tb|-tt|-tx] [-rc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-as] [-rnd] <var>path</var> <var>rnum</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]</code></dt>
+<dd>`00000001'、`00000002' のように変化する8バイトのキーと値を連続してデータベースに追加する。</dd>
+<dt><code>tchtest read [-mt] [-rc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-wb] [-rnd] <var>path</var></code></dt>
+<dd>上記で生成したデータベースの全レコードを検索する。</dd>
+<dt><code>tchtest remove [-mt] [-rc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-rnd] <var>path</var></code></dt>
+<dd>上記で生成したデータベースの全レコードを削除する。</dd>
+<dt><code>tchtest rcat [-mt] [-tl] [-td|-tb|-tt|-tx] [-rc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-pn <var>num</var>] [-dai|-dad|-rl|-ru] <var>path</var> <var>rnum</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]</code></dt>
+<dd>キーがある程度重複するようにレコードの追加を行い、連結モードで処理する。</dd>
+<dt><code>tchtest misc [-mt] [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] <var>path</var> <var>rnum</var></code></dt>
+<dd>各種操作の組み合わせテストを行う。</dd>
+<dt><code>tchtest wicked [-mt] [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] <var>path</var> <var>rnum</var></code></dt>
+<dd>各種更新操作を無作為に選択して実行する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持ちます</p>
+
+<ul class="options">
+<li><code>-mt</code> : 関数 `tchdbsetmutex' を呼び出す。</li>
+<li><code>-tl</code> : オプション `HDBTLARGE' を有効にする。</li>
+<li><code>-td</code> : オプション `HDBTDEFLATE' を有効にする。</li>
+<li><code>-tb</code> : オプション `HDBTBZIP' を有効にする。</li>
+<li><code>-tt</code> : オプション `HDBTTCBS' を有効にする。</li>
+<li><code>-tx</code> : オプション `HDBTEXCODEC' を有効にする。</li>
+<li><code>-rc <var>num</var></code> : レコード用キャッシュの最大数を指定する。</li>
+<li><code>-xm <var>num</var></code> : 拡張マップメモリのサイズを指定する。</li>
+<li><code>-df <var>num</var></code> : 自動デフラグの単位ステップ数を指定する。</li>
+<li><code>-nl</code> : オプション `HDBNOLCK' を有効にする。</li>
+<li><code>-nb</code> : オプション `HDBLCKNB' を有効にする。</li>
+<li><code>-as</code> : 関数 `tchdbput' の代わりに関数 `tchdbputasync' を用いる。</li>
+<li><code>-rnd</code> : キーを無作為に選択する。</li>
+<li><code>-wb</code> : 関数 `tchdbget' の代わりに関数 `tchdbget3' を用いる。</li>
+<li><code>-pn <var>num</var></code> : パターン数を指定する。</li>
+<li><code>-dai</code> : 関数 `tchdbputcat' の代わりに関数 `tchdbaddint' を用いる。</li>
+<li><code>-dad</code> : 関数 `tchdbputcat' の代わりに関数 `tchdbadddouble' を用いる。</li>
+<li><code>-rl</code> : 値を無作為な長さにする。</li>
+<li><code>-ru</code> : 更新操作を無作為に選択する。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了します。</p>
+
+<p>コマンド `<code>tchmttest</code>' は、ハッシュデータベースAPIの機能テストをマルチスレッドで行うツールです。以下の書式で用います。`<var>path</var>' はデータベースファイルのパスを指定し、`<var>tnum</var>' はスレッド数を指定し、`<var>rnum</var>' は試行回数を指定し、`<var>bnum</var>' はバケット数を指定し、`<var>apow</var>' はアラインメント力を指定し、`<var>fpow</var>' はフリーブロックプール力を指定します。</p>
+
+<dl class="api">
+<dt><code>tchmttest write [-tl] [-td|-tb|-tt|-tx] [-rc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-as] [-rnd] <var>path</var> <var>tnum</var> <var>rnum</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]</code></dt>
+<dd>`00000001'、`00000002' のように変化する8バイトのキーと値を連続してデータベースに追加する。</dd>
+<dt><code>tchmttest read [-rc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-wb] [-rnd] <var>path</var> <var>tnum</var></code></dt>
+<dd>上記で生成したデータベースの全レコードを検索する。</dd>
+<dt><code>tchmttest remove [-rc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-rnd] <var>path</var> <var>tnum</var></code></dt>
+<dd>上記で生成したデータベースの全レコードを削除する。</dd>
+<dt><code>tchmttest wicked [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] [-nc] <var>path</var> <var>tnum</var> <var>rnum</var></code></dt>
+<dd>各種更新操作を無作為に選択して実行する。</dd>
+<dt><code>tchmttest typical [-tl] [-td|-tb|-tt|-tx] [-rc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-nc] [-rr <var>num</var>] <var>path</var> <var>tnum</var> <var>rnum</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]</code></dt>
+<dd>典型的な操作を無作為に選択して実行する。</dd>
+<dt><code>tchmttest race [-tl] [-td|-tb|-tt|-tx] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] <var>path</var> <var>tnum</var> <var>rnum</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]</code></dt>
+<dd>レースコンディション検出のテストを行う。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持ちます</p>
+
+<ul class="options">
+<li><code>-tl</code> : オプション `HDBTLARGE' を有効にする。</li>
+<li><code>-td</code> : オプション `HDBTDEFLATE' を有効にする。</li>
+<li><code>-tb</code> : オプション `HDBTBZIP' を有効にする。</li>
+<li><code>-tt</code> : オプション `HDBTTCBS' を有効にする。</li>
+<li><code>-tx</code> : オプション `HDBTEXCODEC' を有効にする。</li>
+<li><code>-rc <var>num</var></code> : レコード用キャッシュの最大数を指定する。</li>
+<li><code>-xm <var>num</var></code> : 拡張マップメモリのサイズを指定する。</li>
+<li><code>-df <var>num</var></code> : 自動デフラグの単位ステップ数を指定する。</li>
+<li><code>-nl</code> : オプション `HDBNOLCK' を有効にする。</li>
+<li><code>-nb</code> : オプション `HDBLCKNB' を有効にする。</li>
+<li><code>-as</code> : 関数 `tchdbput' の代わりに関数 `tchdbputasync' を用いる。</li>
+<li><code>-rnd</code> : キーを無作為に選択する。</li>
+<li><code>-wb</code> : 関数 `tchdbget' の代わりに関数 `tchdbget3' を用いる。</li>
+<li><code>-nc</code> : 比較テストを行わない。</li>
+<li><code>-rr <var>num</var></code> : 読み込み操作の割合を百分率で指定する。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了します。</p>
+
+<p>コマンド `<code>tchmgr</code>' は、ハッシュデータベースAPIやそのアプリケーションのテストやデバッグに役立つツールです。以下の書式で用います。`<var>path</var>' はデータベースファイルのパスを指定し、`<var>bnum</var>' はバケット数を指定し、`<var>apow</var>' はアラインメント力を指定し、`<var>fpow</var>' はフリーブロックプール力を指定し、`<var>key</var>' はレコードのキーを指定し、`<var>value</var>' はレコードの値を指定し、`<var>file</var>' は入力ファイルを指定します。</p>
+
+<dl class="api">
+<dt><code>tchmgr create [-tl] [-td|-tb|-tt|-tx] <var>path</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]</code></dt>
+<dd>データベースファイルを作成する。</dd>
+<dt><code>tchmgr inform [-nl|-nb] <var>path</var></code></dt>
+<dd>データベースの雑多な情報を出力する。</dd>
+<dt><code>tchmgr put [-nl|-nb] [-sx] [-dk|-dc|-dai|-dad] <var>path</var> <var>key</var> <var>value</var></code></dt>
+<dd>レコードを追加する。</dd>
+<dt><code>tchmgr out [-nl|-nb] [-sx] <var>path</var> <var>key</var></code></dt>
+<dd>レコードを削除する。</dd>
+<dt><code>tchmgr get [-nl|-nb] [-sx] [-px] [-pz] <var>path</var> <var>key</var></code></dt>
+<dd>レコードの値を取得して標準出力する。</dd>
+<dt><code>tchmgr list [-nl|-nb] [-m <var>num</var>] [-pv] [-px] [-fm <var>str</var>] <var>path</var></code></dt>
+<dd>全てのレコードのキーを改行で区切って標準出力する。</dd>
+<dt><code>tchmgr optimize [-tl] [-td|-tb|-tt|-tx] [-tz] [-nl|-nb] [-df] <var>path</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]</code></dt>
+<dd>データベースを最適化する。</dd>
+<dt><code>tchmgr importtsv [-nl|-nb] [-sc] <var>path</var> [<var>file</var>]</code></dt>
+<dd>TSVファイルの各行をキーと値とみなしてレコードを登録する。</dd>
+<dt><code>tchmgr version</code></dt>
+<dd>Tokyo Cabinetのバージョン情報を標準出力する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持ちます</p>
+
+<ul class="options">
+<li><code>-tl</code> : オプション `HDBTLARGE' を有効にする。</li>
+<li><code>-td</code> : オプション `HDBTDEFLATE' を有効にする。</li>
+<li><code>-tb</code> : オプション `HDBTBZIP' を有効にする。</li>
+<li><code>-tt</code> : オプション `HDBTTCBS' を有効にする。</li>
+<li><code>-tx</code> : オプション `HDBTEXCODEC' を有効にする。</li>
+<li><code>-nl</code> : オプション `HDBNOLCK' を有効にする。</li>
+<li><code>-nb</code> : オプション `HDBLCKNB' を有効にする。</li>
+<li><code>-sx</code> : 入力を16進数の文字列で行う。</li>
+<li><code>-dk</code> : 関数 `tchdbput' の代わりに関数 `tchdbputkeep' を用いる。</li>
+<li><code>-dc</code> : 関数 `tchdbput' の代わりに関数 `tchdbputcat' を用いる。</li>
+<li><code>-dai</code> : 関数 `tchdbput' の代わりに関数 `tchdbaddint' を用いる。</li>
+<li><code>-dad</code> : 関数 `tchdbput' の代わりに関数 `tchdbadddouble' を用いる。</li>
+<li><code>-px</code> : 出力を16進数の文字列で行う。</li>
+<li><code>-pz</code> : 出力の末尾に改行を付加しない。</li>
+<li><code>-m <var>num</var></code> : 出力の最大数を指定する。</li>
+<li><code>-pv</code> : レコードの値も出力する。</li>
+<li><code>-fm <var>str</var></code> : キーの接頭辞を指定する。</li>
+<li><code>-tz</code> : オプション `UINT8_MAX' を有効にする。</li>
+<li><code>-df</code> : デフラグのみを行う。</li>
+<li><code>-sc</code> : キーを小文字に正規化する。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了します。</p>
+
+<hr />
+
+<h2 id="tcbdbapi">B+木データベースAPI</h2>
+
+<p>B+木データベースは、B+木を単一のファイルに記録したデータベースです。それを扱うのがB+木データベースAPIです。`<code>tcbdb.h</code>' にAPIの仕様の完全な記述があります。</p>
+
+<h3 id="tcbdbapi_description">概要</h3>
+
+<p>ハッシュデータベースAPIを使うためには、`<code>tcutil.h</code>'、`<code>tcbdb.h</code>' および関連する標準ヘッダファイルをインクルードしてください。通常、ソースファイルの冒頭付近で以下の記述を行います。</p>
+
+<dl>
+<dt><code>#include &lt;tcutil.h&gt;</code></dt>
+<dt><code>#include &lt;tcbdb.h&gt;</code></dt>
+<dt><code>#include &lt;stdlib.h&gt;</code></dt>
+<dt><code>#include &lt;stdbool.h&gt;</code></dt>
+<dt><code>#include &lt;stdint.h&gt;</code></dt>
+</dl>
+
+<p>B+木データベースを扱う際には、`<code>TCBDB</code>' 型へのポインタをオブジェクトとして用います。B+木データベースオブジェクトは、関数 `<code>tcbdbnew</code>' で作成し、関数 `<code>tcbdbdel</code>' で破棄します。作成したオブジェクトを使い終わったら必ず破棄してください。そうしないとメモリリークが発生します。</p>
+
+<p>レコードの格納や探索を行う前提として、B+木ーデータベースオブジェクトをデータベースファイルと接続させる必要があります。データベースファイルを開いて接続するには関数 `<code>tcbdbopen</code>' を用い、接続の解除してファイルを閉じるには関数 `<code>tcbdbclose</code>' を用います。開いたデータベースファイルは必ず閉じてください。そうしないとデータベースファイルが壊れたり格納したデータが失われたりする可能性があります。単一のプロセス内で複数のデータベースオブジェクトが同じデータベースファイルを同時に開くことはできません。</p>
+
+<h3 id="tcbdbapi_api">API(英語ゴメソ)</h3>
+
+<p>The function `tcbdberrmsg' is used in order to get the message string corresponding to an error code.</p>
+
+<dl class="api">
+<dt><code>const char *tcbdberrmsg(int <var>ecode</var>);</code></dt>
+<dd>`<var>ecode</var>' specifies the error code.</dd>
+<dd>The return value is the message string of the error code.</dd>
+</dl>
+
+<p>The function `tcbdbnew' is used in order to create a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>TCBDB *tcbdbnew(void);</code></dt>
+<dd>The return value is the new B+ tree database object.</dd>
+</dl>
+
+<p>The function `tcbdbdel' is used in order to delete a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>void tcbdbdel(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>If the database is not closed, it is closed implicitly.  Note that the deleted object and its derivatives can not be used anymore.</dd>
+</dl>
+
+<p>The function `tcbdbecode' is used in order to get the last happened error code of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>int tcbdbecode(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>The return value is the last happened error code.</dd>
+<dd>The following error codes are defined: `TCESUCCESS' for success, `TCETHREAD' for threading error, `TCEINVALID' for invalid operation, `TCENOFILE' for file not found, `TCENOPERM' for no permission, `TCEMETA' for invalid meta data, `TCERHEAD' for invalid record header, `TCEOPEN' for open error, `TCECLOSE' for close error, `TCETRUNC' for trunc error, `TCESYNC' for sync error, `TCESTAT' for stat error, `TCESEEK' for seek error, `TCEREAD' for read error, `TCEWRITE' for write error, `TCEMMAP' for mmap error, `TCELOCK' for lock error, `TCEUNLINK' for unlink error, `TCERENAME' for rename error, `TCEMKDIR' for mkdir error, `TCERMDIR' for rmdir error, `TCEKEEP' for existing record, `TCENOREC' for no record found, and `TCEMISC' for miscellaneous error.</dd>
+</dl>
+
+<p>The function `tcbdbsetmutex' is used in order to set mutual exclusion control of a B+ tree database object for threading.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbsetmutex(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object which is not opened.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the mutual exclusion control is needed if the object is shared by plural threads and this function should be called before the database is opened.</dd>
+</dl>
+
+<p>The function `tcbdbsetcmpfunc' is used in order to set the custom comparison function of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbsetcmpfunc(TCBDB *<var>bdb</var>, TCCMP <var>cmp</var>, void *<var>cmpop</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object which is not opened.</dd>
+<dd>`<var>cmp</var>' specifies the pointer to the custom comparison function.  It receives five parameters.  The first parameter is the pointer to the region of one key.  The second parameter is the size of the region of one key.  The third parameter is the pointer to the region of the other key.  The fourth parameter is the size of the region of the other key.  The fifth parameter is the pointer to the optional opaque object.  It returns positive if the former is big, negative if the latter is big, 0 if both are equivalent.</dd>
+<dd>`<var>cmpop</var>' specifies an arbitrary pointer to be given as a parameter of the comparison function.  If it is not needed, `NULL' can be specified.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>The default comparison function compares keys of two records by lexical order.  The functions `tccmplexical' (dafault), `tccmpdecimal', `tccmpint32', and `tccmpint64' are built-in.  Note that the comparison function should be set before the database is opened.  Moreover, user-defined comparison functions should be set every time the database is being opened.</dd>
+</dl>
+
+<p>The function `tcbdbtune' is used in order to set the tuning parameters of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbtune(TCBDB *<var>bdb</var>, int32_t <var>lmemb</var>, int32_t <var>nmemb</var>, int64_t <var>bnum</var>, int8_t <var>apow</var>, int8_t <var>fpow</var>, uint8_t <var>opts</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object which is not opened.</dd>
+<dd>`<var>lmemb</var>' specifies the number of members in each leaf page.  If it is not more than 0, the default value is specified.  The default value is 128.</dd>
+<dd>`<var>nmemb</var>' specifies the number of members in each non-leaf page.  If it is not more than 0, the default value is specified.  The default value is 256.</dd>
+<dd>`<var>bnum</var>' specifies the number of elements of the bucket array.  If it is not more than 0, the default value is specified.  The default value is 32749.  Suggested size of the bucket array is about from 1 to 4 times of the number of all pages to be stored.</dd>
+<dd>`<var>apow</var>' specifies the size of record alignment by power of 2.  If it is negative, the default value is specified.  The default value is 8 standing for 2^8=256.</dd>
+<dd>`<var>fpow</var>' specifies the maximum number of elements of the free block pool by power of 2.  If it is negative, the default value is specified.  The default value is 10 standing for 2^10=1024.</dd>
+<dd>`<var>opts</var>' specifies options by bitwise-or: `BDBTLARGE' specifies that the size of the database can be larger than 2GB by using 64-bit bucket array, `BDBTDEFLATE' specifies that each page is compressed with Deflate encoding, `BDBTBZIP' specifies that each page is compressed with BZIP2 encoding, `BDBTTCBS' specifies that each page is compressed with TCBS encoding.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the tuning parameters should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tcbdbsetcache' is used in order to set the caching parameters of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbsetcache(TCBDB *<var>bdb</var>, int32_t <var>lcnum</var>, int32_t <var>ncnum</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object which is not opened.</dd>
+<dd>`<var>lcnum</var>' specifies the maximum number of leaf nodes to be cached.  If it is not more than 0, the default value is specified.  The default value is 4096.</dd>
+<dd>`<var>ncnum</var>' specifies the maximum number of non-leaf nodes to be cached.  If it is not more than 0, the default value is specified.  The default value is 512.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the caching parameters should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tcbdbsetxmsiz' is used in order to set the size of the extra mapped memory of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbsetxmsiz(TCBDB *<var>bdb</var>, int64_t <var>xmsiz</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object which is not opened.</dd>
+<dd>`<var>xmsiz</var>' specifies the size of the extra mapped memory.  If it is not more than 0, the extra mapped memory is disabled.  It is disabled by default.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the mapping parameters should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tcbdbsetdfunit' is used in order to set the unit step number of auto defragmentation of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbsetdfunit(TCBDB *<var>bdb</var>, int32_t <var>dfunit</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object which is not opened.</dd>
+<dd>`<var>dfunit</var>' specifie the unit step number.  If it is not more than 0, the auto defragmentation is disabled.  It is disabled by default.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the defragmentation parameter should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tcbdbopen' is used in order to open a database file and connect a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbopen(TCBDB *<var>bdb</var>, const char *<var>path</var>, int <var>omode</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object which is not opened.</dd>
+<dd>`<var>path</var>' specifies the path of the database file.</dd>
+<dd>`<var>omode</var>' specifies the connection mode: `BDBOWRITER' as a writer, `BDBOREADER' as a reader.  If the mode is `BDBOWRITER', the following may be added by bitwise-or: `BDBOCREAT', which means it creates a new database if not exist, `BDBOTRUNC', which means it creates a new database regardless if one exists, `BDBOTSYNC', which means every transaction synchronizes updated contents with the device.  Both of `BDBOREADER' and `BDBOWRITER' can be added to by bitwise-or: `BDBONOLCK', which means it opens the database file without file locking, or `BDBOLCKNB', which means locking is performed without blocking.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcbdbclose' is used in order to close a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbclose(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update of a database is assured to be written when the database is closed.  If a writer opens a database but does not close it appropriately, the database will be broken.</dd>
+</dl>
+
+<p>The function `tcbdbput' is used in order to store a record into a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbput(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcbdbput2' is used in order to store a string record into a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbput2(TCBDB *<var>bdb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcbdbputkeep' is used in order to store a new record into a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbputkeep(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcbdbputkeep2' is used in order to store a new string record into a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbputkeep2(TCBDB *<var>bdb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcbdbputcat' is used in order to concatenate a value at the end of the existing record in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbputcat(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcbdbputcat2' is used in order to concatenate a stirng value at the end of the existing record in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbputcat2(TCBDB *<var>bdb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcbdbputdup' is used in order to store a record into a B+ tree database object with allowing duplication of keys.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbputdup(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, the new record is placed after the existing one.</dd>
+</dl>
+
+<p>The function `tcbdbputdup2' is used in order to store a string record into a B+ tree database object with allowing duplication of keys.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbputdup2(TCBDB *<var>bdb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, the new record is placed after the existing one.</dd>
+</dl>
+
+<p>The function `tcbdbputdup3' is used in order to store records into a B+ tree database object with allowing duplication of keys.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbputdup3(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const TCLIST *<var>vals</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the common key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the common key.</dd>
+<dd>`<var>vals</var>' specifies a list object containing values.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, the new records are placed after the existing one.</dd>
+</dl>
+
+<p>The function `tcbdbout' is used in order to remove a record of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbout(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If the key of duplicated records is specified, the first one is selected.</dd>
+</dl>
+
+<p>The function `tcbdbout2' is used in order to remove a string record of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbout2(TCBDB *<var>bdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If the key of duplicated records is specified, the first one is selected.</dd>
+</dl>
+
+<p>The function `tcbdbout3' is used in order to remove records of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbout3(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If the key of duplicated records is specified, all of them are removed.</dd>
+</dl>
+
+<p>The function `tcbdbget' is used in order to retrieve a record in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>void *tcbdbget(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>If the key of duplicated records is specified, the first one is selected.  Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbdbget2' is used in order to retrieve a string record in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>char *tcbdbget2(TCBDB *<var>bdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the string of the value of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>If the key of duplicated records is specified, the first one is selected.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbdbget3' is used in order to retrieve a record in a B+ tree database object as a volatile buffer.</p>
+
+<dl class="api">
+<dt><code>const void *tcbdbget3(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>If the key of duplicated records is specified, the first one is selected.  Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is volatile and it may be spoiled by another operation of the database, the data should be copied into another involatile buffer immediately.</dd>
+</dl>
+
+<p>The function `tcbdbget4' is used in order to retrieve records in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcbdbget4(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is a list object of the values of the corresponding records.  `NULL' is returned if no record corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbdbvnum' is used in order to get the number of records corresponding a key in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>int tcbdbvnum(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is the number of the corresponding records, else, it is 0.</dd>
+</dl>
+
+<p>The function `tcbdbvnum2' is used in order to get the number of records corresponding a string key in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>int tcbdbvnum2(TCBDB *<var>bdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the number of the corresponding records, else, it is 0.</dd>
+</dl>
+
+<p>The function `tcbdbvsiz' is used in order to get the size of the value of a record in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>int tcbdbvsiz(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+<dd>If the key of duplicated records is specified, the first one is selected.</dd>
+</dl>
+
+<p>The function `tcbdbvsiz2' is used in order to get the size of the value of a string record in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>int tcbdbvsiz2(TCBDB *<var>bdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+<dd>If the key of duplicated records is specified, the first one is selected.</dd>
+</dl>
+
+<p>The function `tcbdbrange' is used in order to get keys of ranged records in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcbdbrange(TCBDB *<var>bdb</var>, const void *<var>bkbuf</var>, int <var>bksiz</var>, bool <var>binc</var>, const void *<var>ekbuf</var>, int <var>eksiz</var>, bool <var>einc</var>, int <var>max</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>bkbuf</var>' specifies the pointer to the region of the key of the beginning border.  If it is `NULL', the first record is specified.</dd>
+<dd>`<var>bksiz</var>' specifies the size of the region of the beginning key.</dd>
+<dd>`<var>binc</var>' specifies whether the beginning border is inclusive or not.</dd>
+<dd>`<var>ekbuf</var>' specifies the pointer to the region of the key of the ending border.  If it is `NULL', the last record is specified.</dd>
+<dd>`<var>eksiz</var>' specifies the size of the region of the ending key.</dd>
+<dd>`<var>einc</var>' specifies whether the ending border is inclusive or not.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the keys of the corresponding records.  This function does never fail.  It returns an empty list even if no record corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbdbrange2' is used in order to get string keys of ranged records in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcbdbrange2(TCBDB *<var>bdb</var>, const char *<var>bkstr</var>, bool <var>binc</var>, const char *<var>ekstr</var>, bool <var>einc</var>, int <var>max</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>bkstr</var>' specifies the string of the key of the beginning border.  If it is `NULL', the first record is specified.</dd>
+<dd>`<var>binc</var>' specifies whether the beginning border is inclusive or not.</dd>
+<dd>`<var>ekstr</var>' specifies the string of the key of the ending border.  If it is `NULL', the last record is specified.</dd>
+<dd>`<var>einc</var>' specifies whether the ending border is inclusive or not.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the keys of the corresponding records.  This function does never fail.  It returns an empty list even if no record corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbdbfwmkeys' is used in order to get forward matching keys in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcbdbfwmkeys(TCBDB *<var>bdb</var>, const void *<var>pbuf</var>, int <var>psiz</var>, int <var>max</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>pbuf</var>' specifies the pointer to the region of the prefix.</dd>
+<dd>`<var>psiz</var>' specifies the size of the region of the prefix.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbdbfwmkeys2' is used in order to get forward matching string keys in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcbdbfwmkeys2(TCBDB *<var>bdb</var>, const char *<var>pstr</var>, int <var>max</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>pstr</var>' specifies the string of the prefix.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbdbaddint' is used in order to add an integer to a record in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>int tcbdbaddint(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int <var>num</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>If successful, the return value is the summation value, else, it is `INT_MIN'.</dd>
+<dd>If the corresponding record exists, the value is treated as an integer and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tcbdbadddouble' is used in order to add a real number to a record in a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>double tcbdbadddouble(TCBDB *<var>bdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, double <var>num</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>If successful, the return value is the summation value, else, it is Not-a-Number.</dd>
+<dd>If the corresponding record exists, the value is treated as a real number and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tcbdbsync' is used in order to synchronize updated contents of a B+ tree database object with the file and the device.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbsync(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>This function is useful when another process connects to the same database file.</dd>
+</dl>
+
+<p>The function `tcbdboptimize' is used in order to optimize the file of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdboptimize(TCBDB *<var>bdb</var>, int32_t <var>lmemb</var>, int32_t <var>nmemb</var>, int64_t <var>bnum</var>, int8_t <var>apow</var>, int8_t <var>fpow</var>, uint8_t <var>opts</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>`<var>lmemb</var>' specifies the number of members in each leaf page.  If it is not more than 0, the current setting is not changed.</dd>
+<dd>`<var>nmemb</var>' specifies the number of members in each non-leaf page.  If it is not more than 0, the current setting is not changed.</dd>
+<dd>`<var>bnum</var>' specifies the number of elements of the bucket array.  If it is not more than 0, the default value is specified.  The default value is two times of the number of pages.</dd>
+<dd>`<var>apow</var>' specifies the size of record alignment by power of 2.  If it is negative, the current setting is not changed.</dd>
+<dd>`<var>fpow</var>' specifies the maximum number of elements of the free block pool by power of 2.  If it is negative, the current setting is not changed.</dd>
+<dd>`<var>opts</var>' specifies options by bitwise-or: `BDBTLARGE' specifies that the size of the database can be larger than 2GB by using 64-bit bucket array, `BDBTDEFLATE' specifies that each record is compressed with Deflate encoding, `BDBTTCBS' specifies that each page is compressed with TCBS encoding.  If it is `UINT8_MAX', the current setting is not changed.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>This function is useful to reduce the size of the database file with data fragmentation by successive updating.</dd>
+</dl>
+
+<p>The function `tcbdbvanish' is used in order to remove all records of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbvanish(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcbdbcopy' is used in order to copy the database file of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbcopy(TCBDB *<var>bdb</var>, const char *<var>path</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>`<var>path</var>' specifies the path of the destination file.  If it begins with `@', the trailing substring is executed as a command line.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned if the executed command returns non-zero code.</dd>
+<dd>The database file is assured to be kept synchronized and not modified while the copying or executing operation is in progress.  So, this function is useful to create a backup file of the database file.</dd>
+</dl>
+
+<p>The function `tcbdbtranbegin' is used in order to begin the transaction of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbtranbegin(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>The database is locked by the thread while the transaction so that only one transaction can be activated with a database object at the same time.  Thus, the serializable isolation level is assumed if every database operation is performed in the transaction.  Because all pages are cached on memory while the transaction, the amount of referred records is limited by the memory capacity.  If the database is closed during transaction, the transaction is aborted implicitly.</dd>
+</dl>
+
+<p>The function `tcbdbtrancommit' is used in order to commit the transaction of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbtrancommit(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update in the transaction is fixed when it is committed successfully.</dd>
+</dl>
+
+<p>The function `tcbdbtranabort' is used in order to abort the transaction of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbtranabort(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update in the transaction is discarded when it is aborted.  The state of the database is rollbacked to before transaction.</dd>
+</dl>
+
+<p>The function `tcbdbpath' is used in order to get the file path of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>const char *tcbdbpath(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>The return value is the path of the database file or `NULL' if the object does not connect to any database file.</dd>
+</dl>
+
+<p>The function `tcbdbrnum' is used in order to get the number of records of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcbdbrnum(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>The return value is the number of records or 0 if the object does not connect to any database file.</dd>
+</dl>
+
+<p>The function `tcbdbfsiz' is used in order to get the size of the database file of a B+ tree database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcbdbfsiz(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>The return value is the size of the database file or 0 if the object does not connect to any database file.</dd>
+</dl>
+
+<p>The function `tcbdbcurnew' is used in order to create a cursor object.</p>
+
+<dl class="api">
+<dt><code>BDBCUR *tcbdbcurnew(TCBDB *<var>bdb</var>);</code></dt>
+<dd>`<var>bdb</var>' specifies the B+ tree database object.</dd>
+<dd>The return value is the new cursor object.</dd>
+<dd>Note that the cursor is available only after initialization with the `tcbdbcurfirst' or the `tcbdbcurjump' functions and so on.  Moreover, the position of the cursor will be indefinite when the database is updated after the initialization of the cursor.</dd>
+</dl>
+
+<p>The function `tcbdbcurdel' is used in order to delete a cursor object.</p>
+
+<dl class="api">
+<dt><code>void tcbdbcurdel(BDBCUR *<var>cur</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+</dl>
+
+<p>The function `tcbdbcurfirst' is used in order to move a cursor object to the first record.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbcurfirst(BDBCUR *<var>cur</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned if there is no record in the database.</dd>
+</dl>
+
+<p>The function `tcbdbcurlast' is used in order to move a cursor object to the last record.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbcurlast(BDBCUR *<var>cur</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned if there is no record in the database.</dd>
+</dl>
+
+<p>The function `tcbdbcurjump' is used in order to move a cursor object to the front of records corresponding a key.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbcurjump(BDBCUR *<var>cur</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned if there is no record corresponding the condition.</dd>
+<dd>The cursor is set to the first record corresponding the key or the next substitute if completely matching record does not exist.</dd>
+</dl>
+
+<p>The function `tcbdbcurjump2' is used in order to move a cursor object to the front of records corresponding a key string.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbcurjump2(BDBCUR *<var>cur</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned if there is no record corresponding the condition.</dd>
+<dd>The cursor is set to the first record corresponding the key or the next substitute if completely matching record does not exist.</dd>
+</dl>
+
+<p>The function `tcbdbcurprev' is used in order to move a cursor object to the previous record.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbcurprev(BDBCUR *<var>cur</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned if there is no previous record.</dd>
+</dl>
+
+<p>The function `tcbdbcurnext' is used in order to move a cursor object to the next record.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbcurnext(BDBCUR *<var>cur</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned if there is no next record.</dd>
+</dl>
+
+<p>The function `tcbdbcurput' is used in order to insert a record around a cursor object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbcurput(BDBCUR *<var>cur</var>, const void *<var>vbuf</var>, int <var>vsiz</var>, int <var>cpmode</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object of writer connection.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>`<var>cpmode</var>' specifies detail adjustment: `BDBCPCURRENT', which means that the value of the current record is overwritten, `BDBCPBEFORE', which means that the new record is inserted before the current record, `BDBCPAFTER', which means that the new record is inserted after the current record.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned when the cursor is at invalid position.</dd>
+<dd>After insertion, the cursor is moved to the inserted record.</dd>
+</dl>
+
+<p>The function `tcbdbcurput2' is used in order to insert a string record around a cursor object.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbcurput2(BDBCUR *<var>cur</var>, const char *<var>vstr</var>, int <var>cpmode</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object of writer connection.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>`<var>cpmode</var>' specifies detail adjustment: `BDBCPCURRENT', which means that the value of the current record is overwritten, `BDBCPBEFORE', which means that the new record is inserted before the current record, `BDBCPAFTER', which means that the new record is inserted after the current record.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned when the cursor is at invalid position.</dd>
+<dd>After insertion, the cursor is moved to the inserted record.</dd>
+</dl>
+
+<p>The function `tcbdbcurout' is used in order to remove the record where a cursor object is.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbcurout(BDBCUR *<var>cur</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object of writer connection.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned when the cursor is at invalid position.</dd>
+<dd>After deletion, the cursor is moved to the next record if possible.</dd>
+</dl>
+
+<p>The function `tcbdbcurkey' is used in order to get the key of the record where the cursor object is.</p>
+
+<dl class="api">
+<dt><code>void *tcbdbcurkey(BDBCUR *<var>cur</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the key, else, it is `NULL'.  `NULL' is returned when the cursor is at invalid position.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbdbcurkey2' is used in order to get the key string of the record where the cursor object is.</p>
+
+<dl class="api">
+<dt><code>char *tcbdbcurkey2(BDBCUR *<var>cur</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>If successful, the return value is the string of the key, else, it is `NULL'.  `NULL' is returned when the cursor is at invalid position.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbdbcurkey3' is used in order to get the key of the record where the cursor object is, as a volatile buffer.</p>
+
+<dl class="api">
+<dt><code>const void *tcbdbcurkey3(BDBCUR *<var>cur</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the key, else, it is `NULL'.  `NULL' is returned when the cursor is at invalid position.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is volatile and it may be spoiled by another operation of the database, the data should be copied into another involatile buffer immediately.</dd>
+</dl>
+
+<p>The function `tcbdbcurval' is used in order to get the value of the record where the cursor object is.</p>
+
+<dl class="api">
+<dt><code>void *tcbdbcurval(BDBCUR *<var>cur</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the value, else, it is `NULL'.  `NULL' is returned when the cursor is at invalid position.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbdbcurval2' is used in order to get the value string of the record where the cursor object is.</p>
+
+<dl class="api">
+<dt><code>char *tcbdbcurval2(BDBCUR *<var>cur</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>If successful, the return value is the string of the value, else, it is `NULL'.  `NULL' is returned when the cursor is at invalid position.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcbdbcurval3' is used in order to get the value of the record where the cursor object is, as a volatile buffer.</p>
+
+<dl class="api">
+<dt><code>const void *tcbdbcurval3(BDBCUR *<var>cur</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the value, else, it is `NULL'.  `NULL' is returned when the cursor is at invalid position.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is volatile and it may be spoiled by another operation of the database, the data should be copied into another involatile buffer immediately.</dd>
+</dl>
+
+<p>The function `tcbdbcurrec' is used in order to get the key and the value of the record where the cursor object is.</p>
+
+<dl class="api">
+<dt><code>bool tcbdbcurrec(BDBCUR *<var>cur</var>, TCXSTR *<var>kxstr</var>, TCXSTR *<var>vxstr</var>);</code></dt>
+<dd>`<var>cur</var>' specifies the cursor object.</dd>
+<dd>`<var>kxstr</var>' specifies the object into which the key is wrote down.</dd>
+<dd>`<var>vxstr</var>' specifies the object into which the value is wrote down.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned when the cursor is at invalid position.</dd>
+</dl>
+
+<h3 id="tcbdbapi_example">コード例</h3>
+
+<p>B+木データベースを使ったコード例を以下に示します。</p>
+
+<pre>#include &lt;tcutil.h&gt;
+#include &lt;tcbdb.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdbool.h&gt;
+#include &lt;stdint.h&gt;
+
+int main(int argc, char **argv){
+  TCBDB *bdb;
+  BDBCUR *cur;
+  int ecode;
+  char *key, *value;
+
+  /* オブジェクトを作成する */
+  bdb = tcbdbnew();
+
+  /* データベースを開く */
+  if(!tcbdbopen(bdb, "casket.tcb", BDBOWRITER | BDBOCREAT)){
+    ecode = tcbdbecode(bdb);
+    fprintf(stderr, "open error: %s\n", tcbdberrmsg(ecode));
+  }
+
+  /* レコードを格納する */
+  if(!tcbdbput2(bdb, "foo", "hop") ||
+     !tcbdbput2(bdb, "bar", "step") ||
+     !tcbdbput2(bdb, "baz", "jump")){
+    ecode = tcbdbecode(bdb);
+    fprintf(stderr, "put error: %s\n", tcbdberrmsg(ecode));
+  }
+
+  /* レコードを取得する */
+  value = tcbdbget2(bdb, "foo");
+  if(value){
+    printf("%s\n", value);
+    free(value);
+  } else {
+    ecode = tcbdbecode(bdb);
+    fprintf(stderr, "get error: %s\n", tcbdberrmsg(ecode));
+  }
+
+  /* 横断的にレコードを参照する */
+  cur = tcbdbcurnew(bdb);
+  tcbdbcurfirst(cur);
+  while((key = tcbdbcurkey2(cur)) != NULL){
+    value = tcbdbcurval2(cur);
+    if(value){
+      printf("%s:%s\n", key, value);
+      free(value);
+    }
+    free(key);
+    tcbdbcurnext(cur);
+  }
+  tcbdbcurdel(cur);
+
+  /* データベースを閉じる */
+  if(!tcbdbclose(bdb)){
+    ecode = tcbdbecode(bdb);
+    fprintf(stderr, "close error: %s\n", tcbdberrmsg(ecode));
+  }
+
+  /* オブジェクトを破棄する */
+  tcbdbdel(bdb);
+
+  return 0;
+}
+</pre>
+
+<h3 id="tcbdbapi_cli">CLI</h3>
+
+<p>B+木データベースAPIを簡単に利用するために、コマンドラインインターフェイスとして `<code>tcbtest</code>' と `<code>tcbmttest</code>' と `<code>tcbmgr</code>' が提供されます。</p>
+
+<p>コマンド `<code>tcbtest</code>' は、B+木データベースAPIの機能テストや性能テストに用いるツールです。以下の書式で用います。`<var>path</var>' はデータベースファイルのパスを指定し、`<var>rnum</var>' は試行回数を指定し、`<var>lmemb</var>' はリーフ内メンバ数を指定し、`<var>nmemb</var>' は非リーフ内メンバ数を指定し、`<var>bnum</var>' はバケット数を指定し、`<var>apow</var>' はアラインメント力を指定し、`<var>fpow</var>' はフリーブロックプール力を指定します。</p>
+
+<dl class="api">
+<dt><code>tcbtest write [-mt] [-cd|-ci|-cj] [-tl] [-td|-tb|-tt|-tx] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-ls <var>num</var>] [-ca <var>num</var>] [-nl|-nb] [-rnd] <var>path</var> <var>rnum</var> [<var>lmemb</var> [<var>nmemb</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]]]</code></dt>
+<dd>`00000001'、`00000002' のように変化する8バイトのキーと値を連続してデータベースに追加する。</dd>
+<dt><code>tcbtest read [-mt] [-cd|-ci|-cj] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-wb] [-rnd] <var>path</var></code></dt>
+<dd>上記で生成したデータベースの全レコードを検索する。</dd>
+<dt><code>tcbtest remove [-mt] [-cd|-ci|-cj] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-rnd] <var>path</var></code></dt>
+<dd>上記で生成したデータベースの全レコードを削除する。</dd>
+<dt><code>tcbtest rcat [-mt] [-cd|-ci|-cj] [-tl] [-td|-tb|-tt|-tx] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-ls <var>num</var>] [-ca <var>num</var>] [-nl|-nb] [-pn <var>num</var>] [-dai|-dad|-rl|-ru] <var>path</var> <var>rnum</var> [<var>lmemb</var> [<var>nmemb</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]]]</code></dt>
+<dd>キーがある程度重複するようにレコードの追加を行い、連結モードで処理する。</dd>
+<dt><code>tcbtest queue [-mt] [-cd|-ci|-cj] [-tl] [-td|-tb|-tt|-tx] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-ls <var>num</var>] [-ca <var>num</var>] [-nl|-nb] <var>path</var> <var>rnum</var> [<var>lmemb</var> [<var>nmemb</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]]]</code></dt>
+<dd>キューの出し入れを行う。</dd>
+<dt><code>tcbtest misc [-mt] [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] <var>path</var> <var>rnum</var></code></dt>
+<dd>各種操作の組み合わせテストを行う。</dd>
+<dt><code>tcbtest wicked [-mt] [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] <var>path</var> <var>rnum</var></code></dt>
+<dd>各種更新操作を無作為に選択して実行する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持ちます</p>
+
+<ul class="options">
+<li><code>-mt</code> : 関数 `tcbdbsetmutex' を呼び出す。</li>
+<li><code>-cd</code> : 比較関数 `tccmpdecimal' を利用する。</li>
+<li><code>-ci</code> : 比較関数 `tccmpint32' を利用する。</li>
+<li><code>-cj</code> : 比較関数 `tccmpint64' を利用する。</li>
+<li><code>-tl</code> : オプション `BDBTLARGE' を有効にする。</li>
+<li><code>-td</code> : オプション `BDBTDEFLATE' を有効にする。</li>
+<li><code>-tb</code> : オプション `BDBTBZIP' を有効にする。</li>
+<li><code>-tt</code> : オプション `BDBTTCBS' を有効にする。</li>
+<li><code>-tx</code> : オプション `BDBTEXCODEC' を有効にする。</li>
+<li><code>-lc <var>num</var></code> : リーフノード用キャッシュの最大数を指定する。</li>
+<li><code>-nc <var>num</var></code> : 非リーフノード用キャッシュの最大数を指定する。</li>
+<li><code>-xm <var>num</var></code> : 拡張マップメモリのサイズを指定する。</li>
+<li><code>-df <var>num</var></code> : 自動デフラグの単位ステップ数を指定する。</li>
+<li><code>-ls <var>num</var></code> : リーフノードの最大サイズを指定する。</li>
+<li><code>-ca <var>num</var></code> : レコードの最大収容数を指定する。</li>
+<li><code>-nl</code> : オプション `BDBNOLCK' を有効にする。</li>
+<li><code>-nb</code> : オプション `BDBLCKNB' を有効にする。</li>
+<li><code>-rnd</code> : キーを無作為に選択する。</li>
+<li><code>-wb</code> : 関数 `tcbdbget' の代わりに関数 `tcbdbget3' を用いる。</li>
+<li><code>-pn <var>num</var></code> : パターン数を指定する。</li>
+<li><code>-dai</code> : 関数 `tcbdbputcat' の代わりに関数 `tcbdbaddint' を用いる。</li>
+<li><code>-dad</code> : 関数 `tcbdbputcat' の代わりに関数 `tcbdbadddouble' を用いる。</li>
+<li><code>-rl</code> : 値を無作為な長さにする。</li>
+<li><code>-ru</code> : 更新操作を無作為に選択する。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了します。</p>
+
+<p>コマンド `<code>tcbmttest</code>' は、B+木データベースAPIの機能テストをマルチスレッドで行うツールです。以下の書式で用います。`<var>path</var>' はデータベースファイルのパスを指定し、`<var>tnum</var>' はスレッド数を指定し、`<var>rnum</var>' は試行回数を指定し、`<var>lmemb</var>' はリーフ内メンバ数を指定し、`<var>nmemb</var>' は非リーフ内メンバ数を指定し、`<var>bnum</var>' はバケット数を指定し、`<var>apow</var>' はアラインメント力を指定し、`<var>fpow</var>' はフリーブロックプール力を指定します。</p>
+
+<dl class="api">
+<dt><code>tcbmttest write [-tl] [-td|-tb|-tt|-tx] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-rnd] <var>path</var> <var>tnum</var> <var>rnum</var> [<var>lmemb</var> [<var>nmemb</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]]]</code></dt>
+<dd>`00000001'、`00000002' のように変化する8バイトのキーと値を連続してデータベースに追加する。</dd>
+<dt><code>tcbmttest read [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-wb] [-rnd] <var>path</var> <var>tnum</var></code></dt>
+<dd>上記で生成したデータベースの全レコードを検索する。</dd>
+<dt><code>tcbmttest remove [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-rnd] <var>path</var> <var>tnum</var></code></dt>
+<dd>上記で生成したデータベースの全レコードを削除する。</dd>
+<dt><code>tcbmttest wicked [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] [-nc] <var>path</var> <var>tnum</var> <var>rnum</var></code></dt>
+<dd>各種更新操作を無作為に選択して実行する。</dd>
+<dt><code>tcbmttest typical [-tl] [-td|-tb|-tt|-tx] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-nc] [-rr <var>num</var>] <var>path</var> <var>tnum</var> <var>rnum</var> [<var>lmemb</var> [<var>nmemb</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]]]</code></dt>
+<dd>典型的な操作を無作為に選択して実行する。</dd>
+<dt><code>tcbmttest race [-tl] [-td|-tb|-tt|-tx] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] <var>path</var> <var>tnum</var> <var>rnum</var> [<var>lmemb</var> [<var>nmemb</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]]]</code></dt>
+<dd>レースコンディション検出のテストを行う。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持ちます</p>
+
+<ul class="options">
+<li><code>-tl</code> : オプション `BDBTLARGE' を有効にする。</li>
+<li><code>-td</code> : オプション `BDBTDEFLATE' を有効にする。</li>
+<li><code>-tb</code> : オプション `BDBTBZIP' を有効にする。</li>
+<li><code>-tt</code> : オプション `BDBTTCBS' を有効にする。</li>
+<li><code>-tx</code> : オプション `BDBTEXCODEC' を有効にする。</li>
+<li><code>-xm <var>num</var></code> : 拡張マップメモリのサイズを指定する。</li>
+<li><code>-df <var>num</var></code> : 自動デフラグの単位ステップ数を指定する。</li>
+<li><code>-nl</code> : オプション `BDBNOLCK' を有効にする。</li>
+<li><code>-nb</code> : オプション `BDBLCKNB' を有効にする。</li>
+<li><code>-rnd</code> : キーを無作為に選択する。</li>
+<li><code>-wb</code> : 関数 `tcbdbget' の代わりに関数 `tcbdbget3' を用いる。</li>
+<li><code>-nc</code> : 比較テストを行わない。</li>
+<li><code>-rr <var>num</var></code> : 読み込み操作の割合を百分率で指定する。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了します。</p>
+
+<p>コマンド `<code>tcbmgr</code>' は、B+木データベースAPIやそのアプリケーションのテストやデバッグに役立つツールです。以下の書式で用います。`<var>path</var>' はデータベースファイルのパスを指定し、`<var>lmemb</var>' はリーフ内メンバ数を指定し、`<var>nmemb</var>' は非リーフ内メンバ数を指定し、`<var>bnum</var>' はバケット数を指定し、`<var>apow</var>' はアラインメント力を指定し、`<var>fpow</var>' はフリーブロックプール力を指定し、`<var>key</var>' はレコードのキーを指定し、`<var>value</var>' はレコードの値を指定し、`<var>file</var>' は入力ファイルを指定します。</p>
+
+<dl class="api">
+<dt><code>tcbmgr create [-cd|-ci|-cj] [-tl] [-td|-tb|-tt|-tx] <var>path</var> [<var>lmemb</var> [<var>nmemb</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]]]</code></dt>
+<dd>データベースファイルを作成する。</dd>
+<dt><code>tcbmgr inform [-nl|-nb] <var>path</var></code></dt>
+<dd>データベースの雑多な情報を出力する。</dd>
+<dt><code>tcbmgr put [-cd|-ci|-cj] [-nl|-nb] [-sx] [-dk|-dc|-dai|-dad] <var>path</var> <var>key</var> <var>value</var></code></dt>
+<dd>レコードを追加する。</dd>
+<dt><code>tcbmgr out [-cd|-ci|-cj] [-nl|-nb] [-sx] <var>path</var> <var>key</var></code></dt>
+<dd>レコードを削除する。</dd>
+<dt><code>tcbmgr get [-cd|-ci|-cj] [-nl|-nb] [-sx] [-px] [-pz] <var>path</var> <var>key</var></code></dt>
+<dd>レコードの値を取得して標準出力する。</dd>
+<dt><code>tcbmgr list [-cd|-ci|-cj] [-nl|-nb] [-m <var>num</var>] [-bk] [-pv] [-px] [-j <var>str</var>] [-rb <var>bkey</var> <var>ekey</var>] [-fm <var>str</var>] <var>path</var></code></dt>
+<dd>全てのレコードのキーを改行で区切って標準出力する。</dd>
+<dt><code>tcbmgr optimize [-cd|-ci|-cj] [-tl] [-td|-tb|-tt|-tx] [-tz] [-nl|-nb] [-df] <var>path</var> [<var>lmemb</var> [<var>nmemb</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]]]</code></dt>
+<dd>データベースを最適化する。</dd>
+<dt><code>tcbmgr importtsv [-nl|-nb] [-sc] <var>path</var> [<var>file</var>]</code></dt>
+<dd>TSVファイルの各行をキーと値とみなしてレコードを登録する。</dd>
+<dt><code>tcbmgr version</code></dt>
+<dd>Tokyo Cabinetのバージョン情報を標準出力する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持ちます</p>
+
+<ul class="options">
+<li><code>-cd</code> : 比較関数 `tccmpdecimal' を利用する。</li>
+<li><code>-ci</code> : 比較関数 `tccmpint32' を利用する。</li>
+<li><code>-cj</code> : 比較関数 `tccmpint64' を利用する。</li>
+<li><code>-tl</code> : オプション `BDBTLARGE' を有効にする。</li>
+<li><code>-td</code> : オプション `BDBTDEFLATE' を有効にする。</li>
+<li><code>-tb</code> : オプション `BDBTBZIP' を有効にする。</li>
+<li><code>-tt</code> : オプション `BDBTTCBS' を有効にする。</li>
+<li><code>-tx</code> : オプション `BDBTEXCODEC' を有効にする。</li>
+<li><code>-nl</code> : オプション `BDBNOLCK' を有効にする。</li>
+<li><code>-nb</code> : オプション `BDBLCKNB' を有効にする。</li>
+<li><code>-sx</code> : 入力を16進数の文字列で行う。</li>
+<li><code>-dk</code> : 関数 `tcbdbput' の代わりに関数 `tcbdbputkeep' を用いる。</li>
+<li><code>-dc</code> : 関数 `tcbdbput' の代わりに関数 `tcbdbputcat' を用いる。</li>
+<li><code>-dd</code> : 関数 `tcbdbput' の代わりに関数 `tcbdbputdup' を用いる。</li>
+<li><code>-db</code> : 関数 `tcbdbput' の代わりに関数 `tcbdbputdupback' を用いる。</li>
+<li><code>-dai</code> : 関数 `tcbdbput' の代わりに関数 `tcbdbaddint' を用いる。</li>
+<li><code>-dad</code> : 関数 `tcbdbput' の代わりに関数 `tcbdbadddouble' を用いる。</li>
+<li><code>-px</code> : 出力を16進数の文字列で行う。</li>
+<li><code>-pz</code> : 出力の末尾に改行を付加しない。</li>
+<li><code>-m <var>num</var></code> : 出力の最大数を指定する。</li>
+<li><code>-bk</code> : 走査を逆方向で行う。</li>
+<li><code>-pv</code> : レコードの値も出力する。</li>
+<li><code>-j <var>str</var></code> : カーソルを指定位置にジャンプさせる。</li>
+<li><code>-rb <var>bkey</var> <var>ekey</var></code> : 処理対象を範囲指定する。</li>
+<li><code>-fm <var>str</var></code> : キーの接頭辞を指定する。</li>
+<li><code>-tz</code> : オプション `UINT8_MAX' を有効にする。</li>
+<li><code>-df</code> : デフラグのみを行う。</li>
+<li><code>-sc</code> : キーを小文字に正規化する。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了します。</p>
+
+<hr />
+
+<h2 id="tcfdbapi">固定長データベースAPI</h2>
+
+<p>固定長データベースは、固定長の要素からなる配列を単一のファイルに記録したデータベースです。それを扱うのが固定長データベースAPIです。`<code>tcfdb.h</code>' にAPIの仕様の完全な記述があります。</p>
+
+<h3 id="tcfdbapi_description">概要</h3>
+
+<p>固定長データベースAPIを使うためには、`<code>tcutil.h</code>'、`<code>tcfdb.h</code>' および関連する標準ヘッダファイルをインクルードしてください。通常、ソースファイルの冒頭付近で以下の記述を行います。</p>
+
+<dl>
+<dt><code>#include &lt;tcutil.h&gt;</code></dt>
+<dt><code>#include &lt;tcfdb.h&gt;</code></dt>
+<dt><code>#include &lt;stdlib.h&gt;</code></dt>
+<dt><code>#include &lt;stdbool.h&gt;</code></dt>
+<dt><code>#include &lt;stdint.h&gt;</code></dt>
+</dl>
+
+<p>固定長データベースを扱う際には、`<code>TCFDB</code>' 型へのポインタをオブジェクトとして用います。固定長データベースオブジェクトは、関数 `<code>tcfdbnew</code>' で作成し、関数 `<code>tcfdbdel</code>' で破棄します。作成したオブジェクトを使い終わったら必ず破棄してください。そうしないとメモリリークが発生します。</p>
+
+<p>レコードの格納や探索を行う前提として、固定長データベースオブジェクトをデータベースファイルと接続させる必要があります。データベースファイルを開いて接続するには関数 `<code>tcfdbopen</code>' を用い、接続の解除してファイルを閉じるには関数 `<code>tcfdbclose</code>' を用います。開いたデータベースファイルは必ず閉じてください。そうしないとデータベースファイルが壊れたり格納したデータが失われたりする可能性があります。単一のプロセス内で複数のデータベースオブジェクトが同じデータベースファイルを同時に開くことはできません。</p>
+
+<h3 id="tcfdbapi_api">API(英語スマソ)</h3>
+
+<p>The function `tcfdberrmsg' is used in order to get the message string corresponding to an error code.</p>
+
+<dl class="api">
+<dt><code>const char *tcfdberrmsg(int <var>ecode</var>);</code></dt>
+<dd>`<var>ecode</var>' specifies the error code.</dd>
+<dd>The return value is the message string of the error code.</dd>
+</dl>
+
+<p>The function `tcfdbnew' is used in order to create a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>TCFDB *tcfdbnew(void);</code></dt>
+<dd>The return value is the new fixed-length database object.</dd>
+</dl>
+
+<p>The function `tcfdbdel' is used in order to delete a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>void tcfdbdel(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>If the database is not closed, it is closed implicitly.  Note that the deleted object and its derivatives can not be used anymore.</dd>
+</dl>
+
+<p>The function `tcfdbecode' is used in order to get the last happened error code of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>int tcfdbecode(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>The return value is the last happened error code.</dd>
+<dd>The following error codes are defined: `TCESUCCESS' for success, `TCETHREAD' for threading error, `TCEINVALID' for invalid operation, `TCENOFILE' for file not found, `TCENOPERM' for no permission, `TCEMETA' for invalid meta data, `TCERHEAD' for invalid record header, `TCEOPEN' for open error, `TCECLOSE' for close error, `TCETRUNC' for trunc error, `TCESYNC' for sync error, `TCESTAT' for stat error, `TCESEEK' for seek error, `TCEREAD' for read error, `TCEWRITE' for write error, `TCEMMAP' for mmap error, `TCELOCK' for lock error, `TCEUNLINK' for unlink error, `TCERENAME' for rename error, `TCEMKDIR' for mkdir error, `TCERMDIR' for rmdir error, `TCEKEEP' for existing record, `TCENOREC' for no record found, and `TCEMISC' for miscellaneous error.</dd>
+</dl>
+
+<p>The function `tcfdbsetmutex' is used in order to set mutual exclusion control of a fixed-length database object for threading.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbsetmutex(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object which is not opened.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the mutual exclusion control is needed if the object is shared by plural threads and this function should be called before the database is opened.</dd>
+</dl>
+
+<p>The function `tcfdbtune' is used in order to set the tuning parameters of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbtune(TCFDB *<var>fdb</var>, int32_t <var>width</var>, int64_t <var>limsiz</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object which is not opened.</dd>
+<dd>`<var>width</var>' specifies the width of the value of each record.  If it is not more than 0, the default value is specified.  The default value is 255.</dd>
+<dd>`<var>limsiz</var>' specifies the limit size of the database file.  If it is not more than 0, the default value is specified.  The default value is 268435456.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the tuning parameters should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tcfdbopen' is used in order to open a database file and connect a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbopen(TCFDB *<var>fdb</var>, const char *<var>path</var>, int <var>omode</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object which is not opened.</dd>
+<dd>`<var>path</var>' specifies the path of the database file.</dd>
+<dd>`<var>omode</var>' specifies the connection mode: `FDBOWRITER' as a writer, `FDBOREADER' as a reader.  If the mode is `FDBOWRITER', the following may be added by bitwise-or: `FDBOCREAT', which means it creates a new database if not exist, `FDBOTRUNC', which means it creates a new database regardless if one exists, `FDBOTSYNC', which means every transaction synchronizes updated contents with the device.  Both of `FDBOREADER' and `FDBOWRITER' can be added to by bitwise-or: `FDBONOLCK', which means it opens the database file without file locking, or `FDBOLCKNB', which means locking is performed without blocking.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcfdbclose' is used in order to close a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbclose(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update of a database is assured to be written when the database is closed.  If a writer opens a database but does not close it appropriately, the database will be broken.</dd>
+</dl>
+
+<p>The function `tcfdbput' is used in order to store a record into a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbput(TCFDB *<var>fdb</var>, int64_t <var>id</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>id</var>' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDPREV', the number less by one than the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.  If it is `FDBIDNEXT', the number greater by one than the maximum ID number of existing records is specified.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.  If the size of the value is greater than the width tuning parameter of the database, the size is cut down to the width.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcfdbput2' is used in order to store a record with a decimal key into a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbput2(TCFDB *<var>fdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "prev", the number less by one than the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.  If it is "next", the number greater by one than the maximum ID number of existing records is specified.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.  If the size of the value is greater than the width tuning parameter of the database, the size is cut down to the width.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcfdbput3' is used in order to store a string record with a decimal key into a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbput3(TCFDB *<var>fdb</var>, const char *<var>kstr</var>, const void *<var>vstr</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "prev", the number less by one than the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.  If it is "next", the number greater by one than the maximum ID number of existing records is specified.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcfdbputkeep' is used in order to store a new record into a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbputkeep(TCFDB *<var>fdb</var>, int64_t <var>id</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>id</var>' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDPREV', the number less by one than the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.  If it is `FDBIDNEXT', the number greater by one than the maximum ID number of existing records is specified.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.  If the size of the value is greater than the width tuning parameter of the database, the size is cut down to the width.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcfdbputkeep2' is used in order to store a new record with a decimal key into a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbputkeep2(TCFDB *<var>fdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "prev", the number less by one than the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.  If it is "next", the number greater by one than the maximum ID number of existing records is specified.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.  If the size of the value is greater than the width tuning parameter of the database, the size is cut down to the width.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcfdbputkeep3' is used in order to store a new string record with a decimal key into a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbputkeep3(TCFDB *<var>fdb</var>, const char *<var>kstr</var>, const void *<var>vstr</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "prev", the number less by one than the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.  If it is "next", the number greater by one than the maximum ID number of existing records is specified.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcfdbputcat' is used in order to concatenate a value at the end of the existing record in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbputcat(TCFDB *<var>fdb</var>, int64_t <var>id</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>id</var>' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDPREV', the number less by one than the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.  If it is `FDBIDNEXT', the number greater by one than the maximum ID number of existing records is specified.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.  If the size of the value is greater than the width tuning parameter of the database, the size is cut down to the width.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcfdbputcat2' is used in order to concatenate a value with a decimal key in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbputcat2(TCFDB *<var>fdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "prev", the number less by one than the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.  If it is "next", the number greater by one than the maximum ID number of existing records is specified.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.  If the size of the value is greater than the width tuning parameter of the database, the size is cut down to the width.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcfdbputcat3' is used in order to concatenate a string value with a decimal key in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbputcat3(TCFDB *<var>fdb</var>, const char *<var>kstr</var>, const void *<var>vstr</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "prev", the number less by one than the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.  If it is "next", the number greater by one than the maximum ID number of existing records is specified.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcfdbout' is used in order to remove a record of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbout(TCFDB *<var>fdb</var>, int64_t <var>id</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>id</var>' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcfdbout2' is used in order to remove a record with a decimal key of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbout2(TCFDB *<var>fdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcfdbout3' is used in order to remove a string record with a decimal key of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbout3(TCFDB *<var>fdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>kstr</var>' specifies the string of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcfdbget' is used in order to retrieve a record in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>void *tcfdbget(TCFDB *<var>fdb</var>, int64_t <var>id</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>id</var>' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcfdbget2' is used in order to retrieve a record with a decimal key in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>void *tcfdbget2(TCFDB *<var>fdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcfdbget3' is used in order to retrieve a string record with a decimal key in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>char *tcfdbget3(TCFDB *<var>fdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.</dd>
+<dd>If successful, the return value is the string of the value of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcfdbget4' is used in order to retrieve a record in a fixed-length database object and write the value into a buffer.</p>
+
+<dl class="api">
+<dt><code>int tcfdbget4(TCFDB *<var>fdb</var>, int64_t <var>id</var>, void *<var>vbuf</var>, int <var>max</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>id</var>' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the buffer into which the value of the corresponding record is written.</dd>
+<dd>`<var>max</var>' specifies the size of the buffer.</dd>
+<dd>If successful, the return value is the size of the written data, else, it is -1.  -1 is returned if no record corresponds to the specified key.</dd>
+<dd>Note that an additional zero code is not appended at the end of the region of the writing buffer.</dd>
+</dl>
+
+<p>The function `tcfdbvsiz' is used in order to get the size of the value of a record in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>int tcfdbvsiz(TCFDB *<var>fdb</var>, int64_t <var>id</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>id</var>' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tcfdbvsiz2' is used in order to get the size of the value with a decimal key in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>int tcfdbvsiz2(TCFDB *<var>fdb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tcfdbvsiz3' is used in order to get the size of the string value with a decimal key in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>int tcfdbvsiz3(TCFDB *<var>fdb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tcfdbiterinit' is used in order to initialize the iterator of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbiterinit(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>The iterator is used in order to access the key of every record stored in a database.</dd>
+</dl>
+
+<p>The function `tcfdbiternext' is used in order to get the next ID number of the iterator of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcfdbiternext(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>If successful, the return value is the next ID number of the iterator, else, it is 0.  0 is returned when no record is to be get out of the iterator.</dd>
+<dd>It is possible to access every record by iteration of calling this function.  It is allowed to update or remove records whose keys are fetched while the iteration.  The order of this traversal access method is ascending of the ID number.</dd>
+</dl>
+
+<p>The function `tcfdbiternext2' is used in order to get the next decimay key of the iterator of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>void *tcfdbiternext2(TCFDB *<var>fdb</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the next decimal key, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  It is possible to access every record by iteration of calling this function.  It is allowed to update or remove records whose keys are fetched while the iteration.  The order of this traversal access method is ascending of the ID number.</dd>
+</dl>
+
+<p>The function `tcfdbiternext3' is used in order to get the next decimay key string of the iterator of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>char *tcfdbiternext3(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>If successful, the return value is the string of the next decimal key, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  It is possible to access every record by iteration of calling this function.  It is allowed to update or remove records whose keys are fetched while the iteration.  The order of this traversal access method is ascending of the ID number.</dd>
+</dl>
+
+<p>The function `tcfdbrange' is used in order to get range matching ID numbers in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t *tcfdbrange(TCFDB *<var>fdb</var>, int64_t <var>lower</var>, int64_t <var>upper</var>, int <var>max</var>, int *<var>np</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>lower</var>' specifies the lower limit of the range.  If it is `FDBIDMIN', the minimum ID is specified.</dd>
+<dd>`<var>upper</var>' specifies the upper limit of the range.  If it is `FDBIDMAX', the maximum ID is specified.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>`<var>np</var>' specifies the pointer to the variable into which the number of elements of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to an array of ID numbers of the corresponding records.  `NULL' is returned on failure.  This function does never fail.  It returns an empty array even if no key corresponds.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcfdbrange2' is used in order to get range matching decimal keys in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcfdbrange2(TCFDB *<var>fdb</var>, const void *<var>lbuf</var>, int <var>lsiz</var>, const void *<var>ubuf</var>, int <var>usiz</var>, int <var>max</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>lbuf</var>' specifies the pointer to the region of the lower key.  If it is "min", the minimum ID number of existing records is specified.</dd>
+<dd>`<var>lsiz</var>' specifies the size of the region of the lower key.</dd>
+<dd>`<var>ubuf</var>' specifies the pointer to the region of the upper key.  If it is "max", the maximum ID number of existing records is specified.</dd>
+<dd>`<var>usiz</var>' specifies the size of the region of the upper key.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding decimal keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.</dd>
+</dl>
+
+<p>The function `tcfdbrange3' is used in order to get range matching decimal keys with strings in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcfdbrange3(TCFDB *<var>fdb</var>, const char *<var>lstr</var>, const char *<var>ustr</var>, int <var>max</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>lstr</var>' specifies the string of the lower key.  If it is "min", the minimum ID number of existing records is specified.</dd>
+<dd>`<var>ustr</var>' specifies the string of the upper key.  If it is "max", the maximum ID number of existing records is specified.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding decimal keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.</dd>
+</dl>
+
+<p>The function `tcfdbrange4' is used in order to get keys with an interval notation in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcfdbrange4(TCFDB *<var>fdb</var>, const void *<var>ibuf</var>, int <var>isiz</var>, int <var>max</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>ibuf</var>' specifies the pointer to the region of the interval notation.</dd>
+<dd>`<var>isiz</var>' specifies the size of the region of the interval notation.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding decimal keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.</dd>
+</dl>
+
+<p>The function `tcfdbrange5' is used in order to get keys with an interval notation string in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcfdbrange5(TCFDB *<var>fdb</var>, const void *<var>istr</var>, int <var>max</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>istr</var>' specifies the pointer to the region of the interval notation string.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding decimal keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.</dd>
+</dl>
+
+<p>The function `tcfdbaddint' is used in order to add an integer to a record in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>int tcfdbaddint(TCFDB *<var>fdb</var>, int64_t <var>id</var>, int <var>num</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>id</var>' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDPREV', the number less by one than the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.  If it is `FDBIDNEXT', the number greater by one than the maximum ID number of existing records is specified.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>If successful, the return value is the summation value, else, it is `INT_MIN'.</dd>
+<dd>If the corresponding record exists, the value is treated as an integer and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tcfdbadddouble' is used in order to add a real number to a record in a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>double tcfdbadddouble(TCFDB *<var>fdb</var>, int64_t <var>id</var>, double <var>num</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>id</var>' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDPREV', the number less by one than the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.  If it is `FDBIDNEXT', the number greater by one than the maximum ID number of existing records is specified.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>If successful, the return value is the summation value, else, it is Not-a-Number.</dd>
+<dd>If the corresponding record exists, the value is treated as a real number and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tcfdbsync' is used in order to synchronize updated contents of a fixed-length database object with the file and the device.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbsync(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>This function is useful when another process connects to the same database file.</dd>
+</dl>
+
+<p>The function `tcfdboptimize' is used in order to optimize the file of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdboptimize(TCFDB *<var>fdb</var>, int32_t <var>width</var>, int64_t <var>limsiz</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>`<var>width</var>' specifies the width of the value of each record.  If it is not more than 0, the current setting is not changed.</dd>
+<dd>`<var>limsiz</var>' specifies the limit size of the database file.  If it is not more than 0, the current setting is not changed.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcfdbvanish' is used in order to remove all records of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbvanish(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcfdbcopy' is used in order to copy the database file of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbcopy(TCFDB *<var>fdb</var>, const char *<var>path</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>`<var>path</var>' specifies the path of the destination file.  If it begins with `@', the trailing substring is executed as a command line.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned if the executed command returns non-zero code.</dd>
+<dd>The database file is assured to be kept synchronized and not modified while the copying or executing operation is in progress.  So, this function is useful to create a backup file of the database file.</dd>
+</dl>
+
+<p>The function `tcfdbtranbegin' is used in order to begin the transaction of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbtranbegin(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>The database is locked by the thread while the transaction so that only one transaction can be activated with a database object at the same time.  Thus, the serializable isolation level is assumed if every database operation is performed in the transaction.  All updated regions are kept track of by write ahead logging while the transaction.  If the database is closed during transaction, the transaction is aborted implicitly.</dd>
+</dl>
+
+<p>The function `tcfdbtrancommit' is used in order to commit the transaction of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbtrancommit(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update in the transaction is fixed when it is committed successfully.</dd>
+</dl>
+
+<p>The function `tcfdbtranabort' is used in order to abort the transaction of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>bool tcfdbtranabort(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update in the transaction is discarded when it is aborted.  The state of the database is rollbacked to before transaction.</dd>
+</dl>
+
+<p>The function `tcfdbpath' is used in order to get the file path of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>const char *tcfdbpath(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>The return value is the path of the database file or `NULL' if the object does not connect to any database file.</dd>
+</dl>
+
+<p>The function `tcfdbrnum' is used in order to get the number of records of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcfdbrnum(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>The return value is the number of records or 0 if the object does not connect to any database file.</dd>
+</dl>
+
+<p>The function `tcfdbfsiz' is used in order to get the size of the database file of a fixed-length database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcfdbfsiz(TCFDB *<var>fdb</var>);</code></dt>
+<dd>`<var>fdb</var>' specifies the fixed-length database object.</dd>
+<dd>The return value is the size of the database file or 0 if the object does not connect to any database file.</dd>
+</dl>
+
+<h3 id="tcfdbapi_example">コード例</h3>
+
+<p>固定長データベースを使ったコード例を以下に示します。</p>
+
+<pre>#include &lt;tcutil.h&gt;
+#include &lt;tcfdb.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdbool.h&gt;
+#include &lt;stdint.h&gt;
+
+int main(int argc, char **argv){
+  TCFDB *fdb;
+  int ecode;
+  char *key, *value;
+
+  /* オブジェクトを作成する */
+  fdb = tcfdbnew();
+
+  /* データベースを開く */
+  if(!tcfdbopen(fdb, "casket.tcf", FDBOWRITER | FDBOCREAT)){
+    ecode = tcfdbecode(fdb);
+    fprintf(stderr, "open error: %s\n", tcfdberrmsg(ecode));
+  }
+
+  /* レコードを格納する */
+  if(!tcfdbput3(fdb, "1", "one") ||
+     !tcfdbput3(fdb, "12", "twelve") ||
+     !tcfdbput3(fdb, "144", "one forty four")){
+    ecode = tcfdbecode(fdb);
+    fprintf(stderr, "put error: %s\n", tcfdberrmsg(ecode));
+  }
+
+  /* レコードを取得する */
+  value = tcfdbget3(fdb, "1");
+  if(value){
+    printf("%s\n", value);
+    free(value);
+  } else {
+    ecode = tcfdbecode(fdb);
+    fprintf(stderr, "get error: %s\n", tcfdberrmsg(ecode));
+  }
+
+  /* 横断的にレコードを参照する */
+  tcfdbiterinit(fdb);
+  while((key = tcfdbiternext3(fdb)) != NULL){
+    value = tcfdbget3(fdb, key);
+    if(value){
+      printf("%s:%s\n", key, value);
+      free(value);
+    }
+    free(key);
+  }
+
+  /* データベースを閉じる */
+  if(!tcfdbclose(fdb)){
+    ecode = tcfdbecode(fdb);
+    fprintf(stderr, "close error: %s\n", tcfdberrmsg(ecode));
+  }
+
+  /* オブジェクトを破棄する */
+  tcfdbdel(fdb);
+
+  return 0;
+}
+</pre>
+
+<h3 id="tcfdbapi_cli">CLI</h3>
+
+<p>固定長データベースAPIを簡単に利用するために、コマンドラインインターフェイスとして `<code>tcftest</code>' と `<code>tcfmttest</code>' と `<code>tcfmgr</code>' が提供されます。</p>
+
+<p>コマンド `<code>tcftest</code>' は、ハッシュデータベースAPIの機能テストや性能テストに用いるツールです。以下の書式で用います。`<var>path</var>' はデータベースファイルのパスを指定し、`<var>rnum</var>' は試行回数を指定し、`<var>width</var>' は各レコードの値の幅を指定し、`<var>limsiz</var>' はデータベースファイルの制限サイズを指定します。</p>
+
+<dl class="api">
+<dt><code>tcftest write [-mt] [-nl|-nb] [-rnd] <var>path</var> <var>rnum</var> [<var>width</var> [<var>limsiz</var>]]</code></dt>
+<dd>`00000001'、`00000002' のように変化する8バイトのキーと値を連続してデータベースに追加する。</dd>
+<dt><code>tcftest read [-mt] [-nl|-nb] [-wb] [-rnd] <var>path</var></code></dt>
+<dd>上記で生成したデータベースの全レコードを検索する。</dd>
+<dt><code>tcftest remove [-mt] [-nl|-nb] [-rnd] <var>path</var></code></dt>
+<dd>上記で生成したデータベースの全レコードを削除する。</dd>
+<dt><code>tcftest rcat [-mt] [-nl|-nb] [-pn <var>num</var>] [-dai|-dad|-rl] <var>path</var> <var>rnum</var> [<var>width</var> [<var>limsiz</var>]]</code></dt>
+<dd>キーがある程度重複するようにレコードの追加を行い、連結モードで処理する。</dd>
+<dt><code>tcftest misc [-mt] [-nl|-nb] <var>path</var> <var>rnum</var></code></dt>
+<dd>各種操作の組み合わせテストを行う。</dd>
+<dt><code>tcftest wicked [-mt] [-nl|-nb] <var>path</var> <var>rnum</var></code></dt>
+<dd>各種更新操作を無作為に選択して実行する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持ちます</p>
+
+<ul class="options">
+<li><code>-mt</code> : 関数 `tcfdbsetmutex' を呼び出す。</li>
+<li><code>-nl</code> : オプション `FDBNOLCK' を有効にする。</li>
+<li><code>-nb</code> : オプション `FDBLCKNB' を有効にする。</li>
+<li><code>-rnd</code> : キーを無作為に選択する。</li>
+<li><code>-wb</code> : 関数 `tcfdbget' の代わりに関数 `tcfdbget3' を用いる。</li>
+<li><code>-pn <var>num</var></code> : パターン数を指定する。</li>
+<li><code>-dai</code> : 関数 `tcfdbputcat' の代わりに関数 `tcfdbaddint' を用いる。</li>
+<li><code>-dad</code> : 関数 `tcfdbputcat' の代わりに関数 `tcfdbadddouble' を用いる。</li>
+<li><code>-rl</code> : 値を無作為な長さにする。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了します。</p>
+
+<p>コマンド `<code>tcfmttest</code>' は、ハッシュデータベースAPIの機能テストをマルチスレッドで行うツールです。以下の書式で用います。`<var>path</var>' はデータベースファイルのパスを指定し、`<var>tnum</var>' はスレッド数を指定し、`<var>rnum</var>' は試行回数を指定し、`<var>width</var>' は各レコードの値の幅を指定し、`<var>limsiz</var>' はデータベースファイルの制限サイズを指定します。</p>
+
+<dl class="api">
+<dt><code>tcfmttest write [-nl|-nb] [-rnd] <var>path</var> <var>tnum</var> <var>rnum</var> [<var>width</var> [<var>limsiz</var>]]</code></dt>
+<dd>`00000001'、`00000002' のように変化する8バイトのキーと値を連続してデータベースに追加する。</dd>
+<dt><code>tcfmttest read [-nl|-nb] [-wb] [-rnd] <var>path</var> <var>tnum</var></code></dt>
+<dd>上記で生成したデータベースの全レコードを検索する。</dd>
+<dt><code>tcfmttest remove [-nl|-nb] [-rnd] <var>path</var> <var>tnum</var></code></dt>
+<dd>上記で生成したデータベースの全レコードを削除する。</dd>
+<dt><code>tcfmttest wicked [-nl|-nb] [-nc] <var>path</var> <var>tnum</var> <var>rnum</var></code></dt>
+<dd>各種更新操作を無作為に選択して実行する。</dd>
+<dt><code>tcfmttest typical [-nl|-nb] [-nc] [-rr <var>num</var>] <var>path</var> <var>tnum</var> <var>rnum</var> [<var>width</var> [<var>limsiz</var>]]</code></dt>
+<dd>典型的な操作を無作為に選択して実行する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持ちます</p>
+
+<ul class="options">
+<li><code>-nl</code> : オプション `FDBNOLCK' を有効にする。</li>
+<li><code>-nb</code> : オプション `FDBLCKNB' を有効にする。</li>
+<li><code>-rnd</code> : キーを無作為に選択する。</li>
+<li><code>-wb</code> : 関数 `tcfdbget' の代わりに関数 `tcfdbget3' を用いる。</li>
+<li><code>-nc</code> : 比較テストを行わない。</li>
+<li><code>-rr <var>num</var></code> : 読み込み操作の割合を百分率で指定する。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了します。</p>
+
+<p>コマンド `<code>tcfmgr</code>' は、ハッシュデータベースAPIやそのアプリケーションのテストやデバッグに役立つツールです。以下の書式で用います。`<var>path</var>' はデータベースファイルのパスを指定し、`<var>width</var>' は各レコードの値の幅を指定し、`<var>limsiz</var>' はデータベースファイルの制限サイズを指定し、`<var>key</var>' はレコードのキーを指定し、`<var>value</var>' はレコードの値を指定し、`<var>file</var>' は入力ファイルを指定します。</p>
+
+<dl class="api">
+<dt><code>tcfmgr create <var>path</var> [<var>width</var> [<var>limsiz</var>]]</code></dt>
+<dd>データベースファイルを作成する。</dd>
+<dt><code>tcfmgr inform [-nl|-nb] <var>path</var></code></dt>
+<dd>データベースの雑多な情報を出力する。</dd>
+<dt><code>tcfmgr put [-nl|-nb] [-sx] [-dk|-dc|-dai|-dad] <var>path</var> <var>key</var> <var>value</var></code></dt>
+<dd>レコードを追加する。</dd>
+<dt><code>tcfmgr out [-nl|-nb] [-sx] <var>path</var> <var>key</var></code></dt>
+<dd>レコードを削除する。</dd>
+<dt><code>tcfmgr get [-nl|-nb] [-sx] [-px] [-pz] <var>path</var> <var>key</var></code></dt>
+<dd>レコードの値を取得して標準出力する。</dd>
+<dt><code>tcfmgr list [-nl|-nb] [-m <var>num</var>] [-pv] [-px] [-rb <var>lkey</var> <var>ukey</var>] [-ri <var>str</var>] <var>path</var></code></dt>
+<dd>全てのレコードのキーを改行で区切って標準出力する。</dd>
+<dt><code>tcfmgr optimize [-tz] [-nl|-nb] <var>path</var> [<var>width</var> [<var>limsiz</var>]]</code></dt>
+<dd>データベースを最適化する。</dd>
+<dt><code>tcfmgr importtsv [-nl|-nb] [-sc] <var>path</var> [<var>file</var>]</code></dt>
+<dd>TSVファイルの各行をキーと値とみなしてレコードを登録する。</dd>
+<dt><code>tcfmgr version</code></dt>
+<dd>Tokyo Cabinetのバージョン情報を標準出力する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持ちます</p>
+
+<ul class="options">
+<li><code>-nl</code> : オプション `FDBNOLCK' を有効にする。</li>
+<li><code>-nb</code> : オプション `FDBLCKNB' を有効にする。</li>
+<li><code>-sx</code> : 入力を16進数の文字列で行う。</li>
+<li><code>-dk</code> : 関数 `tcfdbput' の代わりに関数 `tcfdbputkeep' を用いる。</li>
+<li><code>-dc</code> : 関数 `tcfdbput' の代わりに関数 `tcfdbputcat' を用いる。</li>
+<li><code>-dai</code> : 関数 `tcfdbput' の代わりに関数 `tcfdbaddint' を用いる。</li>
+<li><code>-dad</code> : 関数 `tcfdbput' の代わりに関数 `tcfdbadddouble' を用いる。</li>
+<li><code>-px</code> : 出力を16進数の文字列で行う。</li>
+<li><code>-pz</code> : 出力の末尾に改行を付加しない。</li>
+<li><code>-m <var>num</var></code> : 出力の最大数を指定する。</li>
+<li><code>-pv</code> : レコードの値も出力する。</li>
+<li><code>-rb <var>lkey</var> <var>ukey</var></code> : 処理対象を範囲指定する。</li>
+<li><code>-ri <var>str</var></code> : 処理対象の範囲を区間記法で指定する。</li>
+<li><code>-sc</code> : キーを小文字に正規化する。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了します。</p>
+
+<hr />
+
+<h2 id="tctdbapi">テーブルデータベースAPI</h2>
+
+<p>テーブルデータベースは、プライマリキーと任意のコラムを持つレコード群を単一のファイルに記録したデータベースです。それを扱うのがテーブルデータベースAPIです。`<code>tctdb.h</code>' にAPIの仕様の完全な記述があります。</p>
+
+<h3 id="tctdbapi_description">概要</h3>
+
+<p>テーブルデータベースAPIを使うためには、`<code>tcutil.h</code>'、`<code>tctdb.h</code>' および関連する標準ヘッダファイルをインクルードしてください。通常、ソースファイルの冒頭付近で以下の記述を行います。</p>
+
+<dl>
+<dt><code>#include &lt;tcutil.h&gt;</code></dt>
+<dt><code>#include &lt;tctdb.h&gt;</code></dt>
+<dt><code>#include &lt;stdlib.h&gt;</code></dt>
+<dt><code>#include &lt;stdbool.h&gt;</code></dt>
+<dt><code>#include &lt;stdint.h&gt;</code></dt>
+</dl>
+
+<p>テーブルデータベースを扱う際には、`<code>TCTDB</code>' 型へのポインタをオブジェクトとして用います。テーブルデータベースオブジェクトは、関数 `<code>tctdbnew</code>' で作成し、関数 `<code>tctdbdel</code>' で破棄します。作成したオブジェクトを使い終わったら必ず破棄してください。そうしないとメモリリークが発生します。</p>
+
+<p>レコードの格納や探索を行う前提として、テーブルデータベースオブジェクトをデータベースファイルと接続させる必要があります。データベースファイルを開いて接続するには関数 `<code>tctdbopen</code>' を用い、接続の解除してファイルを閉じるには関数 `<code>tctdbclose</code>' を用います。開いたデータベースファイルは必ず閉じてください。そうしないとデータベースファイルが壊れたり格納したデータが失われたりする可能性があります。単一のプロセス内で複数のデータベースオブジェクトが同じデータベースファイルを同時に開くことはできません。</p>
+
+<h3 id="tctdbapi_api">API(英語スマメ)</h3>
+
+<p>The function `tctdberrmsg' is used in order to get the message string corresponding to an error code.</p>
+
+<dl class="api">
+<dt><code>const char *tctdberrmsg(int <var>ecode</var>);</code></dt>
+<dd>`<var>ecode</var>' specifies the error code.</dd>
+<dd>The return value is the message string of the error code.</dd>
+</dl>
+
+<p>The function `tctdbnew' is used in order to create a table database object.</p>
+
+<dl class="api">
+<dt><code>TCTDB *tctdbnew(void);</code></dt>
+<dd>The return value is the new table database object.</dd>
+</dl>
+
+<p>The function `tctdbdel' is used in order to delete a table database object.</p>
+
+<dl class="api">
+<dt><code>void tctdbdel(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>If the database is not closed, it is closed implicitly.  Note that the deleted object and its derivatives can not be used anymore.</dd>
+</dl>
+
+<p>The function `tctdbecode' is used in order to get the last happened error code of a table database object.</p>
+
+<dl class="api">
+<dt><code>int tctdbecode(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>The return value is the last happened error code.</dd>
+<dd>The following error codes are defined: `TCESUCCESS' for success, `TCETHREAD' for threading error, `TCEINVALID' for invalid operation, `TCENOFILE' for file not found, `TCENOPERM' for no permission, `TCEMETA' for invalid meta data, `TCERHEAD' for invalid record header, `TCEOPEN' for open error, `TCECLOSE' for close error, `TCETRUNC' for trunc error, `TCESYNC' for sync error, `TCESTAT' for stat error, `TCESEEK' for seek error, `TCEREAD' for read error, `TCEWRITE' for write error, `TCEMMAP' for mmap error, `TCELOCK' for lock error, `TCEUNLINK' for unlink error, `TCERENAME' for rename error, `TCEMKDIR' for mkdir error, `TCERMDIR' for rmdir error, `TCEKEEP' for existing record, `TCENOREC' for no record found, and `TCEMISC' for miscellaneous error.</dd>
+</dl>
+
+<p>The function `tctdbsetmutex' is used in order to set mutual exclusion control of a table database object for threading.</p>
+
+<dl class="api">
+<dt><code>bool tctdbsetmutex(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object which is not opened.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the mutual exclusion control is needed if the object is shared by plural threads and this function should be called before the database is opened.</dd>
+</dl>
+
+<p>The function `tctdbtune' is used in order to set the tuning parameters of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbtune(TCTDB *<var>tdb</var>, int64_t <var>bnum</var>, int8_t <var>apow</var>, int8_t <var>fpow</var>, uint8_t <var>opts</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object which is not opened.</dd>
+<dd>`<var>bnum</var>' specifies the number of elements of the bucket array.  If it is not more than 0, the default value is specified.  The default value is 131071.  Suggested size of the bucket array is about from 0.5 to 4 times of the number of all records to be stored.</dd>
+<dd>`<var>apow</var>' specifies the size of record alignment by power of 2.  If it is negative, the default value is specified.  The default value is 4 standing for 2^4=16.</dd>
+<dd>`<var>fpow</var>' specifies the maximum number of elements of the free block pool by power of 2.  If it is negative, the default value is specified.  The default value is 10 standing for 2^10=1024.</dd>
+<dd>`<var>opts</var>' specifies options by bitwise-or: `TDBTLARGE' specifies that the size of the database can be larger than 2GB by using 64-bit bucket array, `TDBTDEFLATE' specifies that each record is compressed with Deflate encoding, `TDBTBZIP' specifies that each record is compressed with BZIP2 encoding, `TDBTTCBS' specifies that each record is compressed with TCBS encoding.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the tuning parameters should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tctdbsetcache' is set the caching parameters of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbsetcache(TCTDB *<var>tdb</var>, int32_t <var>rcnum</var>, int32_t <var>lcnum</var>, int32_t <var>ncnum</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object which is not opened.</dd>
+<dd>`<var>rcnum</var>' specifies the maximum number of records to be cached.  If it is not more than 0, the record cache is disabled.  It is disabled by default.</dd>
+<dd>`<var>lcnum</var>' specifies the maximum number of leaf nodes to be cached.  If it is not more than 0, the default value is specified.  The default value is 2048.</dd>
+<dd>`<var>ncnum</var>' specifies the maximum number of non-leaf nodes to be cached.  If it is not more than 0, the default value is specified.  The default value is 512.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the caching parameters should be set before the database is opened.  Leaf nodes and non-leaf nodes are used in column indices.</dd>
+</dl>
+
+<p>The function `tctdbsetxmsiz' is used in order to set the size of the extra mapped memory of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbsetxmsiz(TCTDB *<var>tdb</var>, int64_t <var>xmsiz</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object which is not opened.</dd>
+<dd>`<var>xmsiz</var>' specifies the size of the extra mapped memory.  If it is not more than 0, the extra mapped memory is disabled.  The default size is 67108864.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the mapping parameters should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tctdbsetdfunit' is used in order to set the unit step number of auto defragmentation of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbsetdfunit(TCTDB *<var>tdb</var>, int32_t <var>dfunit</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object which is not opened.</dd>
+<dd>`<var>dfunit</var>' specifie the unit step number.  If it is not more than 0, the auto defragmentation is disabled.  It is disabled by default.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the defragmentation parameters should be set before the database is opened.</dd>
+</dl>
+
+<p>The function `tctdbopen' is used in order to open a database file and connect a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbopen(TCTDB *<var>tdb</var>, const char *<var>path</var>, int <var>omode</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object which is not opened.</dd>
+<dd>`<var>path</var>' specifies the path of the database file.</dd>
+<dd>`<var>omode</var>' specifies the connection mode: `TDBOWRITER' as a writer, `TDBOREADER' as a reader.  If the mode is `TDBOWRITER', the following may be added by bitwise-or: `TDBOCREAT', which means it creates a new database if not exist, `TDBOTRUNC', which means it creates a new database regardless if one exists, `TDBOTSYNC', which means every transaction synchronizes updated contents with the device.  Both of `TDBOREADER' and `TDBOWRITER' can be added to by bitwise-or: `TDBONOLCK', which means it opens the database file without file locking, or `TDBOLCKNB', which means locking is performed without blocking.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tctdbclose' is used in order to close a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbclose(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update of a database is assured to be written when the database is closed.  If a writer opens a database but does not close it appropriately, the database will be broken.</dd>
+</dl>
+
+<p>The function `tctdbput' is used in order to store a record into a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbput(TCTDB *<var>tdb</var>, const void *<var>pkbuf</var>, int <var>pksiz</var>, TCMAP *<var>cols</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>pkbuf</var>' specifies the pointer to the region of the primary key.</dd>
+<dd>`<var>pksiz</var>' specifies the size of the region of the primary key.</dd>
+<dd>`<var>cols</var>' specifies a map object containing columns.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tctdbput2' is used in order to store a string record into a table database object with a zero separated column string.</p>
+
+<dl class="api">
+<dt><code>bool tctdbput2(TCTDB *<var>tdb</var>, const void *<var>pkbuf</var>, int <var>pksiz</var>, const void *<var>cbuf</var>, int <var>csiz</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>pkbuf</var>' specifies the pointer to the region of the primary key.</dd>
+<dd>`<var>pksiz</var>' specifies the size of the region of the primary key.</dd>
+<dd>`<var>cbuf</var>' specifies the pointer to the region of the zero separated column string where the name and the value of each column are situated one after the other.</dd>
+<dd>`<var>csiz</var>' specifies the size of the region of the column string.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tctdbput3' is used in order to store a string record into a table database object with a tab separated column string.</p>
+
+<dl class="api">
+<dt><code>bool tctdbput3(TCTDB *<var>tdb</var>, const char *<var>pkstr</var>, const char *<var>cstr</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>pkstr</var>' specifies the string of the primary key.</dd>
+<dd>`<var>cstr</var>' specifies the string of the the tab separated column string where the name and the value of each column are situated one after the other.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tctdbputkeep' is used in order to store a new record into a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbputkeep(TCTDB *<var>tdb</var>, const void *<var>pkbuf</var>, int <var>pksiz</var>, TCMAP *<var>cols</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>pkbuf</var>' specifies the pointer to the region of the primary key.</dd>
+<dd>`<var>pksiz</var>' specifies the size of the region of the primary key.</dd>
+<dd>`<var>cols</var>' specifies a map object containing columns.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tctdbputkeep2' is used in order to store a new string record into a table database object with a zero separated column string.</p>
+
+<dl class="api">
+<dt><code>bool tctdbputkeep2(TCTDB *<var>tdb</var>, const void *<var>pkbuf</var>, int <var>pksiz</var>, const void *<var>cbuf</var>, int <var>csiz</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>pkbuf</var>' specifies the pointer to the region of the primary key.</dd>
+<dd>`<var>pksiz</var>' specifies the size of the region of the primary key.</dd>
+<dd>`<var>cbuf</var>' specifies the pointer to the region of the zero separated column string where the name and the value of each column are situated one after the other.</dd>
+<dd>`<var>csiz</var>' specifies the size of the region of the column string.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tctdbputkeep3' is used in order to store a new string record into a table database object with a tab separated column string.</p>
+
+<dl class="api">
+<dt><code>bool tctdbputkeep3(TCTDB *<var>tdb</var>, const char *<var>pkstr</var>, const char *<var>cstr</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>pkstr</var>' specifies the string of the primary key.</dd>
+<dd>`<var>cstr</var>' specifies the string of the the tab separated column string where the name and the value of each column are situated one after the other.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tctdbputcat' is used in order to concatenate columns of the existing record in a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbputcat(TCTDB *<var>tdb</var>, const void *<var>pkbuf</var>, int <var>pksiz</var>, TCMAP *<var>cols</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>pkbuf</var>' specifies the pointer to the region of the primary key.</dd>
+<dd>`<var>pksiz</var>' specifies the size of the region of the primary key.</dd>
+<dd>`<var>cols</var>' specifies a map object containing columns.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tctdbputcat2' is used in order to concatenate columns in a table database object with a zero separated column string.</p>
+
+<dl class="api">
+<dt><code>bool tctdbputcat2(TCTDB *<var>tdb</var>, const void *<var>pkbuf</var>, int <var>pksiz</var>, const void *<var>cbuf</var>, int <var>csiz</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>pkbuf</var>' specifies the pointer to the region of the primary key.</dd>
+<dd>`<var>pksiz</var>' specifies the size of the region of the primary key.</dd>
+<dd>`<var>cbuf</var>' specifies the pointer to the region of the zero separated column string where the name and the value of each column are situated one after the other.</dd>
+<dd>`<var>csiz</var>' specifies the size of the region of the column string.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tctdbputcat3' is used in order to concatenate columns in a table database object with with a tab separated column string.</p>
+
+<dl class="api">
+<dt><code>bool tctdbputcat3(TCTDB *<var>tdb</var>, const char *<var>pkstr</var>, const char *<var>cstr</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>pkstr</var>' specifies the string of the primary key.</dd>
+<dd>`<var>cstr</var>' specifies the string of the the tab separated column string where the name and the value of each column are situated one after the other.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tctdbout' is used in order to remove a record of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbout(TCTDB *<var>tdb</var>, const void *<var>pkbuf</var>, int <var>pksiz</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>pkbuf</var>' specifies the pointer to the region of the primary key.</dd>
+<dd>`<var>pksiz</var>' specifies the size of the region of the primary key.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tctdbout2' is used in order to remove a string record of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbout2(TCTDB *<var>tdb</var>, const char *<var>pkstr</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>pkstr</var>' specifies the string of the primary key.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tctdbget' is used in order to retrieve a record in a table database object.</p>
+
+<dl class="api">
+<dt><code>TCMAP *tctdbget(TCTDB *<var>tdb</var>, const void *<var>pkbuf</var>, int <var>pksiz</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>`<var>pkbuf</var>' specifies the pointer to the region of the primary key.</dd>
+<dd>`<var>pksiz</var>' specifies the size of the region of the primary key.</dd>
+<dd>If successful, the return value is a map object of the columns of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tcmapnew', it should be deleted with the function `tcmapdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tctdbget2' is used in order to retrieve a record in a table database object as a zero separated column string.</p>
+
+<dl class="api">
+<dt><code>char *tctdbget2(TCTDB *<var>tdb</var>, const void *<var>pkbuf</var>, int <var>pksiz</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>`<var>pkbuf</var>' specifies the pointer to the region of the primary key.</dd>
+<dd>`<var>pksiz</var>' specifies the size of the region of the primary key.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the column string of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tctdbget3' is used in order to retrieve a string record in a table database object as a tab separated column string.</p>
+
+<dl class="api">
+<dt><code>char *tctdbget3(TCTDB *<var>tdb</var>, const char *<var>pkstr</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>`<var>pkstr</var>' specifies the string of the primary key.</dd>
+<dd>If successful, the return value is the tab separated column string of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tctdbvsiz' is used in order to get the size of the value of a record in a table database object.</p>
+
+<dl class="api">
+<dt><code>int tctdbvsiz(TCTDB *<var>tdb</var>, const void *<var>pkbuf</var>, int <var>pksiz</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the primary key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the primary key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tctdbvsiz2' is used in order to get the size of the value of a string record in a table database object.</p>
+
+<dl class="api">
+<dt><code>int tctdbvsiz2(TCTDB *<var>tdb</var>, const char *<var>pkstr</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the primary key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tctdbiterinit' is used in order to initialize the iterator of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbiterinit(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>The iterator is used in order to access the primary key of every record stored in a database.</dd>
+</dl>
+
+<p>The function `tctdbiternext' is used in order to get the next primary key of the iterator of a table database object.</p>
+
+<dl class="api">
+<dt><code>void *tctdbiternext(TCTDB *<var>tdb</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the next primary key, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  It is possible to access every record by iteration of calling this function.  It is allowed to update or remove records whose keys are fetched while the iteration.  However, it is not assured if updating the database is occurred while the iteration.  Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of storing matches the one of the traversal access.</dd>
+</dl>
+
+<p>The function `tctdbiternext2' is used in order to get the next primary key string of the iterator of a table database object.</p>
+
+<dl class="api">
+<dt><code>char *tctdbiternext2(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>If successful, the return value is the string of the next primary key, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  It is possible to access every record by iteration of calling this function.  However, it is not assured if updating the database is occurred while the iteration.  Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of storing matches the one of the traversal access.</dd>
+</dl>
+
+<p>The function `tctdbiternext3' is used in order to get the columns of the next record of the iterator of a table database object.</p>
+
+<dl class="api">
+<dt><code>TCMAP *tctdbiternext3(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>If successful, the return value is a map object of the columns of the next record, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.  The primary key is added into the map as a column of an empty string key.</dd>
+<dd>Because the object of the return value is created with the function `tcmapnew', it should be deleted with the function `tcmapdel' when it is no longer in use.  It is possible to access every record by iteration of calling this function.  However, it is not assured if updating the database is occurred while the iteration.  Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of storing matches the one of the traversal access.</dd>
+</dl>
+
+<p>The function `tctdbfwmkeys' is used in order to get forward matching primary keys in a table database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tctdbfwmkeys(TCTDB *<var>tdb</var>, const void *<var>pbuf</var>, int <var>psiz</var>, int <var>max</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>`<var>pbuf</var>' specifies the pointer to the region of the prefix.</dd>
+<dd>`<var>psiz</var>' specifies the size of the region of the prefix.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.</dd>
+</dl>
+
+<p>The function `tctdbfwmkeys2' is used in order to get forward matching string primary keys in a table database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tctdbfwmkeys2(TCTDB *<var>tdb</var>, const char *<var>pstr</var>, int <var>max</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>`<var>pstr</var>' specifies the string of the prefix.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.</dd>
+</dl>
+
+<p>The function `tctdbaddint' is used in order to add an integer to a column of a record in a table database object.</p>
+
+<dl class="api">
+<dt><code>int tctdbaddint(TCTDB *<var>tdb</var>, const void *<var>pkbuf</var>, int <var>pksiz</var>, int <var>num</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the primary key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the primary key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>If successful, the return value is the summation value, else, it is `INT_MIN'.</dd>
+<dd>The additional value is stored as a decimal string value of a column whose name is "_num".  If no record corresponds, a new record with the additional value is stored.</dd>
+</dl>
+
+<p>The function `tctdbadddouble' is used in order to add a real number to a column of a record in a table database object.</p>
+
+<dl class="api">
+<dt><code>double tctdbadddouble(TCTDB *<var>tdb</var>, const void *<var>pkbuf</var>, int <var>pksiz</var>, double <var>num</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the primary key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the primary key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>If successful, the return value is the summation value, else, it is Not-a-Number.</dd>
+<dd>The additional value is stored as a decimal string value of a column whose name is "_num".  If no record corresponds, a new record with the additional value is stored.</dd>
+</dl>
+
+<p>The function `tctdbsync' is used in order to synchronize updated contents of a table database object with the file and the device.</p>
+
+<dl class="api">
+<dt><code>bool tctdbsync(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>This function is useful when another process connects to the same database file.</dd>
+</dl>
+
+<p>The function `tctdboptimize' is used in order to optimize the file of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdboptimize(TCTDB *<var>tdb</var>, int64_t <var>bnum</var>, int8_t <var>apow</var>, int8_t <var>fpow</var>, uint8_t <var>opts</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>bnum</var>' specifies the number of elements of the bucket array.  If it is not more than 0, the default value is specified.  The default value is two times of the number of records.</dd>
+<dd>`<var>apow</var>' specifies the size of record alignment by power of 2.  If it is negative, the current setting is not changed.</dd>
+<dd>`<var>fpow</var>' specifies the maximum number of elements of the free block pool by power of 2.  If it is negative, the current setting is not changed.</dd>
+<dd>`<var>opts</var>' specifies options by bitwise-or: `TDBTLARGE' specifies that the size of the database can be larger than 2GB by using 64-bit bucket array, `TDBTDEFLATE' specifies that each record is compressed with Deflate encoding, `TDBTBZIP' specifies that each record is compressed with BZIP2 encoding, `TDBTTCBS' specifies that each record is compressed with TCBS encoding.  If it is `UINT8_MAX', the current setting is not changed.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>This function is useful to reduce the size of the database file with data fragmentation by successive updating.</dd>
+</dl>
+
+<p>The function `tctdbvanish' is used in order to remove all records of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbvanish(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tctdbcopy' is used in order to copy the database file of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbcopy(TCTDB *<var>tdb</var>, const char *<var>path</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>`<var>path</var>' specifies the path of the destination file.  If it begins with `@', the trailing substring is executed as a command line.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned if the executed command returns non-zero code.</dd>
+<dd>The database file is assured to be kept synchronized and not modified while the copying or executing operation is in progress.  So, this function is useful to create a backup file of the database file.</dd>
+</dl>
+
+<p>The function `tctdbtranbegin' is used in order to begin the transaction of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbtranbegin(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>The database is locked by the thread while the transaction so that only one transaction can be activated with a database object at the same time.  Thus, the serializable isolation level is assumed if every database operation is performed in the transaction.  Because all pages are cached on memory while the transaction, the amount of referred records is limited by the memory capacity.  If the database is closed during transaction, the transaction is aborted implicitly.</dd>
+</dl>
+
+<p>The function `tctdbtrancommit' is used in order to commit the transaction of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbtrancommit(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update in the transaction is fixed when it is committed successfully.</dd>
+</dl>
+
+<p>The function `tctdbtranabort' is used in order to abort the transaction of a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbtranabort(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update in the transaction is discarded when it is aborted.  The state of the database is rollbacked to before transaction.</dd>
+</dl>
+
+<p>The function `tctdbpath' is used in order to get the file path of a table database object.</p>
+
+<dl class="api">
+<dt><code>const char *tctdbpath(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>The return value is the path of the database file or `NULL' if the object does not connect to any database file.</dd>
+</dl>
+
+<p>The function `tctdbrnum' is used in order to get the number of records ccccof a table database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tctdbrnum(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>The return value is the number of records or 0 if the object does not connect to any database file.</dd>
+</dl>
+
+<p>The function `tctdbfsiz' is used in order to get the size of the database file of a table database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tctdbfsiz(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>The return value is the size of the database file or 0 if the object does not connect to any database file.</dd>
+</dl>
+
+<p>The function `tctdbsetindex' is used in order to set a column index to a table database object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbsetindex(TCTDB *<var>tdb</var>, const char *<var>name</var>, int <var>type</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>`<var>name</var>' specifies the name of a column.  If the name of an existing index is specified, the index is rebuilt.  An empty string means the primary key.</dd>
+<dd>`<var>type</var>' specifies the index type: `TDBITLEXICAL' for lexical string, `TDBITDECIMAL' for decimal string, `TDBITTOKEN' for token inverted index, `TDBITQGRAM' for q-gram inverted index.  If it is `TDBITOPT', the index is optimized.  If it is `TDBITVOID', the index is removed.  If `TDBITKEEP' is added by bitwise-or and the index exists, this function merely returns failure.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Note that the setting indices should be set after the database is opened.</dd>
+</dl>
+
+<p>The function `tctdbgenuid' is used in order to generate a unique ID number of a table database object.</p>
+
+<dl class="api">
+<dt><code>int64_t tctdbgenuid(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object connected as a writer.</dd>
+<dd>The return value is the new unique ID number or -1 on failure.</dd>
+</dl>
+
+<p>The function `tctdbqrynew' is used in order to create a query object.</p>
+
+<dl class="api">
+<dt><code>TDBQRY *tctdbqrynew(TCTDB *<var>tdb</var>);</code></dt>
+<dd>`<var>tdb</var>' specifies the table database object.</dd>
+<dd>The return value is the new query object.</dd>
+</dl>
+
+<p>The function `tctdbqrydel' is used in order to delete a query object.</p>
+
+<dl class="api">
+<dt><code>void tctdbqrydel(TDBQRY *<var>qry</var>);</code></dt>
+<dd>`<var>qry</var>' specifies the query object.</dd>
+</dl>
+
+<p>The function `tctdbqryaddcond' is used in order to add a narrowing condition to a query object.</p>
+
+<dl class="api">
+<dt><code>void tctdbqryaddcond(TDBQRY *<var>qry</var>, const char *<var>name</var>, int <var>op</var>, const char *<var>expr</var>);</code></dt>
+<dd>`<var>qry</var>' specifies the query object.</dd>
+<dd>`<var>name</var>' specifies the name of a column.  An empty string means the primary key.</dd>
+<dd>`<var>op</var>' specifies an operation type: `TDBQCSTREQ' for string which is equal to the expression, `TDBQCSTRINC' for string which is included in the expression, `TDBQCSTRBW' for string which begins with the expression, `TDBQCSTREW' for string which ends with the expression, `TDBQCSTRAND' for string which includes all tokens in the expression, `TDBQCSTROR' for string which includes at least one token in the expression, `TDBQCSTROREQ' for string which is equal to at least one token in the expression, `TDBQCSTRRX' for string which matches regular expressions of the expression, `TDBQCNUMEQ' for number which is equal to the expression, `TDBQCNUMGT' for number which is greater than the expression, `TDBQCNUMGE' for number which is greater than or equal to the expression, `TDBQCNUMLT' for number which is less than the expression, `TDBQCNUMLE' for number which is less than or equal to the expression, `TDBQCNUMBT' for number which is between two tokens of the expression, `TDBQCNUMOREQ' for number which is equal to at least one token in the expression, `TDBQCFTSPH' for full-text search with the phrase of the expression, `TDBQCFTSAND' for full-text search with all tokens in the expression, `TDBQCFTSOR' for full-text search with at least one token in the expression, `TDBQCFTSEX' for full-text search with the compound expression.  All operations can be flagged by bitwise-or: `TDBQCNEGATE' for negation, `TDBQCNOIDX' for using no index.</dd>
+<dd>`<var>expr</var>' specifies an operand exression.</dd>
+</dl>
+
+<p>The function `tctdbqrysetorder' is used in order to set the order of a query object.</p>
+
+<dl class="api">
+<dt><code>void tctdbqrysetorder(TDBQRY *<var>qry</var>, const char *<var>name</var>, int <var>type</var>);</code></dt>
+<dd>`<var>qry</var>' specifies the query object.</dd>
+<dd>`<var>name</var>' specifies the name of a column.  An empty string means the primary key.</dd>
+<dd>`<var>type</var>' specifies the order type: `TDBQOSTRASC' for string ascending, `TDBQOSTRDESC' for string descending, `TDBQONUMASC' for number ascending, `TDBQONUMDESC' for number descending.</dd>
+</dl>
+
+<p>The function `tctdbqrysetlimit' is used in order to set the limit number of records of the result of a query object.</p>
+
+<dl class="api">
+<dt><code>void tctdbqrysetlimit(TDBQRY *<var>qry</var>, int <var>max</var>, int <var>skip</var>);</code></dt>
+<dd>`<var>qry</var>' specifies the query object.</dd>
+<dd>`<var>max</var>' specifies the maximum number of records of the result.  If it is negative, no limit is specified.</dd>
+<dd>`<var>skip</var>' specifies the number of skipped records of the result.  If it is not more than 0, no record is skipped.</dd>
+</dl>
+
+<p>The function `tctdbqrysearch' is used in order to execute the search of a query object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tctdbqrysearch(TDBQRY *<var>qry</var>);</code></dt>
+<dd>`<var>qry</var>' specifies the query object.</dd>
+<dd>The return value is a list object of the primary keys of the corresponding records.  This function does never fail.  It returns an empty list even if no record corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tctdbqrysearchout' is used in order to remove each record corresponding to a query object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbqrysearchout(TDBQRY *<var>qry</var>);</code></dt>
+<dd>`<var>qry</var>' specifies the query object of the database connected as a writer.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tctdbqryproc' is used in order to process each record corresponding to a query object.</p>
+
+<dl class="api">
+<dt><code>bool tctdbqryproc(TDBQRY *<var>qry</var>, TDBQRYPROC <var>proc</var>, void *<var>op</var>);</code></dt>
+<dd>`<var>qry</var>' specifies the query object of the database connected as a writer.</dd>
+<dd>`<var>proc</var>' specifies the pointer to the iterator function called for each record.  It receives four parameters.  The first parameter is the pointer to the region of the primary key.  The second parameter is the size of the region of the primary key.  The third parameter is a map object containing columns.  The fourth parameter is the pointer to the optional opaque object.  It returns flags of the post treatment by bitwise-or: `TDBQPPUT' to modify the record, `TDBQPOUT' to remove the record, `TDBQPSTOP' to stop the iteration.</dd>
+<dd>`<var>op</var>' specifies an arbitrary pointer to be given as a parameter of the iterator function.  If it is not needed, `NULL' can be specified.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tctdbqryhint' is used in order to get the hint string of a query object.</p>
+
+<dl class="api">
+<dt><code>const char *tctdbqryhint(TDBQRY *<var>qry</var>);</code></dt>
+<dd>`<var>qry</var>' specifies the query object.</dd>
+<dd>The return value is the hint string.</dd>
+</dl>
+
+<p>The function `tctdbmetasearch' is used in order to retrieve records with multiple query objects and get the set of the result.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tctdbmetasearch(TDBQRY **<var>qrys</var>, int <var>num</var>, int <var>type</var>);</code></dt>
+<dd>`<var>qrys</var>' specifies an array of the query objects.</dd>
+<dd>`<var>num</var>' specifies the number of elements of the array.</dd>
+<dd>`<var>type</var>' specifies a set operation type: `TDBMSUNION' for the union set, `TDBMSISECT' for the intersection set, `TDBMSDIFF' for the difference set.</dd>
+<dd>The return value is a list object of the primary keys of the corresponding records.  This function does never fail.  It returns an empty list even if no record corresponds.</dd>
+<dd>If the first query object has the order setting, the result array is sorted by the order.  Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<h3 id="tctdbapi_example">コード例</h3>
+
+<p>テーブルデータベースを使ったコード例を以下に示します。</p>
+
+<pre>#include &lt;tcutil.h&gt;
+#include &lt;tctdb.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdbool.h&gt;
+#include &lt;stdint.h&gt;
+
+int main(int argc, char **argv){
+  TCTDB *tdb;
+  int ecode, pksiz, i, rsiz;
+  char pkbuf[256];
+  const char *rbuf, *name;
+  TCMAP *cols;
+  TDBQRY *qry;
+  TCLIST *res;
+
+  /* オブジェクトを作成する */
+  tdb = tctdbnew();
+
+  /* データベースを開く */
+  if(!tctdbopen(tdb, "casket.tct", TDBOWRITER | TDBOCREAT)){
+    ecode = tctdbecode(tdb);
+    fprintf(stderr, "open error: %s\n", tctdberrmsg(ecode));
+  }
+
+  /* レコードを格納する */
+  pksiz = sprintf(pkbuf, "%ld", (long)tctdbgenuid(tdb));
+  cols = tcmapnew3("name", "mikio", "age", "30", "lang", "ja,en,c", NULL);
+  if(!tctdbput(tdb, pkbuf, pksiz, cols)){
+    ecode = tctdbecode(tdb);
+    fprintf(stderr, "put error: %s\n", tctdberrmsg(ecode));
+  }
+  tcmapdel(cols);
+
+  /* 素朴な方法でレコードを格納する */
+  pksiz = sprintf(pkbuf, "12345");
+  cols = tcmapnew();
+  tcmapput2(cols, "name", "falcon");
+  tcmapput2(cols, "age", "31");
+  tcmapput2(cols, "lang", "ja");
+  if(!tctdbput(tdb, pkbuf, pksiz, cols)){
+    ecode = tctdbecode(tdb);
+    fprintf(stderr, "put error: %s\n", tctdberrmsg(ecode));
+  }
+  tcmapdel(cols);
+
+  /* TSV文字列を使ってレコードを格納する */
+  if(!tctdbput3(tdb, "abcde", "name\tjoker\tage\t19\tlang\ten,es")){
+    ecode = tctdbecode(tdb);
+    fprintf(stderr, "put error: %s\n", tctdberrmsg(ecode));
+  }
+
+  /* レコードを検索する */
+  qry = tctdbqrynew(tdb);
+  tctdbqryaddcond(qry, "age", TDBQCNUMGE, "20");
+  tctdbqryaddcond(qry, "lang", TDBQCSTROR, "ja,en");
+  tctdbqrysetorder(qry, "name", TDBQOSTRASC);
+  tctdbqrysetlimit(qry, 10, 0);
+  res = tctdbqrysearch(qry);
+  for(i = 0; i &lt; tclistnum(res); i++){
+    rbuf = tclistval(res, i, &amp;rsiz);
+    cols = tctdbget(tdb, rbuf, rsiz);
+    if(cols){
+      printf("%s", rbuf);
+      tcmapiterinit(cols);
+      while((name = tcmapiternext2(cols)) != NULL){
+        printf("\t%s\t%s", name, tcmapget2(cols, name));
+      }
+      printf("\n");
+      tcmapdel(cols);
+    }
+  }
+  tclistdel(res);
+  tctdbqrydel(qry);
+
+  /* データベースを閉じる */
+  if(!tctdbclose(tdb)){
+    ecode = tctdbecode(tdb);
+    fprintf(stderr, "close error: %s\n", tctdberrmsg(ecode));
+  }
+
+  /* オブジェクトを破棄する */
+  tctdbdel(tdb);
+
+  return 0;
+}
+</pre>
+
+<h3 id="tctdbapi_cli">CLI</h3>
+
+<p>テーブルデータベースAPIを簡単に利用するために、コマンドラインインターフェイスとして `<code>tcttest</code>' と `<code>tctmttest</code>' と `<code>tctmgr</code>' が提供されます。</p>
+
+<p>コマンド `<code>tcttest</code>' は、テーブルデータベースAPIの機能テストや性能テストに用いるツールです。以下の書式で用います。`<var>path</var>' はデータベースファイルのパスを指定し、`<var>rnum</var>' は試行回数を指定し、`<var>bnum</var>' はバケット数を指定し、`<var>apow</var>' はアラインメント力を指定し、`<var>fpow</var>' はフリーブロックプール力を指定します。</p>
+
+<dl class="api">
+<dt><code>tcttest write [-mt] [-tl] [-td|-tb|-tt|-tx] [-rc <var>num</var>] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-ip] [-is] [-in] [-it] [-if] [-ix] [-nl|-nb] [-rnd] <var>path</var> <var>rnum</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]</code></dt>
+<dd>"str" と "num" と "type" と "flag" をコラムに持つレコード群を連続してデータベースに追加する。</dd>
+<dt><code>tcttest read [-mt] [-rc <var>num</var>] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-rnd] <var>path</var></code></dt>
+<dd>上記で生成したデータベースの全レコードを検索する。</dd>
+<dt><code>tcttest remove [-mt] [-rc <var>num</var>] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-rnd] <var>path</var></code></dt>
+<dd>上記で生成したデータベースの全レコードを削除する。</dd>
+<dt><code>tcttest rcat [-mt] [-tl] [-td|-tb|-tt|-tx] [-rc <var>num</var>] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-ip] [-is] [-in] [-it] [-if] [-ix] [-nl|-nb] [-pn <var>num</var>] [-dai|-dad|-rl|-ru] <var>path</var> <var>rnum</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]</code></dt>
+<dd>キーがある程度重複するようにレコードの追加を行い、連結モードで処理する。</dd>
+<dt><code>tcttest misc [-mt] [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] <var>path</var> <var>rnum</var></code></dt>
+<dd>各種操作の組み合わせテストを行う。</dd>
+<dt><code>tcttest wicked [-mt] [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] <var>path</var> <var>rnum</var></code></dt>
+<dd>各種更新操作を無作為に選択して実行する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持ちます</p>
+
+<ul class="options">
+<li><code>-mt</code> : 関数 `tctdbsetmutex' を呼び出す。</li>
+<li><code>-tl</code> : オプション `TDBTLARGE' を有効にする。</li>
+<li><code>-td</code> : オプション `TDBTDEFLATE' を有効にする。</li>
+<li><code>-tb</code> : オプション `TDBTBZIP' を有効にする。</li>
+<li><code>-tt</code> : オプション `TDBTTCBS' を有効にする。</li>
+<li><code>-tx</code> : オプション `TDBTEXCODEC' を有効にする。</li>
+<li><code>-rc <var>num</var></code> : レコード用キャッシュの最大数を指定する。</li>
+<li><code>-lc <var>num</var></code> : リーフノード用キャッシュの最大数を指定する。</li>
+<li><code>-nc <var>num</var></code> : 非リーフノード用キャッシュの最大数を指定する。</li>
+<li><code>-xm <var>num</var></code> : 拡張マップメモリのサイズを指定する。</li>
+<li><code>-df <var>num</var></code> : 自動デフラグの単位ステップ数を指定する。</li>
+<li><code>-ip</code> : 主キーに数値型のインデックスを張る。</li>
+<li><code>-is</code> : "str" コラムに文字列型のインデックスを張る。</li>
+<li><code>-in</code> : "num" コラムに数値型のインデックスを張る。</li>
+<li><code>-it</code> : "type" コラムに文字列型のインデックスを張る。</li>
+<li><code>-if</code> : "flag" コラムにトークン転置インデックスを張る。</li>
+<li><code>-ix</code> : "text" コラムにq-gram転置インデックスを張る。</li>
+<li><code>-nl</code> : オプション `TDBNOLCK' を有効にする。</li>
+<li><code>-nb</code> : オプション `TDBLCKNB' を有効にする。</li>
+<li><code>-rnd</code> : キーを無作為に選択する。</li>
+<li><code>-pn <var>num</var></code> : パターン数を指定する。</li>
+<li><code>-dai</code> : 関数 `tctdbputcat' の代わりに関数 `tctdbaddint' を用いる。</li>
+<li><code>-dad</code> : 関数 `tctdbputcat' の代わりに関数 `tctdbadddouble' を用いる。</li>
+<li><code>-rl</code> : 値を無作為な長さにする。</li>
+<li><code>-ru</code> : 更新操作を無作為に選択する。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了します。</p>
+
+<p>コマンド `<code>tctmttest</code>' は、テーブルデータベースAPIの機能テストをマルチスレッドで行うツールです。以下の書式で用います。`<var>path</var>' はデータベースファイルのパスを指定し、`<var>tnum</var>' はスレッド数を指定し、`<var>rnum</var>' は試行回数を指定し、`<var>bnum</var>' はバケット数を指定し、`<var>apow</var>' はアラインメント力を指定し、`<var>fpow</var>' はフリーブロックプール力を指定します。</p>
+
+<dl class="api">
+<dt><code>tctmttest write [-tl] [-td|-tb|-tt|-tx] [-rc <var>num</var>] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-ip] [-is] [-in] [-it] [-if] [-ix] [-nl|-nb] [-rnd] <var>path</var> <var>tnum</var> <var>rnum</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]</code></dt>
+<dd>"str" と "num" と "type" と "flag" をコラムに持つレコード群を連続してデータベースに追加する。</dd>
+<dt><code>tctmttest read [-rc <var>num</var>] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-rnd] <var>path</var> <var>tnum</var></code></dt>
+<dd>上記で生成したデータベースの全レコードを検索する。</dd>
+<dt><code>tctmttest remove [-rc <var>num</var>] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-rnd] <var>path</var> <var>tnum</var></code></dt>
+<dd>上記で生成したデータベースの全レコードを削除する。</dd>
+<dt><code>tctmttest wicked [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] <var>path</var> <var>tnum</var> <var>rnum</var></code></dt>
+<dd>各種更新操作を無作為に選択して実行する。</dd>
+<dt><code>tctmttest typical [-tl] [-td|-tb|-tt|-tx] [-rc <var>num</var>] [-lc <var>num</var>] [-nc <var>num</var>] [-xm <var>num</var>] [-df <var>num</var>] [-nl|-nb] [-rr <var>num</var>] <var>path</var> <var>tnum</var> <var>rnum</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]</code></dt>
+<dd>典型的な操作を無作為に選択して実行する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持ちます</p>
+
+<ul class="options">
+<li><code>-tl</code> : オプション `TDBTLARGE' を有効にする。</li>
+<li><code>-td</code> : オプション `TDBTDEFLATE' を有効にする。</li>
+<li><code>-tb</code> : オプション `TDBTBZIP' を有効にする。</li>
+<li><code>-tt</code> : オプション `TDBTTCBS' を有効にする。</li>
+<li><code>-tx</code> : オプション `TDBTEXCODEC' を有効にする。</li>
+<li><code>-rc <var>num</var></code> : レコード用キャッシュの最大数を指定する。</li>
+<li><code>-lc <var>num</var></code> : リーフノード用キャッシュの最大数を指定する。</li>
+<li><code>-nc <var>num</var></code> : 非リーフノード用キャッシュの最大数を指定する。</li>
+<li><code>-xm <var>num</var></code> : 拡張マップメモリのサイズを指定する。</li>
+<li><code>-df <var>num</var></code> : 自動デフラグの単位ステップ数を指定する。</li>
+<li><code>-ip</code> : 主キーに数値型のインデックスを張る。</li>
+<li><code>-is</code> : "str" コラムに文字列型のインデックスを張る。</li>
+<li><code>-in</code> : "num" コラムに数値型のインデックスを張る。</li>
+<li><code>-it</code> : "type" コラムに文字列型のインデックスを張る。</li>
+<li><code>-if</code> : "flag" コラムにトークン転置インデックスを張る。</li>
+<li><code>-ix</code> : "text" コラムにq-gram転置インデックスを張る。</li>
+<li><code>-nl</code> : オプション `TDBNOLCK' を有効にする。</li>
+<li><code>-nb</code> : オプション `TDBLCKNB' を有効にする。</li>
+<li><code>-rnd</code> : キーを無作為に選択する。</li>
+<li><code>-rr <var>num</var></code> : 読み込み操作の割合を百分率で指定する。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了します。</p>
+
+<p>コマンド `<code>tctmgr</code>' は、テーブルデータベースAPIやそのアプリケーションのテストやデバッグに役立つツールです。以下の書式で用います。`<var>path</var>' はデータベースファイルのパスを指定し、`<var>bnum</var>' はバケット数を指定し、`<var>apow</var>' はアラインメント力を指定し、`<var>fpow</var>' はフリーブロックプール力を指定し、`<var>pkey</var>' はレコードの主キーを指定し、`<var>cols</var>' はコラムの名前と値を交互に指定し、`<var>name</var>' はコラムの名前を指定し、`<var>op</var>' は演算子を指定し、`<var>expr</var>' は条件式を指定し、`<var>file</var>' は入力ファイルを指定します。</p>
+
+<dl class="api">
+<dt><code>tctmgr create [-tl] [-td|-tb|-tt|-tx] <var>path</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]</code></dt>
+<dd>データベースファイルを作成する。</dd>
+<dt><code>tctmgr inform [-nl|-nb] <var>path</var></code></dt>
+<dd>データベースの雑多な情報を出力する。</dd>
+<dt><code>tctmgr put [-nl|-nb] [-sx] [-dk|-dc|-dai|-dad] <var>path</var> <var>pkey</var> [<var>cols</var> ...]</code></dt>
+<dd>レコードを追加する。</dd>
+<dt><code>tctmgr out [-nl|-nb] [-sx] <var>path</var> <var>pkey</var></code></dt>
+<dd>レコードを削除する。</dd>
+<dt><code>tctmgr get [-nl|-nb] [-sx] [-px] [-pz] <var>path</var> <var>pkey</var></code></dt>
+<dd>レコードの値を取得して標準出力する。</dd>
+<dt><code>tctmgr list [-nl|-nb] [-m <var>num</var>] [-pv] [-px] [-fm <var>str</var>] <var>path</var></code></dt>
+<dd>全てのレコードの主キーを改行で区切って標準出力する。</dd>
+<dt><code>tctmgr search [-nl|-nb] [-ord <var>name</var> <var>type</var>] [-m <var>num</var>] [-sk <var>num</var>] [-kw] [-pv] [-px] [-ph] [-bt <var>num</var>] [-rm] [-ms <var>type</var>] <var>path</var> [<var>name</var> <var>op</var> <var>expr</var> ...]</code></dt>
+<dd>検索条件に合致するレコードを改行で区切って標準出力する。</dd>
+<dt><code>tctmgr optimize [-tl] [-td|-tb|-tt|-tx] [-tz] [-nl|-nb] [-df] <var>path</var> [<var>bnum</var> [<var>apow</var> [<var>fpow</var>]]]</code></dt>
+<dd>データベースを最適化する。</dd>
+<dt><code>tctmgr setindex [-nl|-nb] [-it <var>type</var>] <var>path</var> <var>name</var></code></dt>
+<dd>インデックスを設定する。</dd>
+<dt><code>tctmgr importtsv [-nl|-nb] [-sc] <var>path</var> [<var>file</var>]</code></dt>
+<dd>TSVファイルの各行をキーと値とみなしてレコードを登録する。</dd>
+<dt><code>tctmgr version</code></dt>
+<dd>Tokyo Cabinetのバージョン情報を標準出力する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持ちます</p>
+
+<ul class="options">
+<li><code>-tl</code> : オプション `TDBTLARGE' を有効にする。</li>
+<li><code>-td</code> : オプション `TDBTDEFLATE' を有効にする。</li>
+<li><code>-tb</code> : オプション `TDBTBZIP' を有効にする。</li>
+<li><code>-tt</code> : オプション `TDBTTCBS' を有効にする。</li>
+<li><code>-tx</code> : オプション `TDBTEXCODEC' を有効にする。</li>
+<li><code>-nl</code> : オプション `TDBNOLCK' を有効にする。</li>
+<li><code>-nb</code> : オプション `TDBLCKNB' を有効にする。</li>
+<li><code>-sx</code> : 入力を16進数の文字列で行う。</li>
+<li><code>-dk</code> : 関数 `tctdbput' の代わりに関数 `tctdbputkeep' を用いる。</li>
+<li><code>-dc</code> : 関数 `tctdbput' の代わりに関数 `tctdbputcat' を用いる。</li>
+<li><code>-dai</code> : 関数 `tctdbput' の代わりに関数 `tctdbaddint' を用いる。</li>
+<li><code>-dad</code> : 関数 `tctdbput' の代わりに関数 `tctdbadddouble' を用いる。</li>
+<li><code>-px</code> : 出力を16進数の文字列で行う。</li>
+<li><code>-pz</code> : 出力の末尾に改行を付加しない。</li>
+<li><code>-m <var>num</var></code> : 出力の最大数を指定する。</li>
+<li><code>-pv</code> : レコードの値も出力する。</li>
+<li><code>-fm <var>str</var></code> : キーの接頭辞を指定する。</li>
+<li><code>-ord <var>name</var> <var>type</var></code> : 結果の並び順を指定する。</li>
+<li><code>-sk <var>num</var></code> : 結果のスキップ件数を指定する。</li>
+<li><code>-kw</code> : KWIC文字列を出力する。</li>
+<li><code>-ph</code> : ヒント情報も出力する。</li>
+<li><code>-bt</code> : ベンチマークテストの回数を指定する。</li>
+<li><code>-rm</code> : 結果のレコードを全て削除する。</li>
+<li><code>-ms <var>type</var></code> : メタ検索の集合演算を指定する。</li>
+<li><code>-tz</code> : オプション `UINT8_MAX' を有効にする。</li>
+<li><code>-df</code> : デフラグのみを行う。</li>
+<li><code>-it</code> : インデックスの型を "lexical" か "decimal" か "token" か "qgram" か "void" で指定する。</li>
+<li><code>-cv</code> : 既存のインデックスを削除する。</li>
+<li><code>-sc</code> : キーを小文字に正規化する。</li>
+</ul>
+
+<p>`search' サブコマンドの演算子には、"STREQ", "STRINC", "STRBW", "STREW", "STRAND", "STROR", "STROREQ", "STRRX", "NUMEQ", "NUMGT", "NUMGE", "NUMLT", "NUMLE", "NUMBT", "NUMOREQ", "FTSPH", "FTSAND", "FTSOR", "FTSEX" のいずれかを用いることができます。各演算子に "~" を接頭させると論理的な意味が反転されます。"+" を接頭させるとその演算子にはインデックスが適用されません。`-ord' オプションの型指定には、"STRASC", "STRDESC", "NUMASC", "NUMDESC" のいずれかを用いることができます。`-ms' オプションの型指定には、"UNION", "ISECT", "DIFF" のいずれかを用いることができます。このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了します。</p>
+
+<hr />
+
+<h2 id="tcadbapi">抽象データベースAPI</h2>
+
+<p>抽象データベースは、オンメモリハッシュデータベースとオンメモリツリーデータベースとハッシュデータベースとB+木データベースと固定長データベースとテーブルデータベースを同一のAPIで抽象化したデータベースです。それを扱うのが抽象データベースAPIです。`<code>tcadb.h</code>' にAPIの仕様の完全な記述があります。</p>
+
+<h3 id="tcadbapi_description">概要</h3>
+
+<p>抽象データベースAPIを使うためには、`<code>tcutil.h</code>'、`<code>tcadb.h</code>' および関連する標準ヘッダファイルをインクルードしてください。通常、ソースファイルの冒頭付近で以下の記述を行います。</p>
+
+<dl>
+<dt><code>#include &lt;tcutil.h&gt;</code></dt>
+<dt><code>#include &lt;tcadb.h&gt;</code></dt>
+<dt><code>#include &lt;stdlib.h&gt;</code></dt>
+<dt><code>#include &lt;stdbool.h&gt;</code></dt>
+<dt><code>#include &lt;stdint.h&gt;</code></dt>
+</dl>
+
+<p>抽象データベースを扱う際には、`<code>TCADB</code>' 型へのポインタをオブジェクトとして用います。B+木データベースオブジェクトは、関数 `<code>tcadbnew</code>' で作成し、関数 `<code>tcadbdel</code>' で破棄します。作成したオブジェクトを使い終わったら必ず破棄してください。そうしないとメモリリークが発生します。</p>
+
+<p>レコードの格納や探索を行う前提として、抽象データベースオブジェクトを具象データベースと接続させる必要があります。具象データベースを開いて接続するには関数 `<code>tcadbopen</code>' を用い、接続の解除してファイルを閉じるには関数 `<code>tcadbclose</code>' を用います。開いた具象データベースは必ず閉じてください。そうしないと具象データベースが壊れたり格納したデータが失われたりする可能性があります。単一のプロセス内で複数のデータベースオブジェクトが同じデータベースファイルを同時に開くことはできません。</p>
+
+<h3 id="tcadbapi_api">API(英語ごめんね)</h3>
+
+<p>The function `tcadbnew' is used in order to create an abstract database object.</p>
+
+<dl class="api">
+<dt><code>TCADB *tcadbnew(void);</code></dt>
+<dd>The return value is the new abstract database object.</dd>
+</dl>
+
+<p>The function `tcadbdel' is used in order to delete an abstract database object.</p>
+
+<dl class="api">
+<dt><code>void tcadbdel(TCADB *<var>adb</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+</dl>
+
+<p>The function `tcadbopen' is used in order to open an abstract database.</p>
+
+<dl class="api">
+<dt><code>bool tcadbopen(TCADB *<var>adb</var>, const char *<var>name</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>name</var>' specifies the name of the database.  If it is "*", the database will be an on-memory hash database.  If it is "+", the database will be an on-memory tree database.  If its suffix is ".tch", the database will be a hash database.  If its suffix is ".tcb", the database will be a B+ tree database.  If its suffix is ".tcf", the database will be a fixed-length database.  If its suffix is ".tct", the database will be a table database.  Otherwise, this function fails.  Tuning parameters can trail the name, separated by "#".  Each parameter is composed of the name and the value, separated by "=".  On-memory hash database supports "bnum", "capnum", and "capsiz".  On-memory tree database supports "capnum" and "capsiz".  Hash database supports "mode", "bnum", "apow", "fpow", "opts", "rcnum", "xmsiz", and "dfunit".  B+ tree database supports "mode", "lmemb", "nmemb", "bnum", "apow", "fpow", "opts", "lcnum", "ncnum", "xmsiz", and "dfunit".  Fixed-length database supports "mode", "width", and "limsiz".  Table database supports "mode", "bnum", "apow", "fpow", "opts", "rcnum", "lcnum", "ncnum", "xmsiz", "dfunit", and "idx".</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>The tuning parameter "capnum" specifies the capacity number of records.  "capsiz" specifies the capacity size of using memory.  Records spilled the capacity are removed by the storing order.  "mode" can contain "w" of writer, "r" of reader, "c" of creating, "t" of truncating, "e" of no locking, and "f" of non-blocking lock.  The default mode is relevant to "wc".  "opts" can contains "l" of large option, "d" of Deflate option, "b" of BZIP2 option, and "t" of TCBS option.  "idx" specifies the column name of an index and its type separated by ":".  For example, "casket.tch#bnum=1000000#opts=ld" means that the name of the database file is "casket.tch", and the bucket number is 1000000, and the options are large and Deflate.</dd>
+</dl>
+
+<p>The function `tcadbclose' is used in order to close an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbclose(TCADB *<var>adb</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update of a database is assured to be written when the database is closed.  If a writer opens a database but does not close it appropriately, the database will be broken.</dd>
+</dl>
+
+<p>The function `tcadbput' is used in order to store a record into an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbput(TCADB *<var>adb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcadbput2' is used in order to store a string record into an abstract object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbput2(TCADB *<var>adb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, it is overwritten.</dd>
+</dl>
+
+<p>The function `tcadbputkeep' is used in order to store a new record into an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbputkeep(TCADB *<var>adb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcadbputkeep2' is used in order to store a new string record into an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbputkeep2(TCADB *<var>adb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If a record with the same key exists in the database, this function has no effect.</dd>
+</dl>
+
+<p>The function `tcadbputcat' is used in order to concatenate a value at the end of the existing record in an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbputcat(TCADB *<var>adb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, const void *<var>vbuf</var>, int <var>vsiz</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>vbuf</var>' specifies the pointer to the region of the value.</dd>
+<dd>`<var>vsiz</var>' specifies the size of the region of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcadbputcat2' is used in order to concatenate a string value at the end of the existing record in an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbputcat2(TCADB *<var>adb</var>, const char *<var>kstr</var>, const char *<var>vstr</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>`<var>vstr</var>' specifies the string of the value.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>If there is no corresponding record, a new record is created.</dd>
+</dl>
+
+<p>The function `tcadbout' is used in order to remove a record of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbout(TCADB *<var>adb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcadbout2' is used in order to remove a string record of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbout2(TCADB *<var>adb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcadbget' is used in order to retrieve a record in an abstract database object.</p>
+
+<dl class="api">
+<dt><code>void *tcadbget(TCADB *<var>adb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcadbget2' is used in order to retrieve a string record in an abstract database object.</p>
+
+<dl class="api">
+<dt><code>char *tcadbget2(TCADB *<var>adb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the string of the value of the corresponding record.  `NULL' is returned if no record corresponds.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.</dd>
+</dl>
+
+<p>The function `tcadbvsiz' is used in order to get the size of the value of a record in an abstract database object.</p>
+
+<dl class="api">
+<dt><code>int tcadbvsiz(TCADB *<var>adb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tcadbvsiz2' is used in order to get the size of the value of a string record in an abstract database object.</p>
+
+<dl class="api">
+<dt><code>int tcadbvsiz2(TCADB *<var>adb</var>, const char *<var>kstr</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kstr</var>' specifies the string of the key.</dd>
+<dd>If successful, the return value is the size of the value of the corresponding record, else, it is -1.</dd>
+</dl>
+
+<p>The function `tcadbiterinit' is used in order to initialize the iterator of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbiterinit(TCADB *<var>adb</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>The iterator is used in order to access the key of every record stored in a database.</dd>
+</dl>
+
+<p>The function `tcadbiternext' is used in order to get the next key of the iterator of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>void *tcadbiternext(TCADB *<var>adb</var>, int *<var>sp</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>sp</var>' specifies the pointer to the variable into which the size of the region of the return value is assigned.</dd>
+<dd>If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.</dd>
+<dd>Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  It is possible to access every record by iteration of calling this function.  It is allowed to update or remove records whose keys are fetched while the iteration.  However, it is not assured if updating the database is occurred while the iteration.  Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of storing matches the one of the traversal access.</dd>
+</dl>
+
+<p>The function `tcadbiternext2' is used in order to get the next key string of the iterator of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>char *tcadbiternext2(TCADB *<var>adb</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>If successful, the return value is the string of the next key, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.</dd>
+<dd>Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  It is possible to access every record by iteration of calling this function.  However, it is not assured if updating the database is occurred while the iteration.  Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of storing matches the one of the traversal access.</dd>
+</dl>
+
+<p>The function `tcadbfwmkeys' is used in order to get forward matching keys in an abstract database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcadbfwmkeys(TCADB *<var>adb</var>, const void *<var>pbuf</var>, int <var>psiz</var>, int <var>max</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>pbuf</var>' specifies the pointer to the region of the prefix.</dd>
+<dd>`<var>psiz</var>' specifies the size of the region of the prefix.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.</dd>
+</dl>
+
+<p>The function `tcadbfwmkeys2' is used in order to get forward matching string keys in an abstract database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcadbfwmkeys2(TCADB *<var>adb</var>, const char *<var>pstr</var>, int <var>max</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>pstr</var>' specifies the string of the prefix.</dd>
+<dd>`<var>max</var>' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.</dd>
+<dd>The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.</dd>
+</dl>
+
+<p>The function `tcadbaddint' is used in order to add an integer to a record in an abstract database object.</p>
+
+<dl class="api">
+<dt><code>int tcadbaddint(TCADB *<var>adb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, int <var>num</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>If successful, the return value is the summation value, else, it is `INT_MIN'.</dd>
+<dd>If the corresponding record exists, the value is treated as an integer and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tcadbadddouble' is used in order to add a real number to a record in an abstract database object.</p>
+
+<dl class="api">
+<dt><code>double tcadbadddouble(TCADB *<var>adb</var>, const void *<var>kbuf</var>, int <var>ksiz</var>, double <var>num</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>kbuf</var>' specifies the pointer to the region of the key.</dd>
+<dd>`<var>ksiz</var>' specifies the size of the region of the key.</dd>
+<dd>`<var>num</var>' specifies the additional value.</dd>
+<dd>If successful, the return value is the summation value, else, it is Not-a-Number.</dd>
+<dd>If the corresponding record exists, the value is treated as a real number and is added to.  If no record corresponds, a new record of the additional value is stored.</dd>
+</dl>
+
+<p>The function `tcadbsync' is used in order to synchronize updated contents of an abstract database object with the file and the device.</p>
+
+<dl class="api">
+<dt><code>bool tcadbsync(TCADB *<var>adb</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcadboptimize' is used in order to optimize the storage of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadboptimize(TCADB *<var>adb</var>, const char *<var>params</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>params</var>' specifies the string of the tuning parameters, which works as with the tuning of parameters the function `tcadbopen'.  If it is `NULL', it is not used.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>This function is useful to reduce the size of the database storage with data fragmentation by successive updating.</dd>
+</dl>
+
+<p>The function `tcadbvanish' is used in order to remove all records of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbvanish(TCADB *<var>adb</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+</dl>
+
+<p>The function `tcadbcopy' is used in order to copy the database file of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbcopy(TCADB *<var>adb</var>, const char *<var>path</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>path</var>' specifies the path of the destination file.  If it begins with `@', the trailing substring is executed as a command line.</dd>
+<dd>If successful, the return value is true, else, it is false.  False is returned if the executed command returns non-zero code.</dd>
+<dd>The database file is assured to be kept synchronized and not modified while the copying or executing operation is in progress.  So, this function is useful to create a backup file of the database file.</dd>
+</dl>
+
+<p>The function `tcadbtranbegin' is used in order to begin the transaction of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbtranbegin(TCADB *<var>adb</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>The database is locked by the thread while the transaction so that only one transaction can be activated with a database object at the same time.  Thus, the serializable isolation level is assumed if every database operation is performed in the transaction.  All updated regions are kept track of by write ahead logging while the transaction.  If the database is closed during transaction, the transaction is aborted implicitly.</dd>
+</dl>
+
+<p>The function `tcadbtrancommit' is used in order to commit the transaction of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbtrancommit(TCADB *<var>adb</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update in the transaction is fixed when it is committed successfully.</dd>
+</dl>
+
+<p>The function `tcadbtranabort' is used in order to abort the transaction of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>bool tcadbtranabort(TCADB *<var>adb</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>If successful, the return value is true, else, it is false.</dd>
+<dd>Update in the transaction is discarded when it is aborted.  The state of the database is rollbacked to before transaction.</dd>
+</dl>
+
+<p>The function `tcadbpath' is used in order to get the file path of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>const char *tcadbpath(TCADB *<var>adb</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>The return value is the path of the database file or `NULL' if the object does not connect to any database.  "*" stands for on-memory hash database.  "+" stands for on-memory tree database.</dd>
+</dl>
+
+<p>The function `tcadbrnum' is used in order to get the number of records of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcadbrnum(TCADB *<var>adb</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>The return value is the number of records or 0 if the object does not connect to any database instance.</dd>
+</dl>
+
+<p>The function `tcadbsize' is used in order to get the size of the database of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>uint64_t tcadbsize(TCADB *<var>adb</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>The return value is the size of the database or 0 if the object does not connect to any database instance.</dd>
+</dl>
+
+<p>The function `tcadbmisc' is used in order to call a versatile function for miscellaneous operations of an abstract database object.</p>
+
+<dl class="api">
+<dt><code>TCLIST *tcadbmisc(TCADB *<var>adb</var>, const char *<var>name</var>, const TCLIST *<var>args</var>);</code></dt>
+<dd>`<var>adb</var>' specifies the abstract database object.</dd>
+<dd>`<var>name</var>' specifies the name of the function.  All databases support "put", "out", "get", "putlist", "outlist", "getlist", and "getpart".  "put" is to store a record.  It receives a key and a value, and returns an empty list.  "out" is to remove a record.  It receives a key, and returns an empty list.  "get" is to retrieve a record.  It receives a key, and returns a list of the values.  "putlist" is to store records.  It receives keys and values one after the other, and returns an empty list.  "outlist" is to remove records.  It receives keys, and returns an empty list.  "getlist" is to retrieve records.  It receives keys, and returns keys and values of corresponding records one after the other.  "getpart" is to retrieve the partial value of a record.  It receives a key, the offset of the region, and the length of the region.</dd>
+<dd>`<var>args</var>' specifies a list object containing arguments.</dd>
+<dd>If successful, the return value is a list object of the result.  `NULL' is returned on failure.</dd>
+<dd>Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.</dd>
+</dl>
+
+<h3 id="tcadbapi_example">コード例</h3>
+
+<p>抽象データベースを使ったコード例を以下に示します。</p>
+
+<pre>#include &lt;tcutil.h&gt;
+#include &lt;tcadb.h&gt;
+#include &lt;stdlib.h&gt;
+#include &lt;stdbool.h&gt;
+#include &lt;stdint.h&gt;
+
+int main(int argc, char **argv){
+  TCADB *adb;
+  char *key, *value;
+
+  /* オブジェクトを作成する */
+  adb = tcadbnew();
+
+  /* データベースを開く */
+  if(!tcadbopen(adb, "casket.tch")){
+    fprintf(stderr, "open error\n");
+  }
+
+  /* レコードを格納する */
+  if(!tcadbput2(adb, "foo", "hop") ||
+     !tcadbput2(adb, "bar", "step") ||
+     !tcadbput2(adb, "baz", "jump")){
+    fprintf(stderr, "put error\n");
+  }
+
+  /* レコードを取得する */
+  value = tcadbget2(adb, "foo");
+  if(value){
+    printf("%s\n", value);
+    free(value);
+  } else {
+    fprintf(stderr, "get error\n");
+  }
+
+  /* 横断的にレコードを参照する */
+  tcadbiterinit(adb);
+  while((key = tcadbiternext2(adb)) != NULL){
+    value = tcadbget2(adb, key);
+    if(value){
+      printf("%s:%s\n", key, value);
+      free(value);
+    }
+    free(key);
+  }
+
+  /* データベースを閉じる */
+  if(!tcadbclose(adb)){
+    fprintf(stderr, "close error\n");
+  }
+
+  /* オブジェクトを破棄する */
+  tcadbdel(adb);
+
+  return 0;
+}
+</pre>
+
+<h3 id="tcadbapi_cli">CLI</h3>
+
+<p>抽象データベースAPIを簡単に利用するために、コマンドラインインターフェイスとして `<code>tcatest</code>' と `<code>tcamttest</code>' と `<code>tcamgr</code>' が提供されます。</p>
+
+<p>コマンド `<code>tcatest</code>' は、抽象データベースAPIの機能テストや性能テストに用いるツールです。以下の書式で用います。`<var>name</var>' はデータベースの名前を指定し、`<var>rnum</var>' は試行回数を指定し、`<var>tnum</var>' はトランザクションの回数を指定します。</p>
+
+<dl class="api">
+<dt><code>tcatest write <var>name</var> <var>rnum</var></code></dt>
+<dd>`00000001'、`00000002' のように変化する8バイトのキーと値を連続してデータベースに追加する。</dd>
+<dt><code>tcatest read <var>name</var></code></dt>
+<dd>上記で生成したデータベースの全レコードを検索する。</dd>
+<dt><code>tcatest remove <var>name</var></code></dt>
+<dd>上記で生成したデータベースの全レコードを削除する。</dd>
+<dt><code>tcatest rcat <var>name</var> <var>rnum</var></code></dt>
+<dd>キーがある程度重複するようにレコードの追加を行い、連結モードで処理する。</dd>
+<dt><code>tcatest misc <var>name</var> <var>rnum</var></code></dt>
+<dd>各種操作の組み合わせテストを行う。</dd>
+<dt><code>tcatest wicked <var>name</var> <var>rnum</var></code></dt>
+<dd>各種更新操作を無作為に選択して実行する。</dd>
+<dt><code>tcatest compare <var>name</var> <var>tnum</var> <var>rnum</var></code></dt>
+<dd>各種データベースの比較テストを行う。</dd>
+</dl>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了します。</p>
+
+<p>コマンド `<code>tcamttest</code>' は、抽象データベースAPIの機能テストをマルチスレッドで行うツールです。以下の書式で用います。`<var>name</var>' はデータベースの名前を指定し、`<var>tnum</var>' はスレッド数を指定し、`<var>rnum</var>' は試行回数を指定します。</p>
+
+<dl class="api">
+<dt><code>tcamttest write <var>name</var> <var>tnum</var> <var>rnum</var></code></dt>
+<dd>`00000001'、`00000002' のように変化する8バイトのキーと値を連続してデータベースに追加する。</dd>
+<dt><code>tcamttest read <var>name</var> <var>tnum</var></code></dt>
+<dd>上記で生成したデータベースの全レコードを検索する。</dd>
+<dt><code>tcamttest remove <var>name</var> <var>tnum</var></code></dt>
+<dd>上記で生成したデータベースの全レコードを削除する。</dd>
+</dl>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了します。</p>
+
+<p>コマンド `<code>tcamgr</code>' は、抽象データベースAPIやそのアプリケーションのテストやデバッグに役立つツールです。以下の書式で用います。`<var>name</var>' はデータベースの名前を指定し、`<var>key</var>' はレコードのキーを指定し、`<var>value</var>' はレコードの値を指定し、`<var>params</var>' はチューニングパラメータを指定し、`<var>func</var>' は関数の名前を指定し、`<var>arg</var>' は関数の引数を指定し、`<var>dest</var>' は格納先のファイルを指定します。</p>
+
+<dl class="api">
+<dt><code>tcamgr create <var>name</var></code></dt>
+<dd>データベースを作成する。</dd>
+<dt><code>tcamgr inform <var>name</var></code></dt>
+<dd>データベースの雑多な情報を出力する。</dd>
+<dt><code>tcamgr put [-sx] [-sep <var>chr</var>] [-dk|-dc|-dai|-dad] <var>name</var> <var>key</var> <var>value</var></code></dt>
+<dd>レコードを追加する。</dd>
+<dt><code>tcamgr out [-sx] [-sep <var>chr</var>] <var>name</var> <var>key</var></code></dt>
+<dd>レコードを削除する。</dd>
+<dt><code>tcamgr get [-sx] [-sep <var>chr</var>] [-px] [-pz] <var>name</var> <var>key</var></code></dt>
+<dd>レコードの値を取得して標準出力する。</dd>
+<dt><code>tcamgr list [-sep <var>chr</var>] [-m <var>num</var>] [-pv] [-px] [-fm <var>str</var>] <var>name</var></code></dt>
+<dd>全てのレコードのキーを改行で区切って標準出力する。</dd>
+<dt><code>tcamgr optimize <var>name</var> <var>params</var></code></dt>
+<dd>データベースを最適化する。</dd>
+<dt><code>tcamgr misc [-sx] [-sep <var>chr</var>] [-px] <var>name</var> <var>func</var> [<var>arg</var>...]</code></dt>
+<dd>雑多な操作の多目的関数を呼び出す。</dd>
+<dt><code>tcamgr map [-fm <var>str</var>] <var>name</var> <var>dest</var></code></dt>
+<dd>レコードを別のB+木データベース内に写像する。</dd>
+<dt><code>tcamgr version</code></dt>
+<dd>Tokyo Cabinetのバージョン情報を標準出力する。</dd>
+</dl>
+
+<p>各オプションは以下の機能を持ちます</p>
+
+<ul class="options">
+<li><code>-sx</code> : 入力を16進数の文字列で行う。</li>
+<li><code>-sep <var>chr</var></code> : 入力文字列の区切り文字を指定する。</li>
+<li><code>-dk</code> : 関数 `tcadbput' の代わりに関数 `tcadbputkeep' を用いる。</li>
+<li><code>-dc</code> : 関数 `tcadbput' の代わりに関数 `tcadbputcat' を用いる。</li>
+<li><code>-dai</code> : 関数 `tcadbput' の代わりに関数 `tcadbaddint' を用いる。</li>
+<li><code>-dad</code> : 関数 `tcadbput' の代わりに関数 `tcadbadddouble' を用いる。</li>
+<li><code>-px</code> : 出力を16進数の文字列で行う。</li>
+<li><code>-pz</code> : 出力の末尾に改行を付加しない。</li>
+<li><code>-m <var>num</var></code> : 出力の最大数を指定する。</li>
+<li><code>-pv</code> : レコードの値も出力する。</li>
+<li><code>-fm <var>str</var></code> : キーの接頭辞を指定する。</li>
+</ul>
+
+<p>このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了します。</p>
+
+<h3 id="tcadbapi_cgi">CGI</h3>
+
+<p>抽象データベースAPIを簡単に利用するために、コモンゲートウェイインタフェースとして `<code>tcawmgr.cgi</code>' が提供されます。</p>
+
+<p>CGIスクリプト `<code>tcawmgr.cgi</code>' は、Webインターフェイスで抽象データベースの内容を閲覧したり編集したりするのに役立つツールです。操作対象のデータベースは、このCGIスクリプトのカレントディレクトリに "<code>casket.tch</code>" または "<code>casket.tcb</code>" または "<code>casket.tcf</code>" という名前で設置されている必要があります。また、そのパーミッションにおいてCGIスクリプトの実行ユーザに対する読み込みと書き込みが可能になっていることが必要です。このCGIスクリプトをWebサーバの公開ディレクトリに設置したら、割り当てられたURLにWebブラウザでアクセスすると利用を開始することができます。</p>
+
+<hr />
+
+<h2 id="tips">ちょっとしたコツ</h2>
+
+<p>この節ではTokyo Cabinetの使い方のコツや知っておくと便利な小技を紹介します。</p>
+
+<h3 id="tips_tcutil">ユーティリティAPI</h3>
+
+<p>C++、Perl、Ruby、Javaといった高水準な言語では必ずといってリストやマップといったデータ構造を簡単に利用できる機能が標準ライブラリとしてついてきます。しかし、C言語にはそれに相当するものはありません。GNOME GlibやApache APRなどの非標準ライブラリを使うのも一興ですが、Tokyo Cabinetにも高機能・高性能なユーティリティが付属しています。STL(C++の標準テンプレートライブラリ)のstringにあたるものがTCXSTRで、listにあたるものがTCLISTで、mapやsetにあたるものがTCMAPとTCTREEです。他にも文字列処理や各種符号処理のユーティリティも提供されます。それらを使いこなすとC言語でもC++やその他の高水準言語並みの直感的なプログラミングができるでしょう。</p>
+
+<p>TCXSTRの何が便利かと言えば、`<code>tcxstrcat</code>' です。特にバッファリングに有用で、後ろにデータをどんどんくっつけていけるのです。メモリ領域は内部で適宜拡張してくれるので、アプリケーション側でメモリ管理に悩む必要はありませんし、性能もかなり良いです。</p>
+
+<p>TCLISTは配列で実装されたリストです。これはスタック(`<code>tclistpush</code>' で格納して `<code>tclistpop</code>' で取り出す)としてもキュー(`<code>tclistpush</code>' で格納して `<code>tclistshift</code>' で取り出す)としても使えます。もちろんメモリ管理は内部でよろしくやってくれますし、性能もかなり良いです。</p>
+
+<p>TCMAPはハッシュ表によるマップ(連想配列)の実装です。任意のキーに対応づけて任意の値を格納できます。ハッシュデータベースのオンメモリ版と考えてもよいでしょう。TCMAPのイテレータはレコードを格納した順番に取り出すことができるというのが特徴で、かつ任意のレコードを先頭や末尾に移動させることもできるので、LRU消去方式のキャッシュとしても利用することができます。もちろんメモリ管理は内部でよろしくやってくれますし、性能もかなり良いです。</p>
+
+<p>TCTREEは順序木によるマップ(連想配列)の実装です。任意のキーに対応づけて任意の値を格納できます。B+木データベースのオンメモリ版と考えてもよいでしょう。TCTREEのイテレータはレコードを比較関数の昇順に取り出すことができるというのが特徴で、かつイテレータを任意の場所に飛ばすことができるので、文字列の前方一致検索や数値の範囲を行うことができます。もちろんメモリ管理は内部でよろしくやってくれますし、性能もかなり良いです。</p>
+
+<p>TCXSTRとTCLISTとTCMAPとTCTREEの各関数はリエントラントですが、該当のオブジェクトを複数のスレッドで共有する場合にはアプリケーション側で排他制御を行うことが求められます。ただし、ハッシュマップと順序木に関しては排他制御を内部で行う実装としてTCMDBとTCNDBが提供されます。</p>
+
+<p>TCMPOOLというのもあります。これはいわゆるメモリプールの実装で、メモリ管理の単位を一括して楽をすることができる機能です。例えば `<code>malloc</code> で確保した領域は必ず `<code>free</code>' で解放しないとメモリリークになってしまいますが、`<code>tcmpoolmalloc</code>' で確保した領域は明示的に解放しないでよいのです。ではいつ解放されるのかと言えば、メモリプール自体を解放した時です。つまりアプリケーション側ではメモリプールの寿命にだけ気を付ければよく、個々のオブジェクトの寿命を気にしなくてもよくなるということです。メモリプールはTCXSTRやTCLISTやTCMAPやTCTREEのオブジェクトを発生させることもできますし、任意のオブジェクトをデストラクタとともに登録することもできます。典型的には以下のような使い方をします。</p>
+
+<pre>TCMPOOL *mpool;
+int i, j;
+char *buf;
+for(i = 0; i &lt; 100; i++){
+  mpool = tcmpoolnew();
+  for(j = 0; j &lt; 100; ++){
+    buf = tcmpoolmalloc(10); // メモリプール内オブジェクトの生成
+    ...                      // いちいち解放しなくてOK
+  }
+  tcmpooldel(mpool);         // ここで一気に解放
+}
+</pre>
+
+<h3 id="tips_hashtune">ハッシュデータベースのチューニング</h3>
+
+<p>チューニングをするかしないかでデータベース操作の性能は劇的に変わるので、まじめなユースケースでは、チューニングは必須となるでしょう。関数 `<code>tchdbtune</code>' でそれを行います。この関数では「バケット数」と「アラインメント力」と「フリーブロックプール力」と「オプション」が指定されます。</p>
+
+<p>最も重要なのは、バケット数の設定です。これは、データベースに格納するレコードの最終的な数の数倍(2〜4倍程度がオススメ)を指定すべきです。デフォルトは131071なので、100000個以上のレコードを入れるならばまずこれを設定すべきです。例えば100万レコードくらいを入れる予定ならば、バケット数は200万〜400万くらいにしておくとよいでしょう。バケット配列の個々の要素のサイズは4バイト(32ビット)なので、バケット数を200万にした場合にはファイルサイズが8MB増えて、メモリも8MB必要となるわけですが、21世紀のコンピュータならそれくらい大したことないでしょう。とりあえずバケット数は大きめにとりましょう。</p>
+
+<p>アラインメントは、レコードの開始位置を揃える機構です。指定したアラインメント力で1を高位にビットシフトした数に開始アドレスが揃えられます。デフォルトは4です。例えばアラインメント力を8にしたならば、1&lt;&lt;8で、256の倍数に開始位置が揃えられます。アラインメントの利点は三つあります。一つめは、開始アドレスを揃えることでレコード間にパディング(隙間)ができることです。レコードサイズの増減がパディングの範囲に収まれば、更新時にレコードの位置を変えなくてもよくなります。二つめは、レコードの読み書きをファイルシステムのブロック単位にあわせて行うことができるために、OSレベルでのI/Oの処理が効率化されることです。三つめは、開始アドレスをアラインメントの商として記録できるようになるため、4バイトのバケットで表せる変域が増加することです。アラインメントを用いない場合は2GB(1&lt;&lt;31)までのデータベースファイルしか扱えませんが、例えばアラインメントが256であれば、2GB*256で512GBまでのデータベースファイルを扱うことができます。</p>
+
+<p>フリーブロックとは、更新によってできたファイル内の未使用領域のことです。フリーブロックプールはそれを管理して再利用する機構です。指定したフリーブロックプール力で1を高位にビットシフトした数がフリーブロックプールの容量になります。デフォルトは10です。この設定を変える必要はほとんどないでしょう。</p>
+
+<p>オプションとは、レコードの格納方法を指定するフラグの集合のことです。`<code>HDBTLARGE</code>' と `<code>HDBTDEFLATE</code>' と `<code>HDBTBZIP</code>' と `<code>HDBTTCBS</code>' と `<code>HDBTEXCODEC</code>' の論理和で指定します。`<code>HDBTLARGE</code>' を指定すると、バケットの個々の要素を8バイト(64ビット)で扱います。バケット配列のサイズが2倍になるかわりに、データベースのサイズの上限を8EBに引き上げます。`<code>HDBTDEFLATE</code>' を指定すると、レコードをDeflateアルゴリズムで圧縮してから記録します。大きいサイズ(だいたい256バイト以上)のレコードを圧縮して格納する場合に有利です。`<code>HDBTBZIP</code>' を指定すると、レコードをBZIP2アルゴリズムで圧縮して格納します。Deflateよりは遅いですが、圧縮率は有利です。`<code>HDBTTCBS</code>' を指定すると、レコードをBWT、MTF、Elias Gamma符号で圧縮して格納します。小さいサイズ(256バイト未満)のレコードを圧縮して格納する場合に有利です。`<code>HDBTEXCODEC</code>' は外部の圧縮伸長アルゴリズムを使うためのオプションです。具体的なアルゴリズムは隠しAPIの関数 `<code>tchdbsetcodecfunc</code>' で指定します。</p>
+
+<p>チューニングパラメータの設定はデータベースを作成する前に行う必要があります。チューニングパラメータはメタデータとしてデータベース内に記録されるので、作成した後は指定する必要はありません。なお、いったん作成したデータベースのチューニングを変更することはできません(最適化すればできますが)。バケット数を1000000、アラインメント数を12(4096)、フリーブロックをデフォルト、オプションを `<code>HDBTLARGE</code>' と `<code>HDBTDEFLATE</code>' に指定してデータベースを作成する場合、以下のようなコードになります。</p>
+
+<pre>TCHDB *hdb;
+hdb = tchdbnew();
+tchdbtune(hdb, 1000000, 12, -1, HDBTLARGE | HDBTDEFLATE);
+tchdbopen(hdb, "casket.tch", HDBOWRITER | HDBOCREAT);
+...
+</pre>
+
+<p>ハッシュデータベースはキャッシュ機構を備えます。これは一旦検索されたレコードをメモリ上に保持しておくもので、同一のレコードが何度も検索される場合の性能を向上させてくれます。キャッシュ上にあるレコードが更新された場合、そのレコードはキャッシュから削除されますので、検索の頻度よりも更新の頻度が多い場合にはあまり効果はありません。また、キャッシュを有効にするとキャッシュを管理するためのオーバーヘッドがかかるので、キャッシュのヒット率がある程度以上でないと逆に処理が遅くなってしまいます。したがって、キャッシュのヒット率がかなり高い場合(つまり同じレコードを何度も参照するような場合)にのみキャッシュ機構を利用すべきです。ハッシュデータベースのキャッシュはデフォルトでは無効になっていますので、有効にする場合は関数 `<code>tchdbsetcache</code>' で設定してください。キャッシュパラメータの設定はデータベースに接続する前に行う必要があり、また接続する度に毎回行う必要があります。</p>
+
+<p>ハッシュデータベースはmmapを介してファイル入出力を行うための拡張マップメモリという機構を備えます。これは、デフォルトでmmapによってマップされるバケット配列とは別に、レコード用の領域をmmapでメモリにマップしたものです。mmapを介したファイル入出力はpreadやpwriteを使った入出力よりも高速で、並列処理性能も高いという利点もあります。その反面、データベースを開いた瞬間に拡張マップメモリとして指定したサイズの領域が仮想メモリ空間に確保され、そのサイズが実メモリの利用可能量を上回った場合にはスワップが発生してしまいます。デフォルトでは64MBの拡張マップメモリが利用されますが、想定されるデータベースファイルがそれより大きくて実メモリ容量よりも小さいような場合は、データベースサイズよりも少し大きいくらいの拡張マップメモリを指定するとよいでしょう。拡張マップメモリのサイズは関数 `<code>tchdbsetxmsiz</code>' で指定してください。拡張マップメモリのパラメータの設定はデータベースに接続する前に行う必要があり、また接続する度に毎回行う必要があります。</p>
+
+<p>レコードの削除を頻繁に行ったり、値の長さを変えるような更新を頻繁に行ったりする場合は、フリーブロックプールを使ったとしても少しずつ断片化が起こってしまいます。断片化が進むとデータベースファイルのサイズが肥大化してきますが、それを解消してファイルサイズを小さくするためにはデフラグと呼ばれる操作を行うことになります。デフラグの最も簡単な方法は、関数 `<code>tchdboptimize</code>' によってデータベースに最適化をかけることです。これはデータベース全体を一気に作り直すことで断片化を解消します。もう一つの方法は、関数 `<code>tchdbsetdfunit</code>' で自動デフラグ設定をしてから更新を行うことです。そうすると、断片化が発生する度に動的に少しずつ最適化処理を行うようになるので、性能が少し犠牲になりますが、見掛け上は肥大化がほとんど発生しないようになります。この関数のパラメータとして指定する単位ステップ数とは、何個の領域の断片化を検出したらデフラグ操作を行うかを指定するものです。この数を増やした方が処理効率は上がりますが、デフラグ操作を行っている間のロックの粒度が上がるので増やしすぎるのも考え物です。通常は8くらいにしておくとよいでしょう。自動デフラグのパラメータの設定はデータベースに接続する前に行う必要があり、また接続する度に毎回行う必要があります。</p>
+
+<h3 id="tips_btreetune">B+木データベースのチューニング</h3>
+
+<p>チューニングをするかしないかで性能が劇的に変わるのはB+木データベースについても同じです。まじめなユースケースではちゃんとチューニングしましょう。チューニングは関数 `<code>tcbdbtune</code>' で行います。この関数では「リーフ内メンバ数」「非リーフ内メンバ数」「バケット数」と「アラインメント力」と「フリーブロックプール力」と「オプション」が指定されます。</p>
+
+<p>リーフまたはリーフページとは、B+木の末端のノードのことで、複数のレコードのキーと値のリストが格納される記憶単位のことです。リーフ内メンバ数とは、一つのリーフの中にいくつのレコードを格納するかの設定です。デフォルトは128です。比較関数の順序通りにレコードを格納または探索することが多い場合はこの値を大きくした方が性能がよくなり、逆に比較関数の順序とは無関係にレコードを格納または探索することが多い場合は小さくした方がよくなります。非リーフまたは非リーフページとはB+木の末端以外のノードのことで、複数のレコードのキーのみが格納される記憶単位のことです。非リーフの数はリーフに比べて少なく、性能に与える影響はあまり大きくありません。非リーフ内メンバ数をデフォルトから変える必要はほとんどないでしょう。</p>
+
+<p>バケット数やその他のパラメータ、B+木データベースの下層にあるハッシュデータベースにそのまま渡されます。B+木の各ページはハッシュデータベースのレコードとして記録されるので、バケット数などのパラメータはその際に意味を持ちます。したがって、ここで指定するバケット数は、B+木データベースにおける最終的なレコード数をリーフ内メンバ数で割った値の数倍に設定するのが最善です。とはいえB+木データベースにおいてはバケット数などのパラメータを変更する必要はあまりないでしょう。</p>
+
+<p>チューニングの例として、平均8バイトのキーと平均32バイトの値のレコードを100万件格納することを考えてみます。各コードのヘッダなどのオーバーヘッドは5バイト程度です。ファイルシステムのブロックサイズは4096バイトとします。すると、1ブロックに入れられるレコード数は4096/(8+32+5)で90個ほどということになります。さらに、Deflate圧縮オプションを有効にして、その圧縮率が50%ほどだとしましょう。となると180個ほどのレコードが1ブロックに収まることが期待されます。各リーフのサイズは2ブロックか3ブロックのサイズが望ましいので、180を2倍した360がリーフ内メンバ数の理想値になります。となると、バケット数は1000000/360で2777となり、デフォルトの32749から変える必要はないでしょう。アラインメント力はファイルシステムのブロックサイズにあわせるためにlog2(4096)で12にします。以上の設定をコードに反映すると以下のようになります。</p>
+
+<pre>TCHDB *bdb;
+bdb = tcbdbnew();
+tcbdbtune(hdb, 360, -1, -1, 12, -1, BDBTDEFLATE);
+tcbdbopen(hdb, "casket.tcb", BDBOWRITER | BDBOCREAT);
+...
+</pre>
+
+<p>B+木データベースもキャッシュ機構を備えます。これは処理対象のページをメモリ上に保持しておくもので、同一のページが何度も読み書きされる場合の性能を向上させてくれます。キャッシュ上にあるページが更新された場合でも、そのページはメモリ上に保持されたままなので、検索も更新も高速化されます。B+木データベースのキャッシュはデフォルトでは小さめに設定されていますので、メモリを多く使っても高速化したい場合は関数 `<code>tcbdbsetcache</code>' で設定してください。キャッシュパラメータの設定はデータベースに接続する前に行う必要があり、また接続する度に毎回行う必要があります。</p>
+
+<p>B+木データベースでも拡張マップメモリを利用することができます。しかし、B+木のキャッシュ機構がファイル入出力のバッファリングの役目を果たしているので、デフォルトでは無効になっています。メモリ利用効率は無視してとにかくスループットを追求したい場合のみ、関数 `<code>tcbdbsetxmsiz</code>' で拡張マップメモリを有効化してください。拡張マップメモリのパラメータの設定はデータベースに接続する前に行う必要があり、また接続する度に毎回行う必要があります。</p>
+
+<p>B+木データベースでも断片化は起きうるので、デフラグをかけるのはよい考えです。静的な最適化は関数 `<code>tcbdboptimize</code>' で行い、自動デフラグの設定は関数 `<code>tcbdbsetdfunit</code>' で行います。B+木データベースではハッシュデータベースよりもI/Oの粒度が大きいので、自動デフラグを行う際の単位ステップ数は2くらいにしておくとよいでしょう。自動デフラグのパラメータの設定はデータベースに接続する前に行う必要があり、また接続する度に毎回行う必要があります。</p>
+
+<h3 id="tips_hardware">システム設定</h3>
+
+<p>ハードウェアやOSなどの「システム側」の設定も、データベースの操作を高速化するためには重要です。まず、できれば、データベースのサイズと同等以上のRAMをマシンに搭載してください。そして、I/Oバッファのサイズを大きくし、ダーティバッファをフラッシュする頻度が少なくするように設定してください。そうすることによって、デバイスアクセスの頻度を最小化し、I/Oの待ち時間による性能劣化を抑止できます。Linux上では、`<code>sysctl</code>' コマンドや `<code>/etc/sysctl.conf</code>' ファイルでそれらの設定を行うことになるでしょう。ファイルシステムの選択も重要です。Linux上では、通常はEXT2が最高速ですが、EXT3のwritebackモードの方が速いこともあります。ReiserFSもかなり高速です。EXT3のその他のモードはかなり遅いです。他のファイルシステムに関しては各自で実験してみてください。</p>
+
+<p>補助記憶装置にHDD(ハードディスクドライブ)でなくSSD(ソリッドステートドライブ)を使うというのもよい考えです。HDDはシーケンシャルアクセスの速度に比べてランダムアクセスが桁違いに遅くなる傾向にあり、ランダムアクセスを頻繁に行うDBMのストレージとしては不向きなのです。それに対してSSDはランダムアクセスの速度があまり劣化しないので、DBMのストレージとしては最適です。HDDよりバイト毎の単価がかなり高いSSDですが、とっても速くて便利なのでぜひ導入を検討してください。また、購入する製品を選択する際には、カタログに書いてある転送スループットに惑わされてはいけません。それはシーケンシャルアクセスの性能を示しているだけだからです。そうでなく、ランダムアクセスの性能をWebなどで調べて、それが良いものを選んでください。</p>
+
+<h3 id="tips_multithread">マルチスレッド対応</h3>
+
+<p>Tokyo CabinetのAPIにおける各関数はリエントラントなので、引数として与えるデータが各スレッドで別々のものであれば完全に並列に操作を実行することができます。しかし、データベースオブジェクトは内部状態を持つので、一つのデータベースオブジェクトを複数のスレッドで共有する場合には、更新操作に関連して排他制御を行う必要があります。とはいえ、特に難しいことはありません。複数のスレッドで共有するデータベースオブジェクトに対して、作成した直後に関数 `<code>tchdbsetmutex</code>' や `<code>tcbdbsetmutex</code>' を呼び出すだけでOKです。そうすると以後の操作の内部で適切にロックを用いて排他制御が行われるようになります。複数のスレッドを使うが各々が別個のデータベースオブジェクトにアクセスする場合には排他制御は必要ありませんし、排他制御をしない方が高速に動作します。</p>
+
+<p>スレッド間の排他制御はリードライトロックで行われます。`open'、`close'、`put'、`out' などの操作にはライトロック(排他ロック)がかけられ、`get'、`curkey'、`curval' などの操作にはリードロック(共有ロック)がかけられます。ロックの単位は、ハッシュデータベースではレコード単位で、B+木データベースではデータベース単位になります。同一のロックに対する読み込みは激しく同時に行えますが、書き込みをしている間は他のスレッドはブロックされます。排他制御の設定はデータベースに接続する前に行う必要があり、また接続する度に毎回行う必要があります。以下のようなコードになります。</p>
+
+<pre>TCHDB *hdb;
+hdb = tchdbnew();
+tchdbsetmutex(hdb);
+tchdbopen(hdb, "casket.tch", HDBOWRITER);
+...
+</pre>
+
+<h3 id="tips_transaction">トランザクション</h3>
+
+<p>ファイル上の(オンメモリでない)データベースにはトランザクション機構があります。トランザクションを開始してから行った一連の操作は、コミットすることで確定させたり、アボートすることでなかったことにしたりすることができます。トランザクション中にアプリケーションがクラッシュした場合にも、トランザクション中の操作がなかったことになるだけで、データベースの整合性は維持されます。トランザクションは以下のようなコードで用います。</p>
+
+<pre>tchdbtranbegin(hdb);
+do_something();
+if(is_all_ok){
+  tchdbtrancommit(hdb);
+} else {
+  tchdbtranabort(hdb);
+}
+</pre>
+
+<p>トランザクションを実行できるのは同時1スレッドのみで、他のスレッドはその間にトランザクションを開始しようとするとブロックされます。したがって、データベースの参照をトランザクション内でのみ行うならば、トランザクションの分離レベルは直列化可能(serializable)になります。しかし、あるスレッドがトランザクションの最中でも他のスレッドはトランザクションを実行せずにデータベースを参照できます。その場合の分離レベルは非コミット読み取り(read uncommitted)になります。状況に応じて使い分けてください。</p>
+
+<p>トランザクション機構は、ハッシュデータベースではファイル上のログ先行書き込み(write ahead logging)によって実現され、B+木データベースではメモリ上のシャドウページング(shadow paging)によって実現されます。これらの手法とロックによって、データベース単位のACID属性(atomicity、consistency、isolation、durability)が確保されます。</p>
+
+<p>ファイルシステムのdurabilityすらも信用しない場合(突然の電源切断に耐える確率を上げたい場合)には、データベースを開く際に `<code>HDBOTSYNC</code>' または `<code>BDBOTSYNC</code>' オプションをつけてください。そうすると、すべてのトランザクションの前後にfsyncで更新内容とディスクの内容の同期がとられるようになります(めちゃくちゃ遅くなりますが)。とはいえ、いかにトランザクションを使ってもディスクが壊れたらオシマイなので、重要なデータベースに関してはバックアップや冗長化の手法を適用してください。</p>
+
+<h3 id="tips_cursor">カーソル</h3>
+
+<p>B+木データベースにはカーソル機構があります。カーソルは指定したキーの場所にジャンプさせることができ、そこから前後に一つずつずらしながらレコードを参照したり更新したりすることができます。例えば文字列の前方一致検索を行う場合、接頭辞をキーとして指定してカーソルをジャンプさせて、そこから前に進みながらキーを一つ一つ参照していって、前方一致しなかった時点で止めるという処理になります。例えば "tokyo" で始まるキーのレコードを取り出すには以下のようなコードになるでしょう。</p>
+
+<pre>cur = tcbdbcurnew();
+tcbdbcurjump2(cur, "tokyo");
+while((key = tcbdbcurkey2(cur)) != NULL){
+  if(!tcstrfwm(kbuf, "tokyo")){
+    free(key);
+    break;
+  }
+  if((val = tcbdbcurval2(cur)) != NULL){
+    do_something(key, val);
+    free(val);
+  }
+  free(key);
+  tcbdbcurnext();
+}
+tcbdbcurdel(cur);
+</pre>
+
+<p>カーソルをジャンプさせてから、他のスレッドが同一のデータベースに対して更新を行った場合、そのカーソルの位置はずれる可能性があります。具体的には、カーソルのあるリーフ上でカーソルより前にレコード挿入された場合、カーソルは小さい方向に一つずれます。また、カーソルのあるリーフ上でカーソルより前にあるレコードが削除された場合、カーソルは大きい方向に一つずれます。したがって、検索などの非クリティカルな操作では特別な配慮は必要ありませんが、更新にカーソルを使う場合には、処理中にカーソルの位置がずれないようにトランザクションを使うか、アプリケーション側の責任で排他制御をすることになるでしょう。なお、典型的な検索操作である範囲検索をアトミックに行うために関数 `<code>tcbdbrange</code>' および関数 `<code>tcbdbfwmkeys</code>' が提供されています。</p>
+
+<h3 id="tips_backup">バックアップ</h3>
+
+<p>データベースファイルのバックアップは、通常のファイルと同様に<code>cp</code>や<code>tar</code>や<code>cpio</code>といったコマンドで行うことができます。ただし、ライタとして接続しているプロセスがデータベースを更新中である場合、コピー元のファイルの状態が中途半端になっている可能性があるため、コピー先のファイルに不整合が起きる場合があります。したがって、データベースが更新中でないこと確認してからバックアップ作業を行うことが必要となります。</p>
+
+<p>デーモンプロセスなどの常駐プロセスがデータベースに接続し続けるユースケースでは上記の手順は現実的ではありません。そういった場合、その常駐プロセスの責任でバックアップ処理を駆動することができます。関数 `<code>tchdbcopy</code>' や `<code>tcbdbcopy</code>' を呼び出すと、更新内容をデータベースファイルと同期させた上で、その間にファイルの複製を行います。</p>
+
+<p>バックアップ用関数は任意のコマンドを呼び出すこともできます。コピー先のファイル名の代わりに "@" で始まるコマンド名を指定するとそれが呼び出されます。そのコマンドの第1引数にはデータベース名が指定され、第2引数には現在のUNIX時間のマイクロ秒が指定されます。例えば、以下のようなシェルスクリプトを用意してそれを呼び出すようにするとよいでしょう。</p>
+
+<pre>#! /bin/sh
+srcpath="$1"
+destpath="$1.$2"
+rm -f "$destpath"
+cp -f "$srcpath" "$destpath"
+</pre>
+
+<p>バックアップ用のコマンドを実行している間はそのデータベースの更新はブロックしますので、コピーに時間がかかる場合には留意が必要です。無停止のホットバックアップを望むならば、"cp" などによる単純なファイル複製の代わりにファイルシステム(LVM)のスナップショット機能を使うとよいでしょう。</p>
+
+<h3 id="tips_cmphashbtree">ハッシュデータベースとB+木データベースの比較</h3>
+
+<p>キーと値のペアを格納したいというのははっきりしているが、ハッシュデータベースとB+木データベースのどちらを使えばよいかわからないという場合もあるかもしれません。その場合、レコードの検索条件が完全一致だけで済むのなら、ハッシュデータベースを試してください。レコードを順序に基づいて参照したいなら、B+木データベースを試してください。メモリ上だけ保持してファイルに書き出す必要がないならば、ユーティリティAPIのハッシュマップを試してください。</p>
+
+<p>検索条件が完全一致の場合にはハッシュデータベースを使うのが一般的ですが、B+木でも完全一致検索はできます。ファイルシステムのI/Oキャッシュに乗らない大規模のデータベースでは、ハッシュデータベースとB+木データベースの性能特性を考えて、使うデータベースの種類を選択することが重要です。</p>
+
+<p>ハッシュデータベースのキャッシュ機構はレコード単位ですが、B+木データベースはキャッシュ機構はページ単位であるというのが性能上の最大の留意点です。B+木データベースにおいては、データベース内の全てのレコードはキーの昇順で並べられ、順番が近いレコードをページにまとめて管理します。キャッシュやI/Oはページを単位として行います。したがって、順番が近いレコードを参照する場合にはキャッシュがヒットしてI/Oを伴わずに操作が完結するので効率がよくなります。ということは、多数のレコードを格納する際に、対象のレコード群をキーの昇順でソートしてからデータベースに格納すると、I/Oの回数が最小化されて時間効率も空間効率も最高になります。これはアプリケーション層でもキャッシュ機構を持つことを要求するものですが、至高を求めるあなたには不可能ではないはずです。全文検索システムHyper Estraierのインデクシングが高速な秘訣はまさにここにあります。</p>
+
+<p>逆に考えれば、データベースにアクセスする順序が制御できない場合は、B+木データベースよりもハッシュデータベースを使う方が有利ということになります。キャッシュに乗らない場合には、ハッシュデータベースの方がメモリ使用量も小さく、個々のレコードを取り出す際の計算量も小くて済みます。なお、ハッシュデータベースの構築時に一気にレコードを入れるような用途の場合には、非同期モードを使うとB+木データベース以上の更新性能を実現できます。新しいレコードはファイルの末尾に記録されることを利用して、ファイルの末尾部分に特化したキャッシュを作ることができるからです。</p>
+
+<h3 id="tips_tctdb">テーブルデータベースの仕組み</h3>
+
+<p>テーブルデータベースは、リレーショナルデータベースのテーブルのように、複数の列からなるレコードを格納できるデータベースです。ハッシュデータベースのように主キーでレコードを識別しながらも、リレーショナルデータベースのようにレコード内に名前をつけた複数のコラムを持たせることができます。ハッシュデータベースと違って、レコード内の個々のコラムの値を条件にしてレコードの集合を問い合わせることができます。リレーショナルデータベースとは違って、あらかじめスキーマを定義する必要がなく、レコード毎に異なる種類のコラムを持たせることができます。</p>
+
+<p>レコードの検索は、合致条件や順序指定を組み合わせたクエリオブジェクトをデータベースに渡すことで実行されます。合致条件の演算子には以下のものがあります。コマンドラインでは、「TDBQC」の部分を省いた文字列を用います。合致条件の真偽を反転させるには、各演算子と `<code>TDBQCNEGATE</code>' のビット和を用います(コマンドラインでは "~" を接頭させます)。</p>
+
+<ul>
+<li>TDBQCSTREQ : 右辺の文字列が完全一致する</li>
+<li>TDBQCSTRINC : 右辺の文字列を含む</li>
+<li>TDBQCSTRBW : 右辺の文字列で始まる</li>
+<li>TDBQCSTREW : 右辺の文字列で終わる</li>
+<li>TDBQCSTRAND : 右辺の文字列内の空白またはコンマ区切りの文字列の全てを含む</li>
+<li>TDBQCSTROR : 右辺の文字列内の空白またはコンマ区切りの文字列のいずれかを含む</li>
+<li>TDBQCSTROREQ : 右辺の文字列内の空白またはコンマ区切りの文字列のいずれかと完全一致する</li>
+<li>TDBQCSTRRX : 右辺の文字列の正規表現と一致する</li>
+<li>TDBQCNUMEQ : 右辺の数値と一致する</li>
+<li>TDBQCNUMGT : 右辺の数値より大きい</li>
+<li>TDBQCNUMGE : 右辺の数値と同じかより大きい</li>
+<li>TDBQCNUMLT : 右辺の数値より小さい</li>
+<li>TDBQCNUMLE : 右辺の数値と同じかより小さい</li>
+<li>TDBQCNUMBT : 右辺の空白またはコンマ区切りの2つの数値の間である</li>
+<li>TDBQCNUMOREQ : 右辺の空白またはコンマ区切りの数値のいずれかと一致する</li>
+<li>TDBQCFTSPH : 右辺の文字列を用いてフレーズ検索の全文検索を行う</li>
+<li>TDBQCFTSAND : 右辺の空白またはコンマ区切りの文字列を用いてAND検索の全文検索を行う</li>
+<li>TDBQCFTSOR : 右辺の空白またはコンマ区切りの文字列を用いてOR検索の全文検索を行う</li>
+<li>TDBQCFTSEX : 右辺の文字列を用いて複合検索式の全文検索を行う</li>
+</ul>
+
+<p>順序指定の演算子には以下のものがあります。デフォルトは順序不定です。</p>
+
+<ul>
+<li>TDBQOSTRASC : 文字列の辞書順の昇順</li>
+<li>TDBQOSTRDESC : 文字列の辞書順の降順</li>
+<li>TDBQOSTRASC : 数値の昇順</li>
+<li>TDBQOSTRDESC : 数値の降順</li>
+</ul>
+
+<p>`<code>TDBQCSTRAND</code>' と `<code>TDBQCSTROR</code>' は、いわゆるタグ検索のための演算子です。タグ検索とは、空白またはコンマで区切られたトークンをタグとみなして、そのタグが存在するか否かを判定してレコードを探す操作です。`<code>TDBQCSTRINC</code>' のように任意の部分文字列を探すのではなく、トークン単位の完全一致を判定します。タグ検索を高速化するには後述のトークン転置インデックスを張ることが推奨されます。`<code>TDBQCSTROREQ</code>' 演算子もタグ検索に使うことができますが、SQLのIN演算子と同じように、検索されるレコードのコラムには単一のトークンしか含まれてはならないという制約があります。その分、通常の文字列型のインデックスが効くという利点があります。</p>
+
+<p>`<code>TDBQCFTSPH</code>' と `<code>TDBQCFTSAND</code>' と `<code>TDBQCFTSOR</code>' と `<code>TDBQCFTSEX</code>' は、いわゆる全文検索のための演算子です。全文検索は前述のタグ検索と違って、空白やコンマの区切りを単位としない任意の部分文字列の一致を判定できる点で `<code>TDBQCSTRINC</code>' に類似しています。ただし、大文字小文字やアクセントマークなどの違いを吸収するため、ユーザが入力した任意のテキストを検索するのに便利です。全文検索を実用的な速度で動作させるためには後述のq-gram転置インデックスを張っておくことが必要です。`<code>TDBQCFTSEX</code>' 演算子で用いる複合検索式においては、空白で区切って複数のトークンを指定すると、その全てのトークンを含むというAND条件で検索できます。空白および「&amp;&amp;」で区切っても同じ意味になります。空白および「||」で区切ると、両辺のトークンのどちらかを含むというOR条件で検索できます。トークンに空白を含めたい場合は「""」で括ります。演算子の結合優先順位は「""」「||」「&amp;&amp;」の順になります。同一順位の演算子は左結合で評価されます。</p>
+
+<h3 id="tips_tctdbindex">テーブルデータベースのインデックス</h3>
+
+<p>テーブルデータベースを検索する際に、合致条件の判定や順序指定によるソートを高速化するために、任意のコラムを対象としてインデックスを張ることができます。コラムには型がありませんが、インデックスには型があります。文字列型の演算を高速化させたい場合は文字列型のインデックスを、数値型の演算子を高速化させたい場合は数値型のインデックスを、トークン型の演算子を高速化させたい場合はトークン転置インデックスを張ることになります。ただし、型が異なる場合でもインデックスを張っておくと、メインのハッシュデータベースの全表スキャンの代わりに、それよりは小さいインデックスの全表スキャンを用いるので、計算量は同じですが処理時間は短くなります。</p>
+
+<ul>
+<li>文字列型インデックス(TDBITLEXICAL):<ul>
+<li>計算量が小さくなる演算子:TDBQCSTREQ、TDBQCSTRBW、TDBQCSTROREQ</li>
+<li>計算量は同じだが高速化する演算子:TDBQCSTRINC、TDBQCSTREW、TDBQCSTRAND、TDBQCSTROR、TDBQCSTRRX、TDBQCNUMEQ、TDBQCNUMGT、TDBQCNUMGE、TDBQCNUMLT、TDBQCNUMLE、TDBQCNUMBT、TDBQCNUMOREQ</li>
+<li>計算量が小さくなる順序指定:TDBQOSTRASC、TDBQOSTRDESC</li>
+</ul></li>
+<li>数値型インデックス(TDBITDECIMAL):<ul>
+<li>計算量が小さくなる演算子:TDBQCNUMEQ、TDBQCNUMGT、TDBQCNUMGE、TDBQCNUMLT、TDBQCNUMLE、TDBQCNUMBT、TDBQCNUMOREQ</li>
+<li>計算量は同じだが高速化する演算子:TDBQCSTREQ、TDBQCSTRBW、TDBQCSTROREQ、TDBQCSTRINC、TDBQCSTREW、TDBQCSTRAND、TDBQCSTROR、TDBQCSTRRX</li>
+<li>計算量が小さくなる順序指定:TDBQONUMASC、TDBQONUMDESC</li>
+</ul></li>
+<li>トークン転置インデックス(TDBITTOKEN):<ul>
+<li>計算量が小さくなる演算子:TDBQCSTRAND、TDBQCSTROR</li>
+</ul></li>
+<li>q-gram転置インデックス(TDBITQGRAM):<ul>
+<li>計算量が小さくなる演算子:TDBQCFTSPH、TDBQCFTSAND、TDBQCFTSOR、TDBQCFTSEX</li>
+</ul></li>
+</ul>
+
+<p>合致条件に利用できるインデックスが複数ある場合、型が一致する最初に指定された演算子に対して適用されます。したがって、カーディナリティが高い条件を先に指定する方が効率的になります。合致条件の演算子を `<code>TDBQCNOIDX</code>' とのビット和にすると、その条件の判定にインデックスを適用しないようになります(コマンドラインでは "+" を接頭させます)。否定の合致条件にはインデックスは適用されません。インデックスのデータは、レコード本体を格納したデータベースファイルとは別個に、B+木データベースのファイルとして記録されます。</p>
+
+<p>転置インデックスとは、検索対象の文字列がどのレコードに含まれているかを記録して効率的に探し出すためのインデックスです。トークン転置インデックスとq-gram転置インデックスの二種類の方式をサポートしています。トークン転置インデックスは、空白もしくはコンマで区切られた単語をキーにしてレコードを探すための構造で、`<code>TDBQCSTRAND</code>' などの演算子を高速化するので、いわゆるタグ検索などに重宝するでしょう。q-gram転置インデックスは、3文字毎の部分文字列(tri-gram)をキーにしてレコードを探すための構造で、`<code>TDBQCFTSPH</code>' などの演算子を高速化するので、いわゆる全文検索などに重宝するでしょう。全文検索系の演算子は大文字小文字の違いやアクセント記号の有無などを無視して検索してくれるので便利です。転置インデックス(特にq-gram転置インデックス)はサイズがかなり大きくなり、更新処理にかかるオーバーヘッドも大きくなってしまうので、ご利用は計画的にお願いします。</p>
+
+<h3 id="tips_tcadb">抽象データベース</h3>
+
+<p>ハッシュデーターベースかB+木データベースかを実行時に決定したい場合には、抽象データベースAPIを使うとよいでしょう。抽象データベースAPIはハッシュデータベースAPIとB+木データベースAPIの共通のインターフェイスで、関数 `<code>tcadbopen</code>' でデータベースを開く際のデータベース名で具体的にどの種類のデータベースを扱うかを指定することができます。ハッシュデータベースの名前には接尾辞として ".tch" をつけ、B+木データベースの名前には接尾辞として ".tcb" をつけることで区別されます。チューニングパラメータは、名前の後に "#" で区切って "name=value" の形式で指定します。例えば "casket.tch#bnum=1000000#apow=10" などとします。数値表現には "k"、"m"、"g" などの2進接頭辞を接尾させることもできます。また、データベース名の接尾辞に ".tcf" をつけると固定長データベースになります。連番のID番号をキーにして固定長のデータを管理する場合には最も効率が良くなります。</p>
+
+<p>抽象データベースAPIはオンメモリハッシュデータベースやオンメモリツリーデータベースとしても利用することができます。データベース名を "*" とするとオンメモリハッシュデータベースになり、"+" とするとオンメモリツリーデータベースになります。また、それらをキャッシュとして利用したい場合は、"*#capsiz=100m" などとするとよいでしょう。キャッシュの容量を100MBに限定して、それを越えた際には格納した順序が古いレコードから自動的に消していくようになります。オンメモリハッシュデータベースとオンメモリツリーデータベースの使い分けですが、パフォーマンスを求める場合には前者を用い、メモリ効率を求めたり前方一致検索を行いたい場合には後者を用いるとよいでしょう。</p>
+
+<p>DBMとして一般的でない機能は関数 `<code>tcadbmisc</code>' に隠蔽されています。この関数はサブ関数名を第1引数に指定し、それに応じて解釈の変わる引数リストを与えて実行します。サポートされるサブ関数は具象データベースの型によって異なります。複数のレコードを一度に扱える "putlist"、"outlist"、"getlist" は全ての具象データベースでサポートされています。その他にも様々な機能がありますが、作者の気まぐれで増えるのでここでは全てを説明できません。詳しくはソースコードをご覧ください。</p>
+
+<h3 id="tips_tcadbtable">抽象データベースによるテーブル操作</h3>
+
+<p>抽象データベースの接尾辞に ".tct" をつけるとテーブルデータベースになります。テーブルデータベースでは単一の文字列を値とする代わりにコラムの名前と値のマップが用いられます。抽象データベースでテーブルデータベースを扱う場合、単一の文字列とマップを同一のインターフェイスで扱うために、ゼロ文字('\0')を区切り文字としてコラムの名前と値を交互に並べて直列化した文字列を用います。レコードを格納する際(`<code>tcadbput</code>')の引数やレコードを取得する際(`<code>tcadbget</code>')の戻り値にはそのゼロ区切り文字列が使われます。</p>
+
+<p>レコードの検索は関数 `<code>tcadbmisc</code>' のサブ関数 "search" で行います。引数に "addcond" か "setorder" か "setlimit" か "get" か "out" を接頭させてゼロ文字区切りで演算式を記述した文字列を与えることでクエリを組み立てます。例えば "addcond\0name\0STRBW\0john" と "setorder\0age\0NUMASC" を指定すると、「コラム "name" の値が "john" で始まるレコードをコラム "age" の数値の昇順で取り出す」というクエリになります。戻り値は該当するレコードの主キーのリストです。ただし、"get" が指定されると、コラム名と値のマップをゼロ区切り文字列で返します。"get\0name\0age" などとして特定のコラムに絞り込むこともできます。"out" が指定されると、該当のコラムを削除します。"get" と "out" を組み合わせるとキューとして利用することができます。サブ関数 "genuid" は、ユニークなID番号を採番して返します。</p>
+
+<h3 id="tips_hiddenapi">隠しAPI</h3>
+
+<p>この文書に書いてあるAPIは、全体の70%くらいです。つまり、この文書に書いていない隠しAPIが30%くらいあります。興味のある人はヘッダファイル(`<code>tcutil.h</code>'、`<code>tchdb.h</code>'、`<code>tcbdb.h</code>'、`<code>tcfdb.h</code>'、`<code>tctdb.h</code>'、`<code>tcadb.h</code>')の中身を覗いてみてください。上級者用でちょっと癖が強いけれども、使いこなすと機能や性能の面でにかなり有利になるAPIが揃っています。その中でも特に便利なのは、"putproc" 系の関数です。これはputと同様にレコードの挿入を試みるのですが、既存のレコードがあった場合にそれを引数にしてコールバック関数を呼ぶので、任意の更新操作をアトミックに行うことができます。また、"foreach" 系の関数も便利です。これはデータベース内の全てのレコードをアトミックに走査しながら、各々のレコードを引数にしてコールバック関数を呼び出します。</p>
+
+<p>抽象データベースAPIの隠しAPI関数 `<code>tcadbsetskel</code>' は激アツです。拡張データベーススケルトンと呼ばれる構造体によって関数ポインタの集合を指定することで、抽象データベースの全てのメソッドの振る舞いをオーバーライドすることができるようになります。そうすると、DBM風のインターフェイスを持つ全てのライブラリをTokyo Cabinetと同じインターフェイスで使えるようになります。典型的には以下のような実装になります。</p>
+
+<pre>ADBSKEL skel;
+memset(0, &amp;skel, sizeof(skel));
+skel.opq = mydbnew();    // レシーバオブジェクトを生成して設定
+skel.del = mydbdel;      // デストラクタをオーバーライド
+skel.open = mydbopen;    // openメソッドをオーバーライド
+skel.close = mydbclose;  // closeメソッドをオーバーライド
+...                      // その他、好きなメソッドをオーバーライド
+TCADB *adb = tcadbnew();
+tcadbsetskel(adb, &amp;skel);
+tcadbopen(adb, "foobarbaz");
+...
+</pre>
+
+<p>拡張データベーススケルトンの一実装である「複式抽象データベース」を設定するユーティリティとして、隠しAPI関数 `<code>tcadbsetskelmulti</code>' が提供されます。これを適用した抽象データベースは、パラメータで指定した数にデータベースが分割されるようになりますが、具象データベースの種類に関わらず、レコードの挿入や削除などの操作を透過的に行うことができます。データベース名はファイル名ではなくディレクトリ名として扱われ、そのディレクトリの中に複数のデータベースファイルが作られます。レコードはキーのハッシュ値により分散されてどれかひとつのデータベースに格納されます。データベースを分割すると何が嬉しいかというと、データベース操作に要する排他制御の粒度がその分割数に応じて細分化することです。したがって、たとえ下層のデータベースが最適化などのグローバルなロックを要する操作を行っていたとしても、複式抽象データベースを用いていれば、スレッドがブロックする時間を分割数の逆数にまで下げることができます。ただしその代償として、CPUにオーバヘッドがかかることと、`<code>tcadbfwmkeys</code>' などの集合演算の結果の順序が不定になることは覚悟してください。複式抽象データベースに対して `<code>tcadbmisc</code>' を実行する際には、サブ関数名に "@" か "%" を接頭させて引数毎の操作対象を指定できます。"@" は全ての引数をキーとみなして、引数毎に別々の内部データベースを対象としてサブ関数を実行します。"%" は引数をキーと値のペアのリストとみなして、そのペア毎に別々の内部データベースを対象としてサブ関数を実行します。すなわち、"getlist" は "@getlist" として実行すべきで、"putlist" は "%putlist" として実行すべきです。"@" も "%" もつかない場合には各々の内部データベースに対して全ての引数を渡して該当の操作を実行します。</p>
+
+<pre>TCADB *adb = tcadbnew();
+tcadbsetskelmulti(adb, 8);     // 8分割の複式抽象データベースとしてマーク
+tcadbopen(adb, "casket.tch");  // ハッシュデータベースとして開く
+...
+</pre>
+
+<h3 id="tips_tcrdb">リモートインターフェイス</h3>
+
+<p>多種のアプリケーションでデータベースを共有したい場合やWebアプリケーション等でマルチプロセスの並列処理を行う場合は、Tokyo Cabinetのファイルロック機構が鬱陶しく感じるかもしれません。また、複数のマシンからデータベースを参照したい場合にはTokyo Cabinetだと困ってしまうかもしれません。</p>
+
+<p>データベースの管理のみを行うサーバを別プロセスとして立ちあげて、アプリケーションのプロセスがネットワークソケットを介してそのサーバに接続すれば上記の問題は解決します。そのようなデータベースサーバとそれに接続するためのライブラリが別パッケージ「Tokyo Tyrant」として提供されています。Tokyo Tyrantのサーバは抽象データベースを扱うので、Tokyo Cabinetの全種類のデータベースをリモートインターフェイスで操作することができます。</p>
+
+<h3 id="tips_binding">C言語以外の言語のバインディング</h3>
+
+<p>PerlとRubyとJavaとLuaの言語バインディングに関しては、Tokyo Cabinetの作者が開発およびメンテナンスを行います。それ以外の言語に関しては、第三者が提供してくれることを望みます。現状では、少なくともPythonとPHPとSchemeとCommon LispとErlangとHaskellの処理系でもTokyo Cabinetを利用できるようです。</p>
+
+<p>ユーザの利便性を考えると、C言語以外の言語においても、APIのシンボル名や使い方はできるだけ似通ったものにすることが望ましいでしょう。そのために、`<code>tokyocabinet.idl</code>' が提供されます。これはIDLで言語共通の(最大公約数的な)インターフェイスを定義したものですので、新たな言語バインディングを設計する際には、できるだけそれに準拠するようにしてください。IDLで定義されていない機能は各言語の流儀にできるだけ合わせてください。インストールの手順やドキュメントなどのパッケージの構造についても、各言語の流儀にできるだけ合わせるとよいでしょう。</p>
+
+<hr />
+
+<h2 id="fileformat">ファイルフォーマット</h2>
+
+<p>この節ではデータベースファイルのフォーマットに関する仕様を示します。</p>
+
+<h3 id="fileformat_tchdb">ハッシュデータベースのファイルフォーマット</h3>
+
+<p>ハッシュデータベースが管理するデータベースファイルの内容は、ヘッダ部、バケット部、フリーブロックプール部、レコード部の4つに大別されます。ファイルに記録される数値は固定長数値もしくは可変長数値として記録されます。前者は数値を特定の領域にリトルエンディアンで直列化したものです。後者は数値を可変長の領域に128進法のデルタ符号で直列化したものです。</p>
+
+<p>ヘッダ部はファイルの先頭から256バイトの固定長でとられ、以下の情報が記録されます。</p>
+
+<table summary="database header format">
+<tr>
+<td class="label">名前</td>
+<td class="label">オフセット</td>
+<td class="label">データ長</td>
+<td class="label">機能</td>
+</tr>
+<tr>
+<td>マジックナンバ</td>
+<td class="number">0</td>
+<td class="number">32</td>
+<td>データベースファイルであることの判別。「ToKyO CaBiNeT」で始まる</td>
+</tr>
+<tr>
+<td>データベースタイプ</td>
+<td class="number">32</td>
+<td class="number">1</td>
+<td>ハッシュ表(0x01)かB+木(0x02)か固定長(0x03)かテーブル(0x04)</td>
+</tr>
+<tr>
+<td>追加フラグ</td>
+<td class="number">33</td>
+<td class="number">1</td>
+<td>開きっぱなし(1&lt;&lt;0)、致命的エラー(1&lt;&lt;1)の論理和</td>
+</tr>
+<tr>
+<td>アラインメント力</td>
+<td class="number">34</td>
+<td class="number">1</td>
+<td>アラインメントに対する2の冪乗</td>
+</tr>
+<tr>
+<td>フリーブロックプール力</td>
+<td class="number">35</td>
+<td class="number">1</td>
+<td>フリーブロックプールの要素数に対する2の冪乗</td>
+</tr>
+<tr>
+<td>オプション</td>
+<td class="number">36</td>
+<td class="number">1</td>
+<td>ラージモード(1&lt;&lt;0)、Deflate圧縮モード(1&lt;&lt;1)、BZIP2圧縮モード(1&lt;&lt;2)、TCBS圧縮モード(1&lt;&lt;3)、外部圧縮モード(1&lt;&lt;4)の論理和</td>
+</tr>
+<tr>
+<td>バケット数</td>
+<td class="number">40</td>
+<td class="number">8</td>
+<td>バケット配列の要素数</td>
+</tr>
+<tr>
+<td>レコード数</td>
+<td class="number">48</td>
+<td class="number">8</td>
+<td>格納しているレコードの数</td>
+</tr>
+<tr>
+<td>ファイルサイズ</td>
+<td class="number">56</td>
+<td class="number">8</td>
+<td>データベースファイルのサイズ</td>
+</tr>
+<tr>
+<td>先頭レコード</td>
+<td class="number">64</td>
+<td class="number">8</td>
+<td>最初のレコードのオフセット</td>
+</tr>
+<tr>
+<td>不透明領域</td>
+<td class="number">128</td>
+<td class="number">128</td>
+<td>ユーザが自由に使える領域</td>
+</tr>
+</table>
+
+<p>バケット部はヘッダ部の直後にバケット配列の要素数に応じた大きさでとられ、ハッシュチェーンの先頭要素のオフセットが各要素に記録されます。各要素は固定長数値で、そのサイズはノーマルモードでは4バイト、ラージモードでは8バイトです。また、オフセットはアラインメントで割った商として記録されます。</p>
+
+<p>フリーブロックプール部はバケット部の直後にフリーブロックプールの要素数に応じた大きさでとられ、未使用領域のオフセットと長さが各要素に記録されます。オフセットはアラインメントで割った商に変換した上で、直前の要素の値との差分として記録されます。オフセットとサイズは可変長数値として扱われます。</p>
+
+<p>レコード部はバケット部の直後からファイルの末尾までを占め、各レコードの以下の情報を持つ要素が記録されます。各レコードの領域は常にアラインメントされた位置から始まります。</p>
+
+<table summary="record format">
+<tr>
+<td class="label">名前</td>
+<td class="label">オフセット</td>
+<td class="label">データ長</td>
+<td class="label">機能</td>
+</tr>
+<tr>
+<td>マジックナンバ</td>
+<td class="number">0</td>
+<td class="number">1</td>
+<td>データの識別と整合性確認に用いる。0xC8固定</td>
+</tr>
+<tr>
+<td>ハッシュ値</td>
+<td class="number">1</td>
+<td class="number">1</td>
+<td>チェーンの進路決定に用いるハッシュ値</td>
+</tr>
+<tr>
+<td>左チェーン</td>
+<td class="number">2</td>
+<td class="number">4</td>
+<td>左チェーン接続先のオフセットのアラインメント商</td>
+</tr>
+<tr>
+<td>右チェーン</td>
+<td class="number">6</td>
+<td class="number">4</td>
+<td>右チェーン接続先のオフセットのアラインメント商</td>
+</tr>
+<tr>
+<td>パディングサイズ</td>
+<td class="number">10</td>
+<td class="number">2</td>
+<td>パディングのサイズ</td>
+</tr>
+<tr>
+<td>キーサイズ</td>
+<td class="number">12</td>
+<td class="number">可変</td>
+<td>キーのサイズ</td>
+</tr>
+<tr>
+<td>値サイズ</td>
+<td class="number">可変</td>
+<td class="number">可変</td>
+<td>値のサイズ</td>
+</tr>
+<tr>
+<td>キー</td>
+<td class="number">可変</td>
+<td class="number">可変</td>
+<td>キーのデータ</td>
+</tr>
+<tr>
+<td>値</td>
+<td class="number">可変</td>
+<td class="number">可変</td>
+<td>値のデータ</td>
+</tr>
+<tr>
+<td>パディング</td>
+<td class="number">可変</td>
+<td class="number">可変</td>
+<td>意味を持たないデータ</td>
+</tr>
+</table>
+
+<p>ただし、フリーブロックとなった領域には、各レコードの以下の情報を持つ要素が記録されます。</p>
+
+<table summary="free block format">
+<tr>
+<td class="label">名前</td>
+<td class="label">オフセット</td>
+<td class="label">データ長</td>
+<td class="label">機能</td>
+</tr>
+<tr>
+<td>マジックナンバ</td>
+<td class="number">0</td>
+<td class="number">1</td>
+<td>データの識別と整合性確認に用いる。0xB0固定</td>
+</tr>
+<tr>
+<td>ブロックサイズ</td>
+<td class="number">1</td>
+<td class="number">4</td>
+<td>ブロックのサイズ</td>
+</tr>
+</table>
+
+<p>トランザクションログはデータベース名に ".wal" を後置した名前のファイルとして記録されます。ファイルの先頭8バイトにトランザクション開始時のデータベースファイルのサイズを記録し、その後に更新操作による差分情報を持つ以下の要素を連結します。</p>
+
+<table summary="transaction log format">
+<tr>
+<td class="label">名前</td>
+<td class="label">オフセット</td>
+<td class="label">データ長</td>
+<td class="label">機能</td>
+</tr>
+<tr>
+<td>オフセット</td>
+<td class="number">0</td>
+<td class="number">8</td>
+<td>更新された領域の先頭のオフセット</td>
+</tr>
+<tr>
+<td>サイズ</td>
+<td class="number">8</td>
+<td class="number">4</td>
+<td>更新された領域のサイズ</td>
+</tr>
+<tr>
+<td>データ</td>
+<td class="number">12</td>
+<td class="number">可変</td>
+<td>更新される領域の更新前のデータ</td>
+</tr>
+</table>
+
+<h3 id="fileformat_tcbdb">B+木データベースのファイルフォーマット</h3>
+
+<p>B+木データベースが扱う全てのデータはハッシュデータベースに記録されます。記録されるデータは、メタデータと論理ページに分類されます。論理ページはリーフノードと非リーフノードに分類されます。固定長数値と可変長数値の形式はハッシューデータベースと同じです。</p>
+
+<p>メタデータはハッシュデータベースのヘッダにおける不透明領域にとられ、以下の情報が記録されます。</p>
+
+<table summary="database header format">
+<tr>
+<td class="label">名前</td>
+<td class="label">オフセット</td>
+<td class="label">データ長</td>
+<td class="label">機能</td>
+</tr>
+
+<tr>
+<td>比較関数</td>
+<td class="number">0</td>
+<td class="number">1</td>
+<td>比較関数がtccmplexical(デフォルト)なら0x00、tccmpdecimalなら0x01、tccmpint32なら0x02、tccmpint64なら0x03、それ以外なら0xff</td>
+</tr>
+<tr>
+<td>予約領域</td>
+<td class="number">1</td>
+<td class="number">7</td>
+<td>現状では利用していない。</td>
+</tr>
+<tr>
+<td>リーフ内レコード数</td>
+<td class="number">8</td>
+<td class="number">4</td>
+<td>個々のリーフノードに入れるレコードの最大数</td>
+</tr>
+<tr>
+<td>非リーフ内インデックス数</td>
+<td class="number">12</td>
+<td class="number">4</td>
+<td>個々の非リーフノードに入れるインデックスの最大数</td>
+</tr>
+<tr>
+<td>ルートノードID</td>
+<td class="number">16</td>
+<td class="number">8</td>
+<td>B+木のルートノードのページID</td>
+</tr>
+<tr>
+<td>先頭リーフID</td>
+<td class="number">24</td>
+<td class="number">8</td>
+<td>先頭のリーフノードのID</td>
+</tr>
+<tr>
+<td>末尾リーフID</td>
+<td class="number">32</td>
+<td class="number">8</td>
+<td>末尾のリーフノードのID</td>
+</tr>
+<tr>
+<td>リーフ数</td>
+<td class="number">40</td>
+<td class="number">8</td>
+<td>リーフノードの数</td>
+</tr>
+<tr>
+<td>非リーフ数</td>
+<td class="number">48</td>
+<td class="number">8</td>
+<td>非リーフノードの数</td>
+</tr>
+<tr>
+<td>レコード数</td>
+<td class="number">56</td>
+<td class="number">8</td>
+<td>格納しているレコードの数</td>
+</tr>
+</table>
+
+<p>リーフノードはレコードのリストを保持し、非リーフノードはページを参照する疎インデックスを保持します。レコードはユーザデータの論理的な単位です。キーが重複する論理レコードは物理的には単一のレコードにまとめられます。物理レコードは以下の形式で直列化されます。</p>
+
+<table summary="record format">
+<tr>
+<td class="label">名前</td>
+<td class="label">オフセット</td>
+<td class="label">データ長</td>
+<td class="label">機能</td>
+</tr>
+<tr>
+<td>キーサイズ</td>
+<td class="number">0</td>
+<td class="number">可変</td>
+<td>キーのサイズ</td>
+</tr>
+<tr>
+<td>値サイズ</td>
+<td class="number">可変</td>
+<td class="number">可変</td>
+<td>最初の値のサイズ</td>
+</tr>
+<tr>
+<td>重複数</td>
+<td class="number">可変</td>
+<td class="number">可変</td>
+<td>キーが重複した値の数</td>
+</tr>
+<tr>
+<td>キー</td>
+<td class="number">可変</td>
+<td class="number">可変</td>
+<td>キーのデータ</td>
+</tr>
+<tr>
+<td>値</td>
+<td class="number">可変</td>
+<td class="number">可変</td>
+<td>最初の値のデータ</td>
+</tr>
+<tr>
+<td>重複レコード</td>
+<td class="number">可変</td>
+<td class="number">可変</td>
+<td>値のサイズと値のデータのリスト</td>
+</tr>
+</table>
+
+<p>リーフノードはレコードの集合を格納するための物理的な単位です。リーフノードは1からインクリメントして振られるID番号で識別されます。リーフノードはID番号を16進数の文字列として表現したデータをキーとし、以下の値を持つレコードとしてハッシュデータベースに格納されます。レコードは常にキーの昇順に整列した状態で保持されます。</p>
+
+<table summary="leaf node format">
+<tr>
+<td class="label">名前</td>
+<td class="label">オフセット</td>
+<td class="label">データ長</td>
+<td class="label">機能</td>
+</tr>
+<tr>
+<td>前リーフ</td>
+<td class="number">0</td>
+<td class="number">可変</td>
+<td>直前のリーフノードのID</td>
+</tr>
+<tr>
+<td>後リーフ</td>
+<td class="number">可変</td>
+<td class="number">可変</td>
+<td>直後のリーフノードのID</td>
+</tr>
+<tr>
+<td>レコードリスト</td>
+<td class="number">可変</td>
+<td class="number">可変</td>
+<td>ページのレコードを直列化して連結したデータ</td>
+</tr>
+</table>
+
+<p>インデックスは子ページを探索するためのポインタの論理的な単位です。インデックスは以下の形式で直列化されます。</p>
+
+<table summary="index format">
+<tr>
+<td class="label">名前</td>
+<td class="label">オフセット</td>
+<td class="label">データ長</td>
+<td class="label">機能</td>
+</tr>
+<tr>
+<td>ページID</td>
+<td class="number">0</td>
+<td class="number">可変</td>
+<td>参照先のページのID</td>
+</tr>
+<tr>
+<td>キーサイズ</td>
+<td class="number">可変</td>
+<td class="number">可変</td>
+<td>キーのサイズ</td>
+</tr>
+<tr>
+<td>キー</td>
+<td class="number">可変</td>
+<td class="number">可変</td>
+<td>キーのデータ</td>
+</tr>
+</table>
+
+<p>非リーフノードはインデックスの集合を格納するための物理的な単位です。非リーフノードは281474976710657からインクリメントして振られるID番号で識別されます。非リーフノードはID番号から281474976710657を引いた値を16進数の文字列とにした上で「#」を接頭させた文字列をキーとし、以下の値を持つレコードとしてハッシュデータベースに格納されます。インデックスは常に昇順に整列した状態で保持されます。</p>
+
+<table summary="non-leaf format">
+<tr>
+<td class="label">名前</td>
+<td class="label">オフセット</td>
+<td class="label">データ長</td>
+<td class="label">機能</td>
+</tr>
+<tr>
+<td>継承ID</td>
+<td class="number">0</td>
+<td class="number">可変</td>
+<td>最初の子ノードのID</td>
+</tr>
+<tr>
+<td>インデックスリスト</td>
+<td class="number">可変</td>
+<td class="number">可変</td>
+<td>ページ内のインデックスを直列化して連結したデータ</td>
+</tr>
+</table>
+
+<h3 id="fileformat_tcfdb">固定長データベースのファイルフォーマット</h3>
+
+<p>固定長データベースが管理するデータベースファイルの内容は、ヘッダ部とレコード部の2つに大別されます。ファイルに記録される数値はリトルエンディアンの固定長数値として記録されます。</p>
+
+<p>ヘッダ部はファイルの先頭から256バイトの固定長でとられ、以下の情報が記録されます。</p>
+
+<table summary="database header format">
+<tr>
+<td class="label">名前</td>
+<td class="label">オフセット</td>
+<td class="label">データ長</td>
+<td class="label">機能</td>
+</tr>
+<tr>
+<td>マジックナンバ</td>
+<td class="number">0</td>
+<td class="number">32</td>
+<td>データベースファイルであることの判別。「ToKyO CaBiNeT」で始まる</td>
+</tr>
+<tr>
+<td>データベースタイプ</td>
+<td class="number">32</td>
+<td class="number">1</td>
+<td>0x03固定</td>
+</tr>
+<tr>
+<td>追加フラグ</td>
+<td class="number">33</td>
+<td class="number">1</td>
+<td>開きっぱなし(1&lt;&lt;0)、致命的エラー(1&lt;&lt;1)の論理和</td>
+</tr>
+<tr>
+<td>レコード数</td>
+<td class="number">48</td>
+<td class="number">8</td>
+<td>格納しているレコードの数</td>
+</tr>
+<tr>
+<td>ファイルサイズ</td>
+<td class="number">56</td>
+<td class="number">8</td>
+<td>データベースファイルのサイズ</td>
+</tr>
+<tr>
+<td>レコード幅</td>
+<td class="number">64</td>
+<td class="number">8</td>
+<td>各レコードの値の幅</td>
+</tr>
+<tr>
+<td>制限サイズ</td>
+<td class="number">72</td>
+<td class="number">8</td>
+<td>データベースファイルの制限サイズ</td>
+</tr>
+<tr>
+<td>最小ID</td>
+<td class="number">80</td>
+<td class="number">8</td>
+<td>現在のレコードIDの最小値</td>
+</tr>
+<tr>
+<td>最大ID</td>
+<td class="number">88</td>
+<td class="number">8</td>
+<td>現在のレコードIDの最大値</td>
+</tr>
+<tr>
+<td>不透明領域</td>
+<td class="number">128</td>
+<td class="number">128</td>
+<td>ユーザが自由に使える領域</td>
+</tr>
+</table>
+
+<p>レコード部はヘッダ部の直後からファイルの末尾までを占め、各レコードの以下の情報を持つ要素が記録されます。値サイズに必要な領域は、レコード幅が255以下なら1バイト、65535以下なら2バイト、それを越えれば4バイトです。レコード長は値サイズに必要な領域とレコード幅を足したものです。各レコードの領域は、レコードIDから1を引いた値にレコード長を掛け、それに256を足した位置から始まります。</p>
+
+<table summary="record format">
+<tr>
+<td class="label">名前</td>
+<td class="label">オフセット</td>
+<td class="label">データ長</td>
+<td class="label">機能</td>
+</tr>
+<tr>
+<td>値のサイズ</td>
+<td class="number">0</td>
+<td class="number">可変</td>
+<td>値のサイズ</td>
+</tr>
+<tr>
+<td>値</td>
+<td class="number">可変</td>
+<td class="number">可変</td>
+<td>値のデータ</td>
+</tr>
+<tr>
+<td>パディング</td>
+<td class="number">可変</td>
+<td class="number">可変</td>
+<td>値サイズが0の時は、先頭バイトの真偽値でレコードの有無を示す</td>
+</tr>
+</table>
+
+<p>トランザクションログの命名規則やフォーマットはハッシュデータベースのものと同じです。</p>
+
+<h3 id="fileformat_note">注記</h3>
+
+<p>データベースファイルはスパースではないので、通常のファイルと同様に複製等の操作を行うことができます。またフォーマットも実行環境のバイトオーダに依存しないので、バイトオーダの異なる環境にデータベースファイルを移設してもそのままで利用できます。</p>
+
+<p>なるべくなら、ハッシュデータベースのファイルをネットワークで配布する際には、MIMEタイプを `<code>application/x-tokyocabinet-hash</code>' にしてください。ファイル名の接尾辞は `<code>.tch</code>' にしてください。B+木データベースのファイルをネットワークで配布する際には、MIMEタイプを `<code>application/x-tokyocabinet-btree</code>' にしてください。ファイル名の接尾辞は `<code>.tcb</code>' にしてください。固定長データベースのファイルをネットワークで配布する際には、MIMEタイプを `<code>application/x-tokyocabinet-fixed</code>' にしてください。ファイル名の接尾辞は `<code>.tcf</code>' にしてください。テーブルデータベースのファイルをネットワークで配布する際には、MIMEタイプを `<code>application/x-tokyocabinet-table</code>' にしてください。ファイル名の接尾辞は `<code>.tct</code>' にしてください。</p>
+
+<p>データベースファイルのマジックデータを `<code>file</code>' コマンドに識別させたい場合は、`<code>magic</code>' ファイルに以下の行を追記してください。</p>
+
+<pre># Tokyo Cabinet magic data
+0       string    ToKyO\ CaBiNeT\n   Tokyo Cabinet
+&gt;14     string    x                  \b (%s)
+&gt;32     byte      0                  \b, Hash
+!:mime  application/x-tokyocabinet-hash
+&gt;32     byte      1                  \b, B+ tree
+!:mime  application/x-tokyocabinet-btree
+&gt;32     byte      2                  \b, Fixed-length
+!:mime  application/x-tokyocabinet-fixed
+&gt;32     byte      3                  \b, Table
+!:mime  application/x-tokyocabinet-table
+&gt;33     byte      &amp;1                 \b, [open]
+&gt;33     byte      &amp;2                 \b, [fatal]
+&gt;34     byte      x                  \b, apow=%d
+&gt;35     byte      x                  \b, fpow=%d
+&gt;36     byte      &amp;1                 \b, [large]
+&gt;36     byte      &amp;2                 \b, [deflate]
+&gt;36     byte      &amp;4                 \b, [bzip]
+&gt;36     byte      &amp;8                 \b, [tcbs]
+&gt;36     byte      &amp;16                \b, [excodec]
+&gt;40     lequad    x                  \b, bnum=%lld
+&gt;48     lequad    x                  \b, rnum=%lld
+&gt;56     lequad    x                  \b, fsiz=%lld
+</pre>
+
+<hr />
+
+<h2 id="faq">よく聞かれる質問</h2>
+
+<dl>
+<dt>Q. : Tokyo CabinetはSQLをサポートしますか?</dt>
+<dd>A. : Tokyo CabinetはSQLをサポートしません。Tokyo CabinetはRDBMS(関係データベース管理システム)ではありません。組み込みのRDBMSを求めるなら、SQLiteなどを利用するとよいでしょう。</dd>
+<dt>Q. : Berkeley DBとどう違うのですか?</dt>
+<dd>A. : 時間効率と空間効率の双方でTokyo Cabinetが優っています。</dd>
+<dt>Q. : アプリケーションの良いサンプルコードはありますか?</dt>
+<dd>A. : 各APIのコマンドのソースコードを参考にしてください。`<code>tchmgr.c</code>' と `<code>tcbmgr.c</code>' と `<code>tcfmgr.c</code>' が最も簡潔でしょう。</dd>
+<dt>Q. : tchdbputkeep2とか、APIのシグネチャがわかりにくいんですけど、アンダースコア区切りとかCamelCaseとか使わないんですか?</dt>
+<dd>A. : 使いません。UNIX(POSIX)にも、creatとかsbrkとかdup2とかwait3とかwait4とかsigprocmaskとかstrncasecmpとかgethostbyname2とか、あなた好みでないものが多くあります。逆に私はUNIXのようにハードボイルドでユーザに媚びない名前が好きなのです。</dd>
+<dt>Q. : データベースが壊れたのですが、どうしてでしょうか?</dt>
+<dd>A. : 大抵の場合、あなたのアプリケーションがきちんとデータベースを閉じていないのが原因です。デーモンプロセスであろうが、CGIスクリプトであろうが、アプリケーションが終了する際には必ずデータベースを閉じなければなりません。なお、CGIのプロセスはSIGPIPEやSIGTERMによって殺されることがあることにも留意しましょう。</dd>
+<dt>Q. : データベースを壊れにくくするにはどうすればよいですか?</dt>
+<dd>A. : トランザクションを使ってください。ディスクやファイルシステムが壊れなければデータベースが壊れないようにすることができます。</dd>
+<dt>Q. : 壊れたデータベースを修復するにはどうすればよいですか?</dt>
+<dd>A. : データベースファイルをロックなしオプション(<code>HDBONOLCK</code>か<code>BDBONOLCK</code>)をつけて開いて、最適化機能(<code>tchdboptimize</code>か<code>tcbdboptimize</code>)を実行してください。コマンドラインで修復処理を行いたい場合、「<code>tchmgr optimize -nl casket</code>」もしくは「<code>tcbmgr optimize -nl casket</code>」を実行してください。</dd>
+<dt>Q. : 2GBを越えるサイズのファイルを扱おうとするとエラーになるのですが、どうしてですか?</dt>
+<dd>A. : 32ビットのファイルシステムでは、LFSなどの明示的な指定をしないと2GBを越えるサイズのファイルを作ることができません。32ビットOS上でXFSやFeiserFSなどの64ビットファイルシステムを利用する場合は2GBを越えるサイズのファイルを扱うことができますが、その際にはTokyo Cabinetを `<code>--enable-off64</code>' をつけた設定でビルドしておく必要があります。純粋な64ビット環境で利用する場合は特別な設定は必要ありません。なお、ulimitやquotaでファイルサイズの制限がかかっていないことも確認しておいてください。</dd>
+<dt>Q. : RubyやJavaの言語バインディングで、なぜエラーを例外で処理しないのですか?</dt>
+<dd>A. : 例外機構のない言語と共通のインターフェイスにするためです。例外処理を好む人は、独自のラッパーを書いてそれを使ってください。</dd>
+<dt>Q. : データベースファイルの名前の拡張子として「.hdb」「.bdb」などを推奨しないのはなぜですか?</dt>
+<dd>A. : 世の中にはTokyo Cabinet以外にもデータベースライブラリがたくさんあり、それらのハッシュデータベースやB+木データベースと区別がつかなくなるからです。代わりに「.tch」「.tcb」などを使ってください。</dd>
+<dt>Q. : QDBMはもうメンテナンスしないのですか?</dt>
+<dd>A. : メンテナンスは続けます。積極的な機能追加の予定はありませんが、もしバグが見つかれば対処します。</dd>
+<dt>Q. : Windowsで利用できませんか?</dt>
+<dd>A. : 残念ながらできません。今のところ対応予定もありません。</dd>
+<dt>Q. : ライセンスをBSDLかMITLに変えてくれませんか?</dt>
+<dd>A. : 嫌です。そうすることに特に利点を感じません。</dd>
+<dt>Q. : 「Tokyo Cabinet」の名前の由来はなんですか?</dt>
+<dd>A. : 作者が住んでいる街なので「tokyo」で、モノをしまうから「cabinet」です。略して「TC」と呼ぶのもよい考えです。「東京キャビネット」とか「とうきょうきゃびねっと」とかいう表記でも構いません。東京ディズニーランドや東京ラブストーリーや東京パフォーマンスドールとは一切関係ありません。識別子以外で「TokyoCabinet」とつなげて表記するのは推奨しません。</dd>
+<dt>Q. : あなたは千葉県とどういう関係なのですか?</dt>
+<dd>A. : 特に関係はありません。出身地は埼玉県です。落花生は好きです。</dd>
+</dl>
+
+<hr />
+
+<h2 id="license">ライセンス</h2>
+
+<p>Tokyo Cabinetはフリーソフトウェアです。あなたは、Free Software Foundationが公表したGNU Lesser General Public Licenseのバージョン2.1あるいはそれ以降の各バージョンの中からいずれかを選択し、そのバージョンが定める条項に従ってTokyo Cabinetを再頒布または変更することができます。</p>
+
+<p>Tokyo Cabinetは有用であると思われますが、頒布にあたっては、市場性及び特定目的適合性についての暗黙の保証を含めて、いかなる保証も行ないません。詳細についてはGNU Lesser General Public Licenseを読んでください。</p>
+
+<p>あなたは、Tokyo Cabinetと一緒にGNU Lesser General Public Licenseの写しを受け取っているはずです(`<code>COPYING</code>' ファイルを参照してください)。そうでない場合は、Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA へ連絡してください。</p>
+
+<p>Tokyo CabinetはFAL Labsが作成しました。作者と連絡をとるには、`<code>info@fallabs.com</code>' 宛に電子メールを送ってください。</p>
+
+<hr />
+
+</body>
+
+</html>
+
+<!-- END OF FILE -->
diff --git a/tcejdb/doc/tokyoproducts.pdf b/tcejdb/doc/tokyoproducts.pdf
new file mode 100644 (file)
index 0000000..597d784
Binary files /dev/null and b/tcejdb/doc/tokyoproducts.pdf differ
diff --git a/tcejdb/doc/tokyoproducts.ppt b/tcejdb/doc/tokyoproducts.ppt
new file mode 100644 (file)
index 0000000..333b282
Binary files /dev/null and b/tcejdb/doc/tokyoproducts.ppt differ
diff --git a/tcejdb/ejdb.c b/tcejdb/ejdb.c
new file mode 100644 (file)
index 0000000..a973728
--- /dev/null
@@ -0,0 +1,2816 @@
+
+#include <regex.h>
+
+#include "ejdb_private.h"
+#include "ejdbutl.h"
+
+/* private macros */
+#define JBLOCKMETHOD(JB_ejdb, JB_wr)                            \
+  ((JB_ejdb)->mmtx ? _ejdblockmethod((JB_ejdb), (JB_wr)) : true)
+#define JBUNLOCKMETHOD(JB_ejdb)                         \
+  ((JB_ejdb)->mmtx ? _ejdbunlockmethod(JB_ejdb) : true)
+
+#define JBCLOCKMETHOD(JB_col, JB_wr)                            \
+  ((JB_col)->mmtx ? _ejcollockmethod((JB_col), (JB_wr)) : true)
+#define JBCUNLOCKMETHOD(JB_col)                         \
+  ((JB_col)->mmtx ? _ejcollunlockmethod(JB_col) : true)
+
+#define JBISOPEN(JB_jb) ((JB_jb) && (JB_jb)->metadb && (JB_jb)->metadb->open) ? true : false
+
+#define JBISVALCOLNAME(JB_cname) ((JB_cname) && strlen(JB_cname) < JBMAXCOLNAMELEN && !index((JB_cname), '.'))
+
+#define JBENSUREOPENLOCK(JB_jb, JB_lock, JB_ret)  \
+                            assert(JB_jb); \
+                            if (!JBLOCKMETHOD((JB_jb), (JB_lock))) return JB_ret; \
+                            if (!JBISOPEN(JB_jb)) { \
+                                _ejdbsetecode((JB_jb), TCEINVALID, __FILE__, __LINE__, __func__); \
+                                JBUNLOCKMETHOD(JB_jb); \
+                                return JB_ret; \
+                            }
+
+/* private function prototypes */
+typedef union {
+    int64_t inum;
+    double dnum;
+} _ejdbnum;
+
+
+static void _ejdbsetecode(EJDB *jb, int ecode, const char *filename, int line, const char *func);
+static bool _ejdbsetmutex(EJDB *ejdb);
+static bool _ejdblockmethod(EJDB *ejdb, bool wr);
+static bool _ejdbunlockmethod(EJDB *ejdb);
+static bool _ejdbcolsetmutex(EJCOLL *coll);
+static bool _ejcollockmethod(EJCOLL *coll, bool wr);
+static bool _ejcollunlockmethod(EJCOLL *coll);
+static bool _bsonoidkey(bson* bs, bson_oid_t* oid);
+static char* _bsonitstrval(bson_iterator *it, int *vsz, TCLIST *tokens);
+static char* _bsonipathrowldr(TCLIST *tokens, const char *pkbuf, int pksz, const char *rowdata, int rowdatasz,
+        const char *ipath, int ipathsz, void *op, int *vsz);
+static char* _bsonfpathrowldr(TCLIST *tokens, const char *rowdata, int rowdatasz,
+        const char *fpath, int fpathsz, void *op, int *vsz);
+EJDB_INLINE bool _isvalidoidstr(const char* oid);
+static void _ejdbclear(EJDB *jb);
+static bool _createcoldb(const char* colname, EJDB *jb, EJCOLLOPTS *opts, TCTDB** res);
+static bool _addcoldb0(const char* colname, EJDB *jb, EJCOLLOPTS *opts, EJCOLL **res);
+static void _delcoldb(EJCOLL *cdb);
+static void _delqfdata(const EJQ *q, const EJQF* ejqf);
+static bool _updatebsonidx(EJCOLL *jcoll, const bson_oid_t *oid, const bson *bs, const void *obsdata, int obsdatasz);
+static bool _metasetopts(EJDB *jb, const char *colname, EJCOLLOPTS *opts);
+static bool _metagetopts(EJDB *jb, const char *colname, EJCOLLOPTS *opts);
+static bson* _metagetbson(EJDB *jb, const char *colname, int colnamesz, const char* mkey);
+static bson* _metagetbson2(EJCOLL *jcoll, const char* mkey);
+static bool _metasetbson(EJDB *jb, const char *colname, int colnamesz,
+        const char *mkey, bson* val, bool merge, bool mergeoverwrt);
+static bool _metasetbson2(EJCOLL *jcoll, const char *mkey, bson* val, bool merge, bool mergeoverwrt);
+static bson* _imetaidx(EJCOLL *jcoll, const char *ipath);
+static void _qrypreprocess(EJCOLL *jcoll, EJQ *ejq, EJQF **mqf);
+static TCMAP* _parseqobj(EJDB *jb, bson *qspec);
+static int _parse_qobj_impl(EJDB* jb, bson_iterator *it, TCMAP *qmap, TCLIST *pathStack, EJQF *pqf);
+static int _ejdbsoncmp(const TCLISTDATUM* d1, const TCLISTDATUM* d2, void *opaque);
+static bool _qrycondcheckstrand(const char *vbuf, const TCLIST *tokens);
+static bool _qrycondcheckstror(const char *vbuf, const TCLIST *tokens);
+static bool _qrybsvalmatch(const EJQF *qf, bson_iterator *it, bool expandarrays);
+static bool _qrybsmatch(const EJQF *qf, const void *bsbuf, int bsbufsz);
+static bool _qryormatch(EJCOLL *jcoll, EJQ* ejq, const void *pkbuf, int pkbufsz, const void *bsbuf, int bsbufsz);
+static bool _qryallcondsmatch(bool onlycount, int anum, EJCOLL *jcoll, const EJQF **qfs, int qfsz,
+        const void *pkbuf, int pkbufsz, void **bsbuf, int *bsbufsz);
+static void _qrydup(const EJQ *src, EJQ *target, uint32_t qflags);
+static void _qrydel(EJQ *q, bool freequery);
+static TCLIST* _qrysearch(EJCOLL *jcoll, const EJQ *q, uint32_t *count, int qflags, TCXSTR *log);
+EJDB_INLINE void _nufetch(_ejdbnum *nu, const char *sval, bson_type bt);
+EJDB_INLINE int _nucmp(_ejdbnum *nu, const char *sval, bson_type bt);
+EJDB_INLINE int _nucmp2(_ejdbnum *nu1, _ejdbnum *nu2, bson_type bt);
+static EJCOLL* _getcoll(EJDB *jb, const char* colname);
+
+EJDB_EXPORT const char* ejdberrmsg(int ecode) {
+    switch (ecode) {
+        case JDBEINVALIDCOLNAME: return "invalid collection name";
+        case JDBEINVALIDBSON: return "invalid bson object";
+        case JDBEQINVALIDQCONTROL: return "invalid query control field starting with '$'";
+        case JDBEQINOPNOTARRAY: return "$strand, $stror, $in, $nin, $bt keys requires not empty array value";
+        case JDBEMETANVALID: return "inconsistent database metadata";
+        case JDBEFPATHINVALID: return "invalid JSON field path value";
+        case JDBEQINVALIDQRX: return "invalid query regexp value";
+        case JDBEQRSSORTING: return "result set sorting error";
+        case JDBQINVALID: return "query generic error";
+        default: return tcerrmsg(ecode);
+    }
+}
+
+/* Get the last happened error code of a database object. */
+EJDB_EXPORT int ejdbecode(EJDB *jb) {
+    assert(jb && jb->metadb);
+    return tctdbecode(jb->metadb);
+}
+
+EJDB_EXPORT EJDB* ejdbnew(void) {
+    EJDB *jb;
+    TCMALLOC(jb, sizeof (*jb));
+    _ejdbclear(jb);
+    jb->metadb = tctdbnew();
+#ifdef _DEBUG
+    tchdbsetdbgfd(jb->metadb->hdb, fileno(stderr));
+#endif
+    if (!_ejdbsetmutex(jb)) {
+        tctdbdel(jb->metadb);
+        TCFREE(jb);
+        return NULL;
+    }
+    return jb;
+}
+
+EJDB_EXPORT void ejdbdel(EJDB *jb) {
+    assert(jb && jb->metadb);
+    if (JBISOPEN(jb)) ejdbclose(jb);
+    for (int i = 0; i < jb->cdbsnum; ++i) {
+        _delcoldb(jb->cdbs + i);
+    }
+    TCFREE(jb->cdbs);
+    jb->cdbsnum = 0;
+    if (jb->mmtx) {
+        pthread_rwlock_destroy(jb->mmtx);
+        TCFREE(jb->mmtx);
+    }
+    tctdbdel(jb->metadb);
+    TCFREE(jb);
+}
+
+EJDB_EXPORT bool ejdbclose(EJDB *jb) {
+    JBENSUREOPENLOCK(jb, true, false);
+    bool rv = true;
+    for (int i = 0; i < jb->cdbsnum; ++i) {
+        if (!tctdbclose(jb->cdbs[i].tdb)) {
+            rv = false;
+        }
+    }
+    if (!tctdbclose(jb->metadb)) {
+        rv = false;
+    }
+    JBUNLOCKMETHOD(jb);
+    return rv;
+}
+
+EJDB_EXPORT bool ejdbopen(EJDB *jb, const char *path, int mode) {
+    assert(jb && path);
+    assert(jb->metadb);
+    if (!JBLOCKMETHOD(jb, true)) return false;
+    if (JBISOPEN(jb)) {
+        _ejdbsetecode(jb, TCEINVALID, __FILE__, __LINE__, __func__);
+        JBUNLOCKMETHOD(jb);
+        return false;
+    }
+    bool rv = tctdbopen(jb->metadb, path, mode);
+    if (!rv) {
+        goto finish;
+    }
+    TCMALLOC(jb->cdbs, 1);
+    jb->cdbsnum = 0;
+    TCTDB *mdb = jb->metadb;
+    rv = tctdbiterinit(mdb);
+    if (!rv) {
+        goto finish;
+    }
+    char* colname = NULL;
+    for (int i = 0; i < mdb->hdb->rnum && (colname = tctdbiternext2(mdb)) != NULL; ++i) {
+        EJCOLL *cdb;
+        EJCOLLOPTS opts;
+        _metagetopts(jb, colname, &opts);
+        _addcoldb0(colname, jb, &opts, &cdb);
+        TCFREE(colname);
+    }
+finish:
+    JBUNLOCKMETHOD(jb);
+    return rv;
+}
+
+EJDB_EXPORT EJCOLL* ejdbgetcoll(EJDB *jb, const char* colname) {
+    assert(colname);
+    EJCOLL *coll = NULL;
+    JBENSUREOPENLOCK(jb, false, NULL);
+    coll = _getcoll(jb, colname);
+    JBUNLOCKMETHOD(jb);
+    return coll;
+}
+
+EJDB_EXPORT EJCOLL* ejdbcreatecoll(EJDB *jb, const char* colname, EJCOLLOPTS *opts) {
+    assert(colname);
+    EJCOLL *coll = ejdbgetcoll(jb, colname);
+    if (coll) {
+        return coll;
+    }
+    if (!JBISVALCOLNAME(colname)) {
+        _ejdbsetecode(jb, JDBEINVALIDCOLNAME, __FILE__, __LINE__, __func__);
+        return NULL;
+    }
+    JBENSUREOPENLOCK(jb, true, NULL);
+    TCTDB* meta = jb->metadb;
+    char *row = tcsprintf("name\t%s", colname);
+    if (!tctdbput3(meta, colname, row)) {
+        goto finish;
+    }
+    if (!_addcoldb0(colname, jb, opts, &coll)) {
+        tctdbout2(meta, colname); //cleaning
+        goto finish;
+    }
+    _metasetopts(jb, colname, opts);
+finish:
+    JBUNLOCKMETHOD(jb);
+    if (row) {
+        TCFREE(row);
+    }
+    return coll;
+}
+
+EJDB_EXPORT bool ejdbrmcoll(EJDB *jb, const char *colname, bool unlinkfile) {
+    assert(colname);
+    JBENSUREOPENLOCK(jb, true, false);
+    bool rv = true;
+    EJCOLL *coll = _getcoll(jb, colname);
+    if (!coll) {
+        goto finish;
+    }
+    EJCOLL *cdbs = jb->cdbs;
+    for (int i = 0; i < jb->cdbsnum; ++i) {
+        coll = jb->cdbs + i;
+        if (!strcmp(colname, coll->cname)) {
+            if (!JBCLOCKMETHOD(coll, true)) return false;
+            jb->cdbsnum--;
+            memmove(cdbs + i, cdbs + i + 1, sizeof (*cdbs) * (jb->cdbsnum - i));
+            tctdbout2(jb->metadb, colname);
+            tctdbvanish(coll->tdb);
+            TCLIST *paths = tclistnew2(10);
+            tclistpush2(paths, coll->tdb->hdb->path);
+            for (int j = 0; j < coll->tdb->inum; ++j) {
+                TDBIDX *idx = coll->tdb->idxs + j;
+                const char *ipath = tcbdbpath(idx->db);
+                if (ipath) {
+                    tclistpush2(paths, ipath);
+                }
+            }
+            tctdbclose(coll->tdb);
+            if (unlinkfile) {
+                for (int j = 0; j < TCLISTNUM(paths); ++j) {
+                    unlink(tclistval2(paths, j));
+                }
+            }
+            tclistdel(paths);
+            JBCUNLOCKMETHOD(coll);
+            _delcoldb(coll);
+            break;
+        }
+    }
+finish:
+    JBUNLOCKMETHOD(jb);
+    return rv;
+}
+
+/* Save/Update BSON into 'coll' */
+EJDB_EXPORT bool ejdbsavebson(EJCOLL *jcoll, bson *bs, bson_oid_t *oid) {
+    assert(jcoll && bs);
+    if (!JBISOPEN(jcoll->jb)) {
+        _ejdbsetecode(jcoll->jb, TCEINVALID, __FILE__, __LINE__, __func__);
+        return false;
+    }
+    if (!JBCLOCKMETHOD(jcoll, true)) return false;
+    bool rv = false;
+    bson *nbs = NULL;
+    if (!_bsonoidkey(bs, oid)) { //no _id regenerate new BSON with _id primary key
+        bson_oid_gen(oid);
+        nbs = bson_create();
+        bson_init_size(nbs, bson_size(bs) + (strlen(JDBIDKEYNAME) + 1/*key*/ + 1/*type*/ + sizeof (*oid)));
+        bson_append_oid(nbs, JDBIDKEYNAME, oid);
+        bson_ensure_space(nbs, bson_size(bs) - 4);
+        bson_append(nbs, bson_data(bs) + 4, bson_size(bs) - (4 + 1/*BSON_EOO*/));
+        bson_finish(nbs);
+        bs = nbs;
+    }
+    TCTDB *tdb = jcoll->tdb;
+    TCMAP *rowm = (tdb->hdb->rnum > 0) ? tctdbget(tdb, oid, sizeof (*oid)) : NULL;
+    char *obsdata = NULL; //Old bson
+    int obsdatasz = 0;
+    if (rowm) { //Save the copy of old bson data
+        const void* tmp = tcmapget(rowm, JDBCOLBSON, JDBCOLBSONL, &obsdatasz);
+        if (tmp && obsdatasz > 0) {
+            TCMALLOC(obsdata, obsdatasz);
+            memmove(obsdata, tmp, obsdatasz);
+        }
+    } else {
+        rowm = tcmapnew2(128);
+    }
+    tcmapput(rowm, JDBCOLBSON, JDBCOLBSONL, bson_data(bs), bson_size(bs));
+    if (!tctdbput(tdb, oid, sizeof (*oid), rowm)) {
+        goto finish;
+    }
+    //Update indexes
+    rv = _updatebsonidx(jcoll, oid, bs, obsdata, obsdatasz);
+finish:
+    JBCUNLOCKMETHOD(jcoll);
+    if (rowm) {
+        tcmapdel(rowm);
+    }
+    if (obsdata) {
+        TCFREE(obsdata);
+    }
+    if (nbs) {
+        bson_del(nbs);
+    }
+    return rv;
+}
+
+EJDB_EXPORT bool ejdbrmbson(EJCOLL* jcoll, bson_oid_t* oid) {
+    assert(jcoll && oid);
+    if (!JBISOPEN(jcoll->jb)) {
+        _ejdbsetecode(jcoll->jb, TCEINVALID, __FILE__, __LINE__, __func__);
+        return false;
+    }
+    JBCLOCKMETHOD(jcoll, true);
+    bool rv = true;
+    const void *olddata;
+    int olddatasz = 0;
+    TCMAP *rmap = tctdbget(jcoll->tdb, oid, sizeof (*oid));
+    if (!rmap) {
+        goto finish;
+    }
+    olddata = tcmapget3(rmap, JDBCOLBSON, JDBCOLBSONL, &olddatasz);
+    if (!_updatebsonidx(jcoll, oid, NULL, olddata, olddatasz)) {
+        rv = false;
+    }
+    if (!tctdbout(jcoll->tdb, oid, sizeof (*oid))) {
+        rv = false;
+    }
+finish:
+    JBCUNLOCKMETHOD(jcoll);
+    if (rmap) {
+        tcmapdel(rmap);
+    }
+    return rv;
+}
+
+EJDB_EXPORT bson* ejdbloadbson(EJCOLL *jcoll, const bson_oid_t *oid) {
+    assert(jcoll && oid);
+    if (!JBISOPEN(jcoll->jb)) {
+        _ejdbsetecode(jcoll->jb, TCEINVALID, __FILE__, __LINE__, __func__);
+        return false;
+    }
+    JBCLOCKMETHOD(jcoll, false);
+    bson *ret = NULL;
+    int datasz;
+    void *cdata = tchdbget(jcoll->tdb->hdb, oid, sizeof (*oid), &datasz);
+    if (!cdata) {
+        goto finish;
+    }
+    void *bsdata = tcmaploadone(cdata, datasz, JDBCOLBSON, JDBCOLBSONL, &datasz);
+    if (!bsdata) {
+        goto finish;
+    }
+    if (datasz <= 4) {
+        TCFREE(bsdata);
+        goto finish;
+    }
+    ret = bson_create();
+    bson_init_finished_data(ret, bsdata);
+finish:
+    JBCUNLOCKMETHOD(jcoll);
+    if (cdata) {
+        TCFREE(cdata);
+    }
+    return ret;
+}
+
+EJDB_EXPORT EJQ* ejdbcreatequery(EJDB *jb, bson *qobj, bson *orqobjs, int orqobjsnum, bson *hints) {
+    EJQ *q;
+    TCCALLOC(q, 1, sizeof (*q));
+    if (qobj) {
+        q->qobjmap = _parseqobj(jb, qobj);
+        if (!q->qobjmap) {
+            goto error;
+        }
+    }
+    if (orqobjs && orqobjsnum) {
+        q->orqobjsnum = orqobjsnum;
+        TCMALLOC(q->orqobjs, sizeof (*(q->orqobjs)) * q->orqobjsnum);
+        for (int i = 0; i < orqobjsnum; ++i) {
+            bson *oqb = (orqobjs + i);
+            assert(oqb);
+            EJQ *oq = ejdbcreatequery(jb, oqb, NULL, 0, NULL);
+            if (oq == NULL) {
+                goto error;
+            }
+            q->orqobjs[i] = *oq; //copy struct
+            TCFREE(oq);
+        }
+    }
+    if (hints) {
+        q->hints = bson_create();
+        if (bson_copy(q->hints, hints)) {
+            goto error;
+        }
+    }
+    return q;
+error:
+    ejdbquerydel(q);
+    return NULL;
+}
+
+EJDB_EXPORT void ejdbquerydel(EJQ *q) {
+    _qrydel(q, true);
+}
+
+/** Set index */
+EJDB_EXPORT bool ejdbsetindex(EJCOLL *jcoll, const char *fpath, int flags) {
+    assert(jcoll && fpath);
+    bool rv = true;
+    bson *imeta = NULL;
+    bson_iterator it;
+    int tcitype = 0; //TCDB index type
+    int oldiflags = 0; //Old index flags stored in meta
+    bool ibld = (flags & JDIDXREBLD);
+    if (ibld) {
+        flags &= ~JDIDXREBLD;
+    }
+    bool idrop = (flags & JDIDXDROP);
+    if (idrop) {
+        flags &= ~JDIDXDROP;
+    }
+    bool idropall = (flags & JDIDXDROPALL);
+    if (idropall) {
+        idrop = true;
+        flags &= ~JDIDXDROPALL;
+    }
+    bool iop = (flags & JDIDXOP);
+    if (iop) {
+        flags &= ~JDIDXOP;
+    }
+
+    char ipath[BSON_MAX_FPATH_LEN + 2]; //add 2 bytes for one char prefix and '\0'term
+    char ikey[BSON_MAX_FPATH_LEN + 2]; //add 2 bytes for one char prefix and '\0'term
+    int fpathlen = strlen(fpath);
+    if (fpathlen > BSON_MAX_FPATH_LEN) {
+        _ejdbsetecode(jcoll->jb, JDBEFPATHINVALID, __FILE__, __LINE__, __func__);
+        rv = false;
+        goto finish;
+    }
+    memmove(ikey + 1, fpath, fpathlen + 1);
+    ikey[0] = 'i';
+    memmove(ipath + 1, fpath, fpathlen + 1);
+    ipath[0] = 's'; //string index type
+
+    JBENSUREOPENLOCK(jcoll->jb, true, false);
+    imeta = _imetaidx(jcoll, fpath);
+    if (!imeta) {
+        if (idrop) { //Cannot drop/optimize not existent index;
+            JBUNLOCKMETHOD(jcoll->jb);
+            goto finish;
+        }
+        if (iop) {
+            iop = false; //New index will be created
+        }
+        imeta = bson_create();
+        bson_init(imeta);
+        bson_append_string(imeta, "ipath", fpath);
+        bson_append_int(imeta, "iflags", flags);
+        bson_finish(imeta);
+        rv = _metasetbson2(jcoll, ikey, imeta, false, false);
+        if (!rv) {
+            JBUNLOCKMETHOD(jcoll->jb);
+            goto finish;
+        }
+    } else {
+        if (bson_find(&it, imeta, "iflags") != BSON_EOO) {
+            oldiflags = bson_iterator_int(&it);
+        }
+        if (!idrop && oldiflags != flags) { //Update index meta
+            bson imetadelta;
+            bson_init(&imetadelta);
+            bson_append_int(&imetadelta, "iflags", flags);
+            bson_finish(&imetadelta);
+            rv = _metasetbson2(jcoll, ikey, &imetadelta, true, true);
+            bson_destroy(&imetadelta);
+            if (!rv) {
+                JBUNLOCKMETHOD(jcoll->jb);
+                goto finish;
+            }
+        }
+    }
+    JBUNLOCKMETHOD(jcoll->jb);
+
+    if (idrop) {
+        tcitype = TDBITVOID;
+        if (idropall && oldiflags) {
+            flags = oldiflags; //Drop index only for existing types
+        }
+    } else if (iop) {
+        tcitype = TDBITOPT;
+        if (oldiflags) {
+            flags = oldiflags; //Optimize index for all existing types
+        }
+    }
+
+    if (!JBCLOCKMETHOD(jcoll, true)) {
+        rv = false;
+        goto finish;
+    }
+    if (tcitype) {
+        if (flags & JDIDXSTR) {
+            ipath[0] = 's';
+            rv = tctdbsetindexrldr(jcoll->tdb, ipath, tcitype, _bsonipathrowldr, NULL);
+        }
+        if (rv && (flags & JDIDXNUM)) {
+            ipath[0] = 'n';
+            rv = tctdbsetindexrldr(jcoll->tdb, ipath, tcitype, _bsonipathrowldr, NULL);
+        }
+        if (rv && (flags & JDIDXARR)) {
+            ipath[0] = 'a';
+            rv = tctdbsetindexrldr(jcoll->tdb, ipath, tcitype, _bsonipathrowldr, NULL);
+        }
+        if (idrop) { //Update index meta on drop
+            oldiflags &= ~flags;
+            if (oldiflags) { //Index dropped only for some types
+                bson imetadelta;
+                bson_init(&imetadelta);
+                bson_append_int(&imetadelta, "iflags", oldiflags);
+                bson_finish(&imetadelta);
+                rv = _metasetbson2(jcoll, ikey, &imetadelta, true, true);
+                bson_destroy(&imetadelta);
+            } else { //Index dropped completely
+                rv = _metasetbson2(jcoll, ikey, NULL, false, false);
+            }
+        }
+    } else {
+        if ((flags & JDIDXSTR) && (ibld || !(oldiflags & JDIDXSTR))) {
+            ipath[0] = 's';
+            rv = tctdbsetindexrldr(jcoll->tdb, ipath, TDBITLEXICAL, _bsonipathrowldr, NULL);
+        }
+        if (rv && (flags & JDIDXNUM) && (ibld || !(oldiflags & JDIDXNUM))) {
+            ipath[0] = 'n';
+            rv = tctdbsetindexrldr(jcoll->tdb, ipath, TDBITDECIMAL, _bsonipathrowldr, NULL);
+        }
+        if (rv && (flags & JDIDXARR) && (ibld || !(oldiflags & JDIDXARR))) {
+            ipath[0] = 'a';
+            rv = tctdbsetindexrldr(jcoll->tdb, ipath, TDBITTOKEN, _bsonipathrowldr, NULL);
+        }
+    }
+    JBCUNLOCKMETHOD(jcoll);
+finish:
+    if (imeta) {
+        bson_del(imeta);
+    }
+    return rv;
+}
+
+EJDB_EXPORT TCLIST* ejdbqrysearch(EJCOLL *jcoll, const EJQ *q, uint32_t *count, int qflags, TCXSTR *log) {
+    assert(jcoll && q && q->qobjmap);
+    if (!JBISOPEN(jcoll->jb)) {
+        _ejdbsetecode(jcoll->jb, TCEINVALID, __FILE__, __LINE__, __func__);
+        return NULL;
+    }
+    JBCLOCKMETHOD(jcoll, false);
+    TCLIST *res;
+    res = _qrysearch(jcoll, q, count, qflags, log);
+    JBCUNLOCKMETHOD(jcoll);
+    return res;
+}
+
+EJDB_EXPORT bool ejdbsyncoll(EJCOLL *jcoll) {
+    assert(jcoll);
+    if (!JBISOPEN(jcoll->jb)) {
+        _ejdbsetecode(jcoll->jb, TCEINVALID, __FILE__, __LINE__, __func__);
+        return false;
+    }
+    bool rv = false;
+    if (!JBCLOCKMETHOD(jcoll, true)) return false;
+    rv = tctdbsync(jcoll->tdb);
+    JBCUNLOCKMETHOD(jcoll);
+    return rv;
+}
+
+EJDB_EXPORT bool ejdbtranbegin(EJCOLL *jcoll) {
+    assert(jcoll);
+    if (!JBISOPEN(jcoll->jb)) {
+        _ejdbsetecode(jcoll->jb, TCEINVALID, __FILE__, __LINE__, __func__);
+        return false;
+    }
+    for (double wsec = 1.0 / sysconf(_SC_CLK_TCK); true; wsec *= 2) {
+        if (!JBCLOCKMETHOD(jcoll, true)) return false;
+        if (!jcoll->tdb->open || !jcoll->tdb->wmode) {
+            _ejdbsetecode(jcoll->jb, TCEINVALID, __FILE__, __LINE__, __func__);
+            JBCUNLOCKMETHOD(jcoll);
+            return false;
+        }
+        if (!jcoll->tdb->tran) break;
+        JBCUNLOCKMETHOD(jcoll);
+        if (wsec > 1.0) wsec = 1.0;
+        tcsleep(wsec);
+    }
+    if (!tctdbtranbeginimpl(jcoll->tdb)) {
+        JBCUNLOCKMETHOD(jcoll);
+        return false;
+    }
+    jcoll->tdb->tran = true;
+    JBCUNLOCKMETHOD(jcoll);
+    return true;
+}
+
+EJDB_EXPORT bool ejdbtrancommit(EJCOLL *jcoll) {
+    assert(jcoll);
+    if (!JBISOPEN(jcoll->jb)) {
+        _ejdbsetecode(jcoll->jb, TCEINVALID, __FILE__, __LINE__, __func__);
+        return false;
+    }
+    if (!JBCLOCKMETHOD(jcoll, true)) return false;
+    if (!jcoll->tdb->open || !jcoll->tdb->wmode || !jcoll->tdb->tran) {
+        _ejdbsetecode(jcoll->jb, TCEINVALID, __FILE__, __LINE__, __func__);
+        JBCUNLOCKMETHOD(jcoll);
+        return false;
+    }
+    jcoll->tdb->tran = false;
+    bool err = false;
+    if (!tctdbtrancommitimpl(jcoll->tdb)) err = true;
+    JBCUNLOCKMETHOD(jcoll);
+    return !err;
+}
+
+EJDB_EXPORT bool ejdbtranabort(EJCOLL *jcoll) {
+    assert(jcoll);
+    if (!JBISOPEN(jcoll->jb)) {
+        _ejdbsetecode(jcoll->jb, TCEINVALID, __FILE__, __LINE__, __func__);
+        return false;
+    }
+    if (!JBCLOCKMETHOD(jcoll, true)) return false;
+    if (!jcoll->tdb->open || !jcoll->tdb->wmode || !jcoll->tdb->tran) {
+        _ejdbsetecode(jcoll->jb, TCEINVALID, __FILE__, __LINE__, __func__);
+        JBCUNLOCKMETHOD(jcoll);
+        return false;
+    }
+    jcoll->tdb->tran = false;
+    bool err = false;
+    if (!tctdbtranabortimpl(jcoll->tdb)) err = true;
+    JBCUNLOCKMETHOD(jcoll);
+    return !err;
+}
+
+/*************************************************************************************************
+ * private features
+ *************************************************************************************************/
+
+/* Set the error code of a table database object. */
+static void _ejdbsetecode(EJDB *jb, int ecode, const char *filename, int line, const char *func) {
+    assert(jb && filename && line >= 1 && func);
+    tctdbsetecode(jb->metadb, ecode, filename, line, func);
+}
+
+static EJCOLL* _getcoll(EJDB *jb, const char* colname) {
+    assert(colname);
+    EJCOLL *coll = NULL;
+    //check if collection exists
+    for (int i = 0; i < jb->cdbsnum; ++i) {
+        coll = jb->cdbs + i;
+        assert(coll);
+        if (!strcmp(colname, coll->cname)) {
+            break;
+        } else {
+            coll = NULL;
+        }
+    }
+    return coll;
+}
+
+/* Set mutual exclusion control of a table database object for threading. */
+static bool _ejdbsetmutex(EJDB *ejdb) {
+    assert(ejdb);
+    if (!TCUSEPTHREAD) return true;
+    if (ejdb->mmtx || JBISOPEN(ejdb)) {
+        _ejdbsetecode(ejdb, TCEINVALID, __FILE__, __LINE__, __func__);
+        return false;
+    }
+    TCMALLOC(ejdb->mmtx, sizeof (pthread_rwlock_t));
+    bool err = false;
+    if (pthread_rwlock_init(ejdb->mmtx, NULL) != 0) err = true;
+    if (err) {
+        TCFREE(ejdb->mmtx);
+        ejdb->mmtx = NULL;
+        return false;
+    }
+    return true;
+}
+
+/* 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 _ejdblockmethod(EJDB *ejdb, bool wr) {
+    assert(ejdb);
+    if (wr ? pthread_rwlock_wrlock(ejdb->mmtx) != 0 : pthread_rwlock_rdlock(ejdb->mmtx) != 0) {
+        _ejdbsetecode(ejdb, 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 _ejdbunlockmethod(EJDB *ejdb) {
+    assert(ejdb);
+    if (pthread_rwlock_unlock(ejdb->mmtx) != 0) {
+        _ejdbsetecode(ejdb, TCETHREAD, __FILE__, __LINE__, __func__);
+        return false;
+    }
+    TCTESTYIELD();
+    return true;
+}
+
+static bool _ejdbcolsetmutex(EJCOLL *coll) {
+    assert(coll && coll->jb);
+    if (!TCUSEPTHREAD) return true;
+    if (coll->mmtx) {
+        _ejdbsetecode(coll->jb, TCEINVALID, __FILE__, __LINE__, __func__);
+        return false;
+    }
+    TCMALLOC(coll->mmtx, sizeof (pthread_rwlock_t));
+    bool err = false;
+    if (pthread_rwlock_init(coll->mmtx, NULL) != 0) err = true;
+    if (err) {
+        TCFREE(coll->mmtx);
+        coll->mmtx = NULL;
+        return false;
+    }
+    return true;
+}
+
+static bool _ejcollockmethod(EJCOLL *coll, bool wr) {
+    assert(coll && coll->jb);
+    if (wr ? pthread_rwlock_wrlock(coll->mmtx) != 0 : pthread_rwlock_rdlock(coll->mmtx) != 0) {
+        _ejdbsetecode(coll->jb, TCETHREAD, __FILE__, __LINE__, __func__);
+        return false;
+    }
+    TCTESTYIELD();
+    return (coll->tdb && coll->tdb->open);
+}
+
+static bool _ejcollunlockmethod(EJCOLL *coll) {
+    assert(coll && coll->jb);
+    if (pthread_rwlock_unlock(coll->mmtx) != 0) {
+        _ejdbsetecode(coll->jb, TCETHREAD, __FILE__, __LINE__, __func__);
+        return false;
+    }
+    TCTESTYIELD();
+    return true;
+}
+
+static void _qrydel(EJQ *q, bool freequery) {
+    if (!q) {
+        return;
+    }
+    const EJQF *qf = NULL;
+    if (q->qobjmap) {
+        int ksz;
+        const char* kbuf;
+        tcmapiterinit(q->qobjmap);
+        while ((kbuf = tcmapiternext(q->qobjmap, &ksz)) != NULL) {
+            int vsz;
+            qf = tcmapiterval(kbuf, &vsz);
+            assert(qf);
+            assert(vsz == sizeof (*qf));
+            _delqfdata(q, qf);
+        }
+        tcmapdel(q->qobjmap);
+        q->qobjmap = NULL;
+    }
+    if (q->orqobjsnum) {
+        for (int i = 0; i < q->orqobjsnum; ++i) {
+            _qrydel(q->orqobjs + i, false);
+        }
+        TCFREE(q->orqobjs);
+        q->orqobjsnum = 0;
+        q->orqobjs = NULL;
+    }
+    if (q->hints) {
+        bson_del(q->hints);
+        q->hints = NULL;
+    }
+    if (freequery) {
+        TCFREE(q);
+    }
+}
+
+static bool _qrybsvalmatch(const EJQF *qf, bson_iterator *it, bool expandarrays) {
+    if (qf->tcop == TDBQTRUE) {
+        return (true == !qf->negate);
+    }
+    bool rv = false;
+    bson_type bt = bson_iterator_type(it);
+    const char *expr = qf->expr;
+    int exprsz = qf->exprsz;
+
+    if (bt == BSON_ARRAY && expandarrays) { //Iterate over array
+        bson_iterator sit;
+        bson_iterator_subiterator(it, &sit);
+        while ((bt = bson_iterator_next(&sit)) != BSON_EOO) {
+            if (_qrybsvalmatch(qf, &sit, false)) {
+                return true;
+            }
+        }
+        return (false == !qf->negate);
+    }
+    switch (qf->tcop) {
+        case TDBQCSTREQ:
+        {
+            int fvalsz = (bt == BSON_STRING) ? bson_iterator_string_len(it) : 0;
+            const char *fval = (bt == BSON_STRING) ? bson_iterator_string(it) : "";
+            rv = (exprsz == fvalsz - 1) && !memcmp(expr, fval, exprsz);
+            break;
+        }
+        case TDBQCSTRINC:
+        {
+            int fvalsz = (bt == BSON_STRING) ? bson_iterator_string_len(it) : 0;
+            const char *fval = (bt == BSON_STRING) ? bson_iterator_string(it) : "";
+            rv = (exprsz < fvalsz) && strstr(fval, expr);
+            break;
+        }
+        case TDBQCSTRBW:
+        {
+            const char *fval = bson_iterator_string(it);
+            rv = tcstrfwm(fval, expr);
+            break;
+        }
+        case TDBQCSTREW:
+        {
+            const char *fval = bson_iterator_string(it);
+            rv = tcstrbwm(fval, expr);
+            break;
+        }
+        case TDBQCSTRAND:
+        {
+            TCLIST *tokens = qf->exprlist;
+            assert(tokens);
+            const char *fval = bson_iterator_string(it);
+            rv = _qrycondcheckstrand(fval, tokens);
+            break;
+        }
+        case TDBQCSTROR:
+        {
+            TCLIST *tokens = qf->exprlist;
+            assert(tokens);
+            const char *fval = bson_iterator_string(it);
+            rv = _qrycondcheckstror(fval, tokens);
+            break;
+        }
+        case TDBQCSTROREQ:
+        {
+            int fvalsz = (bt == BSON_STRING) ? bson_iterator_string_len(it) : 0;
+            TCLIST *tokens = qf->exprlist;
+            assert(tokens);
+            const char *fval = (bt == BSON_STRING) ? bson_iterator_string(it) : "";
+            for (int i = 0; i < TCLISTNUM(tokens); ++i) {
+                const char* token = TCLISTVALPTR(tokens, i);
+                int tokensz = TCLISTVALSIZ(tokens, i);
+                if (TCLISTVALSIZ(tokens, i) == (fvalsz - 1) && !strncmp(token, fval, tokensz)) {
+                    rv = true;
+                    break;
+                }
+            }
+            break;
+        }
+        case TDBQCSTRRX:
+        {
+            const char *fval = bson_iterator_string(it);
+            rv = qf->regex && (regexec(qf->regex, fval, 0, NULL, 0) == 0);
+            break;
+        }
+        case TDBQCNUMEQ:
+        {
+            if (bt == BSON_DOUBLE) {
+                rv = (qf->exprdblval == bson_iterator_double_raw(it));
+            } else if (bt == BSON_INT || bt == BSON_LONG) {
+                rv = (qf->exprlongval == bson_iterator_long(it));
+            } else {
+                rv = false;
+            }
+            break;
+        }
+        case TDBQCNUMGT:
+        {
+            if (bt == BSON_DOUBLE) {
+                rv = (qf->exprdblval < bson_iterator_double_raw(it));
+            } else if (bt == BSON_INT || bt == BSON_LONG) {
+                rv = (qf->exprlongval < bson_iterator_long(it));
+            } else {
+                rv = false;
+            }
+            break;
+        }
+        case TDBQCNUMGE:
+        {
+            if (bt == BSON_DOUBLE) {
+                rv = (qf->exprdblval <= bson_iterator_double_raw(it));
+            } else if (bt == BSON_INT || bt == BSON_LONG) {
+                rv = (qf->exprlongval <= bson_iterator_long(it));
+            } else {
+                rv = false;
+            }
+            break;
+        }
+        case TDBQCNUMLT:
+        {
+            if (bt == BSON_DOUBLE) {
+                rv = (qf->exprdblval > bson_iterator_double_raw(it));
+            } else if (bt == BSON_INT || bt == BSON_LONG) {
+                rv = (qf->exprlongval > bson_iterator_long(it));
+            } else {
+                rv = false;
+            }
+            break;
+        }
+        case TDBQCNUMLE:
+        {
+            if (bt == BSON_DOUBLE) {
+                rv = (qf->exprdblval >= bson_iterator_double_raw(it));
+            } else if (bt == BSON_INT || bt == BSON_LONG) {
+                rv = (qf->exprlongval >= bson_iterator_long(it));
+            } else {
+                rv = false;
+            }
+            break;
+        }
+        case TDBQCNUMBT:
+        {
+            assert(qf->ftype == BSON_ARRAY);
+            TCLIST *tokens = qf->exprlist;
+            assert(tokens);
+            assert(TCLISTNUM(tokens) == 2);
+            if (bson_iterator_type(it) == BSON_DOUBLE) {
+                double v1 = tctdbatof2(tclistval2(tokens, 0));
+                double v2 = tctdbatof2(tclistval2(tokens, 1));
+                double val = bson_iterator_double(it);
+                rv = (v2 > v1) ? (v2 >= val && v1 <= val) : (v2 <= val && v1 >= val);
+            } else {
+                int64_t v1 = tctdbatoi(tclistval2(tokens, 0));
+                int64_t v2 = tctdbatoi(tclistval2(tokens, 1));
+                int64_t val = bson_iterator_long(it);
+                rv = (v2 > v1) ? (v2 >= val && v1 <= val) : (v2 <= val && v1 >= val);
+            }
+            break;
+        }
+        case TDBQCNUMOREQ:
+        {
+            TCLIST *tokens = qf->exprlist;
+            assert(tokens);
+            if (bt == BSON_DOUBLE) {
+                double nval = bson_iterator_double_raw(it);
+                for (int i = 0; i < TCLISTNUM(tokens); ++i) {
+                    if (tctdbatof2(TCLISTVALPTR(tokens, i)) == nval) {
+                        rv = true;
+                        break;
+                    }
+                }
+            } else if (bt == BSON_INT || bt == BSON_LONG) {
+                int64_t nval = bson_iterator_long(it);
+                for (int i = 0; i < TCLISTNUM(tokens); ++i) {
+                    if (tctdbatoi(TCLISTVALPTR(tokens, i)) == nval) {
+                        rv = true;
+                        break;
+                    }
+                }
+            }
+            break;
+        }
+    }
+    return (rv == !qf->negate);
+}
+
+static bool _qrybsmatch(const EJQF *qf, const void *bsbuf, int bsbufsz) {
+    if (qf->tcop == TDBQTRUE) {
+        return !qf->negate;
+    }
+    bson_iterator it;
+    bson_iterator_from_buffer(&it, bsbuf);
+    bson_type bt = bson_find_fieldpath_value2(qf->fpath, qf->fpathsz, &it);
+    if (bt == BSON_EOO || bt == BSON_UNDEFINED || bt == BSON_NULL) {
+        return qf->negate; //Field missing
+    } else if (qf->tcop == TDBQCEXIST) {
+        return !qf->negate;
+    }
+    return _qrybsvalmatch(qf, &it, true);
+}
+
+static bool _qryormatch(EJCOLL *jcoll,
+        EJQ* ejq,
+        const void *pkbuf, int pkbufsz,
+        const void *bsbuf, int bsbufsz) {
+    if (ejq->orqobjsnum <= 0 || !ejq->orqobjs) {
+        return true;
+    }
+    bool rv = false;
+    void *nbsbuf = NULL;
+    const void *kbuf = NULL;
+    int kbufsz;
+    if (bsbuf == NULL) {
+        int cbufsz;
+        int lbsbufsz;
+        char *cbuf = tchdbget(jcoll->tdb->hdb, pkbuf, pkbufsz, &cbufsz);
+        if (!cbuf) {
+            return false;
+        }
+        nbsbuf = tcmaploadone(cbuf, cbufsz, JDBCOLBSON, JDBCOLBSONL, &lbsbufsz); //BSON buffer
+        if (!nbsbuf) {
+            TCFREE(cbuf);
+            return false;
+        }
+        bsbufsz = lbsbufsz;
+        bsbuf = nbsbuf;
+    }
+    if (ejq->lastmatchedorqf && _qrybsmatch(ejq->lastmatchedorqf, bsbuf, bsbufsz)) {
+        rv = true;
+        goto finish;
+    }
+    for (int i = 0; i < ejq->orqobjsnum; ++i) {
+        const EJQ *oq = (ejq->orqobjs + i);
+        const EJQF *qf;
+        int qfsz;
+        TCMAP *qobjmap = oq->qobjmap;
+        assert(qobjmap);
+        tcmapiterinit(qobjmap);
+        while ((kbuf = tcmapiternext(qobjmap, &kbufsz)) != NULL) {
+            qf = tcmapiterval(kbuf, &qfsz);
+            if (qf == ejq->lastmatchedorqf) {
+                continue;
+            }
+            assert(qfsz == sizeof (EJQF));
+            if (_qrybsmatch(qf, bsbuf, bsbufsz)) {
+                ejq->lastmatchedorqf = qf;
+                rv = true;
+                goto finish;
+            }
+        }
+    }
+finish:
+    if (nbsbuf) { //BSON Buffer created by me
+        TCFREE(nbsbuf);
+    }
+    return rv;
+}
+
+/** Caller must TCFREE(*bsbuf) if it return TRUE */
+static bool _qryallcondsmatch(
+        bool onlycount, int anum,
+        EJCOLL *jcoll, const EJQF **qfs, int qfsz,
+        const void *pkbuf, int pkbufsz,
+        void **bsbuf, int *bsbufsz) {
+    if (onlycount && anum < 1) {
+        *bsbuf = NULL;
+        *bsbufsz = 0;
+        return true;
+    }
+    bool rv = true;
+    //Load BSON
+    int cbufsz;
+    char *cbuf = tchdbget(jcoll->tdb->hdb, pkbuf, pkbufsz, &cbufsz);
+    if (!cbuf) {
+        *bsbuf = NULL;
+        *bsbufsz = 0;
+        return false;
+    }
+    *bsbuf = tcmaploadone(cbuf, cbufsz, JDBCOLBSON, JDBCOLBSONL, bsbufsz); //BSON buffer
+    if (!*bsbuf) {
+        TCFREE(cbuf);
+        *bsbufsz = 0;
+        return false;
+    }
+    if (anum < 1) {
+        TCFREE(cbuf);
+        return true;
+    }
+    for (int i = 0; i < qfsz; ++i) {
+        const EJQF *qf = qfs[i];
+        if (qf->flags & EJFEXCLUDED) continue;
+        if (!_qrybsmatch(qf, *bsbuf, *bsbufsz)) {
+            rv = false;
+            break;
+        }
+    }
+    TCFREE(cbuf);
+    if (!rv) {
+        TCFREE(*bsbuf);
+        *bsbuf = NULL;
+        *bsbufsz = 0;
+    }
+    return rv;
+}
+
+typedef struct {
+    const EJQF **ofs;
+    int ofsz;
+} _ejbsortctx;
+
+/* RS sorting comparison func */
+static int _ejdbsoncmp(const TCLISTDATUM* d1, const TCLISTDATUM* d2, void *opaque) {
+    _ejbsortctx *ctx = opaque;
+    assert(ctx);
+    int res = 0;
+    for (int i = 0; !res && i < ctx->ofsz; ++i) {
+        const EJQF *qf = ctx->ofs[i];
+        if (qf->flags & EJFORDERUSED) {
+            continue;
+        }
+        res = bson_compare(d1->ptr, d2->ptr, qf->fpath, qf->fpathsz) * (qf->order >= 0 ? 1 : -1);
+    }
+    return res;
+}
+
+EJDB_INLINE void _nufetch(_ejdbnum *nu, const char *sval, bson_type bt) {
+    if (bt == BSON_INT || bt == BSON_LONG) {
+        nu->inum = tctdbatoi(sval);
+    } else if (bt == BSON_DOUBLE) {
+        nu->dnum = tctdbatof2(sval);
+    } else {
+        nu->inum = 0;
+        assert(0);
+    }
+}
+
+EJDB_INLINE int _nucmp(_ejdbnum *nu, const char *sval, bson_type bt) {
+    if (bt == BSON_INT || bt == BSON_LONG) {
+        int64_t v = tctdbatoi(sval);
+        return (nu->inum > v) ? 1 : (nu->inum < v ? -1 : 0);
+    } else if (bt == BSON_DOUBLE) {
+        double v = tctdbatof2(sval);
+        return (nu->dnum > v) ? 1 : (nu->dnum < v ? -1 : 0);
+    } else {
+        assert(0);
+    }
+    return 0;
+}
+
+EJDB_INLINE int _nucmp2(_ejdbnum *nu1, _ejdbnum *nu2, bson_type bt) {
+    if (bt == BSON_INT || bt == BSON_LONG) {
+        return (nu1->inum > nu2->inum) ? 1 : (nu1->inum < nu2->inum ? -1 : 0);
+    } else if (bt == BSON_DOUBLE) {
+        return (nu1->dnum > nu2->dnum) ? 1 : (nu1->dnum < nu2->dnum ? -1 : 0);
+    } else {
+        assert(0);
+    }
+    return 0;
+}
+
+static void _qryfieldup(const EJQF *src, EJQF *target, uint32_t qflags) {
+    assert(src && target);
+    memset(target, 0, sizeof (*target));
+    target->exprdblval = src->exprdblval;
+    target->exprlongval = src->exprlongval;
+    target->flags = src->flags;
+    target->ftype = src->ftype;
+    target->negate = src->negate;
+    target->order = src->order;
+    target->orderseq = src->orderseq;
+    target->tcop = src->tcop;
+    if (src->expr) {
+        TCMEMDUP(target->expr, src->expr, src->exprsz);
+        target->exprsz = src->exprsz;
+    }
+    if (src->fpath) {
+        TCMEMDUP(target->fpath, src->fpath, src->fpathsz);
+        target->fpathsz = src->fpathsz;
+    }
+    if (src->regex && (EJQINTERNAL & qflags)) {
+        //We cannot do deep copy of regex_t so do shallow copy only for internal query objects
+        target->regex = src->regex;
+    }
+    if (src->exprlist) {
+        target->exprlist = tclistdup(src->exprlist);
+    }
+}
+
+/* Clone query object */
+static void _qrydup(const EJQ *src, EJQ *target, uint32_t qflags) {
+    assert(src && target);
+    memset(target, 0, sizeof (*target));
+    target->flags = src->flags | qflags;
+    target->max = src->max;
+    target->skip = src->skip;
+    target->orqobjsnum = src->orqobjsnum;
+    target->orqobjs = NULL;
+    const void *kbuf;
+    int ksz, vsz;
+    if (src->qobjmap) {
+        target->qobjmap = tcmapnew2(TCMAPRNUM(src->qobjmap));
+        tcmapiterinit(src->qobjmap);
+        while ((kbuf = tcmapiternext(src->qobjmap, &ksz)) != NULL) {
+            EJQF qf;
+            _qryfieldup(tcmapiterval(kbuf, &vsz), &qf, qflags);
+            tcmapput(target->qobjmap, kbuf, ksz, &qf, sizeof (qf));
+        }
+    }
+    if (src->hints) {
+        target->hints = bson_dup(src->hints);
+    }
+    if (src->orqobjsnum > 0 && src->orqobjs) {
+        TCMALLOC(target->orqobjs, sizeof (*(target->orqobjs)) * src->orqobjsnum);
+        for (int i = 0; i < src->orqobjsnum; ++i) {
+            _qrydup(src->orqobjs + i, target->orqobjs + i, qflags);
+        }
+    }
+}
+
+/** Query */
+static TCLIST* _qrysearch(EJCOLL *jcoll, const EJQ *q, uint32_t *outcount, int qflags, TCXSTR *log) {
+    //Clone the query object
+    EJQ *ejq;
+    TCMALLOC(ejq, sizeof (*ejq));
+    _qrydup(q, ejq, EJQINTERNAL);
+
+    *outcount = 0;
+    bool onlycount = (qflags & EJQRYCOUNT); //quering only for result set count
+    bool all = false; //need all records
+    EJQF *mqf = NULL; //main indexed query condition if exists
+
+    TCLIST *res = onlycount ? NULL : tclistnew2(4096);
+    _qrypreprocess(jcoll, ejq, &mqf);
+
+
+    int anum = 0; //number of active conditions
+    const EJQF **ofs = NULL; //order fields
+    int ofsz = 0; //order fields count
+    int aofsz = 0; //active order fields count
+    const EJQF **qfs = NULL; //condition fields array
+    const int qfsz = TCMAPRNUM(ejq->qobjmap); //number of all condition fields
+    if (qfsz > 0) {
+        TCMALLOC(qfs, qfsz * sizeof (EJQF*));
+    }
+
+    const void *kbuf;
+    int kbufsz;
+    const void *vbuf;
+    int vbufsz;
+    void *bsbuf; //BSON buffer
+    int bsbufsz; //BSON buffer size
+
+    uint32_t count = 0; //current count
+    uint32_t max = (ejq->max > 0) ? ejq->max : UINT_MAX;
+    uint32_t skip = ejq->skip;
+    const TDBIDX *midx = mqf ? mqf->idx : NULL;
+    if (midx) { //Main index used for ordering
+        if (mqf->orderseq == 1 &&
+                !(mqf->tcop == TDBQCSTRAND || mqf->tcop == TDBQCSTROR || mqf->tcop == TDBQCSTRNUMOR)) {
+            mqf->flags |= EJFORDERUSED;
+        }
+    }
+    tcmapiterinit(ejq->qobjmap);
+    for (int i = 0; i < qfsz; ++i) {
+        kbuf = tcmapiternext(ejq->qobjmap, &kbufsz);
+        EJQF *qf = (EJQF*) tcmapget(ejq->qobjmap, kbuf, kbufsz, &vbufsz);
+        assert(qf && vbufsz == sizeof (EJQF));
+        qfs[i] = qf;
+        if (qf->fpathsz > 0 && !(qf->flags & EJFEXCLUDED)) {
+            anum++;
+        }
+        if (qf->orderseq) {
+            ofsz++;
+            if (onlycount) {
+                qf->flags |= EJFORDERUSED;
+            }
+        }
+    }
+    if (ofsz > 0) { //Collect order fields array
+        TCMALLOC(ofs, ofsz * sizeof (EJQF*));
+        for (int i = 0; i < ofsz; ++i) {
+            for (int j = 0; j < qfsz; ++j) {
+                if (qfs[j]->orderseq == i + 1) { //orderseq starts with 1
+                    ofs[i] = qfs[j];
+                    if (!(ofs[i]->flags & EJFORDERUSED)) aofsz++;
+                    break;
+                }
+            }
+        }
+        for (int i = 0; i < ofsz; ++i) assert(ofs[i] != NULL);
+    }
+
+    if (!onlycount && aofsz > 0 && (!midx || mqf->orderseq != 1)) { //Main index is not main order field
+        all = true; //Need all records for ordering for some other fields
+    }
+
+    if (log) {
+        tcxstrprintf(log, "MAX: %u\n", max);
+        tcxstrprintf(log, "SKIP: %u\n", skip);
+        tcxstrprintf(log, "COUNT ONLY: %s\n", onlycount ? "YES" : "NO");
+        tcxstrprintf(log, "MAIN IDX: '%s'\n", midx ? midx->name : "NONE");
+        tcxstrprintf(log, "ORDER FIELDS: %d\n", ofsz);
+        tcxstrprintf(log, "ACTIVE CONDITIONS: %d\n", anum);
+        tcxstrprintf(log, "$OR QUERIES: %d\n", ejq->orqobjsnum);
+        tcxstrprintf(log, "FETCH ALL: %s\n", all ? "TRUE" : "FALSE");
+    }
+    if (max < UINT_MAX - skip) {
+        max += skip;
+    }
+    if (max == 0) {
+        goto finish;
+    }
+    if (!midx) { //Missing main index
+        goto fullscan;
+    }
+    if (log) {
+        tcxstrprintf(log, "MAIN IDX TCOP: %d\n", mqf->tcop);
+    }
+
+#define JBQREGREC(_bsbuf, _bsbufsz)   \
+    ++count; \
+    if (!onlycount && count > skip) { \
+        TCLISTPUSH(res, _bsbuf, _bsbufsz); \
+    } \
+    if (_bsbuf) { \
+        TCFREE(_bsbuf); \
+    }
+
+    bool trim = (*midx->name != '\0');
+    if (anum > 0 && !(mqf->flags & EJFEXCLUDED)) {
+        anum--;
+        mqf->flags |= EJFEXCLUDED;
+    }
+    if (mqf->tcop == TDBQTRUE) {
+        BDBCUR *cur = tcbdbcurnew(midx->db);
+        if (mqf->order >= 0) {
+            tcbdbcurfirst(cur);
+        } else {
+            tcbdbcurlast(cur);
+        }
+        while ((all || count < max) && (kbuf = tcbdbcurkey3(cur, &kbufsz)) != NULL) {
+            if (trim) kbufsz -= 3;
+            vbuf = tcbdbcurval3(cur, &vbufsz);
+            if (_qryallcondsmatch(onlycount, anum, jcoll, qfs, qfsz, vbuf, vbufsz, &bsbuf, &bsbufsz)) {
+                JBQREGREC(bsbuf, bsbufsz);
+            }
+            if (mqf->order >= 0) {
+                tcbdbcurnext(cur);
+            } else {
+                tcbdbcurprev(cur);
+            }
+        }
+        tcbdbcurdel(cur);
+    } else if (mqf->tcop == TDBQCSTREQ) { /* string is equal to */
+        assert(midx->type == TDBITLEXICAL);
+        char* expr = mqf->expr;
+        int exprsz = mqf->exprsz;
+        BDBCUR *cur = tcbdbcurnew(midx->db);
+        tcbdbcurjump(cur, expr, exprsz + trim);
+        while ((all || count < max) && (kbuf = tcbdbcurkey3(cur, &kbufsz)) != NULL) {
+            if (trim) kbufsz -= 3;
+            if (kbufsz == exprsz && !memcmp(kbuf, expr, exprsz)) {
+                vbuf = tcbdbcurval3(cur, &vbufsz);
+                if (_qryallcondsmatch(onlycount, anum, jcoll, qfs, qfsz, vbuf, vbufsz, &bsbuf, &bsbufsz) &&
+                        (ejq->orqobjsnum == 0 || _qryormatch(jcoll, ejq, vbuf, vbufsz, bsbuf, bsbufsz))) {
+                    JBQREGREC(bsbuf, bsbufsz);
+                }
+            } else {
+                break;
+            }
+            tcbdbcurnext(cur);
+        }
+        tcbdbcurdel(cur);
+    } else if (mqf->tcop == TDBQCSTRBW) { /* string begins with */
+        assert(midx->type == TDBITLEXICAL);
+        char* expr = mqf->expr;
+        int exprsz = mqf->exprsz;
+        BDBCUR *cur = tcbdbcurnew(midx->db);
+        tcbdbcurjump(cur, expr, exprsz + trim);
+        while ((all || count < max) && (kbuf = tcbdbcurkey3(cur, &kbufsz)) != NULL) {
+            if (trim) kbufsz -= 3;
+            if (kbufsz >= exprsz && !memcmp(kbuf, expr, exprsz)) {
+                vbuf = tcbdbcurval3(cur, &vbufsz);
+                if (_qryallcondsmatch(onlycount, anum, jcoll, qfs, qfsz, vbuf, vbufsz, &bsbuf, &bsbufsz) &&
+                        (ejq->orqobjsnum == 0 || _qryormatch(jcoll, ejq, vbuf, vbufsz, bsbuf, bsbufsz))) {
+                    JBQREGREC(bsbuf, bsbufsz);
+                }
+            } else {
+                break;
+            }
+            tcbdbcurnext(cur);
+        }
+        tcbdbcurdel(cur);
+    } else if (mqf->tcop == TDBQCSTROREQ) { /* string is equal to at least one token in */
+        assert(mqf->ftype == BSON_ARRAY);
+        assert(midx->type == TDBITLEXICAL);
+        BDBCUR *cur = tcbdbcurnew(midx->db);
+        TCLIST *tokens = mqf->exprlist;
+        assert(tokens);
+        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 (mqf->order < 0 && (mqf->flags & EJFORDERUSED)) {
+            tclistinvert(tokens);
+        }
+        int tnum = TCLISTNUM(tokens);
+        for (int i = 0; (all || count < max) && i < tnum; i++) {
+            const char *token;
+            int tsiz;
+            TCLISTVAL(token, tokens, i, tsiz);
+            if (tsiz < 1) continue;
+            tcbdbcurjump(cur, token, tsiz + trim);
+            while ((all || count < max) && (kbuf = tcbdbcurkey3(cur, &kbufsz)) != NULL) {
+                if (trim) kbufsz -= 3;
+                if (kbufsz == tsiz && !memcmp(kbuf, token, tsiz)) {
+                    vbuf = tcbdbcurval3(cur, &vbufsz);
+                    if (_qryallcondsmatch(onlycount, anum, jcoll, qfs, qfsz, vbuf, vbufsz, &bsbuf, &bsbufsz) &&
+                            (ejq->orqobjsnum == 0 || _qryormatch(jcoll, ejq, vbuf, vbufsz, bsbuf, bsbufsz))) {
+                        JBQREGREC(bsbuf, bsbufsz);
+                    }
+                } else {
+                    break;
+                }
+                tcbdbcurnext(cur);
+            }
+        }
+        tcbdbcurdel(cur);
+    } else if (mqf->tcop == TDBQCNUMEQ) { /* number is equal to */
+        assert(midx->type == TDBITDECIMAL);
+        char* expr = mqf->expr;
+        int exprsz = mqf->exprsz;
+        BDBCUR *cur = tcbdbcurnew(midx->db);
+        _ejdbnum num;
+        _nufetch(&num, expr, mqf->ftype);
+        tctdbqryidxcurjumpnum(cur, expr, exprsz, true);
+        while ((all || count < max) && (kbuf = tcbdbcurkey3(cur, &kbufsz)) != NULL) {
+            if (_nucmp(&num, kbuf, mqf->ftype) == 0) {
+                vbuf = tcbdbcurval3(cur, &vbufsz);
+                if (_qryallcondsmatch(onlycount, anum, jcoll, qfs, qfsz, vbuf, vbufsz, &bsbuf, &bsbufsz) &&
+                        (ejq->orqobjsnum == 0 || _qryormatch(jcoll, ejq, vbuf, vbufsz, bsbuf, bsbufsz))) {
+                    JBQREGREC(bsbuf, bsbufsz);
+                }
+            } else {
+                break;
+            }
+            tcbdbcurnext(cur);
+        }
+        tcbdbcurdel(cur);
+    } else if (mqf->tcop == TDBQCNUMGT || mqf->tcop == TDBQCNUMGE) {
+        /* number is greater than | number is greater than or equal to */
+        assert(midx->type == TDBITDECIMAL);
+        char* expr = mqf->expr;
+        int exprsz = mqf->exprsz;
+        BDBCUR *cur = tcbdbcurnew(midx->db);
+        _ejdbnum xnum;
+        _nufetch(&xnum, expr, mqf->ftype);
+        if (mqf->order < 0 && (mqf->flags & EJFORDERUSED)) { //DESC
+            tcbdbcurlast(cur);
+            while ((all || count < max) && (kbuf = tcbdbcurkey3(cur, &kbufsz)) != NULL) {
+                _ejdbnum knum;
+                _nufetch(&knum, kbuf, mqf->ftype);
+                int cmp = _nucmp2(&knum, &xnum, mqf->ftype);
+                if (cmp < 0) break;
+                if (cmp > 0 || (mqf->tcop == TDBQCNUMGE && cmp >= 0)) {
+                    vbuf = tcbdbcurval3(cur, &vbufsz);
+                    if (_qryallcondsmatch(onlycount, anum, jcoll, qfs, qfsz, vbuf, vbufsz, &bsbuf, &bsbufsz) &&
+                            (ejq->orqobjsnum == 0 || _qryormatch(jcoll, ejq, vbuf, vbufsz, bsbuf, bsbufsz))) {
+                        JBQREGREC(bsbuf, bsbufsz);
+                    }
+                }
+                tcbdbcurprev(cur);
+            }
+        } else { //ASC
+            tctdbqryidxcurjumpnum(cur, expr, exprsz, true);
+            while ((all || count < max) && (kbuf = tcbdbcurkey3(cur, &kbufsz)) != NULL) {
+                _ejdbnum knum;
+                _nufetch(&knum, kbuf, mqf->ftype);
+                int cmp = _nucmp2(&knum, &xnum, mqf->ftype);
+                if (cmp > 0 || (mqf->tcop == TDBQCNUMGE && cmp >= 0)) {
+                    vbuf = tcbdbcurval3(cur, &vbufsz);
+                    if (_qryallcondsmatch(onlycount, anum, jcoll, qfs, qfsz, vbuf, vbufsz, &bsbuf, &bsbufsz) &&
+                            (ejq->orqobjsnum == 0 || _qryormatch(jcoll, ejq, vbuf, vbufsz, bsbuf, bsbufsz))) {
+                        JBQREGREC(bsbuf, bsbufsz);
+                    }
+                }
+                tcbdbcurnext(cur);
+            }
+        }
+        tcbdbcurdel(cur);
+    } else if (mqf->tcop == TDBQCNUMLT || mqf->tcop == TDBQCNUMLE) {
+        /* number is less than | number is less than or equal to */
+        assert(midx->type == TDBITDECIMAL);
+        char* expr = mqf->expr;
+        int exprsz = mqf->exprsz;
+        BDBCUR *cur = tcbdbcurnew(midx->db);
+        _ejdbnum xnum;
+        _nufetch(&xnum, expr, mqf->ftype);
+        if (mqf->order >= 0) { //ASC
+            tcbdbcurfirst(cur);
+            while ((all || count < max) && (kbuf = tcbdbcurkey3(cur, &kbufsz)) != NULL) {
+                _ejdbnum knum;
+                _nufetch(&knum, kbuf, mqf->ftype);
+                int cmp = _nucmp2(&knum, &xnum, mqf->ftype);
+                if (cmp > 0) break;
+                if (cmp < 0 || (cmp <= 0 && mqf->tcop == TDBQCNUMLE)) {
+                    vbuf = tcbdbcurval3(cur, &vbufsz);
+                    if (_qryallcondsmatch(onlycount, anum, jcoll, qfs, qfsz, vbuf, vbufsz, &bsbuf, &bsbufsz) &&
+                            (ejq->orqobjsnum == 0 || _qryormatch(jcoll, ejq, vbuf, vbufsz, bsbuf, bsbufsz))) {
+                        JBQREGREC(bsbuf, bsbufsz);
+                    }
+                }
+                tcbdbcurnext(cur);
+            }
+        } else {
+            tctdbqryidxcurjumpnum(cur, expr, exprsz, false);
+            while ((all || count < max) && (kbuf = tcbdbcurkey3(cur, &kbufsz)) != NULL) {
+                _ejdbnum knum;
+                _nufetch(&knum, kbuf, mqf->ftype);
+                int cmp = _nucmp2(&knum, &xnum, mqf->ftype);
+                if (cmp < 0 || (cmp <= 0 && mqf->tcop == TDBQCNUMLE)) {
+                    vbuf = tcbdbcurval3(cur, &vbufsz);
+                    if (_qryallcondsmatch(onlycount, anum, jcoll, qfs, qfsz, vbuf, vbufsz, &bsbuf, &bsbufsz) &&
+                            (ejq->orqobjsnum == 0 || _qryormatch(jcoll, ejq, vbuf, vbufsz, bsbuf, bsbufsz))) {
+                        JBQREGREC(bsbuf, bsbufsz);
+                    }
+                }
+                tcbdbcurprev(cur);
+            }
+        }
+        tcbdbcurdel(cur);
+    } else if (mqf->tcop == TDBQCNUMBT) { /* number is between two tokens of */
+        assert(mqf->ftype == BSON_ARRAY);
+        assert(midx->type == TDBITDECIMAL);
+        assert(mqf->exprlist);
+        TCLIST *tokens = mqf->exprlist;
+        assert(TCLISTNUM(tokens) == 2);
+        const char* expr;
+        int exprsz;
+        long double lower = tctdbatof(tclistval2(tokens, 0));
+        long double upper = tctdbatof(tclistval2(tokens, 1));
+        expr = tclistval2(tokens, (lower > upper) ? 1 : 0);
+        exprsz = strlen(expr);
+        if (lower > upper) {
+            long double swap = lower;
+            lower = upper;
+            upper = swap;
+        }
+        BDBCUR *cur = tcbdbcurnew(midx->db);
+        tctdbqryidxcurjumpnum(cur, expr, exprsz, true);
+        while ((all || count < max) && (kbuf = tcbdbcurkey3(cur, &kbufsz)) != NULL) {
+            if (tctdbatof(kbuf) > upper) break;
+            vbuf = tcbdbcurval3(cur, &vbufsz);
+            if (_qryallcondsmatch(onlycount, anum, jcoll, qfs, qfsz, vbuf, vbufsz, &bsbuf, &bsbufsz) &&
+                    (ejq->orqobjsnum == 0 || _qryormatch(jcoll, ejq, vbuf, vbufsz, bsbuf, bsbufsz))) {
+                JBQREGREC(bsbuf, bsbufsz);
+            }
+            tcbdbcurnext(cur);
+        }
+        tcbdbcurdel(cur);
+        if (!all && !onlycount && mqf->order < 0 && (mqf->flags & EJFORDERUSED)) { //DESC
+            tclistinvert(res);
+        }
+    } else if (mqf->tcop == TDBQCNUMOREQ) { /* number is equal to at least one token in */
+        assert(mqf->ftype == BSON_ARRAY);
+        assert(midx->type == TDBITDECIMAL);
+        BDBCUR *cur = tcbdbcurnew(midx->db);
+        TCLIST *tokens = mqf->exprlist;
+        assert(tokens);
+        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 (mqf->order < 0 && (mqf->flags & EJFORDERUSED)) {
+            tclistinvert(tokens);
+        }
+        int tnum = TCLISTNUM(tokens);
+        for (int i = 0; (all || count < 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);
+            while ((all || count < max) && (kbuf = tcbdbcurkey3(cur, &kbufsz)) != NULL) {
+                if (tctdbatof(kbuf) == xnum) {
+                    vbuf = tcbdbcurval3(cur, &vbufsz);
+                    if (_qryallcondsmatch(onlycount, anum, jcoll, qfs, qfsz, vbuf, vbufsz, &bsbuf, &bsbufsz) &&
+                            (ejq->orqobjsnum == 0 || _qryormatch(jcoll, ejq, vbuf, vbufsz, bsbuf, bsbufsz))) {
+                        JBQREGREC(bsbuf, bsbufsz);
+                    }
+                } else {
+                    break;
+                }
+                tcbdbcurnext(cur);
+            }
+        }
+        tcbdbcurdel(cur);
+    } else if (mqf->tcop == TDBQCSTRAND || mqf->tcop == TDBQCSTROR || mqf->tcop == TDBQCSTRNUMOR) {
+        /* string includes all tokens in | string includes at least one token in */
+        assert(midx->type == TDBITTOKEN);
+        assert(mqf->ftype == BSON_ARRAY);
+        TCLIST *tokens = mqf->exprlist;
+        assert(tokens);
+        if (mqf->tcop == TDBQCSTRNUMOR) {
+            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--;
+                }
+            }
+        } else {
+            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(jcoll->tdb, midx, tokens, mqf->tcop, log);
+        tcmapiterinit(tres);
+        while ((all || count < max) && (kbuf = tcmapiternext(tres, &kbufsz)) != NULL) {
+            if (_qryallcondsmatch(onlycount, anum, jcoll, qfs, qfsz, kbuf, kbufsz, &bsbuf, &bsbufsz) &&
+                    (ejq->orqobjsnum == 0 || _qryormatch(jcoll, ejq, kbuf, kbufsz, bsbuf, bsbufsz))) {
+                JBQREGREC(bsbuf, bsbufsz);
+            }
+        }
+        tcmapdel(tres);
+    }
+
+    if (onlycount) {
+        goto finish;
+    } else {
+        goto sorting;
+    }
+
+fullscan: /* Full scan */
+    assert(count == 0);
+    assert(!res || TCLISTNUM(res) == 0);
+    if (log) {
+        tcxstrprintf(log, "RUN FULLSCAN\n");
+    }
+    char *pkbuf;
+    int pkbufsz;
+    char *lkbuf = NULL;
+    int lksiz = 0;
+    const char *cbuf;
+    int cbufsz;
+    TCHDB *hdb = jcoll->tdb->hdb;
+    assert(hdb);
+    while ((all || count < max) && (pkbuf = tchdbgetnext3(hdb, lkbuf, lksiz, &pkbufsz, &cbuf, &cbufsz)) != NULL) {
+        void *bsbuf = tcmaploadone(cbuf, cbufsz, JDBCOLBSON, JDBCOLBSONL, &bsbufsz);
+        if (!bsbuf) continue;
+        bool matched = true;
+        for (int i = 0; i < qfsz; ++i) {
+            const EJQF *qf = qfs[i];
+            if (qf->flags & EJFEXCLUDED) continue;
+            if (!_qrybsmatch(qf, bsbuf, bsbufsz)) {
+                matched = false;
+                break;
+            }
+        }
+        if (matched && (ejq->orqobjsnum == 0 || _qryormatch(jcoll, ejq, pkbuf, pkbufsz, bsbuf, bsbufsz))) {
+            JBQREGREC(bsbuf, bsbufsz);
+        } else {
+            TCFREE(bsbuf);
+        }
+        if (lkbuf) TCFREE(lkbuf);
+        lkbuf = pkbuf;
+        lksiz = pkbufsz;
+    }
+    if (lkbuf) TCFREE(lkbuf);
+
+sorting: /* Sorting resultset */
+    if (!res || aofsz <= 0) { //No sorting needed
+        goto finish;
+    }
+    _ejbsortctx sctx; //sorting context
+    sctx.ofs = ofs;
+    sctx.ofsz = ofsz;
+    if (ejdbtimsortlist(res, _ejdbsoncmp, &sctx)) {
+        _ejdbsetecode(jcoll->jb, JDBEQRSSORTING, __FILE__, __LINE__, __func__);
+    }
+finish:
+    //revert max
+    if (max < UINT_MAX && max > skip) {
+        max = max - skip;
+    }
+    if (res && TCLISTNUM(res) > max) { //truncate resulting list if max specified
+        int end = res->start + res->num;
+        TCLISTDATUM *array = res->array;
+        for (int i = (res->start + max); i < end; i++) {
+            TCFREE(array[i].ptr);
+            --(res->num);
+        }
+    }
+    count = (skip < count) ? count - skip : 0;
+    if (count > max) {
+        count = max;
+    }
+    *outcount = count;
+    if (log) {
+        tcxstrprintf(log, "RS COUNT: %d\n", count);
+        tcxstrprintf(log, "RS SIZE: %d\n", (res ? TCLISTNUM(res) : 0));
+        tcxstrprintf(log, "FINAL SORTING: %s\n", (onlycount || aofsz <= 0) ? "FALSE" : "TRUE");
+    }
+    if (qfs) {
+        TCFREE(qfs);
+    }
+    if (ofs) {
+        TCFREE(ofs);
+    }
+    ejdbquerydel(ejq);
+#undef JBQREGREC
+    return res;
+}
+
+static TDBIDX* _qryfindidx(EJCOLL *jcoll, EJQF *qf, bson *idxmeta) {
+    TCTDB *tdb = jcoll->tdb;
+    char p = '\0';
+    switch (qf->tcop) {
+        case TDBQCSTREQ:
+        case TDBQCSTRBW:
+        case TDBQCSTROREQ:
+            p = 's'; //lexical string index
+            break;
+        case TDBQCNUMEQ:
+        case TDBQCNUMGT:
+        case TDBQCNUMGE:
+        case TDBQCNUMLT:
+        case TDBQCNUMLE:
+        case TDBQCNUMBT:
+        case TDBQCNUMOREQ:
+            p = 'n'; //number index
+            break;
+        case TDBQCSTRAND:
+        case TDBQCSTROR:
+            p = 'a'; //token index
+            break;
+        case TDBQTRUE:
+            p = 'o'; //take first appropriate index
+            break;
+    }
+    if (p == '\0' || !qf->fpath || !qf->fpathsz) {
+        return NULL;
+    }
+    for (int i = 0; i < tdb->inum; ++i) {
+        TDBIDX *idx = tdb->idxs + i;
+        assert(idx);
+        if (p == 'o') {
+            if (*idx->name == 'a') {
+                continue;
+            }
+        } else if (*idx->name != p) {
+            continue;
+        }
+        if (!strcmp(qf->fpath, idx->name + 1)) {
+            return idx;
+        }
+    }
+    //No direct operation index. Any alternatives?
+    if (idxmeta && (qf->tcop == TDBQCSTREQ || qf->tcop == TDBQCSTROREQ || qf->tcop == TDBQCNUMOREQ)) {
+        bson_iterator it;
+        bson_type bt = bson_find(&it, idxmeta, "iflags");
+        if (bt != BSON_INT) {
+            return NULL;
+        }
+        int iflags = bson_iterator_int(&it);
+        if (iflags & JDIDXARR) { //array token index exists so convert qf into TDBQCSTROR
+            for (int i = 0; i < tdb->inum; ++i) {
+                TDBIDX *idx = tdb->idxs + i;
+                if (!strcmp(qf->fpath, idx->name + 1)) {
+                    if (qf->tcop == TDBQCSTREQ) {
+                        qf->tcop = TDBQCSTROR;
+                        qf->exprlist = tclistnew2(1);
+                        TCLISTPUSH(qf->exprlist, qf->expr, qf->exprsz);
+                        if (qf->expr) TCFREE(qf->expr);
+                        qf->expr = tclistdump(qf->exprlist, &qf->exprsz);
+                        qf->ftype = BSON_ARRAY;
+                        return idx;
+                    } else if (qf->tcop == TDBQCSTROREQ) {
+                        assert(qf->ftype == BSON_ARRAY);
+                        qf->tcop = TDBQCSTROR;
+                        return idx;
+                    } else if (qf->tcop == TDBQCNUMOREQ) {
+                        assert(qf->ftype == BSON_ARRAY);
+                        qf->tcop = TDBQCSTRNUMOR;
+                        return idx;
+                    }
+                }
+            }
+        }
+    }
+    return NULL;
+}
+
+static void _qrypreprocess(EJCOLL *jcoll, EJQ *ejq, EJQF **mqf) {
+    assert(jcoll && ejq && ejq->qobjmap && mqf);
+
+    *mqf = NULL;
+    EJQF *oqf = NULL; //Order condition
+
+    //Iterate over query fiels
+    TCMAP *qmap = ejq->qobjmap;
+    tcmapiterinit(qmap);
+    const void *mkey;
+    int mkeysz;
+    int mvalsz;
+    bson_iterator it;
+    bson_type bt;
+
+    if (ejq->hints) {
+        bson_type bt;
+        bson_iterator it, sit;
+        //Process $orderby
+        bt = bson_find(&it, ejq->hints, "$orderby");
+        if (bt == BSON_OBJECT) {
+            int orderseq = 1;
+            bson_iterator_subiterator(&it, &sit);
+            while ((bt = bson_iterator_next(&sit)) != BSON_EOO) {
+                if (!BSON_IS_NUM_TYPE(bt)) {
+                    continue;
+                }
+                const char *ofield = bson_iterator_key(&sit);
+                int odir = bson_iterator_int(&sit);
+                odir = (odir > 0) ? 1 : (odir < 0 ? -1 : 0);
+                if (!odir) {
+                    continue;
+                }
+                EJQF nqf;
+                EJQF *qf = (EJQF*) tcmapget(qmap, ofield, strlen(ofield), &mvalsz); //discard const
+                if (qf == NULL) { //Create syntetic query field for orderby ops
+                    memset(&nqf, 0, sizeof (EJQF));
+                    nqf.fpath = tcstrdup(ofield);
+                    nqf.fpathsz = strlen(nqf.fpath);
+                    nqf.expr = tcstrdup("");
+                    nqf.exprsz = 0;
+                    nqf.tcop = TDBQTRUE; //disable any TC matching operation
+                    nqf.ftype = BSON_OBJECT;
+                    nqf.orderseq = orderseq++;
+                    nqf.order = odir;
+                    nqf.flags |= EJFEXCLUDED; //field excluded  from matching
+                    qf = &nqf;
+                    tcmapput(qmap, ofield, strlen(ofield), qf, sizeof (*qf));
+                } else {
+                    qf->orderseq = orderseq++;
+                    qf->order = odir;
+                }
+            }
+        }
+        bt = bson_find(&it, ejq->hints, "$skip");
+        if (BSON_IS_NUM_TYPE(bt)) {
+            int64_t v = bson_iterator_long(&it);
+            ejq->skip = (uint32_t) ((v < 0) ? 0 : v);
+        }
+        bt = bson_find(&it, ejq->hints, "$max");
+        if (BSON_IS_NUM_TYPE(bt)) {
+            int64_t v = bson_iterator_long(&it);
+            ejq->max = (uint32_t) ((v < 0) ? 0 : v);
+        }
+    }
+
+    const int scoreexact = 100;
+    const int scoregtlt = 50;
+    int maxiscore = 0; //Maximum index score
+    int maxselectivity = 0;
+
+    while ((mkey = tcmapiternext(qmap, &mkeysz)) != NULL) {
+        int iscore = 0;
+        EJQF *qf = (EJQF*) tcmapget(qmap, mkey, mkeysz, &mvalsz); //discard const
+        assert(mvalsz == sizeof (EJQF));
+        assert(qf->fpath);
+        bool firstorderqf = false;
+        qf->idxmeta = _imetaidx(jcoll, qf->fpath);
+        qf->idx = _qryfindidx(jcoll, qf, qf->idxmeta);
+        if (qf->order && qf->orderseq == 1) { //Index for first 'orderby' exists
+            oqf = qf;
+            firstorderqf = true;
+        }
+        if (!qf->idx || !qf->idxmeta) {
+            qf->idx = NULL;
+            qf->idxmeta = NULL;
+            continue;
+        }
+        if (qf->tcop == TDBQTRUE || qf->negate) {
+            continue;
+        }
+        int avgreclen = -1;
+        int selectivity = -1;
+        bt = bson_find(&it, qf->idxmeta, "selectivity");
+        if (bt == BSON_DOUBLE) {
+            selectivity = (int) ((double) bson_iterator_double(&it) * 100); //Selectivity percent
+        }
+        bt = bson_find(&it, qf->idxmeta, "avgreclen");
+        if (bt == BSON_DOUBLE) {
+            avgreclen = (int) bson_iterator_double(&it);
+        }
+        if (selectivity > 0) {
+            if (selectivity <= 20) { //Not using index at all if selectivity lesser than 20%
+                continue;
+            }
+            iscore += selectivity;
+        }
+        if (firstorderqf) {
+            iscore += (maxselectivity - selectivity) / 2;
+        }
+        if (selectivity > maxselectivity) {
+            maxselectivity = selectivity;
+        }
+        switch (qf->tcop) {
+            case TDBQCSTREQ:
+            case TDBQCSTROR:
+            case TDBQCNUMEQ:
+            case TDBQCNUMBT:
+                iscore += scoreexact;
+                break;
+            case TDBQCSTRBW:
+            case TDBQCSTREW:
+                if (avgreclen > 0 && qf->exprsz > avgreclen) {
+                    iscore += scoreexact;
+                }
+                break;
+            case TDBQCNUMGT:
+            case TDBQCNUMGE:
+            case TDBQCNUMLT:
+            case TDBQCNUMLE:
+                if (firstorderqf) {
+                    iscore += scoreexact;
+                } else {
+                    iscore += scoregtlt;
+                }
+                break;
+        }
+        if (iscore >= maxiscore) {
+            *mqf = qf;
+            maxiscore = iscore;
+        }
+    }
+
+    if (*mqf == NULL && (oqf && oqf->idx && !oqf->negate)) {
+        *mqf = oqf;
+    }
+}
+
+static bool _metasetopts(EJDB *jb, const char *colname, EJCOLLOPTS *opts) {
+    bool rv = true;
+    if (!opts) {
+        return _metasetbson(jb, colname, strlen(colname), "opts", NULL, false, false);
+    }
+    bson *bsopts = bson_create();
+    bson_init(bsopts);
+    bson_append_bool(bsopts, "compressed", opts->compressed);
+    bson_append_bool(bsopts, "large", opts->large);
+    bson_append_int(bsopts, "cachedrecords", opts->cachedrecords);
+    bson_append_int(bsopts, "records", opts->records);
+    bson_finish(bsopts);
+    rv = _metasetbson(jb, colname, strlen(colname), "opts", bsopts, false, false);
+    bson_del(bsopts);
+    return rv;
+}
+
+static bool _metagetopts(EJDB *jb, const char *colname, EJCOLLOPTS *opts) {
+    assert(opts);
+    bool rv = true;
+    memset(opts, 0, sizeof (*opts));
+    bson *bsopts = _metagetbson(jb, colname, strlen(colname), "opts");
+    if (!bsopts) {
+        return true;
+    }
+    bson_iterator it;
+    bson_type bt = bson_find(&it, bsopts, "compressed");
+    if (bt == BSON_BOOL) {
+        opts->compressed = bson_iterator_bool(&it);
+    }
+    bt = bson_find(&it, bsopts, "large");
+    if (bt == BSON_BOOL) {
+        opts->large = bson_iterator_bool(&it);
+    }
+    bt = bson_find(&it, bsopts, "cachedrecords");
+    if (BSON_IS_NUM_TYPE(bt)) {
+        opts->cachedrecords = bson_iterator_long(&it);
+    }
+    bt = bson_find(&it, bsopts, "records");
+    if (BSON_IS_NUM_TYPE(bt)) {
+        opts->records = bson_iterator_long(&it);
+    }
+    return rv;
+}
+
+static bool _metasetbson(EJDB *jb, const char *colname, int colnamesz,
+        const char *mkey, bson* val, bool merge, bool mergeoverwrt) {
+    assert(jb && colname && mkey);
+    bool rv = true;
+    bson *bsave = NULL;
+    bson *oldval = NULL;
+    bson mresult;
+
+    TCMAP *cmeta = tctdbget(jb->metadb, colname, colnamesz);
+    if (!cmeta) {
+        _ejdbsetecode(jb, JDBEMETANVALID, __FILE__, __LINE__, __func__);
+        rv = false;
+        goto finish;
+    }
+    if (!val) {
+        if (tcmapout2(cmeta, mkey)) {
+            rv = tctdbput(jb->metadb, colname, colnamesz, cmeta);
+        }
+        goto finish;
+    }
+    assert(val);
+    if (merge) { //Merged
+        oldval = _metagetbson(jb, colname, colnamesz, mkey);
+        if (oldval) {
+            bson_init(&mresult);
+            bson_merge(oldval, val, mergeoverwrt, &mresult);
+            bson_finish(&mresult);
+            bsave = &mresult;
+        } else {
+            bsave = val;
+        }
+    } else { //Rewrited
+        bsave = val;
+    }
+
+    assert(bsave);
+    tcmapput(cmeta, mkey, strlen(mkey), bson_data(bsave), bson_size(bsave));
+    rv = tctdbput(jb->metadb, colname, colnamesz, cmeta);
+finish:
+    if (oldval) {
+        if (merge) {
+            bson_destroy(bsave);
+        }
+        bson_del(oldval);
+    }
+    if (cmeta) {
+        tcmapdel(cmeta);
+    }
+    tctdbsync(jb->metadb);
+    return rv;
+}
+
+static bool _metasetbson2(EJCOLL *jcoll, const char *mkey, bson* val, bool merge, bool mergeoverwrt) {
+    assert(jcoll);
+    return _metasetbson(jcoll->jb, jcoll->cname, jcoll->cnamesz, mkey, val, merge, mergeoverwrt);
+}
+
+/**Returned meta BSON data must be freed by 'bson_del' */
+static bson* _metagetbson(EJDB *jb, const char *colname, int colnamesz, const char* mkey) {
+    assert(jb && colname && mkey);
+    bson *rv = NULL;
+    TCMAP *cmeta = tctdbget(jb->metadb, colname, colnamesz);
+    if (!cmeta) {
+        _ejdbsetecode(jb, JDBEMETANVALID, __FILE__, __LINE__, __func__);
+        return NULL;
+    }
+    int bsz;
+    const void* raw = tcmapget(cmeta, mkey, strlen(mkey), &bsz);
+    if (!raw || bsz == 0) {
+        goto finish;
+    }
+    rv = bson_create();
+    bson_init_size(rv, bsz);
+    bson_ensure_space(rv, bsz - 4);
+    bson_append(rv, ((char*) raw) + 4, bsz - (4 + 1/*BSON_EOO*/));
+    bson_finish(rv);
+finish:
+    tcmapdel(cmeta);
+    return rv;
+}
+
+static bson* _metagetbson2(EJCOLL *jcoll, const char *mkey) {
+    assert(jcoll);
+    return _metagetbson(jcoll->jb, jcoll->cname, jcoll->cnamesz, mkey);
+}
+
+/**Returned index meta if not NULL it must be freed by 'bson_del' */
+static bson* _imetaidx(EJCOLL *jcoll, const char *ipath) {
+    assert(jcoll && ipath);
+    if (*ipath == '\0') {
+        return NULL;
+    }
+    bson *rv = NULL;
+    char fpathkey[BSON_MAX_FPATH_LEN + 1];
+    TCMAP *cmeta = tctdbget(jcoll->jb->metadb, jcoll->cname, jcoll->cnamesz);
+    if (!cmeta) {
+        _ejdbsetecode(jcoll->jb, JDBEMETANVALID, __FILE__, __LINE__, __func__);
+        goto finish;
+    }
+    int klen = snprintf(fpathkey, BSON_MAX_FPATH_LEN + 1, "i%s", ipath); //'i' prefix for all columns with index meta
+    if (klen > BSON_MAX_FPATH_LEN) {
+        _ejdbsetecode(jcoll->jb, JDBEFPATHINVALID, __FILE__, __LINE__, __func__);
+        goto finish;
+    }
+    int bsz;
+    const void* bsdata = tcmapget(cmeta, fpathkey, klen, &bsz);
+    if (bsdata) {
+        rv = bson_create();
+        bson_init_size(rv, bsz);
+        bson_ensure_space(rv, bsz - 4);
+        bson_append(rv, ((char*) bsdata) + 4, bsz - (4 + 1));
+        bson_finish(rv);
+    }
+finish:
+    if (cmeta) {
+        tcmapdel(cmeta);
+    }
+    return rv;
+}
+
+/** Free EJQF field **/
+static void _delqfdata(const EJQ *q, const EJQF *qf) {
+    assert(q && qf);
+    if (qf->expr) {
+        TCFREE(qf->expr);
+    }
+    if (qf->fpath) {
+        TCFREE(qf->fpath);
+    }
+    if (qf->idxmeta) {
+        bson_del(qf->idxmeta);
+    }
+    if (qf->regex && !(EJQINTERNAL & q->flags)) {
+        //We do not clear regex_t data because it not deep copy in internal queries
+        regfree(qf->regex);
+        TCFREE(qf->regex);
+    }
+    if (qf->exprlist) {
+        tclistdel(qf->exprlist);
+    }
+}
+
+/**
+ * Copy BSON array into new TCLIST. TCLIST must be freed by 'tclistdel'.
+ * @param it BSON iterator
+ * @param type[out] Detected BSON type of last element
+ */
+static TCLIST* _fetch_bson_str_array(bson_iterator *it, bson_type *type) {
+    TCLIST *res = tclistnew();
+    *type = BSON_EOO;
+    bson_type ftype;
+    for (int i = 0; (ftype = bson_iterator_next(it)) != BSON_EOO; ++i) {
+        switch (ftype) {
+            case BSON_SYMBOL:
+            case BSON_STRING:
+                *type = ftype;
+                tclistpush2(res, bson_iterator_string(it));
+                break;
+            case BSON_INT:
+            case BSON_LONG:
+                *type = ftype;
+                tclistprintf(res, "%ld", bson_iterator_long(it));
+                break;
+            case BSON_DOUBLE:
+                *type = ftype;
+                tclistprintf(res, "%f", bson_iterator_double(it));
+                break;
+            default:
+                break;
+        }
+    }
+    return res;
+}
+
+/** result must be freed by TCFREE */
+static char* _fetch_bson_str_array2(bson_iterator *it, bson_type *type) {
+    TCLIST *res = _fetch_bson_str_array(it, type);
+    char* tokens = tcstrjoin(res, ',');
+    tclistdel(res);
+    return tokens;
+}
+
+static int _parse_qobj_impl(EJDB* jb, bson_iterator *it, TCMAP *qmap, TCLIST *pathStack, EJQF *pqf) {
+    assert(it && qmap && pathStack);
+    int ret = 0;
+    bson_type ftype;
+    while ((ftype = bson_iterator_next(it)) != BSON_EOO) {
+
+        const char* fkey = bson_iterator_key(it);
+        bool isckey = (*fkey == '$'); //Key is a control key: $in, $nin, $not, $all
+
+        if (!isckey && pqf && pqf->ftype == BSON_ARRAY) {
+            //All index keys in array are prefixed with '*'. Eg: 'store.*1.item', 'store.*2.item', ...
+            char akey[TCNUMBUFSIZ];
+            assert(strlen(fkey) <= TCNUMBUFSIZ - 2);
+            akey[0] = '*';
+            memmove(akey + 1, fkey, strlen(fkey) + 1);
+            fkey = akey;
+        }
+
+        EJQF qf;
+        memset(&qf, 0, sizeof (qf));
+
+        if (!isckey) {
+            //Push key on top of path stack
+            tclistpush2(pathStack, fkey);
+            qf.ftype = ftype;
+        } else {
+            if (!pqf) { //Require parent query object
+                ret = JDBQINVALID;
+                break;
+            }
+            qf = *pqf;
+            if (!strcmp("$not", fkey)) {
+                qf.negate = !qf.negate;
+            } else if (!strcmp("$gt", fkey)) {
+                qf.flags |= EJCOMPGT;
+            } else if (!strcmp("$gte", fkey)) {
+                qf.flags |= EJCOMPGTE;
+            } else if (!strcmp("$lt", fkey)) {
+                qf.flags |= EJCOMPLT;
+            } else if (!strcmp("$lte", fkey)) {
+                qf.flags |= EJCOMPLTE;
+            } else if (!strcmp("$begin", fkey)) {
+                qf.flags |= EJCONDSTARTWITH;
+            }
+        }
+
+        switch (ftype) {
+            case BSON_ARRAY:
+            {
+                if (isckey) {
+                    if (strcmp("$in", fkey) &&
+                            strcmp("$nin", fkey) &&
+                            strcmp("$bt", fkey) &&
+                            strcmp("$strand", fkey) &&
+                            strcmp("$stror", fkey)) {
+                        ret = JDBEQINVALIDQCONTROL;
+                        break;
+                    }
+                    bson_iterator sit;
+                    bson_iterator_subiterator(it, &sit);
+                    bson_type atype = 0;
+                    TCLIST *tokens = _fetch_bson_str_array(&sit, &atype);
+                    if (atype == 0) {
+                        ret = JDBEQINOPNOTARRAY;
+                        tclistdel(tokens);
+                        break;
+                    }
+                    assert(!qf.expr);
+                    assert(!qf.fpath);
+                    qf.ftype = BSON_ARRAY;
+                    qf.expr = tclistdump(tokens, &qf.exprsz);
+                    qf.exprlist = tokens;
+                    if (!strcmp("$in", fkey) || !strcmp("$nin", fkey)) {
+                        if (!strcmp("$nin", fkey)) {
+                            qf.negate = true;
+                        }
+                        if (BSON_IS_NUM_TYPE(atype)) {
+                            qf.tcop = TDBQCNUMOREQ;
+                        } else {
+                            qf.tcop = TDBQCSTROREQ;
+                        }
+                    } else if (!strcmp("$bt", fkey)) { //between
+                        qf.tcop = TDBQCNUMBT;
+                        if (TCLISTNUM(tokens) != 2) {
+                            ret = JDBEQINOPNOTARRAY;
+                            TCFREE(qf.expr);
+                            tclistdel(qf.exprlist);
+                            break;
+                        }
+                    } else if (!strcmp("$strand", fkey)) { //$strand
+                        qf.tcop = TDBQCSTRAND;
+                    } else { //$stror
+                        qf.tcop = TDBQCSTROR;
+                    }
+                    qf.fpath = tcstrjoin(pathStack, '.');
+                    qf.fpathsz = strlen(qf.fpath);
+                    tcmapputkeep(qmap, qf.fpath, strlen(qf.fpath), &qf, sizeof (qf));
+                    break;
+                } else {
+                    bson_iterator sit;
+                    bson_iterator_subiterator(it, &sit);
+                    ret = _parse_qobj_impl(jb, &sit, qmap, pathStack, &qf);
+                    break;
+                }
+            }
+
+            case BSON_OBJECT:
+            {
+                bson_iterator sit;
+                bson_iterator_subiterator(it, &sit);
+                if (isckey) {
+
+                }
+                ret = _parse_qobj_impl(jb, &sit, qmap, pathStack, &qf);
+                break;
+            }
+            case BSON_SYMBOL:
+            case BSON_STRING:
+            {
+                assert(!qf.fpath && !qf.expr);
+                qf.ftype = ftype;
+                qf.expr = tcstrdup(bson_iterator_string(it));
+                qf.exprsz = strlen(qf.expr);
+                qf.fpath = tcstrjoin(pathStack, '.');
+                qf.fpathsz = strlen(qf.fpath);
+                if (qf.flags & EJCONDSTARTWITH) {
+                    qf.tcop = TDBQCSTRBW;
+                } else {
+                    qf.tcop = TDBQCSTREQ;
+                }
+                tcmapputkeep(qmap, qf.fpath, strlen(qf.fpath), &qf, sizeof (qf));
+                break;
+            }
+            case BSON_LONG:
+            case BSON_DOUBLE:
+            case BSON_INT:
+            {
+                assert(!qf.fpath && !qf.expr);
+                qf.ftype = ftype;
+                qf.fpath = tcstrjoin(pathStack, '.');
+                qf.fpathsz = strlen(qf.fpath);
+                if (ftype == BSON_LONG || ftype == BSON_INT) {
+                    qf.exprlongval = bson_iterator_long(it);
+                    qf.exprdblval = qf.exprlongval;
+                    qf.expr = tcsprintf("%ld", qf.exprlongval);
+                } else {
+                    qf.exprdblval = bson_iterator_double(it);
+                    qf.exprlongval = (int64_t) qf.exprdblval;
+                    qf.expr = tcsprintf("%f", qf.exprdblval);
+                }
+                qf.exprsz = strlen(qf.expr);
+                if (qf.flags & EJCOMPGT) {
+                    qf.tcop = TDBQCNUMGT;
+                } else if (qf.flags & EJCOMPGTE) {
+                    qf.tcop = TDBQCNUMGE;
+                } else if (qf.flags & EJCOMPLT) {
+                    qf.tcop = TDBQCNUMLT;
+                } else if (qf.flags & EJCOMPLTE) {
+                    qf.tcop = TDBQCNUMLE;
+                } else {
+                    qf.tcop = TDBQCNUMEQ;
+                }
+                tcmapputkeep(qmap, qf.fpath, strlen(qf.fpath), &qf, sizeof (qf));
+                break;
+            }
+            case BSON_REGEX:
+            {
+                assert(!qf.fpath && !qf.expr);
+                qf.ftype = ftype;
+                qf.tcop = TDBQCSTRRX;
+                char* re = tcstrdup(bson_iterator_regex(it));
+                const char* opts = bson_iterator_regex_opts(it);
+                qf.fpath = tcstrjoin(pathStack, '.');
+                qf.fpathsz = strlen(qf.fpath);
+                qf.expr = re;
+                qf.exprsz = strlen(qf.expr);
+                const char *rxstr = qf.expr;
+                regex_t rxbuf;
+                int rxopt = REG_EXTENDED | REG_NOSUB;
+                if (strchr(opts, 'i')) {
+                    rxopt |= REG_ICASE;
+                }
+                if (regcomp(&rxbuf, rxstr, rxopt) == 0) {
+                    TCMALLOC(qf.regex, sizeof (rxbuf));
+                    memcpy(qf.regex, &rxbuf, sizeof (rxbuf));
+                } else {
+                    ret = JDBEQINVALIDQRX;
+                    TCFREE(qf.fpath);
+                    TCFREE(qf.expr);
+                    break;
+                }
+                tcmapputkeep(qmap, qf.fpath, strlen(qf.fpath), &qf, sizeof (qf));
+                break;
+            }
+            case BSON_NULL:
+            case BSON_UNDEFINED:
+                qf.ftype = ftype;
+                qf.tcop = TDBQCEXIST;
+                qf.negate = !qf.negate;
+                qf.expr = tcstrdup(""); //Empty string as expr
+                qf.exprsz = 0;
+                tcmapputkeep(qmap, qf.fpath, strlen(qf.fpath), &qf, sizeof (qf));
+                break;
+            default:
+                break;
+        };
+
+        if (!isckey) {
+            assert(pathStack->num > 0);
+            TCFREE(tclistpop2(pathStack));
+        }
+        if (ret) { //cleanup on error condition
+            //TODO better error reporting
+            _ejdbsetecode(jb, ret, __FILE__, __LINE__, __func__);
+            break;
+        }
+    }
+
+    return ret;
+}
+
+/**
+ * Convert bson query spec into field path -> EJQF instance.
+ *  Created map instance must be freed `tcmapdel`.
+ *  Each element of map must be freed by TODO
+ */
+static TCMAP* _parseqobj(EJDB* jb, bson *qspec) {
+    assert(qspec);
+    int rv = 0;
+    TCMAP *res = tcmapnew();
+    bson_iterator it;
+    bson_iterator_init(&it, qspec);
+    TCLIST *pathStack = tclistnew();
+    rv = _parse_qobj_impl(jb, &it, res, pathStack, NULL);
+    if (rv) {
+        tcmapdel(res);
+        res = NULL;
+    }
+    assert(!pathStack->num);
+    tclistdel(pathStack);
+    return res;
+}
+
+EJDB_INLINE bool _isvalidoidstr(const char *oid) {
+    if (!oid) {
+        return false;
+    }
+    int i = 0;
+    for (; oid[i] != '\0' &&
+            ((oid[i] >= 0x30 && oid[i] <= 0x39) || /* 1 - 9 */
+            (oid[i] >= 0x61 && oid[i] <= 0x66)); ++i); /* a - f */
+    return (i == 24);
+}
+
+/**
+ * Get OID value from the '_id' field of specified bson object.
+ * @param bson[in] BSON object
+ * @param oid[out] Pointer to OID type
+ * @return True if OID value is found int _id field of bson object otherwise False.
+ */
+static bool _bsonoidkey(bson *bs, bson_oid_t *oid) {
+    bool rv = false;
+    bson_iterator it;
+    bson_type t = bson_find(&it, bs, JDBIDKEYNAME);
+    if (t != BSON_STRING && t != BSON_OID) {
+        goto finish;
+    }
+    if (t == BSON_STRING) {
+        const char* oidstr = bson_iterator_string(&it);
+        if (!_isvalidoidstr(oidstr)) {
+            goto finish;
+        }
+    }
+    *oid = *bson_iterator_oid(&it);
+    rv = true;
+finish:
+    return rv;
+}
+
+/**
+ * Return string value representation of value pointed by 'it'.
+ * Resulting value size stored into 'vsz'.
+ * If returned value is not NULL it must be freed by TCFREE.
+ */
+static char* _bsonitstrval(bson_iterator *it, int *vsz, TCLIST *tokens) {
+    char *ret = NULL;
+    bson_type btype = bson_iterator_type(it);
+    if (btype == BSON_STRING) {
+        if (tokens) { //split string into tokens and push it into 'tokens' list
+            const unsigned char *sp = (unsigned char *) bson_iterator_string(it);
+            while (*sp != '\0') {
+                while ((*sp != '\0' && *sp <= ' ') || *sp == ',') {
+                    sp++;
+                }
+                const unsigned char *ep = sp;
+                while (*ep > ' ' && *ep != ',') {
+                    ep++;
+                }
+                if (ep > sp) TCLISTPUSH(tokens, sp, ep - sp);
+                sp = ep;
+            }
+            *vsz = 0;
+            ret = NULL;
+        } else {
+            *vsz = bson_iterator_string_len(it) - 1;
+            ret = (*vsz > 0) ? tcmemdup(bson_iterator_string(it), *vsz) : NULL;
+        }
+    } else if (BSON_IS_NUM_TYPE(btype)) {
+        char nbuff[TCNUMBUFSIZ];
+        if (btype == BSON_INT || btype == BSON_LONG) {
+            *vsz = bson_numstrn(nbuff, TCNUMBUFSIZ, bson_iterator_long(it));
+            if (*vsz >= TCNUMBUFSIZ) {
+                *vsz = TCNUMBUFSIZ - 1;
+                nbuff[TCNUMBUFSIZ - 1] = '\0';
+            }
+            ret = tcmemdup(nbuff, *vsz);
+        } else if (btype == BSON_DOUBLE) {
+            *vsz = snprintf(nbuff, TCNUMBUFSIZ, "%lf", bson_iterator_double(it));
+            if (*vsz >= TCNUMBUFSIZ) {
+                *vsz = TCNUMBUFSIZ - 1;
+                nbuff[TCNUMBUFSIZ - 1] = '\0';
+            }
+            ret = tcmemdup(nbuff, *vsz);
+        }
+    } else if (btype == BSON_ARRAY) {
+        bson_type eltype; //last element bson type
+        bson_iterator sit;
+        bson_iterator_subiterator(it, &sit);
+        if (tokens) {
+            while ((eltype = bson_iterator_next(&sit)) != BSON_EOO) {
+                int vz = 0;
+                char *v = _bsonitstrval(&sit, &vz, NULL);
+                if (v) {
+                    TCLISTPUSH(tokens, v, vz);
+                    TCFREE(v);
+                }
+            }
+            ret = NULL;
+            *vsz = 0;
+        } else {
+            //Array elements are joined with ',' delimeter.
+            ret = _fetch_bson_str_array2(&sit, &eltype);
+            *vsz = strlen(ret);
+        }
+    } else {
+        *vsz = 0;
+    }
+    return ret;
+}
+
+static char* _bsonipathrowldr(
+        TCLIST *tokens,
+        const char *pkbuf, int pksz,
+        const char *rowdata, int rowdatasz,
+        const char *ipath, int ipathsz, void *op, int *vsz) {
+    char* res = NULL;
+    if (ipath && *ipath == '\0') { //PK
+        if (tokens) {
+            const unsigned char *sp = (unsigned char *) pkbuf;
+            while (*sp != '\0') {
+                while ((*sp != '\0' && *sp <= ' ') || *sp == ',') {
+                    sp++;
+                }
+                const unsigned char *ep = sp;
+                while (*ep > ' ' && *ep != ',') {
+                    ep++;
+                }
+                if (ep > sp) TCLISTPUSH(tokens, sp, ep - sp);
+                sp = ep;
+            }
+            *vsz = 0;
+            return NULL;
+        } else {
+            TCMEMDUP(res, pkbuf, pksz);
+            *vsz = pksz;
+            return res;
+        }
+    }
+    if (!ipath || ipathsz < 2 || *(ipath + 1) == '\0' || (*ipath != 's' && *ipath != 'n' && *ipath != 'a')) {
+        return NULL;
+    }
+    //skip index type prefix char with (fpath + 1)
+    return _bsonfpathrowldr(tokens, rowdata, rowdatasz, ipath + 1, ipathsz - 1, op, vsz);
+}
+
+static char* _bsonfpathrowldr(TCLIST *tokens, const char *rowdata, int rowdatasz,
+        const char *fpath, int fpathsz, void *op, int *vsz) {
+    char *ret = NULL;
+    int bsize;
+    bson_iterator it;
+    char *bsdata = tcmaploadone(rowdata, rowdatasz, JDBCOLBSON, JDBCOLBSONL, &bsize);
+    if (!bsdata) {
+        *vsz = 0;
+        return NULL;
+    }
+    bson_iterator_from_buffer(&it, bsdata);
+    bson_find_fieldpath_value2(fpath, fpathsz, &it);
+    ret = _bsonitstrval(&it, vsz, tokens);
+    TCFREE(bsdata);
+    return ret;
+}
+
+static bool _updatebsonidx(EJCOLL *jcoll, const bson_oid_t *oid, const bson *bs,
+        const void *obsdata, int obsdatasz) {
+    bool rv = true;
+    TCMAP *cmeta = tctdbget(jcoll->jb->metadb, jcoll->cname, jcoll->cnamesz);
+    if (!cmeta) {
+        _ejdbsetecode(jcoll->jb, JDBEMETANVALID, __FILE__, __LINE__, __func__);
+        return false;
+    }
+    TCMAP *imap = NULL; //New index map
+    TCMAP *rimap = NULL; //Remove index map
+    bson_type mt = BSON_EOO;
+    bson_type ft = BSON_EOO;
+    bson_type oft = BSON_EOO;
+    bson_iterator fit, oit, mit;
+    int bsz;
+    char ikey[BSON_MAX_FPATH_LEN + 2];
+    const char *mkey;
+    int mkeysz;
+
+    tcmapiterinit(cmeta);
+    while ((mkey = tcmapiternext(cmeta, &mkeysz)) != NULL && mkeysz > 0) {
+        if (*mkey != 'i' || mkeysz > BSON_MAX_FPATH_LEN + 1) {
+            continue;
+        }
+        const void* mraw = tcmapget(cmeta, mkey, mkeysz, &bsz);
+        if (!mraw || !bsz || (mt = bson_find_from_buffer(&mit, mraw, "iflags")) != BSON_INT) {
+            continue;
+        }
+        int iflags = bson_iterator_int(&mit);
+        //OK then process index keys
+        memcpy(ikey + 1, mkey + 1, mkeysz - 1);
+        ikey[mkeysz] = '\0';
+
+        int fvaluesz = 0;
+        char *fvalue = NULL;
+        int ofvaluesz = 0;
+        char *ofvalue = NULL;
+
+        if (obsdata && obsdatasz > 0) {
+            bson_iterator_from_buffer(&oit, obsdata);
+            oft = bson_find_fieldpath_value2(mkey + 1, mkeysz - 1, &oit);
+            TCLIST *tokens = (oft == BSON_ARRAY) ? tclistnew() : NULL;
+            ofvalue = BSON_IS_IDXSUPPORTED_TYPE(oft) ? _bsonitstrval(&oit, &ofvaluesz, tokens) : NULL;
+            if (tokens) {
+                ofvalue = tclistdump(tokens, &ofvaluesz);
+                tclistdel(tokens);
+            }
+        }
+        if (bs) {
+            bson_iterator_init(&fit, bs);
+            ft = bson_find_fieldpath_value2(mkey + 1, mkeysz - 1, &fit);
+            TCLIST *tokens = (ft == BSON_ARRAY) ? tclistnew() : NULL;
+            fvalue = BSON_IS_IDXSUPPORTED_TYPE(ft) ? _bsonitstrval(&fit, &fvaluesz, tokens) : NULL;
+            if (tokens) {
+                fvalue = tclistdump(tokens, &fvaluesz);
+                tclistdel(tokens);
+            }
+        }
+        if (!fvalue && !ofvalue) {
+            continue;
+        }
+        if (imap == NULL) {
+            imap = tcmapnew2(16);
+            rimap = tcmapnew2(16);
+        }
+        for (int i = 4; i <= 6; ++i) { /* JDIDXNUM, JDIDXSTR, JDIDXARR */
+            bool rm = false;
+            if ((1 << i) == JDIDXNUM && (JDIDXNUM & iflags)) {
+                ikey[0] = 'n';
+            } else if ((1 << i) == JDIDXSTR && (JDIDXSTR & iflags)) {
+                ikey[0] = 's';
+            } else if ((1 << i) == JDIDXARR && (JDIDXARR & iflags)) {
+                ikey[0] = 'a';
+                if (ofvalue && oft == BSON_ARRAY &&
+                        (!fvalue || ft != oft || fvaluesz != ofvaluesz || memcmp(fvalue, ofvalue, fvaluesz))) {
+                    tcmapput(rimap, ikey, mkeysz, ofvalue, ofvaluesz);
+                    rm = true;
+                }
+                if (fvalue && ft == BSON_ARRAY && (!ofvalue || rm)) {
+                    tcmapput(imap, ikey, mkeysz, fvalue, fvaluesz);
+                }
+                continue;
+            } else {
+                continue;
+            }
+            if (ofvalue && oft != BSON_ARRAY &&
+                    (!fvalue || ft != oft || fvaluesz != ofvaluesz || memcmp(fvalue, ofvalue, fvaluesz))) {
+                tcmapput(rimap, ikey, mkeysz, ofvalue, ofvaluesz);
+                rm = true;
+            }
+            if (fvalue && ft != BSON_ARRAY && (!ofvalue || rm)) {
+                tcmapput(imap, ikey, mkeysz, fvalue, fvaluesz);
+            }
+        }
+        if (fvalue) {
+            TCFREE(fvalue);
+        }
+        if (ofvalue) {
+            TCFREE(ofvalue);
+        }
+    }
+    tcmapdel(cmeta);
+    if (rimap && !tctdbidxout2(jcoll->tdb, oid, sizeof (*oid), rimap)) rv = false;
+    if (imap && !tctdbidxput2(jcoll->tdb, oid, sizeof (*oid), imap)) rv = false;
+    if (imap) tcmapdel(imap);
+    if (rimap) tcmapdel(rimap);
+    return rv;
+}
+
+static void _delcoldb(EJCOLL *jcoll) {
+    assert(jcoll);
+    tctdbdel(jcoll->tdb);
+    jcoll->tdb = NULL;
+    jcoll->jb = NULL;
+    jcoll->cnamesz = 0;
+    TCFREE(jcoll->cname);
+    if (jcoll->mmtx) {
+        pthread_rwlock_destroy(jcoll->mmtx);
+        TCFREE(jcoll->mmtx);
+    }
+}
+
+static bool _addcoldb0(const char* cname, EJDB *jb, EJCOLLOPTS *opts, EJCOLL **res) {
+    bool rv = true;
+    TCTDB* cdb;
+    rv = _createcoldb(cname, jb, opts, &cdb);
+    if (!rv) {
+        *res = NULL;
+        return rv;
+    }
+    TCREALLOC(jb->cdbs, jb->cdbs, sizeof (jb->cdbs[0]) * (++jb->cdbsnum));
+    EJCOLL *jcoll = jb->cdbs + jb->cdbsnum - 1;
+    jcoll->cname = tcstrdup(cname);
+    jcoll->cnamesz = strlen(cname);
+    jcoll->tdb = cdb;
+    jcoll->jb = jb;
+    jcoll->mmtx = NULL;
+    _ejdbcolsetmutex(jcoll);
+    *res = jcoll;
+    return rv;
+}
+
+static bool _createcoldb(const char* colname, EJDB *jb, EJCOLLOPTS *opts, TCTDB **res) {
+    assert(jb && jb->metadb);
+    if (!JBISVALCOLNAME(colname)) {
+        _ejdbsetecode(jb, JDBEINVALIDCOLNAME, __FILE__, __LINE__, __func__);
+        *res = NULL;
+        return false;
+    }
+    bool rv = true;
+    TCTDB *cdb = tctdbnew();
+    if (opts) {
+        if (opts->cachedrecords > 0) {
+            tctdbsetcache(cdb, opts->cachedrecords, 0, 0);
+        }
+        int bnum = 0;
+        uint8_t tflags = 0;
+        if (opts->records > 0) {
+            bnum = tclmax(opts->records * 2 + 1, TDBDEFBNUM);
+        }
+        if (opts->large) {
+            tflags |= TDBTLARGE;
+        }
+        if (opts->compressed) {
+            tflags |= TDBTDEFLATE;
+        }
+        tctdbtune(cdb, bnum, 0, 0, tflags);
+    }
+    const char* mdbpath = jb->metadb->hdb->path;
+    assert(mdbpath);
+    TCXSTR *cxpath = tcxstrnew2(mdbpath);
+    tcxstrcat2(cxpath, "_");
+    tcxstrcat2(cxpath, colname);
+    rv = tctdbopen(cdb, tcxstrptr(cxpath), jb->metadb->hdb->omode);
+    *res = rv ? cdb : NULL;
+    tcxstrdel(cxpath);
+    return rv;
+}
+
+static void _ejdbclear(EJDB *jb) {
+    assert(jb);
+    jb->cdbs = NULL;
+    jb->cdbsnum = 0;
+    jb->metadb = NULL;
+    jb->mmtx = NULL;
+}
+
+/* Check whether a string includes all tokens in another string.*/
+static bool _qrycondcheckstrand(const char *vbuf, const TCLIST *tokens) {
+    assert(vbuf && tokens);
+    for (int i = 0; i < TCLISTNUM(tokens); ++i) {
+        const char* token = TCLISTVALPTR(tokens, i);
+        int tokensz = TCLISTVALSIZ(tokens, i);
+        bool found = false;
+        const char *str = vbuf;
+        while (true) {
+            const char *sp = str;
+            while (*str != '\0' && !strchr(", ", *str)) {
+                str++;
+            }
+            if (tokensz == (str - sp) && !strncmp(token, sp, tokensz)) { //Token matched
+                found = true;
+                break;
+            }
+            if (*str == '\0') break;
+            str++;
+        }
+        if (!found) {
+            return false;
+        }
+    }
+    return true;
+}
+
+/* Check whether a string includes at least one tokens in another string.*/
+static bool _qrycondcheckstror(const char *vbuf, const TCLIST *tokens) {
+    for (int i = 0; i < TCLISTNUM(tokens); ++i) {
+        const char* token = TCLISTVALPTR(tokens, i);
+        int tokensz = TCLISTVALSIZ(tokens, i);
+        bool found = false;
+        const char *str = vbuf;
+        while (true) {
+            const char *sp = str;
+            while (*str != '\0' && !strchr(", ", *str)) {
+                str++;
+            }
+            if (tokensz == (str - sp) && !strncmp(token, sp, tokensz)) { //Token matched
+                found = true;
+                break;
+            }
+            if (*str == '\0') break;
+            str++;
+        }
+        if (found) {
+            return true;
+        }
+    }
+    return false;
+}
diff --git a/tcejdb/ejdb.h b/tcejdb/ejdb.h
new file mode 100644 (file)
index 0000000..5944cc2
--- /dev/null
@@ -0,0 +1,311 @@
+/*
+ * File:   ejdb.h
+ * Author: adam
+ *
+ * Created on September 8, 2012, 10:09 PM
+ */
+
+#ifndef EJDB_H
+#define        EJDB_H
+
+#include "myconf.h"
+#include "bson.h"
+#include "tcutil.h"
+
+EJDB_EXTERN_C_START
+
+struct EJDB; /**> EJDB database object. */
+typedef struct EJDB EJDB;
+
+struct EJCOLL; /**> EJDB collection handle. */
+typedef struct EJCOLL EJCOLL;
+
+struct EJQ; /**< EJDB query. */
+typedef struct EJQ EJQ;
+
+typedef struct { /**< EJDB collection tuning options. */
+    bool large; /**< Large collection. It can be larger than 2GB. Default false */
+    bool compressed; /**< Collection records will be compressed with DEFLATE compression. Default: false */
+    int64_t records; /**< Expected records number in the collection. Default: 128K */
+    int cachedrecords; /**< Maximum number of cached records. Default: 0 */
+} EJCOLLOPTS;
+
+
+#define JBMAXCOLNAMELEN 128
+
+enum { /** Error codes */
+    JDBEINVALIDCOLNAME = 9000, /**< Invalid collection name. */
+    JDBEINVALIDBSON = 9001, /**< Invalid bson object. */
+    JDBEQINVALIDQCONTROL = 9002, /**< Invalid query control field starting with '$'. */
+    JDBEQINOPNOTARRAY = 9003, /**< $strand, $stror, $in, $nin, $bt keys requires not empty array value. */
+    JDBEMETANVALID = 9004, /**< Inconsistent database metadata. */
+    JDBEFPATHINVALID = 9005, /**< Invalid field path value. */
+    JDBEQINVALIDQRX = 9006, /**< Invalid query regexp value. */
+    JDBEQRSSORTING = 9007, /**< Result set sorting error. */
+    JDBQINVALID = 9008 /**< Query generic error. */
+};
+
+enum { /** Database open modes */
+    JDBOREADER = 1 << 0, /**< Open as a reader. */
+    JDBOWRITER = 1 << 1, /**< Open as a writer. */
+    JDBOCREAT = 1 << 2, /**< Create if db file not exists. */
+    JDBOTRUNC = 1 << 3, /**< Truncate db. */
+    JDBONOLCK = 1 << 4, /**< Open without locking. */
+    JDBOLCKNB = 1 << 5, /**< Lock without blocking. */
+    JDBOTSYNC = 1 << 6 /**< Synchronize every transaction. */
+};
+
+enum { /** Index modes, index types. */
+    JDIDXDROP = 1 << 0, /**< Drop index. */
+    JDIDXDROPALL = 1 << 1, /**< Drop index for all types. */
+    JDIDXOP = 1 << 2, /**< Optimize index. */
+    JDIDXREBLD = 1 << 3, /**< Rebuild index. */
+    JDIDXNUM = 1 << 4, /**< Number index. */
+    JDIDXSTR = 1 << 5, /**< String index.*/
+    JDIDXARR = 1 << 6, /**< Array token index. */
+};
+
+enum { /**> Query search mode flags in ejdbqrysearch() */
+    EJQRYCOUNT = 1 /**> Query only count(*) */
+};
+
+/**
+ * Get the message string corresponding to an error code.
+ * @param ecode `ecode' specifies the error code.
+ * @return The return value is the message string of the error code.
+ */
+EJDB_EXPORT const char *ejdberrmsg(int ecode);
+
+/**
+ * Get the last happened error code of a EJDB database object.
+ * @param jb
+ * @return The return value is the last happened error code.
+ */
+EJDB_EXPORT int ejdbecode(EJDB *jb);
+
+/**
+ * Create a EJDB database object. On error returns NULL.
+ * Created pointer must be freed by ejdbdel()
+ * @return The return value is the new EJDB database object or NULL if error.
+ */
+EJDB_EXPORT EJDB* ejdbnew(void);
+
+/**
+ * Delete database object. If the database is not closed, it is closed implicitly.
+ * Note that the deleted object and its derivatives can not be used anymore
+ * @param jb
+ */
+EJDB_EXPORT void ejdbdel(EJDB *jb);
+
+/**
+ * Close a table database object. If a writer opens a database but does not close it appropriately,
+ * the database will be broken.
+ * @param jb EJDB handle.
+ * @return If successful return true, otherwise return false.
+ */
+EJDB_EXPORT bool ejdbclose(EJDB *jb);
+
+/**
+ * Opens EJDB database.
+ * @param jb   Database object created with `ejdbnew'
+ * @param path Path to the database file.
+ * @param mode Open mode bitmask flags:
+ * `JDBOREADER` Open as a reader.
+ * `JDBOWRITER` Open as a writer.
+ * `JDBOCREAT` Create db if it not exists
+ * `JDBOTRUNC` Truncate db.
+ * `JDBONOLCK` Open without locking.
+ * `JDBOLCKNB` Lock without blocking.
+ * `JDBOTSYNC` Synchronize every transaction.
+ * @return
+ */
+EJDB_EXPORT bool ejdbopen(EJDB *jb, const char *path, int mode);
+
+/**
+ * Retrieve collection handle for collection specified `collname`.
+ * If collection with specified name does't exists it will return NULL.
+ * @param jb EJDB handle.
+ * @param colname Name of collection.
+ * @return If error NULL will be returned.
+ */
+EJDB_EXPORT EJCOLL* ejdbgetcoll(EJDB *jb, const char* colname);
+
+/**
+ * Same as ejdbgetcoll() but automatically creates new collection
+ * if it does't exists.
+ *
+ * @param jb EJDB handle.
+ * @param colname Name of collection.
+ * @param opts Options to be applied for newly created collection.
+ * @return Collection handle or NULL if error.
+ */
+EJDB_EXPORT EJCOLL* ejdbcreatecoll(EJDB *jb, const char* colname, EJCOLLOPTS *opts);
+
+/**
+ * Removes collections specified by `colname`
+ * @param jb EJDB handle.
+ * @param colname Name of collection.
+ * @param unlink It true the collection db file and all of its index files will be removed.
+ * @return If successful return true, otherwise return false.
+ */
+EJDB_EXPORT bool ejdbrmcoll(EJDB *jb, const char* colname, bool unlinkfile);
+
+/**
+ * Persist BSON object in the collection.
+ * If saved bson does't have _id primary key then `oid` will be set to generated bson _id,
+ * otherwise `oid` will be set to the current bson's _id field.
+ *
+ * @param coll JSON collection handle.
+ * @param bson BSON object id pointer.
+ * @return If successful return true, otherwise return false.
+ */
+EJDB_EXPORT bool ejdbsavebson(EJCOLL* coll, bson* bs, bson_oid_t* oid);
+
+/**
+ * Remove BSON object from collection.
+ * The `oid` argument should points the primary key (_id)
+ * of the bson record to be removed.
+ * @param coll JSON collection ref.
+ * @param oid BSON object id pointer.
+ * @return
+ */
+EJDB_EXPORT bool ejdbrmbson(EJCOLL* coll, bson_oid_t* oid);
+
+/**
+ * Load BSON object with specified 'oid'.
+ * If loaded bson is not NULL it must be freed by bson_del().
+ * @param coll
+ * @param oid
+ * @return BSON object if exists otherwise return NULL.
+ */
+EJDB_EXPORT bson* ejdbloadbson(EJCOLL* coll, const bson_oid_t* oid);
+
+/**
+ * Create query object.
+ * Sucessfully created queries must be destroyed with ejdbquerydel().
+ *
+ * EJDB queries inspired by MongoDB (mongodb.org) and follows same philosophy.
+ *
+ *  - Supported queries:
+ *      - Simple matching of String OR Number OR Array value:
+ *          -   {'bson.field.path' : 'val', ...}
+ *      - $not Negate operation.
+ *          -   {'json.field.path' : {'$not' : val}} //Field not equal to val
+ *          -   {'json.field.path' : {'$not' : {'$begin' : prefix}}} //Field not begins with val
+ *      - $begin String starts with prefix
+ *          -   {'json.field.path' : {'$begin' : prefix}}
+ *      - $gt, $gte (>, >=) and $lt, $lte for number types:
+ *          -   {'json.field.path' : {'$gt' : number}, ...}
+ *      - $bt Between for number types:
+ *          -   {'json.field.path' : {'$bt' : [num1, num2]}}
+ *      - $in String OR Number OR Array val matches to value in specified array:
+ *          -   {'json.field.path' : {'$in' : [val1, val2, val3]}}
+ *      - $nin - Not IN
+ *      - $strand String tokens OR String array val matches all tokens in specified array:
+ *          -   {'json.field.path' : {'$strand' : [val1, val2, val3]}}
+ *      - $stror String tokens OR String array val matches any token in specified array:
+ *          -   {'json.field.path' : {'$stror' : [val1, val2, val3]}}
+ *
+ *  NOTE: Negate operations: $not and $nin not using indexes
+ *  so they can be slow in comparison to other matching operations.
+ *
+ *  NOTE: Only one index can be used in search query operation.
+ *
+ *  QUERY HINTS (specified by `hints` argument):
+ *      - $max Maximum number in the result set
+ *      - $skip Number of skipped results in the result set
+ *      - $orderby Sorting order of query fields.
+ *          Eg: ORDER BY field1 ASC, field2 DESC
+ *          hints:    {
+ *                      "$orderby" : {
+ *                          "field1" : 1,
+ *                          "field2" : -1
+ *                      }
+ *                    }
+ *
+ * Many query examples can be found in `testejdb/t2.c` test case.
+ *
+ * @param EJDB database handle.
+ * @param qobj Main BSON query object.
+ * @param orqobjs Array of additional OR query objects (joined with OR predicate).
+ * @param orqobjsnum Number of OR query objects.
+ * @param hints BSON object with query hints.
+ * @return On success return query handle. On error returns NULL.
+ */
+EJDB_EXPORT EJQ* ejdbcreatequery(EJDB *jb, bson *qobj, bson *orqobjs, int orqobjsnum, bson *hints);
+
+/**
+ * Destroy query object created with ejdbcreatequery().
+ * @param q
+ */
+EJDB_EXPORT void ejdbquerydel(EJQ* q);
+
+/**
+ * Set index for JSON field in EJDB collection.
+ *
+ *  - Available index types:
+ *      - `JDIDXSTR` String index for JSON string values.
+ *      - `JDIDXNUM` Index for JSON number values.
+ *      - `JDIDXARR` Token index for JSON arrays and string values.
+ *
+ *  - One JSON field can have several indexes for different types.
+ *
+ *  - Available index operations:
+ *      - `JDIDXDROP` Drop index of specified type.
+ *              - Eg: flag = JDIDXDROP | JDIDXNUM (Drop number index)
+ *      - `JDIDXDROPALL` Drop index for all types.
+ *      - `JDIDXREBLD` Rebuild index of specified type.
+ *      - `JDIDXOP` Optimize index of specified type.
+ *
+ *  Examples:
+ *      - Set index for JSON path `addressbook.number` for strings and numbers:
+ *          `ejdbsetindex(ccoll, "album.number", JDIDXSTR | JDIDXNUM)`
+ *      - Set index for array:
+ *          `ejdbsetindex(ccoll, "album.tags", JDIDXARR)`
+ *      - Rebuild previous index:
+ *          `ejdbsetindex(ccoll, "album.tags", JDIDXARR | JDIDXREBLD)`
+ *
+ *   Many index examples can be found in `testejdb/t2.c` test case.
+ *
+ * @param coll Collection handle.
+ * @param ipath BSON field path.
+ * @param flags Index flags.
+ * @return
+ */
+EJDB_EXPORT bool ejdbsetindex(EJCOLL *coll, const char *ipath, int flags);
+
+/**
+ * Execute query against EJDB collection.
+ *
+ * @param jcoll EJDB database
+ * @param q Query handle created with ejdbcreatequery()
+ * @param count Output count pointer. Result set size will be stored into it.
+ * @param qflags Execution flag. If EJQRYCOUNT is set the only count of matching records will be computed
+ *         without resultset, this operation is analog of count(*) in SQL and can be faster than operations with resultsets.
+ * @param log Optional extended string to collect debug information during query execution, can be NULL.
+ * @return TCLIST with matched bson records data.
+ * If (qflags & EJQRYCOUNT) then NULL will be returned
+ * and only count reported.
+ */
+EJDB_EXPORT TCLIST* ejdbqrysearch(EJCOLL *jcoll, const EJQ *q, uint32_t *count, int qflags, TCXSTR *log);
+
+/**
+ * Synchronize content of a EJDB collection database with the file on device.
+ * @param jcoll EJDB collection.
+ * @return On success return true.
+ */
+EJDB_EXPORT bool ejdbsyncoll(EJCOLL *jcoll);
+
+/** Begin transaction for EJDB collection. */
+EJDB_EXPORT bool ejdbtranbegin(EJCOLL *coll);
+
+/** Commit transaction for EJDB collection. */
+EJDB_EXPORT bool ejdbtrancommit(EJCOLL *coll);
+
+/** Abort transaction for EJDB collection. */
+EJDB_EXPORT bool ejdbtranabort(EJCOLL *coll);
+
+EJDB_EXTERN_C_END
+
+#endif        /* EJDB_H */
+
diff --git a/tcejdb/ejdb_private.h b/tcejdb/ejdb_private.h
new file mode 100644 (file)
index 0000000..aa6fe45
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * File:   ejdb_private.h
+ * Author: adam
+ *
+ * Created on September 28, 2012, 11:43 AM
+ */
+
+#ifndef EJDB_PRIVATE_H
+#define        EJDB_PRIVATE_H
+
+#include "ejdb.h"
+
+#include "myconf.h"
+#include "tcutil.h"
+#include "tctdb.h"
+#include "tchdb.h"
+
+#include <assert.h>
+#include <pthread.h>
+
+EJDB_EXTERN_C_START
+
+#define BSON_IS_IDXSUPPORTED_TYPE(atype) (atype == BSON_STRING || \
+                                          atype == BSON_INT || atype == BSON_LONG || atype == BSON_DOUBLE || \
+                                          atype == BSON_SYMBOL || \
+                                          atype == BSON_ARRAY)
+
+struct EJDB {
+    EJCOLL *cdbs; /*> Collection DBs for JSON collections. */
+    int cdbsnum; /*> Count of collection DB. */
+    TCTDB *metadb; /*> Metadata DB. */
+    void *mmtx; /*> Mutex for method */
+};
+
+struct EJCOLL { /**> EJDB Collection. */
+    char *cname; /**> Collection name. */
+    int cnamesz; /**> Collection name length. */
+    TCTDB *tdb; /**> Collection TCTDB. */
+    EJDB *jb; /**> Database handle. */
+    void *mmtx; /*> Mutex for method */
+};
+
+enum { //Query field flags
+    // Comparison flags
+    EJCOMPGT = 1, //Comparison GT
+    EJCOMPGTE = 1 << 1, //Comparison GTE
+    EJCOMPLT = 1 << 2, //Comparison LT
+    EJCOMPLTE = 1 << 3, //Comparison LTE
+    EJCONDSTARTWITH = 1 << 4, //Starts with
+
+    EJFEXCLUDED = 1 << 5, //If query field excluded from matching
+    EJFNOINDEX = 1 << 6, //Do not use index for field
+    EJFORDERUSED = 1 << 7 //This ordering field was used
+};
+
+
+enum { //Query flags
+    EJQINTERNAL = 1 //Internal query object used in _ejdbqrysearch
+};
+
+struct EJQF { /**> Matching field and status */
+    char *fpath; /**>JSON field path */
+    int fpathsz; /**>JSON field path size */
+    char *expr; /**> Query operand expression, string or TCLIST data */
+    int exprsz; /**> Size of query operand expression */
+    int64_t exprlongval; /** Integer value represeintation */
+    double exprdblval; /** Double value representation */
+    TCLIST *exprlist; /** List representation of expression */
+    regex_t *regex; /**> Regular expression object */
+    int tcop; /**> Matching operation eg. TDBQCSTREQ */
+    bool negate; /**> Negate expression */
+    uint32_t flags; /**> Various field matching|status flags */
+    int order; /**> 0 no order, 1 ASC, -1 DESC */
+    int orderseq; /**> Seq number for order fields */
+    bson_type ftype; /**> BSON field type */
+    const TDBIDX *idx; /**> Column index for this field if exists */
+    bson *idxmeta; /**> Index metainfo */
+};
+typedef struct EJQF EJQF;
+
+struct EJQ { /**> Query object. */
+    TCMAP *qobjmap; /**> Mapping of field path (char*) -> *EJQF */
+    EJQ *orqobjs; /** OR Query objects */
+    int orqobjsnum; /** Number of OR query objects */
+    bson *hints; /**> Hints bson object */
+    uint32_t skip; /**> Number of records to skip. */
+    uint32_t max; /**> Max number of results */
+    uint32_t flags; /**> Control flags */
+    const EJQF *lastmatchedorqf; /**> Reference to the last matched or query field */
+};
+
+
+#define JDBIDKEYNAME "_id"  /**> Name of PK _id field in BSONs */
+#define JDBIDKEYNAMEL 3
+
+#define JDBCOLBSON "$"  /**> TCDB colname with BSON byte data */
+#define JDBCOLBSONL 1  /**> TCDB colname with BSON byte data columen len */
+
+
+
+EJDB_EXTERN_C_END
+
+#endif        /* EJDB_PRIVATE_H */
+
diff --git a/tcejdb/ejdbutl.h b/tcejdb/ejdbutl.h
new file mode 100644 (file)
index 0000000..195d339
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * File:   ejdbutl.h
+ * Author: adam
+ *
+ * Created on October 15, 2012, 1:26 PM
+ */
+
+#ifndef EJDBUTL_H
+#define        EJDBUTL_H
+
+#include "myconf.h"
+#include "tcutil.h"
+
+EJDB_EXTERN_C_START
+
+/**
+ * A stable, adaptive, iterative mergesort that requires far fewer than
+ * n lg(n) comparisons when running on partially sorted arrays, while
+ * offering performance comparable to a traditional mergesort when run
+ * on random arrays.  Like all proper mergesorts, this sort is stable and
+ * runs O(n log n) time (worst case).  In the worst case, this sort requires
+ * temporary storage space for n/2 object references; in the best case,
+ * it requires only a small constant amount of space.
+ *
+ * This implementation was adapted from Josh Bloch's Java implementation of
+ * Tim Peters's list sort for Python, which is described in detail here:
+ *
+ *   http://svn.python.org/projects/python/trunk/Objects/listsort.txt
+ *
+ * Tim's C code may be found here:
+ *
+ *   http://svn.python.org/projects/python/trunk/Objects/listobject.c
+ *
+ * Josh's (Apache 2.0 Licenced) Java code may be found here:
+ *
+ *   http://gee.cs.oswego.edu/cgi-bin/viewcvs.cgi/jsr166/src/main/java/util/TimSort.java?view=co
+ *
+ * The underlying techniques are described in this paper (and may have
+ * even earlier origins):
+ *
+ *  "Optimistic Sorting and Information Theoretic Complexity"
+ *  Peter McIlroy
+ *  SODA (Fourth Annual ACM-SIAM Symposium on Discrete Algorithms),
+ *  pp 467-474, Austin, Texas, 25-27 January 1993.
+ *
+ * While the API to this class consists solely of static methods, it is
+ * (privately) instantiable; a TimSort instance holds the state of an ongoing
+ * sort, assuming the input array is large enough to warrant the full-blown
+ * TimSort. Small arrays are sorted in place, using a binary insertion sort.
+ *
+ * C implementation:
+ *  https://github.com/patperry/timsort
+ *
+ *
+ * @param a the array to be sorted
+ * @param nel the length of the array
+ * @param c the comparator to determine the order of the sort
+ * @param width the element width
+ * @param opaque data for the comparator function
+ * @param opaque data for the comparator function
+ *
+ * @author Josh Bloch
+ * @author Patrick O. Perry
+ */
+
+int ejdbtimsort(void *a, size_t nel, size_t width,
+        int (*c) (const void *, const void *, void *opaque), void *opaque);
+
+int ejdbtimsortlist(TCLIST *list,
+        int (*compar) (const TCLISTDATUM*, const TCLISTDATUM*, void *opaque), void *opaque);
+
+
+EJDB_EXTERN_C_END
+
+#endif        /* EJDBUTL_H */
+
diff --git a/tcejdb/encoding.c b/tcejdb/encoding.c
new file mode 100644 (file)
index 0000000..4bf405f
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2009-2012 10gen, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Portions Copyright 2001 Unicode, Inc.
+ *
+ * Disclaimer
+ *
+ * This source code is provided as is by Unicode, Inc. No claims are
+ * made as to fitness for any particular purpose. No warranties of any
+ * kind are expressed or implied. The recipient agrees to determine
+ * applicability of information provided. If this file has been
+ * purchased on magnetic or optical media from Unicode, Inc., the
+ * sole remedy for any claim will be exchange of defective media
+ * within 90 days of receipt.
+ *
+ * Limitations on Rights to Redistribute This Code
+ *
+ * Unicode, Inc. hereby grants the right to freely use the information
+ * supplied in this file in the creation of products supporting the
+ * Unicode Standard, and to make copies of this file in any form
+ * for internal or external distribution as long as this notice
+ * remains attached.
+ */
+
+
+#include "bson.h"
+#include "encoding.h"
+
+/*
+ * Index into the table below with the first byte of a UTF-8 sequence to
+ * get the number of trailing bytes that are supposed to follow it.
+ */
+static const char trailingBytesForUTF8[256] = {
+    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, 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, 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, 0, 0, 0,
+    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5
+};
+
+/* --------------------------------------------------------------------- */
+
+/*
+ * Utility routine to tell whether a sequence of bytes is legal UTF-8.
+ * This must be called with the length pre-determined by the first byte.
+ * The length can be set by:
+ *  length = trailingBytesForUTF8[*source]+1;
+ * and the sequence is illegal right away if there aren't that many bytes
+ * available.
+ * If presented with a length > 4, this returns 0.  The Unicode
+ * definition of UTF-8 goes up to 4-byte sequences.
+ */
+static int isLegalUTF8(const unsigned char *source, int length) {
+    unsigned char a;
+    const unsigned char *srcptr = source + length;
+    switch (length) {
+        default:
+            return 0;
+            /* Everything else falls through when "true"... */
+        case 4:
+            if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return 0;
+        case 3:
+            if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return 0;
+        case 2:
+            if ((a = (*--srcptr)) > 0xBF) return 0;
+            switch (*source) {
+                    /* no fall-through in this inner switch */
+                case 0xE0:
+                    if (a < 0xA0) return 0;
+                    break;
+                case 0xF0:
+                    if (a < 0x90) return 0;
+                    break;
+                case 0xF4:
+                    if (a > 0x8F) return 0;
+                    break;
+                default:
+                    if (a < 0x80) return 0;
+            }
+        case 1:
+            if (*source >= 0x80 && *source < 0xC2) return 0;
+            if (*source > 0xF4) return 0;
+    }
+    return 1;
+}
+
+/* If the name is part of a db ref ($ref, $db, or $id), then return true. */
+static int bson_string_is_db_ref(const unsigned char *string, const int length) {
+    int result = 0;
+
+    if (length >= 4) {
+        if (string[1] == 'r' && string[2] == 'e' && string[3] == 'f')
+            result = 1;
+    } else if (length >= 3) {
+        if (string[1] == 'i' && string[2] == 'd')
+            result = 1;
+        else if (string[1] == 'd' && string[2] == 'b')
+            result = 1;
+    }
+
+    return result;
+}
+
+static int bson_validate_string(bson *b, const unsigned char *string,
+        const int length, const char check_utf8, const char check_dot,
+        const char check_dollar) {
+
+    int position = 0;
+    int sequence_length = 1;
+
+    if (check_dollar && string[0] == '$') {
+        if (!bson_string_is_db_ref(string, length))
+            b->err |= BSON_FIELD_INIT_DOLLAR;
+    }
+
+    while (position < length) {
+        if (check_dot && *(string + position) == '.') {
+            b->err |= BSON_FIELD_HAS_DOT;
+        }
+
+        if (check_utf8) {
+            sequence_length = trailingBytesForUTF8[*(string + position)] + 1;
+            if ((position + sequence_length) > length) {
+                b->err |= BSON_NOT_UTF8;
+                return BSON_ERROR;
+            }
+            if (!isLegalUTF8(string + position, sequence_length)) {
+                b->err |= BSON_NOT_UTF8;
+                return BSON_ERROR;
+            }
+        }
+        position += sequence_length;
+    }
+
+    return BSON_OK;
+}
+
+int bson_check_string(bson *b, const char *string,
+        const int length) {
+
+    return bson_validate_string(b, (const unsigned char *) string, length, 1, 0, 0);
+}
+
+int bson_check_field_name(bson *b, const char *string,
+        const int length, int check_dot, int check_dollar) {
+
+    return bson_validate_string(b, (const unsigned char *) string, length, 1, check_dot, check_dollar);
+}
diff --git a/tcejdb/encoding.h b/tcejdb/encoding.h
new file mode 100644 (file)
index 0000000..bfc4127
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2009-2012 10gen, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef BSON_ENCODING_H_
+#define BSON_ENCODING_H_
+
+EJDB_EXTERN_C_START
+
+/**
+ * Check that a field name is valid UTF8, does not start with a '$',
+ * and contains no '.' characters. Set bson bit field appropriately.
+ * Note that we don't need to check for '\0' because we're using
+ * strlen(3), which stops at '\0'.
+ *
+ * @param b The bson object to which field name will be appended.
+ * @param string The field name as char*.
+ * @param length The length of the field name.
+ *
+ * @return BSON_OK if valid UTF8 and BSON_ERROR if not. All BSON strings must be
+ *     valid UTF8. This function will also check whether the string
+ *     contains '.' or starts with '$', since the validity of this depends on context.
+ *     Set the value of b->err appropriately.
+ */
+int bson_check_field_name(bson *b, const char *string,
+        const int length, int check_dot, int check_dollar);
+
+/**
+ * Check that a string is valid UTF8. Sets the buffer bit field appropriately.
+ *
+ * @param b The bson object to which string will be appended.
+ * @param string The string to check.
+ * @param length The length of the string.
+ *
+ * @return BSON_OK if valid UTF-8; otherwise, BSON_ERROR.
+ *     Sets b->err on error.
+ */
+bson_bool_t bson_check_string(bson *b, const char *string,
+        const int length);
+
+EJDB_EXTERN_C_END
+#endif
diff --git a/tcejdb/lab/calccomp b/tcejdb/lab/calccomp
new file mode 100755 (executable)
index 0000000..9780cf8
--- /dev/null
@@ -0,0 +1,118 @@
+#! /usr/bin/perl
+
+#================================================================
+# calccomp
+# Measure elapsed time and database size of compression algos
+#================================================================
+
+use strict;
+use warnings;
+use Time::HiRes qw(gettimeofday);
+
+use constant {
+    WORDFILE => '/usr/share/dict/words',
+    TSVFILE => 'words.tsv',
+    NULLFILE => '/dev/null',
+    BNUM => 262144,
+    LCNUM => 8192,
+    TESTCOUNT => 20,
+    REMOVETOP => 2,
+    REMOVEBOTTOM => 8,
+};
+
+open(IN, '<' . WORDFILE) or die(WORDFILE . ' could not be opened');
+open(OUT, '>' . TSVFILE) or die(TSVFILE . ' could not be opened');
+while(defined(my $word = <IN>)){
+    $word =~ s/[\t\r\n]//g;
+    next if(length($word) < 1);
+    printf OUT ("%s\t%d\n", $word, int(rand(10000)));
+}
+close(OUT);
+close(IN);
+
+my @commands = (
+    './tchmgr create casket-hash ' . BNUM . ' ; ./tchmgr importtsv casket-hash ' . TSVFILE,
+    './tchmgr list casket-hash >' . NULLFILE,
+    './tcbmgr create casket-btree ' . LCNUM . ' ; ./tcbmgr importtsv casket-btree ' . TSVFILE,
+    './tcbmgr list casket-btree >' . NULLFILE,
+    './tcbmgr create -td casket-bt-td ' . LCNUM . ' ; ./tcbmgr importtsv casket-bt-td ' . TSVFILE,
+    './tcbmgr list casket-bt-td >' . NULLFILE,
+    './tcbmgr create -tb casket-bt-tb ' . LCNUM . ' ; ./tcbmgr importtsv casket-bt-tb ' . TSVFILE,
+    './tcbmgr list casket-bt-tb >' . NULLFILE,
+    './tcbmgr create -tt casket-bt-tt ' . LCNUM . ' ; ./tcbmgr importtsv casket-bt-tt ' . TSVFILE,
+    './tcbmgr list casket-bt-tt >' . NULLFILE,
+    './tcbmgr create -tx casket-bt-tx ' . LCNUM . ' ; ./tcbmgr importtsv casket-bt-tx ' . TSVFILE,
+    './tcbmgr list casket-bt-tx >' . NULLFILE,
+    );
+
+my @names = (
+    'casket-hash',
+    'casket-btree',
+    'casket-bt-td',
+    'casket-bt-tb',
+    'casket-bt-tt',
+    'casket-bt-tx',
+    );
+
+foreach my $name (@names){
+    my @paths = glob("$name*");
+    foreach my $path (@paths){
+        unlink($path);
+    }
+}
+
+my @table;
+foreach my $command (@commands){
+    system('sync ; sync');
+    $ENV{'HIDEPRGR'} = 1;
+    my @result;
+    for(my $i = 0; $i < TESTCOUNT; $i++){
+        my $stime = gettimeofday();
+        system("$command >/dev/null 2>&1");
+        $stime = gettimeofday() - $stime;
+        printf("%s\t%d\t%0.5f\n", $command, $i + 1, $stime);
+        push(@result, $stime);
+    }
+    @result = sort { $a <=> $b } @result;
+    for(my $i = 0; $i < REMOVETOP; $i++){
+        shift(@result);
+    }
+    for(my $i = 0; $i < REMOVEBOTTOM; $i++){
+        pop(@result);
+    }
+    my $sum = 0;
+    foreach my $result (@result){
+        $sum += $result;
+    }
+    my $avg = $sum / scalar(@result);
+    push(@table, [$command, $avg]);
+}
+
+printf("\n\nRESULT\n\n");
+foreach my $row (@table){
+    printf("%s\t%0.5f\n", $$row[0], $$row[1]);
+}
+printf("\n");
+
+my @sizes;
+foreach my $name (@names){
+    my @sbuf = stat($name);
+    my $size = $sbuf[7];
+    printf("%s\t%s\n", $name, $size);
+    push(@sizes, $size);
+}
+my @sbuf = stat(TSVFILE);
+my $size = $sbuf[7];
+printf("\n");
+
+printf("%s,%.5f,%.5f,%d\n", "ORIGINAL", 0, 0, $size);
+printf("%s,%.5f,%.5f,%d\n", "HASH", $table[0][1], $table[1][1], $sizes[0]);
+printf("%s,%.5f,%.5f,%d\n", "BTREE", $table[2][1], $table[3][1], $sizes[1]);
+printf("%s,%.5f,%.5f,%d\n", "BTREE-DEFLATE", $table[4][1], $table[5][1], $sizes[2]);
+printf("%s,%.5f,%.5f,%d\n", "BTREE-BZIP2", $table[6][1], $table[7][1], $sizes[3]);
+printf("%s,%.5f,%.5f,%d\n", "BTREE-TCBS", $table[8][1], $table[9][1], $sizes[4]);
+printf("%s,%.5f,%.5f,%d\n", "BTREE-EXCODEC", $table[10][1], $table[11][1], $sizes[5]);
+
+
+
+# END OF FILE
diff --git a/tcejdb/lab/datechange b/tcejdb/lab/datechange
new file mode 100755 (executable)
index 0000000..4fd0491
--- /dev/null
@@ -0,0 +1,56 @@
+#! /bin/sh
+
+#================================================================
+# datechange
+# Replace date expressions
+#================================================================
+
+
+# set variables
+LANG=C
+LC_ALL=C
+export LANG LC_ALL
+year=`date '+%Y'`
+date=`date '+%Y-%m-%d'`
+fulldate=`date -R`
+regexsrc='\.(h|c|cc|cpp|cxx|java|pl|pm|pod|xs|rb|rd|py|lua|idl)$'
+regexman='\.[0-9]$'
+regexhtml='\.html$'
+
+
+# edit source files
+find . -type f | egrep "$regexsrc" |
+while read file
+do
+  echo "$file"
+  sed "/opyright/ s/\\(20[0-9][0-9]\\)-\\(20[0-9][0-9]\\)/\\1-$year/" "$file" > "$file.tmp"
+  mv -f "$file.tmp" "$file"
+done
+
+
+# edit manual files
+find . -type f | egrep "$regexman" |
+while read file
+do
+  echo "$file"
+  sed "/\\.TH/ s/\\(20[0-9][0-9]\\)-\\([0-9][0-9]\\)-\\([0-9][0-9]\\)/$date/" "$file" > "$file.tmp"
+  mv -f "$file.tmp" "$file"
+done
+
+
+# edit HTML files
+find . -type f | egrep "$regexhtml" |
+while read file
+do
+  echo "$file"
+  sed -e "/opyright/ s/\\(20[0-9][0-9]\\)-\\(20[0-9][0-9]\\)/\\1-$year/" -e "/<div class=\"note\">/ s/Last Update: *\\([A-Z][a-z][a-z], [0-9][0-9] [A-Z][a-z][a-z] 20[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9] +[0-9][0-9]*\\)/Last Update: $fulldate/" "$file" > "$file.tmp"
+  mv -f "$file.tmp" "$file"
+done
+
+
+# exit normally
+exit 0
+
+
+
+# END OF FILE
diff --git a/tcejdb/lab/diffcheck b/tcejdb/lab/diffcheck
new file mode 100755 (executable)
index 0000000..b84284e
--- /dev/null
@@ -0,0 +1,45 @@
+#! /bin/sh
+
+#================================================================
+# diffcheck
+# List files different from ones of another version
+#================================================================
+
+
+# set variables
+LANG=C
+LC_ALL=C
+export LANG LC_ALL
+regex='\.(h|c|cc|cpp|cxx|java|pl|pm|pod|rb|rd|lua|[1-9]|html|txt)$'
+
+
+# check arguments
+if [ $# != 1 ]
+then
+  printf 'diffcheck: usage: diffcheck directory_of_oldversion\n' 1>&2
+  exit 1
+fi
+
+
+# diff files
+find . -type f | egrep $regex |
+while read file
+do
+  old=`printf '%s\n' "$file" | sed 's/^\.\///'`
+  printf 'Checking %s and %s ... ' "$file" "$1/$old"
+  res=`diff -q "$file" "$1/$old"`
+  if [ -z "$res" ]
+  then
+    printf 'same\n'
+  else
+    printf '### !!! DIFFERENT !!! ###\n'
+  fi
+done
+
+
+# exit normally
+exit 0
+
+
+
+# END OF FILE
diff --git a/tcejdb/lab/htmltotsv b/tcejdb/lab/htmltotsv
new file mode 100755 (executable)
index 0000000..3221a84
--- /dev/null
@@ -0,0 +1,102 @@
+#! /usr/bin/perl
+
+#================================================================
+# htmldoctotsv
+# Generate TSV data from HTML documents
+#================================================================
+
+use strict;
+use warnings;
+use Encode;
+use Cwd 'realpath';
+
+my $path = '.';
+my $mode_kv = 0;
+my $mode_hex = 0;
+
+for(my $i = 0; $i < scalar(@ARGV); $i++){
+    my $arg = $ARGV[$i];
+    if($arg =~ /^-/){
+        if($arg eq '-kv'){
+            $mode_kv = 1;
+        } elsif($arg eq '-x'){
+            $mode_hex = 1;
+        }
+    } else {
+        $path = $arg;
+    }
+}
+
+sub trimhtml {
+    my $text = shift;
+    $text =~ s/<[^>]*>/ /g;
+    $text =~ s/&lt;/</g;
+    $text =~ s/&gt;/>/g;
+    $text =~ s/&quot;/"/g;
+    $text =~ s/&nbsp;/ /g;
+    $text =~ s/&amp;/\&/g;
+    $text =~ s/\s+/ /g;
+    $text =~ s/^ *//;
+    $text =~ s/ *$//;
+    return $text;
+}
+
+$ENV{LANG} = "C";
+$ENV{LC_ALL} = "C";
+open(my $lfh, "find $path -type f -iregex '.*\.html?' -print | sort |") || die("could not open");
+my $id = 0;
+while(defined($path = <$lfh>)){
+    chomp($path);
+    $path = realpath($path);
+    next if(!defined($path));
+    my @stat = stat($path);
+    next if(scalar(@stat) < 10);
+    my $mtime = $stat[9];
+    open(my $ifh, "<$path") || next;
+    my $encname = "UTF-8";
+    my @lines;
+    while(defined(my $line = <$ifh>)){
+        push(@lines, $line);
+        if($line =~ /<meta.*content-type.*charset=/i){
+            $line =~ s/.*charset=?//i;
+            $line =~ s/[^-_a-zA-Z0-9].*//;
+            chomp($line);
+            $encname = $line if(length($line) > 0);
+        }
+    }
+    my $text = join('', @lines);
+    $text = encode("UTF-8", decode($encname, $text)) if($encname ne "UTF-8");
+    $text =~ s/<!--.*?-->//is;
+    my $title = "";
+    if($text =~ /<title[^>]*>[^<]*<\/title>/i){
+        $title = $text;
+        $title =~ s/.*<title[^>]*>([^<]*)<\/title>.*/$1/is;
+        $title = trimhtml($title);
+    }
+    $text =~ s/.*<body[^>]*>(.*)<\/body>.*/$1/is;
+    $text =~ s/<style[^>]*>.*?<\/style>//is;
+    $text =~ s/<script[^>]*>.*?<\/script>//is;
+    $text = trimhtml($text);
+    next if(length($title) < 1 && length($text) < 1);
+    $id++;
+    my $key = $mode_hex ? sprintf("%X", $id) : $id;
+    printf STDERR ("%d: saving: %s\n", $id, $path);
+    if($mode_kv){
+        $text = $title . " " . $text if(length($title) > 0);
+        printf("%s\t%s\n", $key, $text);
+    } else {
+        printf("%s", $key);
+        printf("\turl\t%s", $path);
+        printf("\tsize\t%s", length($text));
+        printf("\tmtime\t%s", $mtime);
+        printf("\ttitle\t%s", $title) if(length($title) > 0);
+        printf("\tbody\t%s", $text);
+        printf("\n");
+    }
+    close($ifh);
+}
+close($lfh);
+
+
+
+# END OF FILE
diff --git a/tcejdb/lab/magic b/tcejdb/lab/magic
new file mode 100644 (file)
index 0000000..e65091a
--- /dev/null
@@ -0,0 +1,19 @@
+# Tokyo Cabinet magic data
+0       string    ToKyO\ CaBiNeT\n   Tokyo Cabinet
+>14     string    x                  \b (%s)
+>32     byte      0                  \b, Hash
+>32     byte      1                  \b, B+ tree
+>32     byte      2                  \b, Fixed-length
+>32     byte      3                  \b, Table
+>33     byte      &1                 \b, [open]
+>33     byte      &2                 \b, [fatal]
+>34     byte      x                  \b, apow=%d
+>35     byte      x                  \b, fpow=%d
+>36     byte      &1                 \b, [large]
+>36     byte      &2                 \b, [deflate]
+>36     byte      &4                 \b, [bzip]
+>36     byte      &8                 \b, [tcbs]
+>36     byte      &16                \b, [excodec]
+>40     lelong    x                  \b, bnum=%d
+>48     lelong    x                  \b, rnum=%d
+>56     lelong    x                  \b, fsiz=%d
diff --git a/tcejdb/lab/printenv.cgi b/tcejdb/lab/printenv.cgi
new file mode 100755 (executable)
index 0000000..6f8f3c9
--- /dev/null
@@ -0,0 +1,27 @@
+#! /bin/sh
+
+#================================================================
+# printenv.cgi
+# Print CGI environment variables
+#================================================================
+
+
+# set variables
+LANG=C
+LC_ALL=C
+export LANG LC_ALL
+
+
+# output the result
+printf 'Content-Type: text/plain\r\n'
+printf '\r\n'
+
+printenv | sort
+
+
+# exit normally
+exit 0
+
+
+
+# END OF FILE
diff --git a/tcejdb/lab/stepcount b/tcejdb/lab/stepcount
new file mode 100755 (executable)
index 0000000..a0092f3
--- /dev/null
@@ -0,0 +1,26 @@
+#! /bin/sh
+
+#================================================================
+# stepcount
+# Find files including dispensable tab characters
+#================================================================
+
+
+# set variables
+LANG=C
+LC_ALL=C
+export LANG LC_ALL
+regex='(\.h|\.c|\.cc|\.java|\.pl|\.pm|\.xs|\.rb|\.js)$'
+
+
+# count steps
+files=`find . -type f | egrep $regex`
+wc -l $files | sort -n
+
+
+# exit normally
+exit 0
+
+
+
+# END OF FILE
diff --git a/tcejdb/lab/stopwatch b/tcejdb/lab/stopwatch
new file mode 100755 (executable)
index 0000000..a583425
--- /dev/null
@@ -0,0 +1,61 @@
+#! /usr/bin/perl
+
+#================================================================
+# stopwatch
+# Measure elapsed time of some test commands.
+#================================================================
+
+use strict;
+use warnings;
+use Time::HiRes qw(gettimeofday);
+
+use constant {
+    TESTCOUNT => 20,
+    REMOVETOP => 2,
+    REMOVEBOTTOM => 8,
+};
+
+my @commands = (
+                "./tchtest write casket 1000000 1000000",
+                "./tchtest read casket",
+                "./tchtest rcat casket 1000000",
+                "./tcbtest write casket 1000000",
+                "./tcbtest read casket",
+                "./tcbtest rcat -lc 256 -nc 128 casket 100000",
+                );
+
+my @table;
+foreach my $command (@commands){
+    system("sync ; sync");
+    my @result;
+    for(my $i = 0; $i < TESTCOUNT; $i++){
+        my $stime = gettimeofday();
+        system("$command >/dev/null 2>&1");
+        $stime = gettimeofday() - $stime;
+        printf("%s\t%d\t%0.5f\n", $command, $i + 1, $stime);
+        push(@result, $stime);
+    }
+    @result = sort { $a <=> $b } @result;
+    for(my $i = 0; $i < REMOVETOP; $i++){
+        shift(@result);
+    }
+    for(my $i = 0; $i < REMOVEBOTTOM; $i++){
+        pop(@result);
+    }
+    my $sum = 0;
+    foreach my $result (@result){
+        $sum += $result;
+    }
+    my $avg = $sum / scalar(@result);
+    push(@table, [$command, $avg]);
+}
+
+printf("\n\nRESULT\n");
+foreach my $row (@table){
+    printf("%s\t%0.5f\n", $$row[0], $$row[1]);
+}
+printf("\n");
+
+
+
+# END OF FILE
diff --git a/tcejdb/lab/tabcheck b/tcejdb/lab/tabcheck
new file mode 100755 (executable)
index 0000000..ba6698e
--- /dev/null
@@ -0,0 +1,43 @@
+#! /bin/sh
+
+#================================================================
+# tabcheck
+# Find files including dispensable tab and space characters
+#================================================================
+
+
+# set variables
+LANG=C
+LC_ALL=C
+export LANG LC_ALL
+regex='\.(h|c|cc|cpp|cxx|java|pl|pm|pod|rb|rd|lua)$'
+tabcode=`printf '\t'`
+
+
+# find tab
+find . -type f | egrep $regex |
+while read file
+do
+  printf 'Checking %s ... ' $file
+  err=0
+  if grep "$tabcode" $file > /dev/null
+  then
+    printf '### !!! TAB FOUND !!! ###'
+    err=1
+  fi
+  if grep ' $' $file > /dev/null
+  then
+    printf '### !!! TAILING SPACE FOUND !!! ###'
+    err=1
+  fi
+  [ "$err" = 0 ] && printf 'ok'
+  printf '\n'
+done
+
+
+# exit normally
+exit 0
+
+
+
+# END OF FILE
diff --git a/tcejdb/lab/wgettsv b/tcejdb/lab/wgettsv
new file mode 100755 (executable)
index 0000000..21b718a
--- /dev/null
@@ -0,0 +1,239 @@
+#! /usr/bin/ruby -w
+
+#================================================================
+# wgettsv
+# Collect WWW resources and generate TSV data
+#================================================================
+
+require 'open-uri'
+require 'iconv'
+require 'kconv'
+require 'date'
+require 'time'
+require 'cgi'
+
+def main
+  seeds = []
+  hist = {}
+  filters = []
+  max = 1 << 30
+  lim = 1 << 20
+  wait = 0
+  ndf = false
+  i = 0
+  while i < ARGV.length
+    if seeds.length < 1 && ARGV[i] =~ /^-/
+      if ARGV[i] == '-allow'
+        usage if (i += 1) >= ARGV.length
+        regex = Regexp::new(ARGV[i])
+        filters.push([true, regex]) if regex
+      elsif ARGV[i] == '-deny'
+        usage if (i += 1) >= ARGV.length
+        regex = Regexp::new(ARGV[i])
+        filters.push([false, regex]) if regex
+      elsif ARGV[i] == '-max'
+        usage if (i += 1) >= ARGV.length
+        max = ARGV[i].to_i
+      elsif ARGV[i] == '-lim'
+        usage if (i += 1) >= ARGV.length
+        lim = ARGV[i].to_i
+      elsif ARGV[i] == '-wait'
+        usage if (i += 1) >= ARGV.length
+        wait = ARGV[i].to_f
+      elsif ARGV[i] == '-ndf'
+        ndf = true
+      else
+        usage
+      end
+    else
+      if ARGV[i] =~ /^http:\/\//i
+        seeds.push(ARGV[i])
+        hist[ARGV[i]] = true
+      else
+        usage
+      end
+    end
+    i += 1
+  end
+  usage if seeds.length < 1
+  if !ndf
+    filters.push([false, /\.(txt|text|asc|c|cc|cxx|cpp|h|hxx|hpp|in)$/i])
+    filters.push([false, /\.(css|js|csv|tsv|log|md5|crc|conf|ini|inf|lnk|sys|tmp|bak)$/i])
+    filters.push([false, /\.(xml|xsl|xslt|rdf|rss|dtd|sgml|sgm)$/i])
+    filters.push([false, /\.(pgp|sig|cer|csr|pem|key|b64|uu|uue|[0-9])$/i])
+    filters.push([false, /\.(rtf|pdf|ps|eps|ai|doc|xls|ppt|sxw|sxc|sxi|xdw|jtd|oas|swf)$/i])
+    filters.push([false, /\.(zip|tar|tgz|gz|bz2|tbz2|z|lha|lzh)?$/i])
+    filters.push([false, /\.(7z|lzo|lzma|cpio|shar|cab|rar|sit|ace|hqx)?$/i])
+    filters.push([false, /\.(bin|o|a|so|exe|dll|lib|obj|ocx|class|jar|war)?$/i])
+    filters.push([false, /\.(rpm|deb|qdb|qdb|dbx|dbf|dat|msi|bat|com|iso)?$/i])
+    filters.push([false, /\.(png|gif|jpg|jpeg|tif|tiff|bmp|ico|pbm|pgm|ppm|xbm|xpm|dvi)$/i])
+    filters.push([false, /\.(au|snd|mid|midi|kar|smf|mp2|mp3|m3u|wav|wma|wmp|asx|at3|aif)$/i])
+    filters.push([false, /\.(mpg|mpeg|qt|mov|avi|wmv|wvx|asf|ram|rm)$/i])
+    filters.push([false, /\.(tch|tdb|tdf|tct)$/i])
+    filters.push([false, /\.idx\./i])
+    filters.push([false, /(core|casket|Makefile|README|NEWS|COPYING|LISENCE)($|\/)/i])
+  end
+  return proc(seeds, hist, filters, max, lim, wait) ? 0 : 1
+end
+
+def usage
+  STDERR.printf("%s: collect WWW resources and generate TSV data\n", $progname)
+  STDERR.printf("\n")
+  STDERR.printf("usage:\n")
+  STDERR.printf("  %s [-allow regex] [-deny regex] [-max num] [-lim num] [-wait num]" +
+                " url ...\n", $progname)
+  STDERR.printf("\n")
+  exit(1)
+end
+
+def proc(seeds, hist, filters, max, lim, wait)
+  cnt = 0
+  while (url = seeds.shift) && cnt < max
+    STDERR.printf("%d: getting: %s\n", cnt + 1, url)
+    begin
+      opts = {}
+      OpenURI.open_uri(url, 0, 0, opts) do |sio|
+        baseuri = sio.base_uri
+        if baseuri && baseuri.to_s != url
+          url = baseuri.to_s
+          hist[url] = true
+        end
+        size = sio.size
+        raise "invalid size" if size > lim || size < 3
+        type = sio.content_type
+        type = "text/plain" if !type
+        str = sio.read
+        head = str[0,2048]
+        if (head[0] == 0xfe && head[1] == 0xff) || (head[0] == 0xff && head[1] == 0xfe)
+          str = Kconv::kconv(str, Kconv::UTF8, Kconv::UTF16)
+          charset = "UTF-8"
+        elsif str.include?(0)
+          raise "binary data"
+        end
+        raise "not HTML" if type != "text/html" && head !~ /<html/i && head !~ /<body/i
+        if !charset && head =~ /<\?xml.*encoding=("|')?[-_a-zA-Z0-9]+("|')/im
+          charset = head.gsub(/.*<\?xml.*encoding=["']?([-_a-zA-Z0-9]+)["']?.*/im, '\1')
+        end
+        if !charset && head =~ /<meta.*content-type.*charset=[-_a-zA-Z0-9]+/im
+          charset = head.gsub(/.*<meta.*content-type.*charset=([-_a-zA-Z0-9]+).*/im, '\1')
+        end
+        charset = sio.charset if !charset || charset.length < 1
+        if charset && charset.length > 0
+          if charset !~ /^UTF-?8$/i
+            begin
+              nstr = Iconv.conv("UTF-8", charset, str)
+              str = nstr if nstr && nstr.length > 0
+            rescue
+              str = str.toutf8
+            end
+          end
+        else
+          str = str.toutf8
+        end
+        body = str.gsub(/.*<body[^>]*>/im, "")
+        body = body.gsub(/<\/body>.*/im, "")
+        body = htmltotext(body)
+        if str =~ /<title[^>]*>[^<]*<\/title>/im
+          title = str.gsub(/.*<title[^>]*>([^<]*)<\/title>.*/im, '\1')
+          title = htmltotext(title)
+        end
+        title = "" if !title
+        title = title[0,128] if title.length > 128
+        mtime = sio.last_modified
+        if mtime
+          mtime = Time::parse(mtime.to_s).to_i
+        else
+          mtime = 0
+        end
+        printf("%d", cnt + 1)
+        printf("\turl\t%s", url)
+        printf("\tsize\t%s", size) if size > 0
+        printf("\tmtime\t%s", mtime) if mtime > 0
+        printf("\ttitle\t%s", title) if title.length > 0
+        printf("\tbody\t%s", body) if body.length > 0
+        printf("\n")
+        str.gsub(/<a[^>]*>/im) do |tag|
+          if tag =~ /href=["']?[^"'>]+["']?/
+            href = tag.gsub(/.*href=["']?([^"'>]+)["']?.*/, '\1')
+            href = URI::join(url, href).to_s
+            href = href.gsub(/#.*/, "")
+            if !hist[href] && checkurl(href, filters)
+              seeds.push(href)
+              hist[href] = true
+            end
+          end
+        end
+      end
+      cnt += 1
+    rescue
+      STDERR.printf("%d: failed: %s: %s\n", cnt + 1, url, $!)
+    end
+    sleep(wait) if wait > 0
+  end
+  return 0
+end
+
+def htmltotext(str)
+  str = str.gsub(/<style[^>]*>.*?<\/style>/im, " ")
+  str = str.gsub(/<script[^>]*>.*?<\/script>/im, " ")
+  str = str.gsub(/<\/?(p|br|div|h1|h2|h3|h4|h5|h6|ul|ol|dl|li|dd|dt|td|th|pre)[^>]*>/im, " ")
+  str = str.gsub(/<[^>]*>/, "")
+  str = str.gsub(/&(nbsp|#160|#0160|#xa0|#x00a0);/i, " ")
+  hexrx = Regexp::new("^&#x[0-9a-zA-Z]+;")
+  decrx = Regexp::new("^&#[0-9]+;")
+  str = str.gsub(/&#?[A-Za-z0-9]+;/) do |pat|
+    case pat
+    when "&lt;"
+      pat = '<'
+    when "&gt;"
+      pat = '>'
+    when "&quot;"
+      pat = '"'
+    when "&apos;"
+      pat = "'"
+    when "&nbsp;"
+      pat = " "
+    else
+      begin
+        if pat =~ hexrx
+          pat = [ pat.gsub(/&#x([A-Za-z0-9]+);/i, '\1').hex ].pack("n")
+          pat = Iconv.conv("UTF-8", "UTF-16BE", pat)
+        elsif pat =~ decrx
+          pat = [ pat.gsub(/&#([A-Za-z0-9]+);/i, '\1').to_i ].pack("n")
+          pat = Iconv.conv("UTF-8", "UTF-16BE", pat)
+        else
+          pat = " "
+        end
+      rescue
+        pat = ""
+      end
+    end
+    pat
+  end
+  str = str.gsub(/[\x00-\x20]/, " ")
+  str = str.gsub(/\xe3\x80\x80/, " ")
+  str = str.gsub(/ +/, " ")
+  str = str.gsub(/^ */, "")
+  str = str.gsub(/ *$/, "")
+  return str
+end
+
+def checkurl(url, filters)
+  return false if url !~ /^http:\/\//i;
+  return true if filters.length < 1
+  ok = !filters[0][0]
+  filters.each do |filter|
+    ok = filter[0] if url =~ filter[1]
+  end
+  return ok
+end
+
+STDOUT.sync = true
+$progname = $0.dup
+$progname.gsub!(/.*\//, "")
+srand
+exit(main)
+
+
+
+# END OF FILE
diff --git a/tcejdb/lab/widthcheck b/tcejdb/lab/widthcheck
new file mode 100755 (executable)
index 0000000..829c8f1
--- /dev/null
@@ -0,0 +1,57 @@
+#! /usr/bin/ruby -w
+
+#================================================================
+# widthcheck
+# Find files including too wide lines
+#================================================================
+
+
+LIMITWIDTH = 97
+
+def checkfile(path)
+  printf("Checking: %s\n", path)
+  ok = true
+  open(path, "r") do |file|
+    num = 0
+    file.each do |line|
+      num += 1
+      line.chomp!
+      if line.length > LIMITWIDTH && !line.index("/*") && !line.index("*/")
+        printf("FOUND: %s: %d: %d: %s\n", path, num, line.length, line);
+        ok = false
+      end
+    end
+  end
+  return ok
+end
+
+ok = true
+list = Array::new(ARGV)
+list.push(".") if list.empty?
+while !list.empty?
+  path = list.shift
+  begin
+    if File::ftype(path) == "directory"
+      Dir.entries(path).each do |cpath|
+        if cpath != "." and cpath != ".."
+          list.push(path + "/" + cpath)
+        end
+      end
+    else
+      ok = false if !checkfile(path)
+    end
+  rescue
+  end
+end
+
+if ok
+  printf("ALL OK\n")
+  exit(0)
+else
+  printf("ERROR\n")
+  exit(1)
+end
+
+
+
+# END OF FILE
diff --git a/tcejdb/man/htmltoman b/tcejdb/man/htmltoman
new file mode 100755 (executable)
index 0000000..34301bd
--- /dev/null
@@ -0,0 +1,104 @@
+#! /usr/bin/awk -f
+
+function strip(text){
+  gsub("^ *<[a-zA-Z0-9]*[^>]*>", "", text)
+  gsub("</[a-zA-Z0-9]*> *$", "", text)
+  return text
+}
+
+function unescape(text){
+  gsub("&lt;", "<", text)
+  gsub("&gt;", ">", text)
+  gsub("&quot;", "\"", text)
+  gsub("&amp;", "\\&", text)
+  gsub("-", "\\-", text)
+  return text
+}
+
+BEGIN {
+  date = strftime("%Y-%m-%d")
+  printf(".TH \"%s\" %d \"%s\" \"%s\" \"%s\"\n\n", "INTRO", 3, date, "Man Page", "Tokyo Cabinet")
+}
+
+/ *<h[1-3] *[^>]*>.*<\/h[1-3]> *$/ {
+  text = $0
+  text = strip(text)
+  text = unescape(text)
+  text = toupper(text)
+  printf("\n")
+  printf(".SH %s\n", text)
+}
+
+/ *<p *[^>]*>.*<\/p> *$/ {
+  text = $0
+  text = strip(text)
+  text = gensub("<code *[^>]*>([^<]*)</code>", "\\\\fB\\1\\\\fR", "g", text)
+  text = gensub("<var *[^>]*>([^<]*)</var>", "\\\\fI\\1\\\\fR", "g", text)
+  gsub("<[^>]*>", "", text)
+  text = unescape(text)
+  printf(".PP\n")
+  printf("%s\n", text)
+}
+
+/ *<dl *[^>]*> *$/ {
+  printf(".PP\n")
+  printf(".RS\n")
+}
+/ *<\/dl> *$/ {
+  printf(".RE\n")
+}
+/ *<dt *[^>]*>.*<\/dt> *$/ {
+  text = $0
+  text = strip(text)
+  text = gensub("<var *[^>]*>([^<]*)</var>", "\\\\fI\\1\\\\fB", "g", text)
+  gsub("<[^>]*>", "", text)
+  gsub("[\\||\\[|\\]]", "\\fR&\\fB", text)
+  text = unescape(text)
+  printf(".br\n")
+  printf("\\fB%s\\fR\n", text)
+}
+/ *<dd *[^>]*>.*<\/dd> *$/ {
+  text = $0
+  text = strip(text)
+  text = gensub("<code *[^>]*>([^<]*)</code>", "\\\\fB\\1\\\\fR", "g", text)
+  text = gensub("<var *[^>]*>([^<]*)</var>", "\\\\fI\\1\\\\fR", "g", text)
+  gsub("<[^>]*>", "", text)
+  text = unescape(text)
+  printf(".RS\n")
+  printf("%s\n", text)
+  printf(".RE\n")
+}
+
+/ *<ul *[^>]*> *$/ {
+  printf(".PP\n")
+  printf(".RS\n")
+}
+/ *<\/ul> *$/ {
+  printf(".RE\n")
+}
+/ *<li *[^>]*>.*<\/li> *$/ {
+  text = $0
+  text = strip(text)
+  text = gensub("<code *[^>]*>(.*)</code>", "\\\\fB\\1\\\\fR", "g", text)
+  text = gensub("<var *[^>]*>([^<]*)</var>", "\\\\fI\\1\\\\fR", "g", text)
+  gsub("<[^>]*>", "", text)
+  text = unescape(text)
+  printf("%s\n", text)
+  printf(".br\n")
+}
+
+END {
+  printf("\n")
+  printf(".SH SEE ALSO\n")
+  printf(".PP\n")
+  printf(".BR tcutil (3),\n")
+  printf(".BR tchdb (3),\n")
+  printf(".BR tcbdb (3),\n")
+  printf(".BR tcfdb (3),\n")
+  printf(".BR tctdb (3),\n")
+  printf(".BR tcadb (3)\n")
+  printf(".PP\n")
+  printf("Please see\n")
+  printf(".I http://1978th.net/tokyocabinet/\n")
+  printf("for detail.\n")
+}
diff --git a/tcejdb/man/tcadb.3 b/tcejdb/man/tcadb.3
new file mode 100644 (file)
index 0000000..538f929
--- /dev/null
@@ -0,0 +1,676 @@
+.TH "TCADB" 3 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tcadb \- the abstract database API
+
+.SH DESCRIPTION
+.PP
+Abstract database is a set of interfaces to use on\-memory hash database, on\-memory tree database, hash database, B+ tree database, fixed\-length database, and table database with the same API.
+.PP
+To use the abstract database API, include `\fBtcutil.h\fR', `\fBtcadb.h\fR', and related standard header files.  Usually, write the following description near the front of a source file.
+.PP
+.RS
+.br
+\fB#include <tcutil.h>\fR
+.br
+\fB#include <tcadb.h>\fR
+.br
+\fB#include <stdlib.h>\fR
+.br
+\fB#include <stdbool.h>\fR
+.br
+\fB#include <stdint.h>\fR
+.RE
+.PP
+Objects whose type is pointer to `\fBTCADB\fR' are used to handle abstract databases.  An abstract database object is created with the function `\fBtcadbnew\fR' and is deleted with the function `\fBtcadbdel\fR'.  To avoid memory leak, it is important to delete every object when it is no longer in use.
+.PP
+Before operations to store or retrieve records, it is necessary to connect the abstract database object to the concrete one.  The function `\fBtcadbopen\fR' is used to open a concrete database and the function `\fBtcadbclose\fR' is used to close the database.  To avoid data missing or corruption, it is important to close every database instance when it is no longer in use.  It is forbidden for multible database objects in a process to open the same database at the same time.
+
+.SH API
+.PP
+The function `tcadbnew' is used in order to create an abstract database object.
+.PP
+.RS
+.br
+\fBTCADB *tcadbnew(void);\fR
+.RS
+The return value is the new abstract database object.
+.RE
+.RE
+.PP
+The function `tcadbdel' is used in order to delete an abstract database object.
+.PP
+.RS
+.br
+\fBvoid tcadbdel(TCADB *\fIadb\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RE
+.PP
+The function `tcadbopen' is used in order to open an abstract database.
+.PP
+.RS
+.br
+\fBbool tcadbopen(TCADB *\fIadb\fB, const char *\fIname\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+`\fIname\fR' specifies the name of the database.  If it is "*", the database will be an on\-memory hash database.  If it is "+", the database will be an on\-memory tree database.  If its suffix is ".tch", the database will be a hash database.  If its suffix is ".tcb", the database will be a B+ tree database.  If its suffix is ".tcf", the database will be a fixed\-length database.  If its suffix is ".tct", the database will be a table database.  Otherwise, this function fails.  Tuning parameters can trail the name, separated by "#".  Each parameter is composed of the name and the value, separated by "=".  On\-memory hash database supports "bnum", "capnum", and "capsiz".  On\-memory tree database supports "capnum" and "capsiz".  Hash database supports "mode", "bnum", "apow", "fpow", "opts", "rcnum", "xmsiz", and "dfunit".  B+ tree database supports "mode", "lmemb", "nmemb", "bnum", "apow", "fpow", "opts", "lcnum", "ncnum", "xmsiz", and "dfunit".  Fixed\-length database supports "mode", "width", and "limsiz".  Table database supports "mode", "bnum", "apow", "fpow", "opts", "rcnum", "lcnum", "ncnum", "xmsiz", "dfunit", and "idx".
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+The tuning parameter "capnum" specifies the capacity number of records.  "capsiz" specifies the capacity size of using memory.  Records spilled the capacity are removed by the storing order.  "mode" can contain "w" of writer, "r" of reader, "c" of creating, "t" of truncating, "e" of no locking, and "f" of non\-blocking lock.  The default mode is relevant to "wc".  "opts" can contains "l" of large option, "d" of Deflate option, "b" of BZIP2 option, and "t" of TCBS option.  "idx" specifies the column name of an index and its type separated by ":".  For example, "casket.tch#bnum=1000000#opts=ld" means that the name of the database file is "casket.tch", and the bucket number is 1000000, and the options are large and Deflate.
+.RE
+.RE
+.PP
+The function `tcadbclose' is used in order to close an abstract database object.
+.PP
+.RS
+.br
+\fBbool tcadbclose(TCADB *\fIadb\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Update of a database is assured to be written when the database is closed.  If a writer opens a database but does not close it appropriately, the database will be broken.
+.RE
+.RE
+.PP
+The function `tcadbput' is used in order to store a record into an abstract database object.
+.PP
+.RS
+.br
+\fBbool tcadbput(TCADB *\fIadb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, it is overwritten.
+.RE
+.RE
+.PP
+The function `tcadbput2' is used in order to store a string record into an abstract object.
+.PP
+.RS
+.br
+\fBbool tcadbput2(TCADB *\fIadb\fB, const char *\fIkstr\fB, const char *\fIvstr\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, it is overwritten.
+.RE
+.RE
+.PP
+The function `tcadbputkeep' is used in order to store a new record into an abstract database object.
+.PP
+.RS
+.br
+\fBbool tcadbputkeep(TCADB *\fIadb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, this function has no effect.
+.RE
+.RE
+.PP
+The function `tcadbputkeep2' is used in order to store a new string record into an abstract database object.
+.PP
+.RS
+.br
+\fBbool tcadbputkeep2(TCADB *\fIadb\fB, const char *\fIkstr\fB, const char *\fIvstr\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, this function has no effect.
+.RE
+.RE
+.PP
+The function `tcadbputcat' is used in order to concatenate a value at the end of the existing record in an abstract database object.
+.PP
+.RS
+.br
+\fBbool tcadbputcat(TCADB *\fIadb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If there is no corresponding record, a new record is created.
+.RE
+.RE
+.PP
+The function `tcadbputcat2' is used in order to concatenate a string value at the end of the existing record in an abstract database object.
+.PP
+.RS
+.br
+\fBbool tcadbputcat2(TCADB *\fIadb\fB, const char *\fIkstr\fB, const char *\fIvstr\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If there is no corresponding record, a new record is created.
+.RE
+.RE
+.PP
+The function `tcadbout' is used in order to remove a record of an abstract database object.
+.PP
+.RS
+.br
+\fBbool tcadbout(TCADB *\fIadb\fB, const void *\fIkbuf\fB, int \fIksiz\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RE
+.PP
+The function `tcadbout2' is used in order to remove a string record of an abstract database object.
+.PP
+.RS
+.br
+\fBbool tcadbout2(TCADB *\fIadb\fB, const char *\fIkstr\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RE
+.PP
+The function `tcadbget' is used in order to retrieve a record in an abstract database object.
+.PP
+.RS
+.br
+\fBvoid *tcadbget(TCADB *\fIadb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, int *\fIsp\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned if no record corresponds.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcadbget2' is used in order to retrieve a string record in an abstract database object.
+.PP
+.RS
+.br
+\fBchar *tcadbget2(TCADB *\fIadb\fB, const char *\fIkstr\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+If successful, the return value is the string of the value of the corresponding record.  `NULL' is returned if no record corresponds.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcadbvsiz' is used in order to get the size of the value of a record in an abstract database object.
+.PP
+.RS
+.br
+\fBint tcadbvsiz(TCADB *\fIadb\fB, const void *\fIkbuf\fB, int \fIksiz\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+If successful, the return value is the size of the value of the corresponding record, else, it is \-1.
+.RE
+.RE
+.PP
+The function `tcadbvsiz2' is used in order to get the size of the value of a string record in an abstract database object.
+.PP
+.RS
+.br
+\fBint tcadbvsiz2(TCADB *\fIadb\fB, const char *\fIkstr\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+If successful, the return value is the size of the value of the corresponding record, else, it is \-1.
+.RE
+.RE
+.PP
+The function `tcadbiterinit' is used in order to initialize the iterator of an abstract database object.
+.PP
+.RS
+.br
+\fBbool tcadbiterinit(TCADB *\fIadb\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+The iterator is used in order to access the key of every record stored in a database.
+.RE
+.RE
+.PP
+The function `tcadbiternext' is used in order to get the next key of the iterator of an abstract database object.
+.PP
+.RS
+.br
+\fBvoid *tcadbiternext(TCADB *\fIadb\fB, int *\fIsp\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  It is possible to access every record by iteration of calling this function.  It is allowed to update or remove records whose keys are fetched while the iteration.  However, it is not assured if updating the database is occurred while the iteration.  Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of storing matches the one of the traversal access.
+.RE
+.RE
+.PP
+The function `tcadbiternext2' is used in order to get the next key string of the iterator of an abstract database object.
+.PP
+.RS
+.br
+\fBchar *tcadbiternext2(TCADB *\fIadb\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+If successful, the return value is the string of the next key, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  It is possible to access every record by iteration of calling this function.  However, it is not assured if updating the database is occurred while the iteration.  Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of storing matches the one of the traversal access.
+.RE
+.RE
+.PP
+The function `tcadbfwmkeys' is used in order to get forward matching keys in an abstract database object.
+.PP
+.RS
+.br
+\fBTCLIST *tcadbfwmkeys(TCADB *\fIadb\fB, const void *\fIpbuf\fB, int \fIpsiz\fB, int \fImax\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+`\fIpbuf\fR' specifies the pointer to the region of the prefix.
+.RE
+.RS
+`\fIpsiz\fR' specifies the size of the region of the prefix.
+.RE
+.RS
+`\fImax\fR' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.
+.RE
+.RS
+The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.
+.RE
+.RE
+.PP
+The function `tcadbfwmkeys2' is used in order to get forward matching string keys in an abstract database object.
+.PP
+.RS
+.br
+\fBTCLIST *tcadbfwmkeys2(TCADB *\fIadb\fB, const char *\fIpstr\fB, int \fImax\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+`\fIpstr\fR' specifies the string of the prefix.
+.RE
+.RS
+`\fImax\fR' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.
+.RE
+.RS
+The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.
+.RE
+.RE
+.PP
+The function `tcadbaddint' is used in order to add an integer to a record in an abstract database object.
+.PP
+.RS
+.br
+\fBint tcadbaddint(TCADB *\fIadb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, int \fInum\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fInum\fR' specifies the additional value.
+.RE
+.RS
+If successful, the return value is the summation value, else, it is `INT_MIN'.
+.RE
+.RS
+If the corresponding record exists, the value is treated as an integer and is added to.  If no record corresponds, a new record of the additional value is stored.
+.RE
+.RE
+.PP
+The function `tcadbadddouble' is used in order to add a real number to a record in an abstract database object.
+.PP
+.RS
+.br
+\fBdouble tcadbadddouble(TCADB *\fIadb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, double \fInum\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fInum\fR' specifies the additional value.
+.RE
+.RS
+If successful, the return value is the summation value, else, it is Not-a-Number.
+.RE
+.RS
+If the corresponding record exists, the value is treated as a real number and is added to.  If no record corresponds, a new record of the additional value is stored.
+.RE
+.RE
+.PP
+The function `tcadbsync' is used in order to synchronize updated contents of an abstract database object with the file and the device.
+.PP
+.RS
+.br
+\fBbool tcadbsync(TCADB *\fIadb\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RE
+.PP
+The function `tcadboptimize' is used in order to optimize the storage of an abstract database object.
+.PP
+.RS
+.br
+\fBbool tcadboptimize(TCADB *\fIadb\fB, const char *\fIparams\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+`\fIparams\fR' specifies the string of the tuning parameters, which works as with the tuning of parameters the function `tcadbopen'.  If it is `NULL', it is not used.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+This function is useful to reduce the size of the database storage with data fragmentation by successive updating.
+.RE
+.RE
+.PP
+The function `tcadbvanish' is used in order to remove all records of an abstract database object.
+.PP
+.RS
+.br
+\fBbool tcadbvanish(TCADB *\fIadb\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RE
+.PP
+The function `tcadbcopy' is used in order to copy the database file of an abstract database object.
+.PP
+.RS
+.br
+\fBbool tcadbcopy(TCADB *\fIadb\fB, const char *\fIpath\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+`\fIpath\fR' specifies the path of the destination file.  If it begins with `@', the trailing substring is executed as a command line.
+.RE
+.RS
+If successful, the return value is true, else, it is false.  False is returned if the executed command returns non\-zero code.
+.RE
+.RS
+The database file is assured to be kept synchronized and not modified while the copying or executing operation is in progress.  So, this function is useful to create a backup file of the database file.
+.RE
+.RE
+.PP
+The function `tcadbtranbegin' is used in order to begin the transaction of an abstract database object.
+.PP
+.RS
+.br
+\fBbool tcadbtranbegin(TCADB *\fIadb\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+The database is locked by the thread while the transaction so that only one transaction can be activated with a database object at the same time.  Thus, the serializable isolation level is assumed if every database operation is performed in the transaction.  All updated regions are kept track of by write ahead logging while the transaction.  If the database is closed during transaction, the transaction is aborted implicitly.
+.RE
+.RE
+.PP
+The function `tcadbtrancommit' is used in order to commit the transaction of an abstract database object.
+.PP
+.RS
+.br
+\fBbool tcadbtrancommit(TCADB *\fIadb\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Update in the transaction is fixed when it is committed successfully.
+.RE
+.RE
+.PP
+The function `tcadbtranabort' is used in order to abort the transaction of an abstract database object.
+.PP
+.RS
+.br
+\fBbool tcadbtranabort(TCADB *\fIadb\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Update in the transaction is discarded when it is aborted.  The state of the database is rollbacked to before transaction.
+.RE
+.RE
+.PP
+The function `tcadbpath' is used in order to get the file path of an abstract database object.
+.PP
+.RS
+.br
+\fBconst char *tcadbpath(TCADB *\fIadb\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+The return value is the path of the database file or `NULL' if the object does not connect to any database.  "*" stands for on\-memory hash database.  "+" stands for on\-memory tree database.
+.RE
+.RE
+.PP
+The function `tcadbrnum' is used in order to get the number of records of an abstract database object.
+.PP
+.RS
+.br
+\fBuint64_t tcadbrnum(TCADB *\fIadb\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+The return value is the number of records or 0 if the object does not connect to any database instance.
+.RE
+.RE
+.PP
+The function `tcadbsize' is used in order to get the size of the database of an abstract database object.
+.PP
+.RS
+.br
+\fBuint64_t tcadbsize(TCADB *\fIadb\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+The return value is the size of the database or 0 if the object does not connect to any database instance.
+.RE
+.RE
+.PP
+The function `tcadbmisc' is used in order to call a versatile function for miscellaneous operations of an abstract database object.
+.PP
+.RS
+.br
+\fBTCLIST *tcadbmisc(TCADB *\fIadb\fB, const char *\fIname\fB, const TCLIST *\fIargs\fB);\fR
+.RS
+`\fIadb\fR' specifies the abstract database object.
+.RE
+.RS
+`\fIname\fR' specifies the name of the function.  All databases support "put", "out", "get", "putlist", "outlist", "getlist", and "getpart".  "put" is to store a record.  It receives a key and a value, and returns an empty list.  "out" is to remove a record.  It receives a key, and returns an empty list.  "get" is to retrieve a record.  It receives a key, and returns a list of the values.  "putlist" is to store records.  It receives keys and values one after the other, and returns an empty list.  "outlist" is to remove records.  It receives keys, and returns an empty list.  "getlist" is to retrieve records.  It receives keys, and returns keys and values of corresponding records one after the other.  "getpart" is to retrieve the partial value of a record.  It receives a key, the offset of the region, and the length of the region.
+.RE
+.RS
+`\fIargs\fR' specifies a list object containing arguments.
+.RE
+.RS
+If successful, the return value is a list object of the result.  `NULL' is returned on failure.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.
+.RE
+.RE
+
+.SH SEE ALSO
+.PP
+.BR tcatest (1),
+.BR tcamttest (1),
+.BR tcamgr (1),
+.BR tokyocabinet (3)
diff --git a/tcejdb/man/tcamgr.1 b/tcejdb/man/tcamgr.1
new file mode 100644 (file)
index 0000000..67f13be
--- /dev/null
@@ -0,0 +1,97 @@
+.TH "TCATEST" 3 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tcamgr \- the command line utility of the abstract database API
+
+.SH DESCRIPTION
+.PP
+The command `\fBtcamgr\fR' is a utility for test and debugging of the abstract database API and its applications.  `\fIname\fR' specifies the name of a database.  `\fIkey\fR' specifies the key of a record.  `\fIvalue\fR' specifies the value of a record.  `\fIparams\fR' specifies the tuning parameters.  `\fIfunc\fR' specifies the name of a function.  `\fIarg\fR' specifies the arguments of the function.  `\fIdest\fR' specifies the path of the destination file.
+.PP
+.RS
+.br
+\fBtcamgr create \fIname\fB\fR
+.RS
+Create a database file.
+.RE
+.br
+\fBtcamgr inform \fIname\fB\fR
+.RS
+Print miscellaneous information to the standard output.
+.RE
+.br
+\fBtcamgr put \fR[\fB\-sx\fR]\fB \fR[\fB\-sep \fIchr\fB\fR]\fB \fR[\fB\-dk\fR|\fB\-dc\fR|\fB\-dai\fR|\fB\-dad\fR]\fB \fIname\fB \fIkey\fB \fIvalue\fB\fR
+.RS
+Store a record.
+.RE
+.br
+\fBtcamgr out \fR[\fB\-sx\fR]\fB \fR[\fB\-sep \fIchr\fB\fR]\fB \fIname\fB \fIkey\fB\fR
+.RS
+Remove a record.
+.RE
+.br
+\fBtcamgr get \fR[\fB\-sx\fR]\fB \fR[\fB\-sep \fIchr\fB\fR]\fB \fR[\fB\-px\fR]\fB \fR[\fB\-pz\fR]\fB \fIname\fB \fIkey\fB\fR
+.RS
+Print the value of a record.
+.RE
+.br
+\fBtcamgr list \fR[\fB\-sep \fIchr\fB\fR]\fB \fR[\fB\-m \fInum\fB\fR]\fB \fR[\fB\-pv\fR]\fB \fR[\fB\-px\fR]\fB \fR[\fB\-fm \fIstr\fB\fR]\fB \fIname\fB\fR
+.RS
+Print keys of all records, separated by line feeds.
+.RE
+.br
+\fBtcamgr optimize \fIname\fB \fIparams\fB\fR
+.RS
+Optimize a database file.
+.RE
+.br
+\fBtcamgr misc \fR[\fB\-sx\fR]\fB \fR[\fB\-sep \fIchr\fB\fR]\fB \fR[\fB\-px\fR]\fB \fIname\fB \fIfunc\fB \fR[\fB\fIarg\fB...\fR]\fB\fR
+.RS
+Call a versatile function for miscellaneous operations.
+.RE
+.br
+\fBtcamgr map \fR[\fB\-fm \fIstr\fB\fR]\fB \fIname\fB \fIdest\fB\fR
+.RS
+Map records into another B+ tree database.
+.RE
+.br
+\fBtcamgr version\fR
+.RS
+Print the version information of Tokyo Cabinet.
+.RE
+.RE
+.PP
+Options feature the following.
+.PP
+.RS
+\fB\-sx\fR : the input data is evaluated as a hexadecimal data string.
+.br
+\fB\-sep \fIchr\fR\fR : specify the separator of the input data.
+.br
+\fB\-dk\fR : use the function `tcadbputkeep' instead of `tcadbput'.
+.br
+\fB\-dc\fR : use the function `tcadbputcat' instead of `tcadbput'.
+.br
+\fB\-dai\fR : use the function `tcadbaddint' instead of `tcadbput'.
+.br
+\fB\-dad\fR : use the function `tcadbadddouble' instead of `tcadbput'.
+.br
+\fB\-px\fR : the output data is converted into a hexadecimal data string.
+.br
+\fB\-pz\fR : do not append line feed at the end of the output.
+.br
+\fB\-m \fInum\fR\fR : specify the maximum number of the output.
+.br
+\fB\-pv\fR : print values of records also.
+.br
+\fB\-fm \fIstr\fR\fR : specify the prefix of keys.
+.br
+.RE
+.PP
+This command returns 0 on success, another on failure.
+
+.SH SEE ALSO
+.PP
+.BR tcatest (1),
+.BR tcamttest (1),
+.BR tcadb (3),
+.BR tokyocabinet (3)
diff --git a/tcejdb/man/tcamttest.1 b/tcejdb/man/tcamttest.1
new file mode 100644 (file)
index 0000000..abad640
--- /dev/null
@@ -0,0 +1,35 @@
+.TH "TCAMTTEST" 1 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tcamttest \- test cases of the abstract database API
+
+.SH DESCRIPTION
+.PP
+The command `\fBtcamttest\fR' is a utility for facility test under multi\-thread situation.  This command is used in the following format.  `\fIname\fR' specifies the database name.  `\fItnum\fR' specifies the number of running threads.  `\fIrnum\fR' specifies the number of iterations.
+.PP
+.RS
+.br
+\fBtcamttest write \fIname\fB \fItnum\fB \fIrnum\fB\fR
+.RS
+Store records with keys of 8 bytes.  They change as `00000001', `00000002'...
+.RE
+.br
+\fBtcamttest read \fIname\fB \fItnum\fB\fR
+.RS
+Retrieve all records of the database above.
+.RE
+.br
+\fBtcamttest remove \fIname\fB \fItnum\fB\fR
+.RS
+Remove all records of the database above.
+.RE
+.RE
+.PP
+This command returns 0 on success, another on failure.
+
+.SH SEE ALSO
+.PP
+.BR tcatest (1),
+.BR tcamgr (1),
+.BR tcadb (3),
+.BR tokyocabinet (3)
diff --git a/tcejdb/man/tcatest.1 b/tcejdb/man/tcatest.1
new file mode 100644 (file)
index 0000000..55fe2b1
--- /dev/null
@@ -0,0 +1,55 @@
+.TH "TCATEST" 3 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tcatest \- test cases of the abstract database API
+
+.SH DESCRIPTION
+.PP
+The command `\fBtcatest\fR' is a utility for facility test and performance test.  This command is used in the following format.  `\fIname\fR' specifies the database name.  `\fIrnum\fR' specifies the number of iterations.  `\fItnum\fR' specifies the number of transactions.
+.PP
+.RS
+.br
+\fBtcatest write \fIname\fB \fIrnum\fB\fR
+.RS
+Store records with keys of 8 bytes.  They change as `00000001', `00000002'...
+.RE
+.br
+\fBtcatest read \fIname\fB\fR
+.RS
+Retrieve all records of the database above.
+.RE
+.br
+\fBtcatest remove \fIname\fB\fR
+.RS
+Remove all records of the database above.
+.RE
+.br
+\fBtcatest rcat \fIname\fB \fIrnum\fB\fR
+.RS
+Store records with partway duplicated keys using concatenate mode.
+.RE
+.br
+\fBtcatest misc \fIname\fB \fIrnum\fB\fR
+.RS
+Perform miscellaneous test of various operations.
+.RE
+.br
+\fBtcatest wicked \fIname\fB \fIrnum\fB\fR
+.RS
+Perform updating operations of list and map selected at random.
+.RE
+.br
+\fBtcatest compare \fIname\fB \fItnum\fB \fIrnum\fB\fR
+.RS
+Perform comparison test of database schema.
+.RE
+.RE
+.PP
+This command returns 0 on success, another on failure.
+
+.SH SEE ALSO
+.PP
+.BR tcamttest (1),
+.BR tcamgr (1),
+.BR tcadb (3),
+.BR tokyocabinet (3)
diff --git a/tcejdb/man/tcbdb.3 b/tcejdb/man/tcbdb.3
new file mode 100644 (file)
index 0000000..f0c9b23
--- /dev/null
@@ -0,0 +1,1355 @@
+.TH "TCBDB" 3 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tcbdb \- the B+ tree database API
+
+.SH DESCRIPTION
+.PP
+B+ tree database is a file containing a B+ tree and is handled with the B+ tree database API.
+.PP
+To use the B+ tree database API, include `\fBtcutil.h\fR', `\fBtcbdb.h\fR', and related standard header files.  Usually, write the following description near the front of a source file.
+.PP
+.RS
+.br
+\fB#include <tcutil.h>\fR
+.br
+\fB#include <tcbdb.h>\fR
+.br
+\fB#include <stdlib.h>\fR
+.br
+\fB#include <time.h>\fR
+.br
+\fB#include <stdbool.h>\fR
+.br
+\fB#include <stdint.h>\fR
+.RE
+.PP
+Objects whose type is pointer to `\fBTCBDB\fR' are used to handle B+ tree databases.  A B+ tree database object is created with the function `\fBtcbdbnew\fR' and is deleted with the function `\fBtcbdbdel\fR'.  To avoid memory leak, it is important to delete every object when it is no longer in use.
+.PP
+Before operations to store or retrieve records, it is necessary to open a database file and connect the B+ tree database object to it.  The function `\fBtcbdbopen\fR' is used to open a database file and the function `\fBtcbdbclose\fR' is used to close the database file.  To avoid data missing or corruption, it is important to close every database file when it is no longer in use.  It is forbidden for multible database objects in a process to open the same database at the same time.
+
+.SH API
+.PP
+The function `tcbdberrmsg' is used in order to get the message string corresponding to an error code.
+.PP
+.RS
+.br
+\fBconst char *tcbdberrmsg(int \fIecode\fB);\fR
+.RS
+`\fIecode\fR' specifies the error code.
+.RE
+.RS
+The return value is the message string of the error code.
+.RE
+.RE
+.PP
+The function `tcbdbnew' is used in order to create a B+ tree database object.
+.PP
+.RS
+.br
+\fBTCBDB *tcbdbnew(void);\fR
+.RS
+The return value is the new B+ tree database object.
+.RE
+.RE
+.PP
+The function `tcbdbdel' is used in order to delete a B+ tree database object.
+.PP
+.RS
+.br
+\fBvoid tcbdbdel(TCBDB *\fIbdb\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object.
+.RE
+.RS
+If the database is not closed, it is closed implicitly.  Note that the deleted object and its derivatives can not be used anymore.
+.RE
+.RE
+.PP
+The function `tcbdbecode' is used in order to get the last happened error code of a B+ tree database object.
+.PP
+.RS
+.br
+\fBint tcbdbecode(TCBDB *\fIbdb\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object.
+.RE
+.RS
+The return value is the last happened error code.
+.RE
+.RS
+The following error codes are defined: `TCESUCCESS' for success, `TCETHREAD' for threading error, `TCEINVALID' for invalid operation, `TCENOFILE' for file not found, `TCENOPERM' for no permission, `TCEMETA' for invalid meta data, `TCERHEAD' for invalid record header, `TCEOPEN' for open error, `TCECLOSE' for close error, `TCETRUNC' for trunc error, `TCESYNC' for sync error, `TCESTAT' for stat error, `TCESEEK' for seek error, `TCEREAD' for read error, `TCEWRITE' for write error, `TCEMMAP' for mmap error, `TCELOCK' for lock error, `TCEUNLINK' for unlink error, `TCERENAME' for rename error, `TCEMKDIR' for mkdir error, `TCERMDIR' for rmdir error, `TCEKEEP' for existing record, `TCENOREC' for no record found, and `TCEMISC' for miscellaneous error.
+.RE
+.RE
+.PP
+The function `tcbdbsetmutex' is used in order to set mutual exclusion control of a B+ tree database object for threading.
+.PP
+.RS
+.br
+\fBbool tcbdbsetmutex(TCBDB *\fIbdb\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object which is not opened.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Note that the mutual exclusion control of the database should be set before the database is opened.
+.RE
+.RE
+.PP
+The function `tcbdbsetcmpfunc' is used in order to set the custom comparison function of a B+ tree database object.
+.PP
+.RS
+.br
+\fBbool tcbdbsetcmpfunc(TCBDB *\fIbdb\fB, TCCMP \fIcmp\fB, void *\fIcmpop\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object which is not opened.
+.RE
+.RS
+`\fIcmp\fR' specifies the pointer to the custom comparison function.  It receives five parameters.  The first parameter is the pointer to the region of one key.  The second parameter is the size of the region of one key.  The third parameter is the pointer to the region of the other key.  The fourth parameter is the size of the region of the other key.  The fifth parameter is the pointer to the optional opaque object.  It returns positive if the former is big, negative if the latter is big, 0 if both are equivalent.
+.RE
+.RS
+`\fIcmpop\fR' specifies an arbitrary pointer to be given as a parameter of the comparison function.  If it is not needed, `NULL' can be specified.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+The default comparison function compares keys of two records by lexical order.  The functions `tccmplexical' (dafault), `tccmpdecimal', `tccmpint32', and `tccmpint64' are built\-in.  Note that the comparison function should be set before the database is opened.  Moreover, user\-defined comparison functions should be set every time the database is being opened.
+.RE
+.RE
+.PP
+The function `tcbdbtune' is used in order to set the tuning parameters of a B+ tree database object.
+.PP
+.RS
+.br
+\fBbool tcbdbtune(TCBDB *\fIbdb\fB, int32_t \fIlmemb\fB, int32_t \fInmemb\fB, int64_t \fIbnum\fB, int8_t \fIapow\fB, int8_t \fIfpow\fB, uint8_t \fIopts\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object which is not opened.
+.RE
+.RS
+`\fIlmemb\fR' specifies the number of members in each leaf page.  If it is not more than 0, the default value is specified.  The default value is 128.
+.RE
+.RS
+`\fInmemb\fR' specifies the number of members in each non\-leaf page.  If it is not more than 0, the default value is specified.  The default value is 256.
+.RE
+.RS
+`\fIbnum\fR' specifies the number of elements of the bucket array.  If it is not more than 0, the default value is specified.  The default value is 16381.  Suggested size of the bucket array is about from 1 to 4 times of the number of all pages to be stored.
+.RE
+.RS
+`\fIapow\fR' specifies the size of record alignment by power of 2.  If it is negative, the default value is specified.  The default value is 8 standing for 2^8=256.
+.RE
+.RS
+`\fIfpow\fR' specifies the maximum number of elements of the free block pool by power of 2.  If it is negative, the default value is specified.  The default value is 10 standing for 2^10=1024.
+.RE
+.RS
+`\fIopts\fR' specifies options by bitwise-or: `BDBTLARGE' specifies that the size of the database can be larger than 2GB by using 64\-bit bucket array, `BDBTDEFLATE' specifies that each page is compressed with Deflate encoding, `BDBTBZIP' specifies that each page is compressed with BZIP2 encoding, `BDBTTCBS' specifies that each page is compressed with TCBS encoding.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Note that the tuning parameters should be set before the database is opened.
+.RE
+.RE
+.PP
+The function `tcbdbsetcache' is used in order to set the caching parameters of a B+ tree database object.
+.PP
+.RS
+.br
+\fBbool tcbdbsetcache(TCBDB *\fIbdb\fB, int32_t \fIlcnum\fB, int32_t \fIncnum\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object which is not opened.
+.RE
+.RS
+`\fIlcnum\fR' specifies the maximum number of leaf nodes to be cached.  If it is not more than 0, the default value is specified.  The default value is 1024.
+.RE
+.RS
+`\fIncnum\fR' specifies the maximum number of non\-leaf nodes to be cached.  If it is not more than 0, the default value is specified.  The default value is 512.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Note that the caching parameters should be set before the database is opened.
+.RE
+.RE
+.PP
+The function `tcbdbsetxmsiz' is used in order to set the size of the extra mapped memory of a B+ tree database object.
+.PP
+.RS
+.br
+\fBbool tcbdbsetxmsiz(TCBDB *\fIbdb\fB, int64_t \fIxmsiz\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object which is not opened.
+.RE
+.RS
+`\fIxmsiz\fR' specifies the size of the extra mapped memory.  If it is not more than 0, the extra mapped memory is disabled.  It is disabled by default.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Note that the mapping parameters should be set before the database is opened.
+.RE
+.RE
+.PP
+The function `tcbdbsetdfunit' is used in order to set the unit step number of auto defragmentation of a B+ tree database object.
+.PP
+.RS
+.br
+\fBbool tcbdbsetdfunit(TCBDB *\fIbdb\fB, int32_t \fIdfunit\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object which is not opened.
+.RE
+.RS
+`\fIdfunit\fR' specifie the unit step number.  If it is not more than 0, the auto defragmentation is disabled.  It is disabled by default.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Note that the defragmentation parameter should be set before the database is opened.
+.RE
+.RE
+.PP
+The function `tcbdbopen' is used in order to open a database file and connect a B+ tree database object.
+.PP
+.RS
+.br
+\fBbool tcbdbopen(TCBDB *\fIbdb\fB, const char *\fIpath\fB, int \fIomode\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object which is not opened.
+.RE
+.RS
+`\fIpath\fR' specifies the path of the database file.
+.RE
+.RS
+`\fIomode\fR' specifies the connection mode: `BDBOWRITER' as a writer, `BDBOREADER' as a reader.  If the mode is `BDBOWRITER', the following may be added by bitwise-or: `BDBOCREAT', which means it creates a new database if not exist, `BDBOTRUNC', which means it creates a new database regardless if one exists, `BDBOTSYNC', which means every transaction synchronizes updated contents with the device.  Both of `BDBOREADER' and `BDBOWRITER' can be added to by bitwise-or: `BDBONOLCK', which means it opens the database file without file locking, or `BDBOLCKNB', which means locking is performed without blocking.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RE
+.PP
+The function `tcbdbclose' is used in order to close a B+ tree database object.
+.PP
+.RS
+.br
+\fBbool tcbdbclose(TCBDB *\fIbdb\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Update of a database is assured to be written when the database is closed.  If a writer opens a database but does not close it appropriately, the database will be broken.
+.RE
+.RE
+.PP
+The function `tcbdbput' is used in order to store a record into a B+ tree database object.
+.PP
+.RS
+.br
+\fBbool tcbdbput(TCBDB *\fIbdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object connected as a writer.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, it is overwritten.
+.RE
+.RE
+.PP
+The function `tcbdbput2' is used in order to store a string record into a B+ tree database object.
+.PP
+.RS
+.br
+\fBbool tcbdbput2(TCBDB *\fIbdb\fB, const char *\fIkstr\fB, const char *\fIvstr\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object connected as a writer.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, it is overwritten.
+.RE
+.RE
+.PP
+The function `tcbdbputkeep' is used in order to store a new record into a B+ tree database object.
+.PP
+.RS
+.br
+\fBbool tcbdbputkeep(TCBDB *\fIbdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object connected as a writer.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, this function has no effect.
+.RE
+.RE
+.PP
+The function `tcbdbputkeep2' is used in order to store a new string record into a B+ tree database object.
+.PP
+.RS
+.br
+\fBbool tcbdbputkeep2(TCBDB *\fIbdb\fB, const char *\fIkstr\fB, const char *\fIvstr\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object connected as a writer.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, this function has no effect.
+.RE
+.RE
+.PP
+The function `tcbdbputcat' is used in order to concatenate a value at the end of the existing record in a B+ tree database object.
+.PP
+.RS
+.br
+\fBbool tcbdbputcat(TCBDB *\fIbdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object connected as a writer.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If there is no corresponding record, a new record is created.
+.RE
+.RE
+.PP
+The function `tcbdbputcat2' is used in order to concatenate a string value at the end of the existing record in a B+ tree database object.
+.PP
+.RS
+.br
+\fBbool tcbdbputcat2(TCBDB *\fIbdb\fB, const char *\fIkstr\fB, const char *\fIvstr\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object connected as a writer.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If there is no corresponding record, a new record is created.
+.RE
+.RE
+.PP
+The function `tcbdbputdup' is used in order to store a record into a B+ tree database object with allowing duplication of keys.
+.PP
+.RS
+.br
+\fBbool tcbdbputdup(TCBDB *\fIbdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object connected as a writer.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, the new record is placed after the existing one.
+.RE
+.RE
+.PP
+The function `tcbdbputdup2' is used in order to store a string record into a B+ tree database object with allowing duplication of keys.
+.PP
+.RS
+.br
+\fBbool tcbdbputdup2(TCBDB *\fIbdb\fB, const char *\fIkstr\fB, const char *\fIvstr\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object connected as a writer.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, the new record is placed after the existing one.
+.RE
+.RE
+.PP
+The function `tcbdbputdup3' is used in order to store records into a B+ tree database object with allowing duplication of keys.
+.PP
+.RS
+.br
+\fBbool tcbdbputdup3(TCBDB *\fIbdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const TCLIST *\fIvals\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object connected as a writer.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the common key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the common key.
+.RE
+.RS
+`\fIvals\fR' specifies a list object containing values.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, the new records are placed after the existing one.
+.RE
+.RE
+.PP
+The function `tcbdbout' is used in order to remove a record of a B+ tree database object.
+.PP
+.RS
+.br
+\fBbool tcbdbout(TCBDB *\fIbdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object connected as a writer.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If the key of duplicated records is specified, the first one is selected.
+.RE
+.RE
+.PP
+The function `tcbdbout2' is used in order to remove a string record of a B+ tree database object.
+.PP
+.RS
+.br
+\fBbool tcbdbout2(TCBDB *\fIbdb\fB, const char *\fIkstr\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object connected as a writer.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If the key of duplicated records is specified, the first one is selected.
+.RE
+.RE
+.PP
+The function `tcbdbout3' is used in order to remove records of a B+ tree database object.
+.PP
+.RS
+.br
+\fBbool tcbdbout3(TCBDB *\fIbdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object connected as a writer.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If the key of duplicated records is specified, all of them are removed.
+.RE
+.RE
+.PP
+The function `tcbdbget' is used in order to retrieve a record in a B+ tree database object.
+.PP
+.RS
+.br
+\fBvoid *tcbdbget(TCBDB *\fIbdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, int *\fIsp\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned if no record corresponds.
+.RE
+.RS
+If the key of duplicated records is specified, the first one is selected.  Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcbdbget2' is used in order to retrieve a string record in a B+ tree database object.
+.PP
+.RS
+.br
+\fBchar *tcbdbget2(TCBDB *\fIbdb\fB, const char *\fIkstr\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+If successful, the return value is the string of the value of the corresponding record.  `NULL' is returned if no record corresponds.
+.RE
+.RS
+If the key of duplicated records is specified, the first one is selected.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcbdbget3' is used in order to retrieve a record in a B+ tree database object as a volatile buffer.
+.PP
+.RS
+.br
+\fBconst void *tcbdbget3(TCBDB *\fIbdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, int *\fIsp\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned if no record corresponds.
+.RE
+.RS
+If the key of duplicated records is specified, the first one is selected.  Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is volatile and it may be spoiled by another operation of the database, the data should be copied into another involatile buffer immediately.
+.RE
+.RE
+.PP
+The function `tcbdbget4' is used in order to retrieve records in a B+ tree database object.
+.PP
+.RS
+.br
+\fBTCLIST *tcbdbget4(TCBDB *\fIbdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+If successful, the return value is a list object of the values of the corresponding records.  `NULL' is returned if no record corresponds.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcbdbvnum' is used in order to get the number of records corresponding a key in a B+ tree database object.
+.PP
+.RS
+.br
+\fBint tcbdbvnum(TCBDB *\fIbdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+If successful, the return value is the number of the corresponding records, else, it is 0.
+.RE
+.RE
+.PP
+The function `tcbdbvnum2' is used in order to get the number of records corresponding a string key in a B+ tree database object.
+.PP
+.RS
+.br
+\fBint tcbdbvnum2(TCBDB *\fIbdb\fB, const char *\fIkstr\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+If successful, the return value is the number of the corresponding records, else, it is 0.
+.RE
+.RE
+.PP
+The function `tcbdbvsiz' is used in order to get the size of the value of a record in a B+ tree database object.
+.PP
+.RS
+.br
+\fBint tcbdbvsiz(TCBDB *\fIbdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+If successful, the return value is the size of the value of the corresponding record, else, it is \-1.
+.RE
+.RS
+If the key of duplicated records is specified, the first one is selected.
+.RE
+.RE
+.PP
+The function `tcbdbvsiz2' is used in order to get the size of the value of a string record in a B+ tree database object.
+.PP
+.RS
+.br
+\fBint tcbdbvsiz2(TCBDB *\fIbdb\fB, const char *\fIkstr\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+If successful, the return value is the size of the value of the corresponding record, else, it is \-1.
+.RE
+.RS
+If the key of duplicated records is specified, the first one is selected.
+.RE
+.RE
+.PP
+The function `tcbdbrange' is used in order to get keys of ranged records in a B+ tree database object.
+.PP
+.RS
+.br
+\fBTCLIST *tcbdbrange(TCBDB *\fIbdb\fB, const void *\fIbkbuf\fB, int \fIbksiz\fB, bool \fIbinc\fB, const void *\fIekbuf\fB, int \fIeksiz\fB, bool \fIeinc\fB, int \fImax\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object.
+.RE
+.RS
+`\fIbkbuf\fR' specifies the pointer to the region of the key of the beginning border.  If it is `NULL', the first record is specified.
+.RE
+.RS
+`\fIbksiz\fR' specifies the size of the region of the beginning key.
+.RE
+.RS
+`\fIbinc\fR' specifies whether the beginning border is inclusive or not.
+.RE
+.RS
+`\fIekbuf\fR' specifies the pointer to the region of the key of the ending border.  If it is `NULL', the last record is specified.
+.RE
+.RS
+`\fIeksiz\fR' specifies the size of the region of the ending key.
+.RE
+.RS
+`\fIeinc\fR' specifies whether the ending border is inclusive or not.
+.RE
+.RS
+`\fImax\fR' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.
+.RE
+.RS
+The return value is a list object of the keys of the corresponding records.  This function does never fail.  It returns an empty list even if no record corresponds.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcbdbrange2' is used in order to get string keys of ranged records in a B+ tree database object.
+.PP
+.RS
+.br
+\fBTCLIST *tcbdbrange2(TCBDB *\fIbdb\fB, const char *\fIbkstr\fB, bool \fIbinc\fB, const char *\fIekstr\fB, bool \fIeinc\fB, int \fImax\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object.
+.RE
+.RS
+`\fIbkstr\fR' specifies the string of the key of the beginning border.  If it is `NULL', the first record is specified.
+.RE
+.RS
+`\fIbinc\fR' specifies whether the beginning border is inclusive or not.
+.RE
+.RS
+`\fIekstr\fR' specifies the string of the key of the ending border.  If it is `NULL', the last record is specified.
+.RE
+.RS
+`\fIeinc\fR' specifies whether the ending border is inclusive or not.
+.RE
+.RS
+`\fImax\fR' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.
+.RE
+.RS
+The return value is a list object of the keys of the corresponding records.  This function does never fail.  It returns an empty list even if no record corresponds.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcbdbfwmkeys' is used in order to get forward matching keys in a B+ tree database object.
+.PP
+.RS
+.br
+\fBTCLIST *tcbdbfwmkeys(TCBDB *\fIbdb\fB, const void *\fIpbuf\fB, int \fIpsiz\fB, int \fImax\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object.
+.RE
+.RS
+`\fIpbuf\fR' specifies the pointer to the region of the prefix.
+.RE
+.RS
+`\fIpsiz\fR' specifies the size of the region of the prefix.
+.RE
+.RS
+`\fImax\fR' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.
+.RE
+.RS
+The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcbdbfwmkeys2' is used in order to get forward matching string keys in a B+ tree database object.
+.PP
+.RS
+.br
+\fBTCLIST *tcbdbfwmkeys2(TCBDB *\fIbdb\fB, const char *\fIpstr\fB, int \fImax\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object.
+.RE
+.RS
+`\fIpstr\fR' specifies the string of the prefix.
+.RE
+.RS
+`\fImax\fR' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.
+.RE
+.RS
+The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcbdbaddint' is used in order to add an integer to a record in a B+ tree database object.
+.PP
+.RS
+.br
+\fBint tcbdbaddint(TCBDB *\fIbdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, int \fInum\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object connected as a writer.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fInum\fR' specifies the additional value.
+.RE
+.RS
+If successful, the return value is the summation value, else, it is `INT_MIN'.
+.RE
+.RS
+If the corresponding record exists, the value is treated as an integer and is added to.  If no record corresponds, a new record of the additional value is stored.
+.RE
+.RE
+.PP
+The function `tcbdbadddouble' is used in order to add a real number to a record in a B+ tree database object.
+.PP
+.RS
+.br
+\fBdouble tcbdbadddouble(TCBDB *\fIbdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, double \fInum\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object connected as a writer.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fInum\fR' specifies the additional value.
+.RE
+.RS
+If successful, the return value is the summation value, else, it is Not-a-Number.
+.RE
+.RS
+If the corresponding record exists, the value is treated as a real number and is added to.  If no record corresponds, a new record of the additional value is stored.
+.RE
+.RE
+.PP
+The function `tcbdbsync' is used in order to synchronize updated contents of a B+ tree database object with the file and the device.
+.PP
+.RS
+.br
+\fBbool tcbdbsync(TCBDB *\fIbdb\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object connected as a writer.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+This function is useful when another process connects to the same database file.
+.RE
+.RE
+.PP
+The function `tcbdboptimize' is used in order to optimize the file of a B+ tree database object.
+.PP
+.RS
+.br
+\fBbool tcbdboptimize(TCBDB *\fIbdb\fB, int32_t \fIlmemb\fB, int32_t \fInmemb\fB, int64_t \fIbnum\fB, int8_t \fIapow\fB, int8_t \fIfpow\fB, uint8_t \fIopts\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object connected as a writer.
+.RE
+.RS
+`\fIlmemb\fR' specifies the number of members in each leaf page.  If it is not more than 0, the current setting is not changed.
+.RE
+.RS
+`\fInmemb\fR' specifies the number of members in each non\-leaf page.  If it is not more than 0, the current setting is not changed.
+.RE
+.RS
+`\fIbnum\fR' specifies the number of elements of the bucket array.  If it is not more than 0, the default value is specified.  The default value is two times of the number of pages.
+.RE
+.RS
+`\fIapow\fR' specifies the size of record alignment by power of 2.  If it is negative, the current setting is not changed.
+.RE
+.RS
+`\fIfpow\fR' specifies the maximum number of elements of the free block pool by power of 2.  If it is negative, the current setting is not changed.
+.RE
+.RS
+`\fIopts\fR' specifies options by bitwise-or: `BDBTLARGE' specifies that the size of the database can be larger than 2GB by using 64\-bit bucket array, `BDBTDEFLATE' specifies that each record is compressed with Deflate encoding, `BDBTBZIP' specifies that each page is compressed with BZIP2 encoding, `BDBTTCBS' specifies that each page is compressed with TCBS encoding.  If it is `UINT8_MAX', the current setting is not changed.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+This function is useful to reduce the size of the database file with data fragmentation by successive updating.
+.RE
+.RE
+.PP
+The function `tcbdbvanish' is used in order to remove all records of a B+ tree database object.
+.PP
+.RS
+.br
+\fBbool tcbdbvanish(TCBDB *\fIbdb\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object connected as a writer.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RE
+.PP
+The function `tcbdbcopy' is used in order to copy the database file of a B+ tree database object.
+.PP
+.RS
+.br
+\fBbool tcbdbcopy(TCBDB *\fIbdb\fB, const char *\fIpath\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object.
+.RE
+.RS
+`\fIpath\fR' specifies the path of the destination file.  If it begins with `@', the trailing substring is executed as a command line.
+.RE
+.RS
+If successful, the return value is true, else, it is false.  False is returned if the executed command returns non\-zero code.
+.RE
+.RS
+The database file is assured to be kept synchronized and not modified while the copying or executing operation is in progress.  So, this function is useful to create a backup file of the database file.
+.RE
+.RE
+.PP
+The function `tcbdbtranbegin' is used in order to begin the transaction of a B+ tree database object.
+.PP
+.RS
+.br
+\fBbool tcbdbtranbegin(TCBDB *\fIbdb\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object connected as a writer.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+The database is locked by the thread while the transaction so that only one transaction can be activated with a database object at the same time.  Thus, the serializable isolation level is assumed if every database operation is performed in the transaction.  Because all pages are cached on memory while the transaction, the amount of referred records is limited by the memory capacity.  If the database is closed during transaction, the transaction is aborted implicitly.
+.RE
+.RE
+.PP
+The function `tcbdbtrancommit' is used in order to commit the transaction of a B+ tree database object.
+.PP
+.RS
+.br
+\fBbool tcbdbtrancommit(TCBDB *\fIbdb\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object connected as a writer.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Update in the transaction is fixed when it is committed successfully.
+.RE
+.RE
+.PP
+The function `tcbdbtranabort' is used in order to abort the transaction of a B+ tree database object.
+.PP
+.RS
+.br
+\fBbool tcbdbtranabort(TCBDB *\fIbdb\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object connected as a writer.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Update in the transaction is discarded when it is aborted.  The state of the database is rollbacked to before transaction.
+.RE
+.RE
+.PP
+The function `tcbdbpath' is used in order to get the file path of a B+ tree database object.
+.PP
+.RS
+.br
+\fBconst char *tcbdbpath(TCBDB *\fIbdb\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object.
+.RE
+.RS
+The return value is the path of the database file or `NULL' if the object does not connect to any database file.
+.RE
+.RE
+.PP
+The function `tcbdbrnum' is used in order to get the number of records of a B+ tree database object.
+.PP
+.RS
+.br
+\fBuint64_t tcbdbrnum(TCBDB *\fIbdb\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object.
+.RE
+.RS
+The return value is the number of records or 0 if the object does not connect to any database file.
+.RE
+.RE
+.PP
+The function `tcbdbfsiz' is used in order to get the size of the database file of a B+ tree database object.
+.PP
+.RS
+.br
+\fBuint64_t tcbdbfsiz(TCBDB *\fIbdb\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object.
+.RE
+.RS
+The return value is the size of the database file or 0 if the object does not connect to any database file.
+.RE
+.RE
+.PP
+The function `tcbdbcurnew' is used in order to create a cursor object.
+.PP
+.RS
+.br
+\fBBDBCUR *tcbdbcurnew(TCBDB *\fIbdb\fB);\fR
+.RS
+`\fIbdb\fR' specifies the B+ tree database object.
+.RE
+.RS
+The return value is the new cursor object.
+.RE
+.RS
+Note that the cursor is available only after initialization with the `tcbdbcurfirst' or the `tcbdbcurjump' functions and so on.  Moreover, the position of the cursor will be indefinite when the database is updated after the initialization of the cursor.
+.RE
+.RE
+.PP
+The function `tcbdbcurdel' is used in order to delete a cursor object.
+.PP
+.RS
+.br
+\fBvoid tcbdbcurdel(BDBCUR *\fIcur\fB);\fR
+.RS
+`\fIcur\fR' specifies the cursor object.
+.RE
+.RE
+.PP
+The function `tcbdbcurfirst' is used in order to move a cursor object to the first record.
+.PP
+.RS
+.br
+\fBbool tcbdbcurfirst(BDBCUR *\fIcur\fB);\fR
+.RS
+`\fIcur\fR' specifies the cursor object.
+.RE
+.RS
+If successful, the return value is true, else, it is false.  False is returned if there is no record in the database.
+.RE
+.RE
+.PP
+The function `tcbdbcurlast' is used in order to move a cursor object to the last record.
+.PP
+.RS
+.br
+\fBbool tcbdbcurlast(BDBCUR *\fIcur\fB);\fR
+.RS
+`\fIcur\fR' specifies the cursor object.
+.RE
+.RS
+If successful, the return value is true, else, it is false.  False is returned if there is no record in the database.
+.RE
+.RE
+.PP
+The function `tcbdbcurjump' is used in order to move a cursor object to the front of records corresponding a key.
+.PP
+.RS
+.br
+\fBbool tcbdbcurjump(BDBCUR *\fIcur\fB, const void *\fIkbuf\fB, int \fIksiz\fB);\fR
+.RS
+`\fIcur\fR' specifies the cursor object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+If successful, the return value is true, else, it is false.  False is returned if there is no record corresponding the condition.
+.RE
+.RS
+The cursor is set to the first record corresponding the key or the next substitute if completely matching record does not exist.
+.RE
+.RE
+.PP
+The function `tcbdbcurjump2' is used in order to move a cursor object to the front of records corresponding a key string.
+.PP
+.RS
+.br
+\fBbool tcbdbcurjump2(BDBCUR *\fIcur\fB, const char *\fIkstr\fB);\fR
+.RS
+`\fIcur\fR' specifies the cursor object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+If successful, the return value is true, else, it is false.  False is returned if there is no record corresponding the condition.
+.RE
+.RS
+The cursor is set to the first record corresponding the key or the next substitute if completely matching record does not exist.
+.RE
+.RE
+.PP
+The function `tcbdbcurprev' is used in order to move a cursor object to the previous record.
+.PP
+.RS
+.br
+\fBbool tcbdbcurprev(BDBCUR *\fIcur\fB);\fR
+.RS
+`\fIcur\fR' specifies the cursor object.
+.RE
+.RS
+If successful, the return value is true, else, it is false.  False is returned if there is no previous record.
+.RE
+.RE
+.PP
+The function `tcbdbcurnext' is used in order to move a cursor object to the next record.
+.PP
+.RS
+.br
+\fBbool tcbdbcurnext(BDBCUR *\fIcur\fB);\fR
+.RS
+`\fIcur\fR' specifies the cursor object.
+.RE
+.RS
+If successful, the return value is true, else, it is false.  False is returned if there is no next record.
+.RE
+.RE
+.PP
+The function `tcbdbcurput' is used in order to insert a record around a cursor object.
+.PP
+.RS
+.br
+\fBbool tcbdbcurput(BDBCUR *\fIcur\fB, const void *\fIvbuf\fB, int \fIvsiz\fB, int \fIcpmode\fB);\fR
+.RS
+`\fIcur\fR' specifies the cursor object of writer connection.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.
+.RE
+.RS
+`\fIcpmode\fR' specifies detail adjustment: `BDBCPCURRENT', which means that the value of the current record is overwritten, `BDBCPBEFORE', which means that the new record is inserted before the current record, `BDBCPAFTER', which means that the new record is inserted after the current record.
+.RE
+.RS
+If successful, the return value is true, else, it is false.  False is returned when the cursor is at invalid position.
+.RE
+.RS
+After insertion, the cursor is moved to the inserted record.
+.RE
+.RE
+.PP
+The function `tcbdbcurput2' is used in order to insert a string record around a cursor object.
+.PP
+.RS
+.br
+\fBbool tcbdbcurput2(BDBCUR *\fIcur\fB, const char *\fIvstr\fB, int \fIcpmode\fB);\fR
+.RS
+`\fIcur\fR' specifies the cursor object of writer connection.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+`\fIcpmode\fR' specifies detail adjustment: `BDBCPCURRENT', which means that the value of the current record is overwritten, `BDBCPBEFORE', which means that the new record is inserted before the current record, `BDBCPAFTER', which means that the new record is inserted after the current record.
+.RE
+.RS
+If successful, the return value is true, else, it is false.  False is returned when the cursor is at invalid position.
+.RE
+.RS
+After insertion, the cursor is moved to the inserted record.
+.RE
+.RE
+.PP
+The function `tcbdbcurout' is used in order to remove the record where a cursor object is.
+.PP
+.RS
+.br
+\fBbool tcbdbcurout(BDBCUR *\fIcur\fB);\fR
+.RS
+`\fIcur\fR' specifies the cursor object of writer connection.
+.RE
+.RS
+If successful, the return value is true, else, it is false.  False is returned when the cursor is at invalid position.
+.RE
+.RS
+After deletion, the cursor is moved to the next record if possible.
+.RE
+.RE
+.PP
+The function `tcbdbcurkey' is used in order to get the key of the record where the cursor object is.
+.PP
+.RS
+.br
+\fBchar *tcbdbcurkey(BDBCUR *\fIcur\fB, int *\fIsp\fB);\fR
+.RS
+`\fIcur\fR' specifies the cursor object.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the key, else, it is `NULL'.  `NULL' is returned when the cursor is at invalid position.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcbdbcurkey2' is used in order to get the key string of the record where the cursor object is.
+.PP
+.RS
+.br
+\fBchar *tcbdbcurkey2(BDBCUR *\fIcur\fB);\fR
+.RS
+`\fIcur\fR' specifies the cursor object.
+.RE
+.RS
+If successful, the return value is the string of the key, else, it is `NULL'.  `NULL' is returned when the cursor is at invalid position.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcbdbcurkey3' is used in order to get the key of the record where the cursor object is, as a volatile buffer.
+.PP
+.RS
+.br
+\fBconst char *tcbdbcurkey3(BDBCUR *\fIcur\fB, int *\fIsp\fB);\fR
+.RS
+`\fIcur\fR' specifies the cursor object.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the key, else, it is `NULL'.  `NULL' is returned when the cursor is at invalid position.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is volatile and it may be spoiled by another operation of the database, the data should be copied into another involatile buffer immediately.
+.RE
+.RE
+.PP
+The function `tcbdbcurval' is used in order to get the value of the record where the cursor object is.
+.PP
+.RS
+.br
+\fBchar *tcbdbcurval(BDBCUR *\fIcur\fB, int *\fIsp\fB);\fR
+.RS
+`\fIcur\fR' specifies the cursor object.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the value, else, it is `NULL'.  `NULL' is returned when the cursor is at invalid position.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcbdbcurval2' is used in order to get the value string of the record where the cursor object is.
+.PP
+.RS
+.br
+\fBchar *tcbdbcurval2(BDBCUR *\fIcur\fB);\fR
+.RS
+`\fIcur\fR' specifies the cursor object.
+.RE
+.RS
+If successful, the return value is the string of the value, else, it is `NULL'.  `NULL' is returned when the cursor is at invalid position.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcbdbcurval3' is used in order to get the value of the record where the cursor object is, as a volatile buffer.
+.PP
+.RS
+.br
+\fBconst char *tcbdbcurval3(BDBCUR *\fIcur\fB, int *\fIsp\fB);\fR
+.RS
+`\fIcur\fR' specifies the cursor object.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the value, else, it is `NULL'.  `NULL' is returned when the cursor is at invalid position.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is volatile and it may be spoiled by another operation of the database, the data should be copied into another involatile buffer immediately.
+.RE
+.RE
+.PP
+The function `tcbdbcurrec' is used in order to get the key and the value of the record where the cursor object is.
+.PP
+.RS
+.br
+\fBbool tcbdbcurrec(BDBCUR *\fIcur\fB, TCXSTR *\fIkxstr\fB, TCXSTR *\fIvxstr\fB);\fR
+.RS
+`\fIcur\fR' specifies the cursor object.
+.RE
+.RS
+`\fIkxstr\fR' specifies the object into which the key is wrote down.
+.RE
+.RS
+`\fIvxstr\fR' specifies the object into which the value is wrote down.
+.RE
+.RS
+If successful, the return value is true, else, it is false.  False is returned when the cursor is at invalid position.
+.RE
+.RE
+
+.SH SEE ALSO
+.PP
+.BR tcbtest (1),
+.BR tcbmttest (1),
+.BR tcbmgr (1),
+.BR tokyocabinet (3)
diff --git a/tcejdb/man/tcbmgr.1 b/tcejdb/man/tcbmgr.1
new file mode 100644 (file)
index 0000000..1970464
--- /dev/null
@@ -0,0 +1,125 @@
+.TH "TCBMGR" 1 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tcbmgr \- the command line utility of the B+ tree database API
+
+.SH DESCRIPTION
+The command `\fBtcbmgr\fR' is a utility for test and debugging of the B+ tree database API and its applications.  `\fIpath\fR' specifies the path of a database file.  `\fIlmemb\fR' specifies the number of members in each leaf page.  `\fInmemb\fR' specifies the number of members in each non\-leaf page.  `\fIbnum\fR' specifies the number of buckets.  `\fIapow\fR' specifies the power of the alignment.  `\fIfpow\fR' specifies the power of the free block pool.  `\fIkey\fR' specifies the key of a record.  `\fIvalue\fR' specifies the value of a record.  `\fIfile\fR' specifies the input file.
+.PP
+.RS
+.br
+\fBtcbmgr create \fR[\fB\-cd\fR|\fB\-ci\fR|\fB\-cj\fR]\fB \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fIpath\fB \fR[\fB\fIlmemb\fB \fR[\fB\fInmemb\fB \fR[\fB\fIbnum\fB \fR[\fB\fIapow\fB \fR[\fB\fIfpow\fB\fR]\fB\fR]\fB\fR]\fB\fR]\fB\fR]\fB\fR
+.RS
+Create a database file.
+.RE
+.br
+\fBtcbmgr inform \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fIpath\fB\fR
+.RS
+Print miscellaneous information to the standard output.
+.RE
+.br
+\fBtcbmgr put \fR[\fB\-cd\fR|\fB\-ci\fR|\fB\-cj\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-sx\fR]\fB \fR[\fB\-dk\fR|\fB\-dc\fR|\fB\-dd\fR|\fB\-db\fR|\fB\-dai\fR|\fB\-dad\fR]\fB \fIpath\fB \fIkey\fB \fIvalue\fB\fR
+.RS
+Store a record.
+.RE
+.br
+\fBtcbmgr out \fR[\fB\-cd\fR|\fB\-ci\fR|\fB\-cj\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-sx\fR]\fB \fIpath\fB \fIkey\fB\fR
+.RS
+Remove a record.
+.RE
+.br
+\fBtcbmgr get \fR[\fB\-cd\fR|\fB\-ci\fR|\fB\-cj\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-sx\fR]\fB \fR[\fB\-px\fR]\fB \fR[\fB\-pz\fR]\fB \fIpath\fB \fIkey\fB\fR
+.RS
+Print the value of a record.
+.RE
+.br
+\fBtcbmgr list \fR[\fB\-cd\fR|\fB\-ci\fR|\fB\-cj\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-m \fInum\fB\fR]\fB \fR[\fB\-bk\fR]\fB \fR[\fB\-pv\fR]\fB \fR[\fB\-px\fR]\fB \fR[\fB\-j \fIstr\fB\fR]\fB \fR[\fB\-rb \fIbkey\fB \fIekey\fB\fR]\fB \fR[\fB\-fm \fIstr\fB\fR]\fB \fIpath\fB\fR
+.RS
+Print keys of all records, separated by line feeds.
+.RE
+.br
+\fBtcbmgr optimize \fR[\fB\-cd\fR|\fB\-ci\fR|\fB\-cj\fR]\fB \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-tz\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-df\fR]\fB \fIpath\fB \fR[\fB\fIlmemb\fB \fR[\fB\fInmemb\fB \fR[\fB\fIbnum\fB \fR[\fB\fIapow\fB \fR[\fB\fIfpow\fB\fR]\fB\fR]\fB\fR]\fB\fR]\fB\fR]\fB\fR
+.RS
+Optimize a database file.
+.RE
+.br
+\fBtcbmgr importtsv \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-sc\fR]\fB \fIpath\fB \fR[\fB\fIfile\fB\fR]\fB\fR
+.RS
+Store records of TSV in each line of a file.
+.RE
+.br
+\fBtcbmgr version\fR
+.RS
+Print the version information of Tokyo Cabinet.
+.RE
+.RE
+.PP
+Options feature the following.
+.PP
+.RS
+\fB\-cd\fR : use the comparison function `tccmpdecimal'.
+.br
+\fB\-ci\fR : use the comparison function  `tccmpint32'.
+.br
+\fB\-cj\fR : use the comparison function  `tccmpint64'.
+.br
+\fB\-tl\fR : enable the option `BDBTLARGE'.
+.br
+\fB\-td\fR : enable the option `BDBTDEFLATE'.
+.br
+\fB\-tb\fR : enable the option `BDBTBZIP'.
+.br
+\fB\-tt\fR : enable the option `BDBTTCBS'.
+.br
+\fB\-tx\fR : enable the option `BDBTEXCODEC'.
+.br
+\fB\-nl\fR : enable the option `BDBNOLCK'.
+.br
+\fB\-nb\fR : enable the option `BDBLCKNB'.
+.br
+\fB\-sx\fR : the input data is evaluated as a hexadecimal data string.
+.br
+\fB\-dk\fR : use the function `tcbdbputkeep' instead of `tcbdbput'.
+.br
+\fB\-dc\fR : use the function `tcbdbputcat' instead of `tcbdbput'.
+.br
+\fB\-dd\fR : use the function `tcbdbputdup' instead of `tcbdbput'.
+.br
+\fB\-db\fR : use the function `tcbdbputdupback' instead of `tcbdbput'.
+.br
+\fB\-dai\fR : use the function `tcbdbaddint' instead of `tcbdbput'.
+.br
+\fB\-dad\fR : use the function `tcbdbadddouble' instead of `tcbdbput'.
+.br
+\fB\-px\fR : the output data is converted into a hexadecimal data string.
+.br
+\fB\-pz\fR : do not append line feed at the end of the output.
+.br
+\fB\-m \fInum\fR\fR : specify the maximum number of the output.
+.br
+\fB\-bk\fR : perform backword scanning.
+.br
+\fB\-pv\fR : print values of records also.
+.br
+\fB\-j \fIstr\fR\fR : specify the key where the cursor jump to.
+.br
+\fB\-rb \fIbkey\fR \fIekey\fR\fR : specify the range of keys.
+.br
+\fB\-fm \fIstr\fR\fR : specify the prefix of keys.
+.br
+\fB\-tz\fR : enable the option `UINT8_MAX'.
+.br
+\fB\-df\fR : perform defragmentation only.
+.br
+\fB\-sc\fR : normalize keys as lower cases.
+.br
+.RE
+.PP
+This command returns 0 on success, another on failure.
+
+.SH SEE ALSO
+.PP
+.BR tcbtest (1),
+.BR tcbmttest (1),
+.BR tcbdb (3),
+.BR tokyocabinet (3)
diff --git a/tcejdb/man/tcbmttest.1 b/tcejdb/man/tcbmttest.1
new file mode 100644 (file)
index 0000000..0bebd46
--- /dev/null
@@ -0,0 +1,81 @@
+.TH "TCBMTTEST" 1 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tcbmttest \- test cases of the B+ tree database API
+
+.SH DESCRIPTION
+.PP
+The command `\fBtcbmttest\fR' is a utility for facility test and performance test.  This command is used in the following format.  `\fIpath\fR' specifies the path of a database file.  `\fItnum\fR' specifies the number of running threads.  `\fIrnum\fR' specifies the number of iterations.  `\fIlmemb\fR' specifies the number of members in each leaf page.  `\fInmemb\fR' specifies the number of members in each non\-leaf page.  `\fIbnum\fR' specifies the number of buckets.  `\fIapow\fR' specifies the power of the alignment.  `\fIfpow\fR' specifies the power of the free block pool.
+.PP
+.RS
+.br
+\fBtcbmttest write \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-rnd\fR]\fB \fIpath\fB \fItnum\fB \fIrnum\fB \fR[\fB\fIlmemb\fB \fR[\fB\fInmemb\fB \fR[\fB\fIbnum\fB \fR[\fB\fIapow\fB \fR[\fB\fIfpow\fB\fR]\fB\fR]\fB\fR]\fB\fR]\fB\fR]\fB\fR
+.RS
+Store records with keys of 8 bytes.  They change as `00000001', `00000002'...
+.RE
+.br
+\fBtcbmttest read \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-wb\fR]\fB \fR[\fB\-rnd\fR]\fB \fIpath\fB \fItnum\fB\fR
+.RS
+Retrieve all records of the database above.
+.RE
+.br
+\fBtcbmttest remove \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-rnd\fR]\fB \fIpath\fB \fItnum\fB\fR
+.RS
+Remove all records of the database above.
+.RE
+.br
+\fBtcbmttest wicked \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-nc\fR]\fB \fIpath\fB \fItnum\fB \fIrnum\fB\fR
+.RS
+Perform updating operations selected at random.
+.RE
+.br
+\fBtcbmttest typical \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-nc\fR]\fB \fR[\fB\-rr \fInum\fB\fR]\fB \fIpath\fB \fItnum\fB \fIrnum\fB \fR[\fB\fIlmemb\fB \fR[\fB\fInmemb\fB \fR[\fB\fIbnum\fB \fR[\fB\fIapow\fB \fR[\fB\fIfpow\fB\fR]\fB\fR]\fB\fR]\fB\fR]\fB\fR]\fB\fR
+.RS
+Perform typical operations selected at random.
+.RE
+.br
+\fBtcbmttest race \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fIpath\fB \fItnum\fB \fIrnum\fB \fR[\fB\fIlmemb\fB \fR[\fB\fInmemb\fB \fR[\fB\fIbnum\fB \fR[\fB\fIapow\fB \fR[\fB\fIfpow\fB\fR]\fB\fR]\fB\fR]\fB\fR]\fB\fR]\fB\fR
+.RS
+Perform race condition test.
+.RE
+.RE
+.PP
+Options feature the following.
+.PP
+.RS
+\fB\-tl\fR : enable the option `BDBTLARGE'.
+.br
+\fB\-td\fR : enable the option `BDBTDEFLATE'.
+.br
+\fB\-tb\fR : enable the option `BDBTBZIP'.
+.br
+\fB\-tt\fR : enable the option `BDBTTCBS'.
+.br
+\fB\-tx\fR : enable the option `BDBTEXCODEC'.
+.br
+\fB\-xm \fInum\fR\fR : specify the size of the extra mapped memory.
+.br
+\fB\-df \fInum\fR\fR : specify the unit step number of auto defragmentation.
+.br
+\fB\-nl\fR : enable the option `BDBNOLCK'.
+.br
+\fB\-nb\fR : enable the option `BDBLCKNB'.
+.br
+\fB\-rnd\fR : select keys at random.
+.br
+\fB\-wb\fR : use the function `tchdbget3' instead of `tchdbget'.
+.br
+\fB\-nc\fR : omit the comparison test.
+.br
+\fB\-rr \fInum\fR\fR : specify the ratio of reading operation by percentage.
+.br
+.RE
+.PP
+This command returns 0 on success, another on failure.
+
+.SH SEE ALSO
+.PP
+.BR tcbtest (1),
+.BR tcbmgr (1),
+.BR tcbdb (3),
+.BR tokyocabinet (3)
diff --git a/tcejdb/man/tcbtest.1 b/tcejdb/man/tcbtest.1
new file mode 100644 (file)
index 0000000..5e23865
--- /dev/null
@@ -0,0 +1,107 @@
+.TH "TCBTEST" 1 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tcbtest \- test cases of the B+ tree database API
+
+.SH DESCRIPTION
+The command `\fBtcbtest\fR' is a utility for facility test and performance test.  This command is used in the following format.  `\fIpath\fR' specifies the path of a database file.  `\fIrnum\fR' specifies the number of iterations.  `\fIlmemb\fR' specifies the number of members in each leaf page.  `\fInmemb\fR' specifies the number of members in each non\-leaf page.  `\fIbnum\fR' specifies the number of buckets.  `\fIapow\fR' specifies the power of the alignment.  `\fIfpow\fR' specifies the power of the free block pool.
+.PP
+.RS
+.br
+\fBtcbtest write \fR[\fB\-mt\fR]\fB \fR[\fB\-cd\fR|\fB\-ci\fR|\fB\-cj\fR]\fB \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-lc \fInum\fB\fR]\fB \fR[\fB\-nc \fInum\fB\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-ls \fInum\fB\fR]\fB \fR[\fB\-ca \fInum\fB\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-rnd\fR]\fB \fIpath\fB \fIrnum\fB \fR[\fB\fIlmemb\fB \fR[\fB\fInmemb\fB \fR[\fB\fIbnum\fB \fR[\fB\fIapow\fB \fR[\fB\fIfpow\fB\fR]\fB\fR]\fB\fR]\fB\fR]\fB\fR]\fB\fR
+.RS
+Store records with keys of 8 bytes.  They change as `00000001', `00000002'...
+.RE
+.br
+\fBtcbtest read \fR[\fB\-mt\fR]\fB \fR[\fB\-cd\fR|\fB\-ci\fR|\fB\-cj\fR]\fB \fR[\fB\-lc \fInum\fB\fR]\fB \fR[\fB\-nc \fInum\fB\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-wb\fR]\fB \fR[\fB\-rnd\fR]\fB \fIpath\fB\fR
+.RS
+Retrieve all records of the database above.
+.RE
+.br
+\fBtcbtest remove \fR[\fB\-mt\fR]\fB \fR[\fB\-cd\fR|\fB\-ci\fR|\fB\-cj\fR]\fB \fR[\fB\-lc \fInum\fB\fR]\fB \fR[\fB\-nc \fInum\fB\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-rnd\fR]\fB \fIpath\fB\fR
+.RS
+Remove all records of the database above.
+.RE
+.br
+\fBtcbtest rcat \fR[\fB\-mt\fR]\fB \fR[\fB\-cd\fR|\fB\-ci\fR|\fB\-cj\fR]\fB \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-lc \fInum\fB\fR]\fB \fR[\fB\-nc \fInum\fB\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-ls \fInum\fB\fR]\fB \fR[\fB\-ca \fInum\fB\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-pn \fInum\fB\fR]\fB \fR[\fB\-dai\fR|\fB\-dad\fR|\fB\-rl\fR|\fB\-ru\fR]\fB \fIpath\fB \fIrnum\fB \fR[\fB\fIlmemb\fB \fR[\fB\fInmemb\fB \fR[\fB\fIbnum\fB \fR[\fB\fIapow\fB \fR[\fB\fIfpow\fB\fR]\fB\fR]\fB\fR]\fB\fR]\fB\fR]\fB\fR
+.RS
+Store records with partway duplicated keys using concatenate mode.
+.RE
+.br
+\fBtcbtest queue \fR[\fB\-mt\fR]\fB \fR[\fB\-cd\fR|\fB\-ci\fR|\fB\-cj\fR]\fB \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-lc \fInum\fB\fR]\fB \fR[\fB\-nc \fInum\fB\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-ls \fInum\fB\fR]\fB \fR[\fB\-ca \fInum\fB\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fIpath\fB \fIrnum\fB \fR[\fB\fIlmemb\fB \fR[\fB\fInmemb\fB \fR[\fB\fIbnum\fB \fR[\fB\fIapow\fB \fR[\fB\fIfpow\fB\fR]\fB\fR]\fB\fR]\fB\fR]\fB\fR]\fB\fR
+.RS
+Perform queueing and dequeueing.
+.RE
+.br
+\fBtcbtest misc \fR[\fB\-mt\fR]\fB \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fIpath\fB \fIrnum\fB\fR
+.RS
+Perform miscellaneous test of various operations.
+.RE
+.br
+\fBtcbtest wicked \fR[\fB\-mt\fR]\fB \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fIpath\fB \fIrnum\fB\fR
+.RS
+Perform updating operations selected at random.
+.RE
+.RE
+.PP
+Options feature the following.
+.PP
+.RS
+\fB\-mt\fR : call the function `tchdbsetmutex'.
+.br
+\fB\-cd\fR : use the comparison function `tccmpdecimal'.
+.br
+\fB\-ci\fR : use the comparison function  `tccmpint32'.
+.br
+\fB\-cj\fR : use the comparison function  `tccmpint64'.
+.br
+\fB\-tl\fR : enable the option `BDBTLARGE'.
+.br
+\fB\-td\fR : enable the option `BDBTDEFLATE'.
+.br
+\fB\-tb\fR : enable the option `BDBTBZIP'.
+.br
+\fB\-tt\fR : enable the option `BDBTTCBS'.
+.br
+\fB\-tx\fR : enable the option `BDBTEXCODEC'.
+.br
+\fB\-lc \fInum\fR\fR : specify the number of cached leaf pages.
+.br
+\fB\-nc \fInum\fR\fR : specify the number of cached non\-leaf pages.
+.br
+\fB\-xm \fInum\fR\fR : specify the size of the extra mapped memory.
+.br
+\fB\-df \fInum\fR\fR : specify the unit step number of auto defragmentation.
+.br
+\fB\-ls \fInum\fR\fR : specify the maximum size of each leaf page.
+.br
+\fB\-ca \fInum\fR\fR : specify the capacity number of records.
+.br
+\fB\-nl\fR : enable the option `BDBNOLCK'.
+.br
+\fB\-nb\fR : enable the option `BDBLCKNB'.
+.br
+\fB\-rnd\fR : select keys at random.
+.br
+\fB\-wb\fR : use the function `tcbdbget3' instead of `tcbdbget'.
+.br
+\fB\-pn \fInum\fR\fR : specify the number of patterns.
+.br
+\fB\-dai\fR : use the function `tcbdbaddint' instead of `tcbdbputcat'.
+.br
+\fB\-dad\fR : use the function `tcbdbadddouble' instead of `tcbdbputcat'.
+.br
+\fB\-rl\fR : set the length of values at random.
+.br
+\fB\-ru\fR : select update operations at random.
+.br
+.RE
+.PP
+This command returns 0 on success, another on failure.
+
+.SH SEE ALSO
+.PP
+.BR tcbmttest (1),
+.BR tcbmgr (1),
+.BR tcbdb (3),
+.BR tokyocabinet (3)
diff --git a/tcejdb/man/tcfdb.3 b/tcejdb/man/tcfdb.3
new file mode 100644 (file)
index 0000000..3ac8946
--- /dev/null
@@ -0,0 +1,975 @@
+.TH "TCFDB" 3 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tcfdb \- the fixed-length database API
+
+.SH DESCRIPTION
+.PP
+Fixed\-length database is a file containing an array of fixed\-length elements and is handled with the fixed\-length database API.
+.PP
+To use the fixed\-length database API, include `\fBtcutil.h\fR', `\fBtcfdb.h\fR', and related standard header files.  Usually, write the following description near the front of a source file.
+.PP
+.RS
+.br
+\fB#include <tcutil.h>\fR
+.br
+\fB#include <tcfdb.h>\fR
+.br
+\fB#include <stdlib.h>\fR
+.br
+\fB#include <stdbool.h>\fR
+.br
+\fB#include <stdint.h>\fR
+.RE
+.PP
+Objects whose type is pointer to `\fBTCFDB\fR' are used to handle fixed\-length databases.  A fixed\-length database object is created with the function `\fBtcfdbnew\fR' and is deleted with the function `\fBtcfdbdel\fR'.  To avoid memory leak, it is important to delete every object when it is no longer in use.
+.PP
+Before operations to store or retrieve records, it is necessary to open a database file and connect the fixed\-length database object to it.  The function `\fBtcfdbopen\fR' is used to open a database file and the function `\fBtcfdbclose\fR' is used to close the database file.  To avoid data missing or corruption, it is important to close every database file when it is no longer in use.  It is forbidden for multible database objects in a process to open the same database at the same time.
+
+.SH API
+.PP
+The function `tcfdberrmsg' is used in order to get the message string corresponding to an error code.
+.PP
+.RS
+.br
+\fBconst char *tcfdberrmsg(int \fIecode\fB);\fR
+.RS
+`\fIecode\fR' specifies the error code.
+.RE
+.RS
+The return value is the message string of the error code.
+.RE
+.RE
+.PP
+The function `tcfdbnew' is used in order to create a fixed\-length database object.
+.PP
+.RS
+.br
+\fBTCFDB *tcfdbnew(void);\fR
+.RS
+The return value is the new fixed\-length database object.
+.RE
+.RE
+.PP
+The function `tcfdbdel' is used in order to delete a fixed\-length database object.
+.PP
+.RS
+.br
+\fBvoid tcfdbdel(TCFDB *\fIfdb\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object.
+.RE
+.RS
+If the database is not closed, it is closed implicitly.  Note that the deleted object and its derivatives can not be used anymore.
+.RE
+.RE
+.PP
+The function `tcfdbecode' is used in order to get the last happened error code of a fixed\-length database object.
+.PP
+.RS
+.br
+\fBint tcfdbecode(TCFDB *\fIfdb\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object.
+.RE
+.RS
+The return value is the last happened error code.
+.RE
+.RS
+The following error codes are defined: `TCESUCCESS' for success, `TCETHREAD' for threading error, `TCEINVALID' for invalid operation, `TCENOFILE' for file not found, `TCENOPERM' for no permission, `TCEMETA' for invalid meta data, `TCERHEAD' for invalid record header, `TCEOPEN' for open error, `TCECLOSE' for close error, `TCETRUNC' for trunc error, `TCESYNC' for sync error, `TCESTAT' for stat error, `TCESEEK' for seek error, `TCEREAD' for read error, `TCEWRITE' for write error, `TCEMMAP' for mmap error, `TCELOCK' for lock error, `TCEUNLINK' for unlink error, `TCERENAME' for rename error, `TCEMKDIR' for mkdir error, `TCERMDIR' for rmdir error, `TCEKEEP' for existing record, `TCENOREC' for no record found, and `TCEMISC' for miscellaneous error.
+.RE
+.RE
+.PP
+The function `tcfdbsetmutex' is used in order to set mutual exclusion control of a fixed\-length database object for threading.
+.PP
+.RS
+.br
+\fBbool tcfdbsetmutex(TCFDB *\fIfdb\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object which is not opened.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Note that the mutual exclusion control is needed if the object is shared by plural threads and this function should be called before the database is opened.
+.RE
+.RE
+.PP
+The function `tcfdbtune' is used in order to set the tuning parameters of a fixed\-length database object.
+.PP
+.RS
+.br
+\fBbool tcfdbtune(TCFDB *\fIfdb\fB, int32_t \fIwidth\fB, int64_t \fIlimsiz\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object which is not opened.
+.RE
+.RS
+`\fIwidth\fR' specifies the width of the value of each record.  If it is not more than 0, the default value is specified.  The default value is 255.
+.RE
+.RS
+`\fIlimsiz\fR' specifies the limit size of the database file.  If it is not more than 0, the default value is specified.  The default value is 268435456.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Note that the tuning parameters should be set before the database is opened.
+.RE
+.RE
+.PP
+The function `tcfdbopen' is used in order to open a database file and connect a fixed\-length database object.
+.PP
+.RS
+.br
+\fBbool tcfdbopen(TCFDB *\fIfdb\fB, const char *\fIpath\fB, int \fIomode\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object which is not opened.
+.RE
+.RS
+`\fIpath\fR' specifies the path of the database file.
+.RE
+.RS
+`\fIomode\fR' specifies the connection mode: `FDBOWRITER' as a writer, `FDBOREADER' as a reader.  If the mode is `FDBOWRITER', the following may be added by bitwise\-or: `FDBOCREAT', which means it creates a new database if not exist, `FDBOTRUNC', which means it creates a new database regardless if one exists, `FDBOTSYNC', which means every transaction synchronizes updated contents with the device.  Both of `FDBOREADER' and `FDBOWRITER' can be added to by bitwise\-or: `FDBONOLCK', which means it opens the database file without file locking, or `FDBOLCKNB', which means locking is performed without blocking.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RE
+.PP
+The function `tcfdbclose' is used in order to close a fixed\-length database object.
+.PP
+.RS
+.br
+\fBbool tcfdbclose(TCFDB *\fIfdb\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Update of a database is assured to be written when the database is closed.  If a writer opens a database but does not close it appropriately, the database will be broken.
+.RE
+.RE
+.PP
+The function `tcfdbput' is used in order to store a record into a fixed\-length database object.
+.PP
+.RS
+.br
+\fBbool tcfdbput(TCFDB *\fIfdb\fB, int64_t \fIid\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object connected as a writer.
+.RE
+.RS
+`\fIid\fR' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDPREV', the number less by one than the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.  If it is `FDBIDNEXT', the number greater by one than the maximum ID number of existing records is specified.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.  If the size of the value is greater than the width tuning parameter of the database, the size is cut down to the width.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, it is overwritten.
+.RE
+.RE
+.PP
+The function `tcfdbput2' is used in order to store a record with a decimal key into a fixed\-length database object.
+.PP
+.RS
+.br
+\fBbool tcfdbput2(TCFDB *\fIfdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object connected as a writer.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "prev", the number less by one than the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.  If it is "next", the number greater by one than the maximum ID number of existing records is specified.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.  If the size of the value is greater than the width tuning parameter of the database, the size is cut down to the width.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, it is overwritten.
+.RE
+.RE
+.PP
+The function `tcfdbput3' is used in order to store a string record with a decimal key into a fixed\-length database object.
+.PP
+.RS
+.br
+\fBbool tcfdbput3(TCFDB *\fIfdb\fB, const char *\fIkstr\fB, const void *\fIvstr\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object connected as a writer.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "prev", the number less by one than the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.  If it is "next", the number greater by one than the maximum ID number of existing records is specified.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, it is overwritten.
+.RE
+.RE
+.PP
+The function `tcfdbputkeep' is used in order to store a new record into a fixed\-length database object.
+.PP
+.RS
+.br
+\fBbool tcfdbputkeep(TCFDB *\fIfdb\fB, int64_t \fIid\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object connected as a writer.
+.RE
+.RS
+`\fIid\fR' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDPREV', the number less by one than the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.  If it is `FDBIDNEXT', the number greater by one than the maximum ID number of existing records is specified.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.  If the size of the value is greater than the width tuning parameter of the database, the size is cut down to the width.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, this function has no effect.
+.RE
+.RE
+.PP
+The function `tcfdbputkeep2' is used in order to store a new record with a decimal key into a fixed\-length database object.
+.PP
+.RS
+.br
+\fBbool tcfdbputkeep2(TCFDB *\fIfdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object connected as a writer.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "prev", the number less by one than the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.  If it is "next", the number greater by one than the maximum ID number of existing records is specified.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.  If the size of the value is greater than the width tuning parameter of the database, the size is cut down to the width.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, this function has no effect.
+.RE
+.RE
+.PP
+The function `tcfdbputkeep3' is used in order to store a new string record with a decimal key into a fixed\-length database object.
+.PP
+.RS
+.br
+\fBbool tcfdbputkeep3(TCFDB *\fIfdb\fB, const char *\fIkstr\fB, const void *\fIvstr\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object connected as a writer.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "prev", the number less by one than the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.  If it is "next", the number greater by one than the maximum ID number of existing records is specified.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, this function has no effect.
+.RE
+.RE
+.PP
+The function `tcfdbputcat' is used in order to concatenate a value at the end of the existing record in a fixed\-length database object.
+.PP
+.RS
+.br
+\fBbool tcfdbputcat(TCFDB *\fIfdb\fB, int64_t \fIid\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object connected as a writer.
+.RE
+.RS
+`\fIid\fR' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDPREV', the number less by one than the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.  If it is `FDBIDNEXT', the number greater by one than the maximum ID number of existing records is specified.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.  If the size of the value is greater than the width tuning parameter of the database, the size is cut down to the width.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If there is no corresponding record, a new record is created.
+.RE
+.RE
+.PP
+The function `tcfdbputcat2' is used in order to concatenate a value with a decimal key in a fixed\-length database object.
+.PP
+.RS
+.br
+\fBbool tcfdbputcat2(TCFDB *\fIfdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object connected as a writer.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "prev", the number less by one than the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.  If it is "next", the number greater by one than the maximum ID number of existing records is specified.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.  If the size of the value is greater than the width tuning parameter of the database, the size is cut down to the width.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If there is no corresponding record, a new record is created.
+.RE
+.RE
+.PP
+The function `tcfdbputcat3' is used in order to concatenate a string value with a decimal key in a fixed\-length database object.
+.PP
+.RS
+.br
+\fBbool tcfdbputcat3(TCFDB *\fIfdb\fB, const char *\fIkstr\fB, const void *\fIvstr\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object connected as a writer.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "prev", the number less by one than the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.  If it is "next", the number greater by one than the maximum ID number of existing records is specified.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If there is no corresponding record, a new record is created.
+.RE
+.RE
+.PP
+The function `tcfdbout' is used in order to remove a record of a fixed\-length database object.
+.PP
+.RS
+.br
+\fBbool tcfdbout(TCFDB *\fIfdb\fB, int64_t \fIid\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object connected as a writer.
+.RE
+.RS
+`\fIid\fR' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RE
+.PP
+The function `tcfdbout2' is used in order to remove a record with a decimal key of a fixed\-length database object.
+.PP
+.RS
+.br
+\fBbool tcfdbout2(TCFDB *\fIfdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object connected as a writer.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RE
+.PP
+The function `tcfdbout3' is used in order to remove a string record with a decimal key of a fixed\-length database object.
+.PP
+.RS
+.br
+\fBbool tcfdbout3(TCFDB *\fIfdb\fB, const char *\fIkstr\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object connected as a writer.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RE
+.PP
+The function `tcfdbget' is used in order to retrieve a record in a fixed\-length database object.
+.PP
+.RS
+.br
+\fBvoid *tcfdbget(TCFDB *\fIfdb\fB, int64_t \fIid\fB, int *\fIsp\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object.
+.RE
+.RS
+`\fIid\fR' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned if no record corresponds.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcfdbget2' is used in order to retrieve a record with a decimal key in a fixed\-length database object.
+.PP
+.RS
+.br
+\fBvoid *tcfdbget2(TCFDB *\fIfdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, int *\fIsp\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned if no record corresponds.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcfdbget3' is used in order to retrieve a string record with a decimal key in a fixed\-length database object.
+.PP
+.RS
+.br
+\fBchar *tcfdbget3(TCFDB *\fIfdb\fB, const char *\fIkstr\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.
+.RE
+.RS
+If successful, the return value is the string of the value of the corresponding record.  `NULL' is returned if no record corresponds.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcfdbget4' is used in order to retrieve a record in a fixed\-length database object and write the value into a buffer.
+.PP
+.RS
+.br
+\fBint tcfdbget4(TCFDB *\fIfdb\fB, int64_t \fIid\fB, void *\fIvbuf\fB, int \fImax\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object.
+.RE
+.RS
+`\fIid\fR' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the buffer into which the value of the corresponding record is written.
+.RE
+.RS
+`\fImax\fR' specifies the size of the buffer.
+.RE
+.RS
+If successful, the return value is the size of the written data, else, it is \-1.  \-1 is returned if no record corresponds to the specified key.
+.RE
+.RS
+Note that an additional zero code is not appended at the end of the region of the writing buffer.
+.RE
+.RE
+.PP
+The function `tcfdbvsiz' is used in order to get the size of the value of a record in a fixed\-length database object.
+.PP
+.RS
+.br
+\fBint tcfdbvsiz(TCFDB *\fIfdb\fB, int64_t \fIid\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object.
+.RE
+.RS
+`\fIid\fR' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.
+.RE
+.RS
+If successful, the return value is the size of the value of the corresponding record, else, it is \-1.
+.RE
+.RE
+.PP
+The function `tcfdbvsiz2' is used in order to get the size of the value with a decimal key in a fixed\-length database object.
+.PP
+.RS
+.br
+\fBint tcfdbvsiz2(TCFDB *\fIfdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+If successful, the return value is the size of the value of the corresponding record, else, it is \-1.
+.RE
+.RE
+.PP
+The function `tcfdbvsiz3' is used in order to get the size of the string value with a decimal key in a fixed\-length database object.
+.PP
+.RS
+.br
+\fBint tcfdbvsiz3(TCFDB *\fIfdb\fB, const char *\fIkstr\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the decimal key.  It should be more than 0.  If it is "min", the minimum ID number of existing records is specified.  If it is "max", the maximum ID number of existing records is specified.
+.RE
+.RS
+If successful, the return value is the size of the value of the corresponding record, else, it is \-1.
+.RE
+.RE
+.PP
+The function `tcfdbiterinit' is used in order to initialize the iterator of a fixed\-length database object.
+.PP
+.RS
+.br
+\fBbool tcfdbiterinit(TCFDB *\fIfdb\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+The iterator is used in order to access the key of every record stored in a database.
+.RE
+.RE
+.PP
+The function `tcfdbiternext' is used in order to get the next ID number of the iterator of a fixed\-length database object.
+.PP
+.RS
+.br
+\fBuint64_t tcfdbiternext(TCFDB *\fIfdb\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object.
+.RE
+.RS
+If successful, the return value is the next ID number of the iterator, else, it is 0.  0 is returned when no record is to be get out of the iterator.
+.RE
+.RS
+It is possible to access every record by iteration of calling this function.  It is allowed to update or remove records whose keys are fetched while the iteration.  The order of this traversal access method is ascending of the ID number.
+.RE
+.RE
+.PP
+The function `tcfdbiternext2' is used in order to get the next decimay key of the iterator of a fixed\-length database object.
+.PP
+.RS
+.br
+\fBvoid *tcfdbiternext2(TCFDB *\fIfdb\fB, int *\fIsp\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the next decimal key, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  It is possible to access every record by iteration of calling this function.  It is allowed to update or remove records whose keys are fetched while the iteration.  The order of this traversal access method is ascending of the ID number.
+.RE
+.RE
+.PP
+The function `tcfdbiternext3' is used in order to get the next decimay key string of the iterator of a fixed\-length database object.
+.PP
+.RS
+.br
+\fBchar *tcfdbiternext3(TCFDB *\fIfdb\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object.
+.RE
+.RS
+If successful, the return value is the string of the next decimal key, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  It is possible to access every record by iteration of calling this function.  It is allowed to update or remove records whose keys are fetched while the iteration.  The order of this traversal access method is ascending of the ID number.
+.RE
+.RE
+.PP
+The function `tcfdbrange' is used in order to get range matching ID numbers in a fixed\-length database object.
+.PP
+.RS
+.br
+\fBuint64_t *tcfdbrange(TCFDB *\fIfdb\fB, int64_t \fIlower\fB, int64_t \fIupper\fB, int \fImax\fB, int *\fInp\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object.
+.RE
+.RS
+`\fIlower\fR' specifies the lower limit of the range.  If it is `FDBIDMIN', the minimum ID is specified.
+.RE
+.RS
+`\fIupper\fR' specifies the upper limit of the range.  If it is `FDBIDMAX', the maximum ID is specified.
+.RE
+.RS
+`\fImax\fR' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.
+.RE
+.RS
+`\fInp\fR' specifies the pointer to the variable into which the number of elements of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to an array of ID numbers of the corresponding records.  `NULL' is returned on failure.  This function does never fail.  It returns an empty array even if no key corresponds.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcfdbrange2' is used in order to get range matching decimal keys in a fixed\-length database object.
+.PP
+.RS
+.br
+\fBTCLIST *tcfdbrange2(TCFDB *\fIfdb\fB, const void *\fIlbuf\fB, int \fIlsiz\fB, const void *\fIubuf\fB, int \fIusiz\fB, int \fImax\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object.
+.RE
+.RS
+`\fIlbuf\fR' specifies the pointer to the region of the lower key.  If it is "min", the minimum ID number of existing records is specified.
+.RE
+.RS
+`\fIlsiz\fR' specifies the size of the region of the lower key.
+.RE
+.RS
+`\fIubuf\fR' specifies the pointer to the region of the upper key.  If it is "max", the maximum ID number of existing records is specified.
+.RE
+.RS
+`\fIusiz\fR' specifies the size of the region of the upper key.
+.RE
+.RS
+`\fImax\fR' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.
+.RE
+.RS
+The return value is a list object of the corresponding decimal keys.  This function does never fail.  It returns an empty list even if no key corresponds.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.
+.RE
+.RE
+.PP
+The function `tcfdbrange3' is used in order to get range matching decimal keys with strings in a fixed\-length database object.
+.PP
+.RS
+.br
+\fBTCLIST *tcfdbrange3(TCFDB *\fIfdb\fB, const char *\fIlstr\fB, const char *\fIustr\fB, int \fImax\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object.
+.RE
+.RS
+`\fIlstr\fR' specifies the string of the lower key.  If it is "min", the minimum ID number of existing records is specified.
+.RE
+.RS
+`\fIustr\fR' specifies the string of the upper key.  If it is "max", the maximum ID number of existing records is specified.
+.RE
+.RS
+`\fImax\fR' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.
+.RE
+.RS
+The return value is a list object of the corresponding decimal keys.  This function does never fail.  It returns an empty list even if no key corresponds.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.
+.RE
+.RE
+.PP
+The function `tcfdbrange4' is used in order to get keys with an interval notation in a fixed\-length database object.
+.PP
+.RS
+.br
+\fBTCLIST *tcfdbrange4(TCFDB *\fIfdb\fB, const void *\fIibuf\fB, int \fIisiz\fB, int \fImax\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object.
+.RE
+.RS
+`\fIibuf\fR' specifies the pointer to the region of the interval notation.
+.RE
+.RS
+`\fIisiz\fR' specifies the size of the region of the interval notation.
+.RE
+.RS
+`\fImax\fR' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.
+.RE
+.RS
+The return value is a list object of the corresponding decimal keys.  This function does never fail.  It returns an empty list even if no key corresponds.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.
+.RE
+.RE
+.PP
+The function `tcfdbrange5' is used in order to get keys with an interval notation string in a fixed\-length database object.
+.PP
+.RS
+.br
+\fBTCLIST *tcfdbrange5(TCFDB *\fIfdb\fB, const void *\fIistr\fB, int \fImax\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object.
+.RE
+.RS
+`\fIistr\fR' specifies the pointer to the region of the interval notation string.
+.RE
+.RS
+`\fImax\fR' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.
+.RE
+.RS
+The return value is a list object of the corresponding decimal keys.  This function does never fail.  It returns an empty list even if no key corresponds.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.
+.RE
+.RE
+.PP
+The function `tcfdbaddint' is used in order to add an integer to a record in a fixed\-length database object.
+.PP
+.RS
+.br
+\fBint tcfdbaddint(TCFDB *\fIfdb\fB, int64_t \fIid\fB, int \fInum\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object connected as a writer.
+.RE
+.RS
+`\fIid\fR' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDPREV', the number less by one than the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.  If it is `FDBIDNEXT', the number greater by one than the maximum ID number of existing records is specified.
+.RE
+.RS
+`\fInum\fR' specifies the additional value.
+.RE
+.RS
+If successful, the return value is the summation value, else, it is `INT_MIN'.
+.RE
+.RS
+If the corresponding record exists, the value is treated as an integer and is added to.  If no record corresponds, a new record of the additional value is stored.
+.RE
+.RE
+.PP
+The function `tcfdbadddouble' is used in order to add a real number to a record in a fixed\-length database object.
+.PP
+.RS
+.br
+\fBdouble tcfdbadddouble(TCFDB *\fIfdb\fB, int64_t \fIid\fB, double \fInum\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object connected as a writer.
+.RE
+.RS
+`\fIid\fR' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID number of existing records is specified.  If it is `FDBIDPREV', the number less by one than the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of existing records is specified.  If it is `FDBIDNEXT', the number greater by one than the maximum ID number of existing records is specified.
+.RE
+.RS
+`\fInum\fR' specifies the additional value.
+.RE
+.RS
+If successful, the return value is the summation value, else, it is Not\-a\-Number.
+.RE
+.RS
+If the corresponding record exists, the value is treated as a real number and is added to.  If no record corresponds, a new record of the additional value is stored.
+.RE
+.RE
+.PP
+The function `tcfdbsync' is used in order to synchronize updated contents of a fixed\-length database object with the file and the device.
+.PP
+.RS
+.br
+\fBbool tcfdbsync(TCFDB *\fIfdb\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object connected as a writer.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+This function is useful when another process connects to the same database file.
+.RE
+.RE
+.PP
+The function `tcfdboptimize' is used in order to optimize the file of a fixed\-length database object.
+.PP
+.RS
+.br
+\fBbool tcfdboptimize(TCFDB *\fIfdb\fB, int32_t \fIwidth\fB, int64_t \fIlimsiz\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object connected as a writer.
+.RE
+.RS
+`\fIwidth\fR' specifies the width of the value of each record.  If it is not more than 0, the current setting is not changed.
+.RE
+.RS
+`\fIlimsiz\fR' specifies the limit size of the database file.  If it is not more than 0, the current setting is not changed.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RE
+.PP
+The function `tcfdbvanish' is used in order to remove all records of a fixed\-length database object.
+.PP
+.RS
+.br
+\fBbool tcfdbvanish(TCFDB *\fIfdb\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object connected as a writer.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RE
+.PP
+The function `tcfdbcopy' is used in order to copy the database file of a fixed\-length database object.
+.PP
+.RS
+.br
+\fBbool tcfdbcopy(TCFDB *\fIfdb\fB, const char *\fIpath\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object.
+.RE
+.RS
+`\fIpath\fR' specifies the path of the destination file.  If it begins with `@', the trailing substring is executed as a command line.
+.RE
+.RS
+If successful, the return value is true, else, it is false.  False is returned if the executed command returns non\-zero code.
+.RE
+.RS
+The database file is assured to be kept synchronized and not modified while the copying or executing operation is in progress.  So, this function is useful to create a backup file of the database file.
+.RE
+.RE
+.PP
+The function `tcfdbtranbegin' is used in order to begin the transaction of a fixed\-length database object.
+.PP
+.RS
+.br
+\fBbool tcfdbtranbegin(TCFDB *\fIfdb\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object connected as a writer.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+The database is locked by the thread while the transaction so that only one transaction can be activated with a database object at the same time.  Thus, the serializable isolation level is assumed if every database operation is performed in the transaction.  All updated regions are kept track of by write ahead logging while the transaction.  If the database is closed during transaction, the transaction is aborted implicitly.
+.RE
+.RE
+.PP
+The function `tcfdbtrancommit' is used in order to commit the transaction of a fixed\-length database object.
+.PP
+.RS
+.br
+\fBbool tcfdbtrancommit(TCFDB *\fIfdb\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object connected as a writer.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Update in the transaction is fixed when it is committed successfully.
+.RE
+.RE
+.PP
+The function `tcfdbtranabort' is used in order to abort the transaction of a fixed\-length database object.
+.PP
+.RS
+.br
+\fBbool tcfdbtranabort(TCFDB *\fIfdb\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object connected as a writer.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Update in the transaction is discarded when it is aborted.  The state of the database is rollbacked to before transaction.
+.RE
+.RE
+.PP
+The function `tcfdbpath' is used in order to get the file path of a fixed\-length database object.
+.PP
+.RS
+.br
+\fBconst char *tcfdbpath(TCFDB *\fIfdb\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object.
+.RE
+.RS
+The return value is the path of the database file or `NULL' if the object does not connect to any database file.
+.RE
+.RE
+.PP
+The function `tcfdbrnum' is used in order to get the number of records of a fixed\-length database object.
+.PP
+.RS
+.br
+\fBuint64_t tcfdbrnum(TCFDB *\fIfdb\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object.
+.RE
+.RS
+The return value is the number of records or 0 if the object does not connect to any database file.
+.RE
+.RE
+.PP
+The function `tcfdbfsiz' is used in order to get the size of the database file of a fixed\-length database object.
+.PP
+.RS
+.br
+\fBuint64_t tcfdbfsiz(TCFDB *\fIfdb\fB);\fR
+.RS
+`\fIfdb\fR' specifies the fixed\-length database object.
+.RE
+.RS
+The return value is the size of the database file or 0 if the object does not connect to any database file.
+.RE
+.RE
+
+.SH SEE ALSO
+.PP
+.BR tcftest (1),
+.BR tcfmttest (1),
+.BR tcfmgr (1),
+.BR tokyocabinet (3)
diff --git a/tcejdb/man/tcfmgr.1 b/tcejdb/man/tcfmgr.1
new file mode 100644 (file)
index 0000000..e7d3eef
--- /dev/null
@@ -0,0 +1,98 @@
+.TH "TCFMGR" 1 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tcfmgr \- the command line utility of the fixed-length database API
+
+.SH DESCRIPTION
+.PP
+The command `\fBtcfmgr\fR' is a utility for test and debugging of the fixed\-length database API and its applications.  `\fIpath\fR' specifies the path of a database file.  `\fIwidth\fR' specifies the width of the value of each record.  `\fIlimsiz\fR' specifies the limit size of the database file.  `\fIkey\fR' specifies the key of a record.  `\fIvalue\fR' specifies the value of a record.  `\fIfile\fR' specifies the input file.
+.PP
+.RS
+.br
+\fBtcfmgr create \fIpath\fB \fR[\fB\fIwidth\fB \fR[\fB\fIlimsiz\fB\fR]\fB\fR]\fB\fR
+.RS
+Create a database file.
+.RE
+.br
+\fBtcfmgr inform \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fIpath\fB\fR
+.RS
+Print miscellaneous information to the standard output.
+.RE
+.br
+\fBtcfmgr put \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-sx\fR]\fB \fR[\fB\-dk\fR|\fB\-dc\fR|\fB\-dai\fR|\fB\-dad\fR]\fB \fIpath\fB \fIkey\fB \fIvalue\fB\fR
+.RS
+Store a record.
+.RE
+.br
+\fBtcfmgr out \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-sx\fR]\fB \fIpath\fB \fIkey\fB\fR
+.RS
+Remove a record.
+.RE
+.br
+\fBtcfmgr get \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-sx\fR]\fB \fR[\fB\-px\fR]\fB \fR[\fB\-pz\fR]\fB \fIpath\fB \fIkey\fB\fR
+.RS
+Print the value of a record.
+.RE
+.br
+\fBtcfmgr list \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-m \fInum\fB\fR]\fB \fR[\fB\-pv\fR]\fB \fR[\fB\-px\fR]\fB \fR[\fB\-rb \fIlkey\fB \fIukey\fB\fR]\fB \fR[\fB\-ri \fIstr\fB\fR]\fB \fIpath\fB\fR
+.RS
+Print keys of all records, separated by line feeds.
+.RE
+.br
+\fBtcfmgr optimize \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fIpath\fB \fR[\fB\fIwidth\fB \fR[\fB\fIlimsiz\fB\fR]\fB\fR]\fB\fR
+.RS
+Optimize a database file.
+.RE
+.br
+\fBtcfmgr importtsv \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-sc\fR]\fB \fIpath\fB \fR[\fB\fIfile\fB\fR]\fB\fR
+.RS
+Store records of TSV in each line of a file.
+.RE
+.br
+\fBtcfmgr version\fR
+.RS
+Print the version information of Tokyo Cabinet.
+.RE
+.RE
+.PP
+Options feature the following.
+.PP
+.RS
+\fB\-nl\fR : enable the option `FDBNOLCK'.
+.br
+\fB\-nb\fR : enable the option `FDBLCKNB'.
+.br
+\fB\-sx\fR : the input data is evaluated as a hexadecimal data string.
+.br
+\fB\-dk\fR : use the function `tcfdbputkeep' instead of `tcfdbput'.
+.br
+\fB\-dc\fR : use the function `tcfdbputcat' instead of `tcfdbput'.
+.br
+\fB\-dai\fR : use the function `tcfdbaddint' instead of `tcfdbput'.
+.br
+\fB\-dad\fR : use the function `tcfdbadddouble' instead of `tcfdbput'.
+.br
+\fB\-px\fR : the output data is converted into a hexadecimal data string.
+.br
+\fB\-pz\fR : do not append line feed at the end of the output.
+.br
+\fB\-m \fInum\fR\fR : specify the maximum number of the output.
+.br
+\fB\-pv\fR : print values of records also.
+.br
+\fB\-rb \fIlkey\fR \fIukey\fR\fR : specify the range of keys.
+.br
+\fB\-ri \fIstr\fR\fR : specify the interval notation of keys.
+.br
+\fB\-sc\fR : normalize keys as lower cases.
+.br
+.RE
+.PP
+This command returns 0 on success, another on failure.
+
+.SH SEE ALSO
+.PP
+.BR tcftest (1),
+.BR tcfmttest (1),
+.BR tcfdb (3),
+.BR tokyocabinet (3)
diff --git a/tcejdb/man/tcfmttest.1 b/tcejdb/man/tcfmttest.1
new file mode 100644 (file)
index 0000000..d74a4e8
--- /dev/null
@@ -0,0 +1,62 @@
+.TH "TCFMTTEST" 1 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tcfmttest \- test cases of the fixed-length database API
+
+.SH DESCRIPTION
+.PP
+The command `\fBtcfmttest\fR' is a utility for facility test under multi\-thread situation.  This command is used in the following format.  `\fIpath\fR' specifies the path of a database file.  `\fItnum\fR' specifies the number of running threads.  `\fIrnum\fR' specifies the number of iterations.  `\fIwidth\fR' specifies the width of the value of each record.  `\fIlimsiz\fR' specifies the limit size of the database file.
+.PP
+.RS
+.br
+\fBtcfmttest write \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-rnd\fR]\fB \fIpath\fB \fItnum\fB \fIrnum\fB \fR[\fB\fIwidth\fB \fR[\fB\fIlimsiz\fB\fR]\fB\fR]\fB\fR
+.RS
+Store records with keys of 8 bytes.  They change as `00000001', `00000002'...
+.RE
+.br
+\fBtcfmttest read \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-wb\fR]\fB \fR[\fB\-rnd\fR]\fB \fIpath\fB \fItnum\fB\fR
+.RS
+Retrieve all records of the database above.
+.RE
+.br
+\fBtcfmttest remove \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-rnd\fR]\fB \fIpath\fB \fItnum\fB\fR
+.RS
+Remove all records of the database above.
+.RE
+.br
+\fBtcfmttest wicked \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-nc\fR]\fB \fIpath\fB \fItnum\fB \fIrnum\fB\fR
+.RS
+Perform updating operations selected at random.
+.RE
+.br
+\fBtcfmttest typical \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-nc\fR]\fB \fR[\fB\-rr \fInum\fB\fR]\fB \fIpath\fB \fItnum\fB \fIrnum\fB \fR[\fB\fIwidth\fB \fR[\fB\fIlimsiz\fB\fR]\fB\fR]\fB\fR
+.RS
+Perform typical operations selected at random.
+.RE
+.RE
+.PP
+Options feature the following.
+.PP
+.RS
+\fB\-nl\fR : enable the option `FDBNOLCK'.
+.br
+\fB\-nb\fR : enable the option `FDBLCKNB'.
+.br
+\fB\-rnd\fR : select keys at random.
+.br
+\fB\-wb\fR : use the function `tcfdbget4' instead of `tcfdbget2'.
+.br
+\fB\-nc\fR : omit the comparison test.
+.br
+\fB\-rr\fR \fInum\fR : specifiy the ratio of reading operation by percentage.
+.br
+.RE
+.PP
+This command returns 0 on success, another on failure.
+
+.SH SEE ALSO
+.PP
+.BR tcftest (1),
+.BR tcfmgr (1),
+.BR tcfdb (3),
+.BR tokyocabinet (3)
diff --git a/tcejdb/man/tcftest.1 b/tcejdb/man/tcftest.1
new file mode 100644 (file)
index 0000000..01ea450
--- /dev/null
@@ -0,0 +1,73 @@
+.TH "TCFTEST" 1 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tcftest \- test cases of the fixed-length database API
+
+.SH DESCRIPTION
+.PP
+The command `\fBtcftest\fR' is a utility for facility test and performance test.  This command is used in the following format.  `\fIpath\fR' specifies the path of a database file.  `\fIrnum\fR' specifies the number of iterations.  `\fIwidth\fR' specifies the width of the value of each record.  `\fIlimsiz\fR' specifies the limit size of the database file.
+.PP
+.RS
+.br
+\fBtcftest write \fR[\fB\-mt\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-rnd\fR]\fB \fIpath\fB \fIrnum\fB \fR[\fB\fIwidth\fB \fR[\fB\fIlimsiz\fB\fR]\fB\fR]\fB\fR
+.RS
+Store records with keys of 8 bytes.  They change as `00000001', `00000002'...
+.RE
+.br
+\fBtcftest read \fR[\fB\-mt\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-wb\fR]\fB \fR[\fB\-rnd\fR]\fB \fIpath\fB\fR
+.RS
+Retrieve all records of the database above.
+.RE
+.br
+\fBtcftest remove \fR[\fB\-mt\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-rnd\fR]\fB \fIpath\fB\fR
+.RS
+Remove all records of the database above.
+.RE
+.br
+\fBtcftest rcat \fR[\fB\-mt\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-pn \fInum\fB\fR]\fB \fR[\fB\-dai\fR|\fB\-dad\fR|\fB\-rl\fR]\fB \fIpath\fB \fIrnum\fB \fR[\fB\fIlimsiz\fB\fR]\fB\fR]\fB\fR
+.RS
+Store records with partway duplicated keys using concatenate mode.
+.RE
+.br
+\fBtcftest misc \fR[\fB\-mt\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fIpath\fB \fIrnum\fB\fR
+.RS
+Perform miscellaneous test of various operations.
+.RE
+.br
+\fBtcftest wicked \fR[\fB\-mt\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fIpath\fB \fIrnum\fB\fR
+.RS
+Perform updating operations selected at random.
+.RE
+.RE
+.PP
+Options feature the following.
+.PP
+.RS
+\fB\-mt\fR : call the function `tcfdbsetmutex'.
+.br
+\fB\-nl\fR : enable the option `FDBNOLCK'.
+.br
+\fB\-nb\fR : enable the option `FDBLCKNB'.
+.br
+\fB\-rnd\fR : select keys at random.
+.br
+\fB\-wb\fR : use the function `tcfdbget4' instead of `tcfdbget2'.
+.br
+\fB\-pn \fInum\fR\fR : specify the number of patterns.
+.br
+\fB\-dai\fR : use the function `tcfdbaddint' instead of `tcfdbputcat'.
+.br
+\fB\-dad\fR : use the function `tcfdbadddouble' instead of `tcfdbputcat'.
+.br
+\fB\-rl\fR : set the length of values at random.
+.br
+.RE
+.PP
+This command returns 0 on success, another on failure.
+
+.SH SEE ALSO
+.PP
+.BR tcfmttest (1),
+.BR tcfmgr (1),
+.BR tcfdb (3),
+.BR tokyocabinet (3)
diff --git a/tcejdb/man/tchdb.3 b/tcejdb/man/tchdb.3
new file mode 100644 (file)
index 0000000..dbf2639
--- /dev/null
@@ -0,0 +1,898 @@
+.TH "TCHDB" 3 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tchdb \- the hash database API
+
+.SH DESCRIPTION
+.PP
+Hash database is a file containing a hash table and is handled with the hash database API.
+.PP
+To use the hash database API, include `\fBtcutil.h\fR', `\fBtchdb.h\fR', and related standard header files.  Usually, write the following description near the front of a source file.
+.PP
+.RS
+.br
+\fB#include <tcutil.h>\fR
+.br
+\fB#include <tchdb.h>\fR
+.br
+\fB#include <stdlib.h>\fR
+.br
+\fB#include <time.h>\fR
+.br
+\fB#include <stdbool.h>\fR
+.br
+\fB#include <stdint.h>\fR
+.RE
+.PP
+Objects whose type is pointer to `\fBTCHDB\fR' are used to handle hash databases.  A hash database object is created with the function `\fBtchdbnew\fR' and is deleted with the function `\fBtchdbdel\fR'.  To avoid memory leak, it is important to delete every object when it is no longer in use.
+.PP
+Before operations to store or retrieve records, it is necessary to open a database file and connect the hash database object to it.  The function `\fBtchdbopen\fR' is used to open a database file and the function `\fBtchdbclose\fR' is used to close the database file.  To avoid data missing or corruption, it is important to close every database file when it is no longer in use.  It is forbidden for multible database objects in a process to open the same database at the same time.
+
+.SH API
+.PP
+The function `tchdberrmsg' is used in order to get the message string corresponding to an error code.
+.PP
+.RS
+.br
+\fBconst char *tchdberrmsg(int \fIecode\fB);\fR
+.RS
+`\fIecode\fR' specifies the error code.
+.RE
+.RS
+The return value is the message string of the error code.
+.RE
+.RE
+.PP
+The function `tchdbnew' is used in order to create a hash database object.
+.PP
+.RS
+.br
+\fBTCHDB *tchdbnew(void);\fR
+.RS
+The return value is the new hash database object.
+.RE
+.RE
+.PP
+The function `tchdbdel' is used in order to delete a hash database object.
+.PP
+.RS
+.br
+\fBvoid tchdbdel(TCHDB *\fIhdb\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object.
+.RE
+.RS
+If the database is not closed, it is closed implicitly.  Note that the deleted object and its derivatives can not be used anymore.
+.RE
+.RE
+.PP
+The function `tchdbecode' is used in order to get the last happened error code of a hash database object.
+.PP
+.RS
+.br
+\fBint tchdbecode(TCHDB *\fIhdb\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object.
+.RE
+.RS
+The return value is the last happened error code.
+.RE
+.RS
+The following error codes are defined: `TCESUCCESS' for success, `TCETHREAD' for threading error, `TCEINVALID' for invalid operation, `TCENOFILE' for file not found, `TCENOPERM' for no permission, `TCEMETA' for invalid meta data, `TCERHEAD' for invalid record header, `TCEOPEN' for open error, `TCECLOSE' for close error, `TCETRUNC' for trunc error, `TCESYNC' for sync error, `TCESTAT' for stat error, `TCESEEK' for seek error, `TCEREAD' for read error, `TCEWRITE' for write error, `TCEMMAP' for mmap error, `TCELOCK' for lock error, `TCEUNLINK' for unlink error, `TCERENAME' for rename error, `TCEMKDIR' for mkdir error, `TCERMDIR' for rmdir error, `TCEKEEP' for existing record, `TCENOREC' for no record found, and `TCEMISC' for miscellaneous error.
+.RE
+.RE
+.PP
+The function `tchdbsetmutex' is used in order to set mutual exclusion control of a hash database object for threading.
+.PP
+.RS
+.br
+\fBbool tchdbsetmutex(TCHDB *\fIhdb\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object which is not opened.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Note that the mutual exclusion control of the database should be set before the database is opened.
+.RE
+.RE
+.PP
+The function `tchdbtune' is used in order to set the tuning parameters of a hash database object.
+.PP
+.RS
+.br
+\fBbool tchdbtune(TCHDB *\fIhdb\fB, int64_t \fIbnum\fB, int8_t \fIapow\fB, int8_t \fIfpow\fB, uint8_t \fIopts\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object which is not opened.
+.RE
+.RS
+`\fIbnum\fR' specifies the number of elements of the bucket array.  If it is not more than 0, the default value is specified.  The default value is 16381.  Suggested size of the bucket array is about from 0.5 to 4 times of the number of all records to be stored.
+.RE
+.RS
+`\fIapow\fR' specifies the size of record alignment by power of 2.  If it is negative, the default value is specified.  The default value is 4 standing for 2^4=16.
+.RE
+.RS
+`\fIfpow\fR' specifies the maximum number of elements of the free block pool by power of 2.  If it is negative, the default value is specified.  The default value is 10 standing for 2^10=1024.
+.RE
+.RS
+`\fIopts\fR' specifies options by bitwise-or: `HDBTLARGE' specifies that the size of the database can be larger than 2GB by using 64\-bit bucket array, `HDBTDEFLATE' specifies that each record is compressed with Deflate encoding, `HDBTBZIP' specifies that each record is compressed with BZIP2 encoding, `HDBTTCBS' specifies that each record is compressed with TCBS encoding.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Note that the tuning parameters should be set before the database is opened.
+.RE
+.RE
+.PP
+The function `tchdbsetcache' is used in order to set the caching parameters of a hash database object.
+.PP
+.RS
+.br
+\fBbool tchdbsetcache(TCHDB *\fIhdb\fB, int32_t \fIrcnum\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object which is not opened.
+.RE
+.RS
+`\fIrcnum\fR' specifies the maximum number of records to be cached.  If it is not more than 0, the record cache is disabled.  It is disabled by default.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Note that the caching parameters should be set before the database is opened.
+.RE
+.RE
+.PP
+The function `tchdbsetxmsiz' is used in order to set the size of the extra mapped memory of a hash database object.
+.PP
+.RS
+.br
+\fBbool tchdbsetxmsiz(TCHDB *\fIhdb\fB, int64_t \fIxmsiz\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object which is not opened.
+.RE
+.RS
+`\fIxmsiz\fR' specifies the size of the extra mapped memory.  If it is not more than 0, the extra mapped memory is disabled.  The default size is 67108864.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Note that the mapping parameters should be set before the database is opened.
+.RE
+.RE
+.PP
+The function `tchdbsetdfunit' is used in order to set the unit step number of auto defragmentation of a hash database object.
+.PP
+.RS
+.br
+\fBbool tchdbsetdfunit(TCHDB *\fIhdb\fB, int32_t \fIdfunit\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object which is not opened.
+.RE
+.RS
+`\fIdfunit\fR' specifie the unit step number.  If it is not more than 0, the auto defragmentation is disabled.  It is disabled by default.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Note that the defragmentation parameters should be set before the database is opened.
+.RE
+.RE
+.PP
+The function `tchdbopen' is used in order to open a database file and connect a hash database object.
+.PP
+.RS
+.br
+\fBbool tchdbopen(TCHDB *\fIhdb\fB, const char *\fIpath\fB, int \fIomode\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object which is not opened.
+.RE
+.RS
+`\fIpath\fR' specifies the path of the database file.
+.RE
+.RS
+`\fIomode\fR' specifies the connection mode: `HDBOWRITER' as a writer, `HDBOREADER' as a reader.  If the mode is `HDBOWRITER', the following may be added by bitwise-or: `HDBOCREAT', which means it creates a new database if not exist, `HDBOTRUNC', which means it creates a new database regardless if one exists, `HDBOTSYNC', which means every transaction synchronizes updated contents with the device.  Both of `HDBOREADER' and `HDBOWRITER' can be added to by bitwise-or: `HDBONOLCK', which means it opens the database file without file locking, or `HDBOLCKNB', which means locking is performed without blocking.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RE
+.PP
+The function `tchdbclose' is used in order to close a hash database object.
+.PP
+.RS
+.br
+\fBbool tchdbclose(TCHDB *\fIhdb\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Update of a database is assured to be written when the database is closed.  If a writer opens a database but does not close it appropriately, the database will be broken.
+.RE
+.RE
+.PP
+The function `tchdbput' is used in order to store a record into a hash database object.
+.PP
+.RS
+.br
+\fBbool tchdbput(TCHDB *\fIhdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object connected as a writer.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, it is overwritten.
+.RE
+.RE
+.PP
+The function `tchdbput2' is used in order to store a string record into a hash database object.
+.PP
+.RS
+.br
+\fBbool tchdbput2(TCHDB *\fIhdb\fB, const char *\fIkstr\fB, const char *\fIvstr\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object connected as a writer.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, it is overwritten.
+.RE
+.RE
+.PP
+The function `tchdbputkeep' is used in order to store a new record into a hash database object.
+.PP
+.RS
+.br
+\fBbool tchdbputkeep(TCHDB *\fIhdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object connected as a writer.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, this function has no effect.
+.RE
+.RE
+.PP
+The function `tchdbputkeep2' is used in order to store a new string record into a hash database object.
+.PP
+.RS
+.br
+\fBbool tchdbputkeep2(TCHDB *\fIhdb\fB, const char *\fIkstr\fB, const char *\fIvstr\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object connected as a writer.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, this function has no effect.
+.RE
+.RE
+.PP
+The function `tchdbputcat' is used in order to concatenate a value at the end of the existing record in a hash database object.
+.PP
+.RS
+.br
+\fBbool tchdbputcat(TCHDB *\fIhdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object connected as a writer.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If there is no corresponding record, a new record is created.
+.RE
+.RE
+.PP
+The function `tchdbputcat2' is used in order to concatenate a string value at the end of the existing record in a hash database object.
+.PP
+.RS
+.br
+\fBbool tchdbputcat2(TCHDB *\fIhdb\fB, const char *\fIkstr\fB, const char *\fIvstr\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object connected as a writer.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If there is no corresponding record, a new record is created.
+.RE
+.RE
+.PP
+The function `tchdbputasync' is used in order to store a record into a hash database object in asynchronous fashion.
+.PP
+.RS
+.br
+\fBbool tchdbputasync(TCHDB *\fIhdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object connected as a writer.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, it is overwritten.  Records passed to this function are accumulated into the inner buffer and wrote into the file at a blast.
+.RE
+.RE
+.PP
+The function `tchdbputasync2' is used in order to store a string record into a hash database object in asynchronous fashion.
+.PP
+.RS
+.br
+\fBbool tchdbputasync2(TCHDB *\fIhdb\fB, const char *\fIkstr\fB, const char *\fIvstr\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object connected as a writer.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, it is overwritten.  Records passed to this function are accumulated into the inner buffer and wrote into the file at a blast.
+.RE
+.RE
+.PP
+The function `tchdbout' is used in order to remove a record of a hash database object.
+.PP
+.RS
+.br
+\fBbool tchdbout(TCHDB *\fIhdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object connected as a writer.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RE
+.PP
+The function `tchdbout2' is used in order to remove a string record of a hash database object.
+.PP
+.RS
+.br
+\fBbool tchdbout2(TCHDB *\fIhdb\fB, const char *\fIkstr\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object connected as a writer.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RE
+.PP
+The function `tchdbget' is used in order to retrieve a record in a hash database object.
+.PP
+.RS
+.br
+\fBvoid *tchdbget(TCHDB *\fIhdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, int *\fIsp\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned if no record corresponds.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tchdbget2' is used in order to retrieve a string record in a hash database object.
+.PP
+.RS
+.br
+\fBchar *tchdbget2(TCHDB *\fIhdb\fB, const char *\fIkstr\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+If successful, the return value is the string of the value of the corresponding record.  `NULL' is returned if no record corresponds.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tchdbget3' is used in order to retrieve a record in a hash database object and write the value into a buffer.
+.PP
+.RS
+.br
+\fBint tchdbget3(TCHDB *\fIhdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, void *\fIvbuf\fB, int \fImax\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the buffer into which the value of the corresponding record is written.
+.RE
+.RS
+`\fImax\fR' specifies the size of the buffer.
+.RE
+.RS
+If successful, the return value is the size of the written data, else, it is \-1.  \-1 is returned if no record corresponds to the specified key.
+.RE
+.RS
+Note that an additional zero code is not appended at the end of the region of the writing buffer.
+.RE
+.RE
+.PP
+The function `tchdbvsiz' is used in order to get the size of the value of a record in a hash database object.
+.PP
+.RS
+.br
+\fBint tchdbvsiz(TCHDB *\fIhdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+If successful, the return value is the size of the value of the corresponding record, else, it is \-1.
+.RE
+.RE
+.PP
+The function `tchdbvsiz2' is used in order to get the size of the value of a string record in a hash database object.
+.PP
+.RS
+.br
+\fBint tchdbvsiz2(TCHDB *\fIhdb\fB, const char *\fIkstr\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+If successful, the return value is the size of the value of the corresponding record, else, it is \-1.
+.RE
+.RE
+.PP
+The function `tchdbiterinit' is used in order to initialize the iterator of a hash database object.
+.PP
+.RS
+.br
+\fBbool tchdbiterinit(TCHDB *\fIhdb\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+The iterator is used in order to access the key of every record stored in a database.
+.RE
+.RE
+.PP
+The function `tchdbiternext' is used in order to get the next key of the iterator of a hash database object.
+.PP
+.RS
+.br
+\fBvoid *tchdbiternext(TCHDB *\fIhdb\fB, int *\fIsp\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  It is possible to access every record by iteration of calling this function.  It is allowed to update or remove records whose keys are fetched while the iteration.  However, it is not assured if updating the database is occurred while the iteration.  Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of storing matches the one of the traversal access.
+.RE
+.RE
+.PP
+The function `tchdbiternext2' is used in order to get the next key string of the iterator of a hash database object.
+.PP
+.RS
+.br
+\fBchar *tchdbiternext2(TCHDB *\fIhdb\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object.
+.RE
+.RS
+If successful, the return value is the string of the next key, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  It is possible to access every record by iteration of calling this function.  However, it is not assured if updating the database is occurred while the iteration.  Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of storing matches the one of the traversal access.
+.RE
+.RE
+.PP
+The function `tchdbiternext3' is used in order to get the next extensible objects of the iterator of a hash database object.
+.PP
+.RS
+.br
+\fBbool tchdbiternext3(TCHDB *\fIhdb\fB, TCXSTR *\fIkxstr\fB, TCXSTR *\fIvxstr\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object.
+.RE
+.RS
+`\fIkxstr\fR' specifies the object into which the next key is wrote down.
+.RE
+.RS
+`\fIvxstr\fR' specifies the object into which the next value is wrote down.
+.RE
+.RS
+If successful, the return value is true, else, it is false.  False is returned when no record is to be get out of the iterator.
+.RE
+.RE
+.PP
+The function `tchdbfwmkeys' is used in order to get forward matching keys in a hash database object.
+.PP
+.RS
+.br
+\fBTCLIST *tchdbfwmkeys(TCHDB *\fIhdb\fB, const void *\fIpbuf\fB, int \fIpsiz\fB, int \fImax\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object.
+.RE
+.RS
+`\fIpbuf\fR' specifies the pointer to the region of the prefix.
+.RE
+.RS
+`\fIpsiz\fR' specifies the size of the region of the prefix.
+.RE
+.RS
+`\fImax\fR' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.
+.RE
+.RS
+The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.
+.RE
+.RE
+.PP
+The function `tchdbfwmkeys2' is used in order to get forward matching string keys in a hash database object.
+.PP
+.RS
+.br
+\fBTCLIST *tchdbfwmkeys2(TCHDB *\fIhdb\fB, const char *\fIpstr\fB, int \fImax\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object.
+.RE
+.RS
+`\fIpstr\fR' specifies the string of the prefix.
+.RE
+.RS
+`\fImax\fR' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.
+.RE
+.RS
+The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.
+.RE
+.RE
+.PP
+The function `tchdbaddint' is used in order to add an integer to a record in a hash database object.
+.PP
+.RS
+.br
+\fBint tchdbaddint(TCHDB *\fIhdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, int \fInum\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object connected as a writer.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fInum\fR' specifies the additional value.
+.RE
+.RS
+If successful, the return value is the summation value, else, it is `INT_MIN'.
+.RE
+.RS
+If the corresponding record exists, the value is treated as an integer and is added to.  If no record corresponds, a new record of the additional value is stored.
+.RE
+.RE
+.PP
+The function `tchdbdbadddouble' is used in order to add a real number to a record in a hash database object.
+.PP
+.RS
+.br
+\fBdouble tchdbadddouble(TCHDB *\fIhdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, double \fInum\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object connected as a writer.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fInum\fR' specifies the additional value.
+.RE
+.RS
+If successful, the return value is the summation value, else, it is Not-a-Number.
+.RE
+.RS
+If the corresponding record exists, the value is treated as a real number and is added to.  If no record corresponds, a new record of the additional value is stored.
+.RE
+.RE
+.PP
+The function `tchdbsync' is used in order to synchronize updated contents of a hash database object with the file and the device.
+.PP
+.RS
+.br
+\fBbool tchdbsync(TCHDB *\fIhdb\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object connected as a writer.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+This function is useful when another process connects to the same database file.
+.RE
+.RE
+.PP
+The function `tchdboptimize' is used in order to optimize the file of a hash database object.
+.PP
+.RS
+.br
+\fBbool tchdboptimize(TCHDB *\fIhdb\fB, int64_t \fIbnum\fB, int8_t \fIapow\fB, int8_t \fIfpow\fB, uint8_t \fIopts\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object connected as a writer.
+.RE
+.RS
+`\fIbnum\fR' specifies the number of elements of the bucket array.  If it is not more than 0, the default value is specified.  The default value is two times of the number of records.
+.RE
+.RS
+`\fIapow\fR' specifies the size of record alignment by power of 2.  If it is negative, the current setting is not changed.
+.RE
+.RS
+`\fIfpow\fR' specifies the maximum number of elements of the free block pool by power of 2.  If it is negative, the current setting is not changed.
+.RE
+.RS
+`\fIopts\fR' specifies options by bitwise-or: `HDBTLARGE' specifies that the size of the database can be larger than 2GB by using 64\-bit bucket array, `HDBTDEFLATE' specifies that each record is compressed with Deflate encoding, `HDBTBZIP' specifies that each record is compressed with BZIP2 encoding, `HDBTTCBS' specifies that each record is compressed with TCBS encoding.  If it is `UINT8_MAX', the current setting is not changed.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+This function is useful to reduce the size of the database file with data fragmentation by successive updating.
+.RE
+.RE
+.PP
+The function `tchdbvanish' is used in order to remove all records of a hash database object.
+.PP
+.RS
+.br
+\fBbool tchdbvanish(TCHDB *\fIhdb\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object connected as a writer.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RE
+.PP
+The function `tchdbcopy' is used in order to copy the database file of a hash database object.
+.PP
+.RS
+.br
+\fBbool tchdbcopy(TCHDB *\fIhdb\fB, const char *\fIpath\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object.
+.RE
+.RS
+`\fIpath\fR' specifies the path of the destination file.  If it begins with `@', the trailing substring is executed as a command line.
+.RE
+.RS
+If successful, the return value is true, else, it is false.  False is returned if the executed command returns non\-zero code.
+.RE
+.RS
+The database file is assured to be kept synchronized and not modified while the copying or executing operation is in progress.  So, this function is useful to create a backup file of the database file.
+.RE
+.RE
+.PP
+The function `tchdbtranbegin' is used in order to begin the transaction of a hash database object.
+.PP
+.RS
+.br
+\fBbool tchdbtranbegin(TCHDB *\fIhdb\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object connected as a writer.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+The database is locked by the thread while the transaction so that only one transaction can be activated with a database object at the same time.  Thus, the serializable isolation level is assumed if every database operation is performed in the transaction.  All updated regions are kept track of by write ahead logging while the transaction.  If the database is closed during transaction, the transaction is aborted implicitly.
+.RE
+.RE
+.PP
+The function `tchdbtrancommit' is used in order to commit the transaction of a hash database object.
+.PP
+.RS
+.br
+\fBbool tchdbtrancommit(TCHDB *\fIhdb\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object connected as a writer.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Update in the transaction is fixed when it is committed successfully.
+.RE
+.RE
+.PP
+The function `tchdbtranabort' is used in order to abort the transaction of a hash database object.
+.PP
+.RS
+.br
+\fBbool tchdbtranabort(TCHDB *\fIhdb\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object connected as a writer.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Update in the transaction is discarded when it is aborted.  The state of the database is rollbacked to before transaction.
+.RE
+.RE
+.PP
+The function `tchdbpath' is used in order to get the file path of a hash database object.
+.PP
+.RS
+.br
+\fBconst char *tchdbpath(TCHDB *\fIhdb\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object.
+.RE
+.RS
+The return value is the path of the database file or `NULL' if the object does not connect to any database file.
+.RE
+.RE
+.PP
+The function `tchdbrnum' is used in order to get the number of records of a hash database object.
+.PP
+.RS
+.br
+\fBuint64_t tchdbrnum(TCHDB *\fIhdb\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object.
+.RE
+.RS
+The return value is the number of records or 0 if the object does not connect to any database file.
+.RE
+.RE
+.PP
+The function `tchdbfsiz' is used in order to get the size of the database file of a hash database object.
+.PP
+.RS
+.br
+\fBuint64_t tchdbfsiz(TCHDB *\fIhdb\fB);\fR
+.RS
+`\fIhdb\fR' specifies the hash database object.
+.RE
+.RS
+The return value is the size of the database file or 0 if the object does not connect to any database file.
+.RE
+.RE
+
+.SH SEE ALSO
+.PP
+.BR tchtest (1),
+.BR tchmttest (1),
+.BR tchmgr (1),
+.BR tokyocabinet (3)
diff --git a/tcejdb/man/tchmgr.1 b/tcejdb/man/tchmgr.1
new file mode 100644 (file)
index 0000000..6e517cb
--- /dev/null
@@ -0,0 +1,110 @@
+.TH "TCHMGR" 1 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tchmgr \- the command line utility of the hash database API
+
+.SH DESCRIPTION
+.PP
+The command `\fBtchmgr\fR' is a utility for test and debugging of the hash database API and its applications.  `\fIpath\fR' specifies the path of a database file.  `\fIbnum\fR' specifies the number of buckets.  `\fIapow\fR' specifies the power of the alignment.  `\fIfpow\fR' specifies the power of the free block pool.  `\fIkey\fR' specifies the key of a record.  `\fIvalue\fR' specifies the value of a record.  `\fIfile\fR' specifies the input file.
+.PP
+.RS
+.br
+\fBtchmgr create \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fIpath\fB \fR[\fB\fIbnum\fB \fR[\fB\fIapow\fB \fR[\fB\fIfpow\fB\fR]\fB\fR]\fB\fR]\fB\fR
+.RS
+Create a database file.
+.RE
+.br
+\fBtchmgr inform \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fIpath\fB\fR
+.RS
+Print miscellaneous information to the standard output.
+.RE
+.br
+\fBtchmgr put \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-sx\fR]\fB \fR[\fB\-dk\fR|\fB\-dc\fR|\fB\-dai\fR|\fB\-dad\fR]\fB \fIpath\fB \fIkey\fB \fIvalue\fB\fR
+.RS
+Store a record.
+.RE
+.br
+\fBtchmgr out \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-sx\fR]\fB \fIpath\fB \fIkey\fB\fR
+.RS
+Remove a record.
+.RE
+.br
+\fBtchmgr get \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-sx\fR]\fB \fR[\fB\-px\fR]\fB \fR[\fB\-pz\fR]\fB \fIpath\fB \fIkey\fB\fR
+.RS
+Print the value of a record.
+.RE
+.br
+\fBtchmgr list \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-m \fInum\fB\fR]\fB \fR[\fB\-pv\fR]\fB \fR[\fB\-px\fR]\fB \fR[\fB\-fm \fIstr\fB\fR]\fB \fIpath\fB\fR
+.RS
+Print keys of all records, separated by line feeds.
+.RE
+.br
+\fBtchmgr optimize \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-tz\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-df\fR]\fB \fIpath\fB \fR[\fB\fIbnum\fB \fR[\fB\fIapow\fB \fR[\fB\fIfpow\fB\fR]\fB\fR]\fB\fR]\fB\fR
+.RS
+Optimize a database file.
+.RE
+.br
+\fBtchmgr importtsv \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-sc\fR]\fB \fIpath\fB \fR[\fB\fIfile\fB\fR]\fB\fR
+.RS
+Store records of TSV in each line of a file.
+.RE
+.br
+\fBtchmgr version\fR
+.RS
+Print the version information of Tokyo Cabinet.
+.RE
+.RE
+.PP
+Options feature the following.
+.PP
+.RS
+\fB\-tl\fR : enable the option `HDBTLARGE'.
+.br
+\fB\-td\fR : enable the option `HDBTDEFLATE'.
+.br
+\fB\-tb\fR : enable the option `HDBTBZIP'.
+.br
+\fB\-tt\fR : enable the option `HDBTTCBS'.
+.br
+\fB\-tx\fR : enable the option `HDBTEXCODEC'.
+.br
+\fB\-nl\fR : enable the option `HDBNOLCK'.
+.br
+\fB\-nb\fR : enable the option `HDBLCKNB'.
+.br
+\fB\-sx\fR : the input data is evaluated as a hexadecimal data string.
+.br
+\fB\-dk\fR : use the function `tchdbputkeep' instead of `tchdbput'.
+.br
+\fB\-dc\fR : use the function `tchdbputcat' instead of `tchdbput'.
+.br
+\fB\-dai\fR : use the function `tchdbaddint' instead of `tchdbput'.
+.br
+\fB\-dad\fR : use the function `tchdbadddouble' instead of `tchdbput'.
+.br
+\fB\-px\fR : the output data is converted into a hexadecimal data string.
+.br
+\fB\-pz\fR : do not append line feed at the end of the output.
+.br
+\fB\-m \fInum\fR\fR : specify the maximum number of the output.
+.br
+\fB\-pv\fR : print values of records also.
+.br
+\fB\-fm \fIstr\fR\fR : specify the prefix of keys.
+.br
+\fB\-tz\fR : enable the option `UINT8_MAX'.
+.br
+\fB\-df\fR : perform defragmentation only.
+.br
+\fB\-sc\fR : normalize keys as lower cases.
+.br
+.RE
+.PP
+This command returns 0 on success, another on failure.
+
+.SH SEE ALSO
+.PP
+.BR tchtest (1),
+.BR tchmttest (1),
+.BR tchdb (3),
+.BR tokyocabinet (3)
diff --git a/tcejdb/man/tchmttest.1 b/tcejdb/man/tchmttest.1
new file mode 100644 (file)
index 0000000..af8fe7e
--- /dev/null
@@ -0,0 +1,85 @@
+.TH "TCHMTTEST" 1 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tchmttest \- test cases of the hash database API
+
+.SH DESCRIPTION
+.PP
+The command `\fBtchmttest\fR' is a utility for facility test under multi\-thread situation.  This command is used in the following format.  `\fIpath\fR' specifies the path of a database file.  `\fItnum\fR' specifies the number of running threads.  `\fIrnum\fR' specifies the number of iterations.  `\fIbnum\fR' specifies the number of buckets.  `\fIapow\fR' specifies the power of the alignment.  `\fIfpow\fR' specifies the power of the free block pool.
+.PP
+.RS
+.br
+\fBtchmttest write \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-rc \fInum\fB\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-as\fR]\fB \fR[\fB\-rnd\fR]\fB \fIpath\fB \fItnum\fB \fIrnum\fB \fR[\fB\fIbnum\fB \fR[\fB\fIapow\fB \fR[\fB\fIfpow\fB\fR]\fB\fR]\fB\fR]\fB\fR
+.RS
+Store records with keys of 8 bytes.  They change as `00000001', `00000002'...
+.RE
+.br
+\fBtchmttest read \fR[\fB\-rc \fInum\fB\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-wb\fR]\fB \fR[\fB\-rnd\fR]\fB \fIpath\fB \fItnum\fB\fR
+.RS
+Retrieve all records of the database above.
+.RE
+.br
+\fBtchmttest remove \fR[\fB\-rc \fInum\fB\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-rnd\fR]\fB \fIpath\fB \fItnum\fB\fR
+.RS
+Remove all records of the database above.
+.RE
+.br
+\fBtchmttest wicked \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-nc\fR]\fB \fIpath\fB \fItnum\fB \fIrnum\fB\fR
+.RS
+Perform updating operations selected at random.
+.RE
+.br
+\fBtchmttest typical \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-rc \fInum\fB\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-nc\fR]\fB \fR[\fB\-rr \fInum\fB\fR]\fB \fIpath\fB \fItnum\fB \fIrnum\fB \fR[\fB\fIbnum\fB \fR[\fB\fIapow\fB \fR[\fB\fIfpow\fB\fR]\fB\fR]\fB\fR
+.RS
+Perform typical operations selected at random.
+.RE
+.br
+\fBtchmttest race \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fIpath\fB \fItnum\fB \fIrnum\fB \fR[\fB\fIbnum\fB \fR[\fB\fIapow\fB \fR[\fB\fIfpow\fB\fR]\fB\fR]\fB\fR
+.RS
+Perform race condition test.
+.RE
+.RE
+.PP
+Options feature the following.
+.PP
+.RS
+\fB\-tl\fR : enable the option `HDBTLARGE'.
+.br
+\fB\-td\fR : enable the option `HDBTDEFLATE'.
+.br
+\fB\-tb\fR : enable the option `HDBTBZIP'.
+.br
+\fB\-tt\fR : enable the option `HDBTTCBS'.
+.br
+\fB\-tx\fR : enable the option `HDBTEXCODEC'.
+.br
+\fB\-rc \fInum\fR\fR : specify the number of cached records.
+.br
+\fB\-xm \fInum\fR\fR : specify the size of the extra mapped memory.
+.br
+\fB\-df \fInum\fR\fR : specify the unit step number of auto defragmentation.
+.br
+\fB\-nl\fR : enable the option `HDBNOLCK'.
+.br
+\fB\-nb\fR : enable the option `HDBLCKNB'.
+.br
+\fB\-as\fR : use the function `tchdbputasync' instead of `tchdbput'.
+.br
+\fB\-rnd\fR : select keys at random.
+.br
+\fB\-wb\fR : use the function `tchdbget3' instead of `tchdbget'.
+.br
+\fB\-nc\fR : omit the comparison test.
+.br
+\fB\-rr \fInum\fR\fR : specify the ratio of reading operation by percentage.
+.br
+.RE
+.PP
+This command returns 0 on success, another on failure.
+
+.SH SEE ALSO
+.PP
+.BR tchtest (1),
+.BR tchmgr (1),
+.BR tchdb (3),
+.BR tokyocabinet (3)
diff --git a/tcejdb/man/tchtest.1 b/tcejdb/man/tchtest.1
new file mode 100644 (file)
index 0000000..1caabde
--- /dev/null
@@ -0,0 +1,95 @@
+.TH "TCHTEST" 1 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tchtest \- test cases of the hash database API
+
+.SH DESCRIPTION
+.PP
+To use the hash database API easily, the commands `\fBtchtest\fR', `\fBtchmttest\fR', and `\fBtchmgr\fR' are provided.
+.PP
+The command `\fBtchtest\fR' is a utility for facility test and performance test.  This command is used in the following format.  `\fIpath\fR' specifies the path of a database file.  `\fIrnum\fR' specifies the number of iterations.  `\fIbnum\fR' specifies the number of buckets.  `\fIapow\fR' specifies the power of the alignment.  `\fIfpow\fR' specifies the power of the free block pool.
+.PP
+.RS
+.br
+\fBtchtest write \fR[\fB\-mt\fR]\fB \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-rc \fInum\fB\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-as\fR]\fB \fR[\fB\-rnd\fR]\fB \fIpath\fB \fIrnum\fB \fR[\fB\fIbnum\fB \fR[\fB\fIapow\fB \fR[\fB\fIfpow\fB\fR]\fB\fR]\fB\fR]\fB\fR
+.RS
+Store records with keys of 8 bytes.  They change as `00000001', `00000002'...
+.RE
+.br
+\fBtchtest read \fR[\fB\-mt\fR]\fB \fR[\fB\-rc \fInum\fB\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-wb\fR]\fB \fR[\fB\-rnd\fR]\fB \fIpath\fB\fR
+.RS
+Retrieve all records of the database above.
+.RE
+.br
+\fBtchtest remove \fR[\fB\-mt\fR]\fB \fR[\fB\-rc \fInum\fB\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-rnd\fR]\fB \fIpath\fB\fR
+.RS
+Remove all records of the database above.
+.RE
+.br
+\fBtchtest rcat \fR[\fB\-mt\fR]\fB \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-rc \fInum\fB\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-pn \fInum\fB\fR]\fB \fR[\fB\-dai\fR|\fB\-dad\fR|\fB\-rl\fR|\fB\-ru\fR]\fB \fIpath\fB \fIrnum\fB \fR[\fB\fIbnum\fB \fR[\fB\fIapow\fB \fR[\fB\fIfpow\fB\fR]\fB\fR]\fB\fR]\fB\fR
+.RS
+Store records with partway duplicated keys using concatenate mode.
+.RE
+.br
+\fBtchtest misc \fR[\fB\-mt\fR]\fB \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fIpath\fB \fIrnum\fB\fR
+.RS
+Perform miscellaneous test of various operations.
+.RE
+.br
+\fBtchtest wicked \fR[\fB\-mt\fR]\fB \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fIpath\fB \fIrnum\fB\fR
+.RS
+Perform updating operations selected at random.
+.RE
+.RE
+.PP
+Options feature the following.
+.PP
+.RS
+\fB\-mt\fR : call the function `tchdbsetmutex'.
+.br
+\fB\-tl\fR : enable the option `HDBTLARGE'.
+.br
+\fB\-td\fR : enable the option `HDBTDEFLATE'.
+.br
+\fB\-tb\fR : enable the option `HDBTBZIP'.
+.br
+\fB\-tt\fR : enable the option `HDBTTCBS'.
+.br
+\fB\-tx\fR : enable the option `HDBTEXCODEC'.
+.br
+\fB\-rc \fInum\fR\fR : specify the number of cached records.
+.br
+\fB\-xm \fInum\fR\fR : specify the size of the extra mapped memory.
+.br
+\fB\-df \fInum\fR\fR : specify the unit step number of auto defragmentation.
+.br
+\fB\-nl\fR : enable the option `HDBNOLCK'.
+.br
+\fB\-nb\fR : enable the option `HDBLCKNB'.
+.br
+\fB\-as\fR : use the function `tchdbputasync' instead of `tchdbput'.
+.br
+\fB\-rnd\fR : select keys at random.
+.br
+\fB\-wb\fR : use the function `tchdbget3' instead of `tchdbget'.
+.br
+\fB\-pn \fInum\fR\fR : specify the number of patterns.
+.br
+\fB\-dai\fR : use the function `tchdbaddint' instead of `tchdbputcat'.
+.br
+\fB\-dad\fR : use the function `tchdbadddouble' instead of `tchdbputcat'.
+.br
+\fB\-rl\fR : set the length of values at random.
+.br
+\fB\-ru\fR : select update operations at random.
+.br
+.RE
+.PP
+This command returns 0 on success, another on failure.
+
+.SH SEE ALSO
+.PP
+.BR tchmttest (1),
+.BR tchmgr (1),
+.BR tchdb (3),
+.BR tokyocabinet (3)
diff --git a/tcejdb/man/tclist.3 b/tcejdb/man/tclist.3
new file mode 100644 (file)
index 0000000..03538a8
--- /dev/null
@@ -0,0 +1 @@
+.so man3/tcutil.3
diff --git a/tcejdb/man/tcmap.3 b/tcejdb/man/tcmap.3
new file mode 100644 (file)
index 0000000..03538a8
--- /dev/null
@@ -0,0 +1 @@
+.so man3/tcutil.3
diff --git a/tcejdb/man/tcmdb.3 b/tcejdb/man/tcmdb.3
new file mode 100644 (file)
index 0000000..03538a8
--- /dev/null
@@ -0,0 +1 @@
+.so man3/tcutil.3
diff --git a/tcejdb/man/tcmpool.3 b/tcejdb/man/tcmpool.3
new file mode 100644 (file)
index 0000000..03538a8
--- /dev/null
@@ -0,0 +1 @@
+.so man3/tcutil.3
diff --git a/tcejdb/man/tctdb.3 b/tcejdb/man/tctdb.3
new file mode 100644 (file)
index 0000000..f605a04
--- /dev/null
@@ -0,0 +1,1110 @@
+.TH "TCTDB" 3 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tctdb \- the table database API
+
+.SH DESCRIPTION
+.PP
+Table database is a file containing records composed of the primary keys and arbitrary columns and is handled with the table database API.
+.PP
+To use the table database API, include `\fBtcutil.h\fR', `\fBtctdb.h\fR', and related standard header files.  Usually, write the following description near the front of a source file.
+.PP
+.RS
+.br
+\fB#include <tcutil.h>\fR
+.br
+\fB#include <tctdb.h>\fR
+.br
+\fB#include <stdlib.h>\fR
+.br
+\fB#include <stdbool.h>\fR
+.br
+\fB#include <stdint.h>\fR
+.RE
+.PP
+Objects whose type is pointer to `\fBTCTDB\fR' are used to handle table databases.  A table database object is created with the function `\fBtctdbnew\fR' and is deleted with the function `\fBtctdbdel\fR'.  To avoid memory leak, it is important to delete every object when it is no longer in use.
+.PP
+Before operations to store or retrieve records, it is necessary to open a database file and connect the table database object to it.  The function `\fBtctdbopen\fR' is used to open a database file and the function `\fBtctdbclose\fR' is used to close the database file.  To avoid data missing or corruption, it is important to close every database file when it is no longer in use.  It is forbidden for multible database objects in a process to open the same database at the same time.
+
+.SH API
+.PP
+The function `tctdberrmsg' is used in order to get the message string corresponding to an error code.
+.PP
+.RS
+.br
+\fBconst char *tctdberrmsg(int \fIecode\fB);\fR
+.RS
+`\fIecode\fR' specifies the error code.
+.RE
+.RS
+The return value is the message string of the error code.
+.RE
+.RE
+.PP
+The function `tctdbnew' is used in order to create a table database object.
+.PP
+.RS
+.br
+\fBTCTDB *tctdbnew(void);\fR
+.RS
+The return value is the new table database object.
+.RE
+.RE
+.PP
+The function `tctdbdel' is used in order to delete a table database object.
+.PP
+.RS
+.br
+\fBvoid tctdbdel(TCTDB *\fItdb\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object.
+.RE
+.RS
+If the database is not closed, it is closed implicitly.  Note that the deleted object and its derivatives can not be used anymore.
+.RE
+.RE
+.PP
+The function `tctdbecode' is used in order to get the last happened error code of a table database object.
+.PP
+.RS
+.br
+\fBint tctdbecode(TCTDB *\fItdb\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object.
+.RE
+.RS
+The return value is the last happened error code.
+.RE
+.RS
+The following error codes are defined: `TCESUCCESS' for success, `TCETHREAD' for threading error, `TCEINVALID' for invalid operation, `TCENOFILE' for file not found, `TCENOPERM' for no permission, `TCEMETA' for invalid meta data, `TCERHEAD' for invalid record header, `TCEOPEN' for open error, `TCECLOSE' for close error, `TCETRUNC' for trunc error, `TCESYNC' for sync error, `TCESTAT' for stat error, `TCESEEK' for seek error, `TCEREAD' for read error, `TCEWRITE' for write error, `TCEMMAP' for mmap error, `TCELOCK' for lock error, `TCEUNLINK' for unlink error, `TCERENAME' for rename error, `TCEMKDIR' for mkdir error, `TCERMDIR' for rmdir error, `TCEKEEP' for existing record, `TCENOREC' for no record found, and `TCEMISC' for miscellaneous error.
+.RE
+.RE
+.PP
+The function `tctdbsetmutex' is used in order to set mutual exclusion control of a table database object for threading.
+.PP
+.RS
+.br
+\fBbool tctdbsetmutex(TCTDB *\fItdb\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object which is not opened.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Note that the mutual exclusion control is needed if the object is shared by plural threads and this function should be called before the database is opened.
+.RE
+.RE
+.PP
+The function `tctdbtune' is used in order to set the tuning parameters of a table database object.
+.PP
+.RS
+.br
+\fBbool tctdbtune(TCTDB *\fItdb\fB, int64_t \fIbnum\fB, int8_t \fIapow\fB, int8_t \fIfpow\fB, uint8_t \fIopts\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object which is not opened.
+.RE
+.RS
+`\fIbnum\fR' specifies the number of elements of the bucket array.  If it is not more than 0, the default value is specified.  The default value is 131071.  Suggested size of the bucket array is about from 0.5 to 4 times of the number of all records to be stored.
+.RE
+.RS
+`\fIapow\fR' specifies the size of record alignment by power of 2.  If it is negative, the default value is specified.  The default value is 4 standing for 2^4=16.
+.RE
+.RS
+`\fIfpow\fR' specifies the maximum number of elements of the free block pool by power of 2.  If it is negative, the default value is specified.  The default value is 10 standing for 2^10=1024.
+.RE
+.RS
+`\fIopts\fR' specifies options by bitwise-or: `TDBTLARGE' specifies that the size of the database can be larger than 2GB by using 64\-bit bucket array, `TDBTDEFLATE' specifies that each record is compressed with Deflate encoding, `TDBTBZIP' specifies that each record is compressed with BZIP2 encoding, `TDBTTCBS' specifies that each record is compressed with TCBS encoding.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Note that the tuning parameters should be set before the database is opened.
+.RE
+.RE
+.PP
+The function `tctdbsetcache' is set the caching parameters of a table database object.
+.PP
+.RS
+.br
+\fBbool tctdbsetcache(TCTDB *\fItdb\fB, int32_t \fIrcnum\fB, int32_t \fIlcnum\fB, int32_t \fIncnum\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object which is not opened.
+.RE
+.RS
+`\fIrcnum\fR' specifies the maximum number of records to be cached.  If it is not more than 0, the record cache is disabled.  It is disabled by default.
+.RE
+.RS
+`\fIlcnum\fR' specifies the maximum number of leaf nodes to be cached.  If it is not more than 0, the default value is specified.  The default value is 4096.
+.RE
+.RS
+`\fIncnum\fR' specifies the maximum number of non\-leaf nodes to be cached.  If it is not more than 0, the default value is specified.  The default value is 512.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Note that the caching parameters should be set before the database is opened.  Leaf nodes and non\-leaf nodes are used in column indices.
+.RE
+.RE
+.PP
+The function `tctdbsetxmsiz' is used in order to set the size of the extra mapped memory of a table database object.
+.PP
+.RS
+.br
+\fBbool tctdbsetxmsiz(TCTDB *\fItdb\fB, int64_t \fIxmsiz\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object which is not opened.
+.RE
+.RS
+`\fIxmsiz\fR' specifies the size of the extra mapped memory.  If it is not more than 0, the extra mapped memory is disabled.  The default size is 67108864.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Note that the mapping parameters should be set before the database is opened.
+.RE
+.RE
+.PP
+The function `tctdbsetdfunit' is used in order to set the unit step number of auto defragmentation of a table database object.
+.PP
+.RS
+.br
+\fBbool tctdbsetdfunit(TCTDB *\fItdb\fB, int32_t \fIdfunit\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object which is not opened.
+.RE
+.RS
+`\fIdfunit\fR' specifie the unit step number.  If it is not more than 0, the auto defragmentation is disabled.  It is disabled by default.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Note that the defragmentation parameters should be set before the database is opened.
+.RE
+.RE
+.PP
+The function `tctdbopen' is used in order to open a database file and connect a table database object.
+.PP
+.RS
+.br
+\fBbool tctdbopen(TCTDB *\fItdb\fB, const char *\fIpath\fB, int \fIomode\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object which is not opened.
+.RE
+.RS
+`\fIpath\fR' specifies the path of the database file.
+.RE
+.RS
+`\fIomode\fR' specifies the connection mode: `TDBOWRITER' as a writer, `TDBOREADER' as a reader.  If the mode is `TDBOWRITER', the following may be added by bitwise-or: `TDBOCREAT', which means it creates a new database if not exist, `TDBOTRUNC', which means it creates a new database regardless if one exists, `TDBOTSYNC', which means every transaction synchronizes updated contents with the device.  Both of `TDBOREADER' and `TDBOWRITER' can be added to by bitwise-or: `TDBONOLCK', which means it opens the database file without file locking, or `TDBOLCKNB', which means locking is performed without blocking.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RE
+.PP
+The function `tctdbclose' is used in order to close a table database object.
+.PP
+.RS
+.br
+\fBbool tctdbclose(TCTDB *\fItdb\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Update of a database is assured to be written when the database is closed.  If a writer opens a database but does not close it appropriately, the database will be broken.
+.RE
+.RE
+.PP
+The function `tctdbput' is used in order to store a record into a table database object.
+.PP
+.RS
+.br
+\fBbool tctdbput(TCTDB *\fItdb\fB, const void *\fIpkbuf\fB, int \fIpksiz\fB, TCMAP *\fIcols\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object connected as a writer.
+.RE
+.RS
+`\fIpkbuf\fR' specifies the pointer to the region of the primary key.
+.RE
+.RS
+`\fIpksiz\fR' specifies the size of the region of the primary key.
+.RE
+.RS
+`\fIcols\fR' specifies a map object containing columns.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, it is overwritten.
+.RE
+.RE
+.PP
+The function `tctdbput2' is used in order to store a string record into a table database object with a zero separated column string.
+.PP
+.RS
+.br
+\fBbool tctdbput2(TCTDB *\fItdb\fB, const void *\fIpkbuf\fB, int \fIpksiz\fB, const void *\fIcbuf\fB, int \fIcsiz\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object connected as a writer.
+.RE
+.RS
+`\fIpkbuf\fR' specifies the pointer to the region of the primary key.
+.RE
+.RS
+`\fIpksiz\fR' specifies the size of the region of the primary key.
+.RE
+.RS
+`\fIcbuf\fR' specifies the pointer to the region of the zero separated column string where the name and the value of each column are situated one after the other.
+.RE
+.RS
+`\fIcsiz\fR' specifies the size of the region of the column string.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, it is overwritten.
+.RE
+.RE
+.PP
+The function `tctdbput3' is used in order to store a string record into a table database object with a tab separated column string.
+.PP
+.RS
+.br
+\fBbool tctdbput3(TCTDB *\fItdb\fB, const char *\fIpkstr\fB, const char *\fIcstr\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object connected as a writer.
+.RE
+.RS
+`\fIpkstr\fR' specifies the string of the primary key.
+.RE
+.RS
+`\fIcstr\fR' specifies the string of the the tab separated column string where the name and the value of each column are situated one after the other.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, it is overwritten.
+.RE
+.RE
+.PP
+The function `tctdbputkeep' is used in order to store a new record into a table database object.
+.PP
+.RS
+.br
+\fBbool tctdbputkeep(TCTDB *\fItdb\fB, const void *\fIpkbuf\fB, int \fIpksiz\fB, TCMAP *\fIcols\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object connected as a writer.
+.RE
+.RS
+`\fIpkbuf\fR' specifies the pointer to the region of the primary key.
+.RE
+.RS
+`\fIpksiz\fR' specifies the size of the region of the primary key.
+.RE
+.RS
+`\fIcols\fR' specifies a map object containing columns.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, this function has no effect.
+.RE
+.RE
+.PP
+The function `tctdbputkeep2' is used in order to store a new string record into a table database object with a zero separated column string.
+.PP
+.RS
+.br
+\fBbool tctdbputkeep2(TCTDB *\fItdb\fB, const void *\fIpkbuf\fB, int \fIpksiz\fB, const void *\fIcbuf\fB, int \fIcsiz\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object connected as a writer.
+.RE
+.RS
+`\fIpkbuf\fR' specifies the pointer to the region of the primary key.
+.RE
+.RS
+`\fIpksiz\fR' specifies the size of the region of the primary key.
+.RE
+.RS
+`\fIcbuf\fR' specifies the pointer to the region of the zero separated column string where the name and the value of each column are situated one after the other.
+.RE
+.RS
+`\fIcsiz\fR' specifies the size of the region of the column string.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, this function has no effect.
+.RE
+.RE
+.PP
+The function `tctdbputkeep3' is used in order to store a new string record into a table database object with a tab separated column string.
+.PP
+.RS
+.br
+\fBbool tctdbputkeep3(TCTDB *\fItdb\fB, const char *\fIpkstr\fB, const char *\fIcstr\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object connected as a writer.
+.RE
+.RS
+`\fIpkstr\fR' specifies the string of the primary key.
+.RE
+.RS
+`\fIcstr\fR' specifies the string of the the tab separated column string where the name and the value of each column are situated one after the other.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, this function has no effect.
+.RE
+.RE
+.PP
+The function `tctdbputcat' is used in order to concatenate columns of the existing record in a table database object.
+.PP
+.RS
+.br
+\fBbool tctdbputcat(TCTDB *\fItdb\fB, const void *\fIpkbuf\fB, int \fIpksiz\fB, TCMAP *\fIcols\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object connected as a writer.
+.RE
+.RS
+`\fIpkbuf\fR' specifies the pointer to the region of the primary key.
+.RE
+.RS
+`\fIpksiz\fR' specifies the size of the region of the primary key.
+.RE
+.RS
+`\fIcols\fR' specifies a map object containing columns.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If there is no corresponding record, a new record is created.
+.RE
+.RE
+.PP
+The function `tctdbputcat2' is used in order to concatenate columns in a table database object with a zero separated column string.
+.PP
+.RS
+.br
+\fBbool tctdbputcat2(TCTDB *\fItdb\fB, const void *\fIpkbuf\fB, int \fIpksiz\fB, const void *\fIcbuf\fB, int \fIcsiz\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object connected as a writer.
+.RE
+.RS
+`\fIpkbuf\fR' specifies the pointer to the region of the primary key.
+.RE
+.RS
+`\fIpksiz\fR' specifies the size of the region of the primary key.
+.RE
+.RS
+`\fIcbuf\fR' specifies the pointer to the region of the zero separated column string where the name and the value of each column are situated one after the other.
+.RE
+.RS
+`\fIcsiz\fR' specifies the size of the region of the column string.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If there is no corresponding record, a new record is created.
+.RE
+.RE
+.PP
+The function `tctdbputcat3' is used in order to concatenate columns in a table database object with with a tab separated column string.
+.PP
+.RS
+.br
+\fBbool tctdbputcat3(TCTDB *\fItdb\fB, const char *\fIpkstr\fB, const char *\fIcstr\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object connected as a writer.
+.RE
+.RS
+`\fIpkstr\fR' specifies the string of the primary key.
+.RE
+.RS
+`\fIcstr\fR' specifies the string of the the tab separated column string where the name and the value of each column are situated one after the other.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If there is no corresponding record, a new record is created.
+.RE
+.RE
+.PP
+The function `tctdbout' is used in order to remove a record of a table database object.
+.PP
+.RS
+.br
+\fBbool tctdbout(TCTDB *\fItdb\fB, const void *\fIpkbuf\fB, int \fIpksiz\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object connected as a writer.
+.RE
+.RS
+`\fIpkbuf\fR' specifies the pointer to the region of the primary key.
+.RE
+.RS
+`\fIpksiz\fR' specifies the size of the region of the primary key.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RE
+.PP
+The function `tctdbout2' is used in order to remove a string record of a table database object.
+.PP
+.RS
+.br
+\fBbool tctdbout2(TCTDB *\fItdb\fB, const char *\fIpkstr\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object connected as a writer.
+.RE
+.RS
+`\fIpkstr\fR' specifies the string of the primary key.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RE
+.PP
+The function `tctdbget' is used in order to retrieve a record in a table database object.
+.PP
+.RS
+.br
+\fBTCMAP *tctdbget(TCTDB *\fItdb\fB, const void *\fIpkbuf\fB, int \fIpksiz\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object.
+.RE
+.RS
+`\fIpkbuf\fR' specifies the pointer to the region of the primary key.
+.RE
+.RS
+`\fIpksiz\fR' specifies the size of the region of the primary key.
+.RE
+.RS
+If successful, the return value is a map object of the columns of the corresponding record.  `NULL' is returned if no record corresponds.
+.RE
+.RS
+Because the object of the return value is created with the function `tcmapnew', it should be deleted with the function `tcmapdel' when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tctdbget2' is used in order to retrieve a record in a table database object as a zero separated column string.
+.PP
+.RS
+.br
+\fBchar *tctdbget2(TCTDB *\fItdb\fB, const void *\fIpkbuf\fB, int \fIpksiz\fB, int *\fIsp\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object.
+.RE
+.RS
+`\fIpkbuf\fR' specifies the pointer to the region of the primary key.
+.RE
+.RS
+`\fIpksiz\fR' specifies the size of the region of the primary key.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the column string of the corresponding record.  `NULL' is returned if no record corresponds.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tctdbget3' is used in order to retrieve a string record in a table database object as a tab separated column string.
+.PP
+.RS
+.br
+\fBchar *tctdbget3(TCTDB *\fItdb\fB, const char *\fIpkstr\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object.
+.RE
+.RS
+`\fIpkstr\fR' specifies the string of the primary key.
+.RE
+.RS
+If successful, the return value is the tab separated column string of the corresponding record.  `NULL' is returned if no record corresponds.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tctdbvsiz' is used in order to get the size of the value of a record in a table database object.
+.PP
+.RS
+.br
+\fBint tctdbvsiz(TCTDB *\fItdb\fB, const void *\fIpkbuf\fB, int \fIpksiz\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the primary key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the primary key.
+.RE
+.RS
+If successful, the return value is the size of the value of the corresponding record, else, it is \-1.
+.RE
+.RE
+.PP
+The function `tctdbvsiz2' is used in order to get the size of the value of a string record in a table database object.
+.PP
+.RS
+.br
+\fBint tctdbvsiz2(TCTDB *\fItdb\fB, const char *\fIpkstr\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the primary key.
+.RE
+.RS
+If successful, the return value is the size of the value of the corresponding record, else, it is \-1.
+.RE
+.RE
+.PP
+The function `tctdbiterinit' is used in order to initialize the iterator of a table database object.
+.PP
+.RS
+.br
+\fBbool tctdbiterinit(TCTDB *\fItdb\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+The iterator is used in order to access the primary key of every record stored in a database.
+.RE
+.RE
+.PP
+The function `tctdbiternext' is used in order to get the next primary key of the iterator of a table database object.
+.PP
+.RS
+.br
+\fBvoid *tctdbiternext(TCTDB *\fItdb\fB, int *\fIsp\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the next primary key, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  It is possible to access every record by iteration of calling this function.  It is allowed to update or remove records whose keys are fetched while the iteration.  However, it is not assured if updating the database is occurred while the iteration.  Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of storing matches the one of the traversal access.
+.RE
+.RE
+.PP
+The function `tctdbiternext2' is used in order to get the next primary key string of the iterator of a table database object.
+.PP
+.RS
+.br
+\fBchar *tctdbiternext2(TCTDB *\fItdb\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object.
+.RE
+.RS
+If successful, the return value is the string of the next primary key, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  It is possible to access every record by iteration of calling this function.  However, it is not assured if updating the database is occurred while the iteration.  Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of storing matches the one of the traversal access.
+.RE
+.RE
+.PP
+The function `tctdbiternext3' is used in order to get the columns of the next record of the iterator of a table database object.
+.PP
+.RS
+.br
+\fBTCMAP *tctdbiternext3(TCTDB *\fItdb\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object.
+.RE
+.RS
+If successful, the return value is a map object of the columns of the next record, else, it is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.  The primary key is added into the map as a column of an empty string key.
+.RE
+.RS
+Because the object of the return value is created with the function `tcmapnew', it should be deleted with the function `tcmapdel' when it is no longer in use.  It is possible to access every record by iteration of calling this function.  However, it is not assured if updating the database is occurred while the iteration.  Besides, the order of this traversal access method is arbitrary, so it is not assured that the order of storing matches the one of the traversal access.
+.RE
+.RE
+.PP
+The function `tctdbfwmkeys' is used in order to get forward matching primary keys in a table database object.
+.PP
+.RS
+.br
+\fBTCLIST *tctdbfwmkeys(TCTDB *\fItdb\fB, const void *\fIpbuf\fB, int \fIpsiz\fB, int \fImax\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object.
+.RE
+.RS
+`\fIpbuf\fR' specifies the pointer to the region of the prefix.
+.RE
+.RS
+`\fIpsiz\fR' specifies the size of the region of the prefix.
+.RE
+.RS
+`\fImax\fR' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.
+.RE
+.RS
+The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.
+.RE
+.RE
+.PP
+The function `tctdbfwmkeys2' is used in order to get forward matching string primary keys in a table database object.
+.PP
+.RS
+.br
+\fBTCLIST *tctdbfwmkeys2(TCTDB *\fItdb\fB, const char *\fIpstr\fB, int \fImax\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object.
+.RE
+.RS
+`\fIpstr\fR' specifies the string of the prefix.
+.RE
+.RS
+`\fImax\fR' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.
+.RE
+.RS
+The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.
+.RE
+.RE
+.PP
+The function `tctdbaddint' is used in order to add an integer to a column of a record in a table database object.
+.PP
+.RS
+.br
+\fBint tctdbaddint(TCTDB *\fItdb\fB, const void *\fIpkbuf\fB, int \fIpksiz\fB, int \fInum\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object connected as a writer.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the primary key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the primary key.
+.RE
+.RS
+`\fInum\fR' specifies the additional value.
+.RE
+.RS
+If successful, the return value is the summation value, else, it is `INT_MIN'.
+.RE
+.RS
+The additional value is stored as a decimal string value of a column whose name is "_num".  If no record corresponds, a new record with the additional value is stored.
+.RE
+.RE
+.PP
+The function `tctdbadddouble' is used in order to add a real number to a column of a record in a table database object.
+.PP
+.RS
+.br
+\fBdouble tctdbadddouble(TCTDB *\fItdb\fB, const void *\fIpkbuf\fB, int \fIpksiz\fB, double \fInum\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object connected as a writer.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the primary key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the primary key.
+.RE
+.RS
+`\fInum\fR' specifies the additional value.
+.RE
+.RS
+If successful, the return value is the summation value, else, it is Not\-a\-Number.
+.RE
+.RS
+The additional value is stored as a decimal string value of a column whose name is "_num".  If no record corresponds, a new record with the additional value is stored.
+.RE
+.RE
+.PP
+The function `tctdbsync' is used in order to synchronize updated contents of a table database object with the file and the device.
+.PP
+.RS
+.br
+\fBbool tctdbsync(TCTDB *\fItdb\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object connected as a writer.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+This function is useful when another process connects to the same database file.
+.RE
+.RE
+.PP
+The function `tctdboptimize' is used in order to optimize the file of a table database object.
+.PP
+.RS
+.br
+\fBbool tctdboptimize(TCTDB *\fItdb\fB, int64_t \fIbnum\fB, int8_t \fIapow\fB, int8_t \fIfpow\fB, uint8_t \fIopts\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object connected as a writer.
+.RE
+.RS
+`\fIbnum\fR' specifies the number of elements of the bucket array.  If it is not more than 0, the default value is specified.  The default value is two times of the number of records.
+.RE
+.RS
+`\fIapow\fR' specifies the size of record alignment by power of 2.  If it is negative, the current setting is not changed.
+.RE
+.RS
+`\fIfpow\fR' specifies the maximum number of elements of the free block pool by power of 2.  If it is negative, the current setting is not changed.
+.RE
+.RS
+`\fIopts\fR' specifies options by bitwise-or: `TDBTLARGE' specifies that the size of the database can be larger than 2GB by using 64\-bit bucket array, `TDBTDEFLATE' specifies that each record is compressed with Deflate encoding, `TDBTBZIP' specifies that each record is compressed with BZIP2 encoding, `TDBTTCBS' specifies that each record is compressed with TCBS encoding.  If it is `UINT8_MAX', the current setting is not changed.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+This function is useful to reduce the size of the database file with data fragmentation by successive updating.
+.RE
+.RE
+.PP
+The function `tctdbvanish' is used in order to remove all records of a table database object.
+.PP
+.RS
+.br
+\fBbool tctdbvanish(TCTDB *\fItdb\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object connected as a writer.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RE
+.PP
+The function `tctdbcopy' is used in order to copy the database file of a table database object.
+.PP
+.RS
+.br
+\fBbool tctdbcopy(TCTDB *\fItdb\fB, const char *\fIpath\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object.
+.RE
+.RS
+`\fIpath\fR' specifies the path of the destination file.  If it begins with `@', the trailing substring is executed as a command line.
+.RE
+.RS
+If successful, the return value is true, else, it is false.  False is returned if the executed command returns non\-zero code.
+.RE
+.RS
+The database file is assured to be kept synchronized and not modified while the copying or executing operation is in progress.  So, this function is useful to create a backup file of the database file.
+.RE
+.RE
+.PP
+The function `tctdbtranbegin' is used in order to begin the transaction of a table database object.
+.PP
+.RS
+.br
+\fBbool tctdbtranbegin(TCTDB *\fItdb\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object connected as a writer.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+The database is locked by the thread while the transaction so that only one transaction can be activated with a database object at the same time.  Thus, the serializable isolation level is assumed if every database operation is performed in the transaction.  Because all pages are cached on memory while the transaction, the amount of referred records is limited by the memory capacity.  If the database is closed during transaction, the transaction is aborted implicitly.
+.RE
+.RE
+.PP
+The function `tctdbtrancommit' is used in order to commit the transaction of a table database object.
+.PP
+.RS
+.br
+\fBbool tctdbtrancommit(TCTDB *\fItdb\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object connected as a writer.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Update in the transaction is fixed when it is committed successfully.
+.RE
+.RE
+.PP
+The function `tctdbtranabort' is used in order to abort the transaction of a table database object.
+.PP
+.RS
+.br
+\fBbool tctdbtranabort(TCTDB *\fItdb\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object connected as a writer.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Update in the transaction is discarded when it is aborted.  The state of the database is rollbacked to before transaction.
+.RE
+.RE
+.PP
+The function `tctdbpath' is used in order to get the file path of a table database object.
+.PP
+.RS
+.br
+\fBconst char *tctdbpath(TCTDB *\fItdb\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object.
+.RE
+.RS
+The return value is the path of the database file or `NULL' if the object does not connect to any database file.
+.RE
+.RE
+.PP
+The function `tctdbrnum' is used in order to get the number of records ccccof a table database object.
+.PP
+.RS
+.br
+\fBuint64_t tctdbrnum(TCTDB *\fItdb\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object.
+.RE
+.RS
+The return value is the number of records or 0 if the object does not connect to any database file.
+.RE
+.RE
+.PP
+The function `tctdbfsiz' is used in order to get the size of the database file of a table database object.
+.PP
+.RS
+.br
+\fBuint64_t tctdbfsiz(TCTDB *\fItdb\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object.
+.RE
+.RS
+The return value is the size of the database file or 0 if the object does not connect to any database file.
+.RE
+.RE
+.PP
+The function `tctdbsetindex' is used in order to set a column index to a table database object.
+.PP
+.RS
+.br
+\fBbool tctdbsetindex(TCTDB *\fItdb\fB, const char *\fIname\fB, int \fItype\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object connected as a writer.
+.RE
+.RS
+`\fIname\fR' specifies the name of a column.  If the name of an existing index is specified, the index is rebuilt.  An empty string means the primary key.
+.RE
+.RS
+`\fItype\fR' specifies the index type: `TDBITLEXICAL' for lexical string, `TDBITDECIMAL' for decimal string, `TDBITTOKEN' for token inverted index, `TDBITQGRAM' for q\-gram inverted index.  If it is `TDBITOPT', the index is optimized.  If it is `TDBITVOID', the index is removed.  If `TDBITKEEP' is added by bitwise\-or and the index exists, this function merely returns failure.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+Note that the setting indices should be set after the database is opened.
+.RE
+.RE
+.PP
+The function `tctdbgenuid' is used in order to generate a unique ID number of a table database object.
+.PP
+.RS
+.br
+\fBint64_t tctdbgenuid(TCTDB *\fItdb\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object connected as a writer.
+.RE
+.RS
+The return value is the new unique ID number or \-1 on failure.
+.RE
+.RE
+.PP
+The function `tctdbqrynew' is used in order to create a query object.
+.PP
+.RS
+.br
+\fBTDBQRY *tctdbqrynew(TCTDB *\fItdb\fB);\fR
+.RS
+`\fItdb\fR' specifies the table database object.
+.RE
+.RS
+The return value is the new query object.
+.RE
+.RE
+.PP
+The function `tctdbqrydel' is used in order to delete a query object.
+.PP
+.RS
+.br
+\fBvoid tctdbqrydel(TDBQRY *\fIqry\fB);\fR
+.RS
+`\fIqry\fR' specifies the query object.
+.RE
+.RE
+.PP
+The function `tctdbqryaddcond' is used in order to add a narrowing condition to a query object.
+.PP
+.RS
+.br
+\fBvoid tctdbqryaddcond(TDBQRY *\fIqry\fB, const char *\fIname\fB, int \fIop\fB, const char *\fIexpr\fB);\fR
+.RS
+`\fIqry\fR' specifies the query object.
+.RE
+.RS
+`\fIname\fR' specifies the name of a column.  An empty string means the primary key.
+.RE
+.RS
+`\fIop\fR' specifies an operation type: `TDBQCSTREQ' for string which is equal to the expression, `TDBQCSTRINC' for string which is included in the expression, `TDBQCSTRBW' for string which begins with the expression, `TDBQCSTREW' for string which ends with the expression, `TDBQCSTRAND' for string which includes all tokens in the expression, `TDBQCSTROR' for string which includes at least one token in the expression, `TDBQCSTROREQ' for string which is equal to at least one token in the expression, `TDBQCSTRRX' for string which matches regular expressions of the expression, `TDBQCNUMEQ' for number which is equal to the expression, `TDBQCNUMGT' for number which is greater than the expression, `TDBQCNUMGE' for number which is greater than or equal to the expression, `TDBQCNUMLT' for number which is less than the expression, `TDBQCNUMLE' for number which is less than or equal to the expression, `TDBQCNUMBT' for number which is between two tokens of the expression, `TDBQCNUMOREQ' for number which is equal to at least one token in the expression, `TDBQCFTSPH' for full\-text search with the phrase of the expression, `TDBQCFTSAND' for full\-text search with all tokens in the expression, `TDBQCFTSOR' for full\-text search with at least one token in the expression, `TDBQCFTSEX' for full\-text search with the compound expression.  All operations can be flagged by bitwise\-or: `TDBQCNEGATE' for negation, `TDBQCNOIDX' for using no index.
+.RE
+.RS
+`\fIexpr\fR' specifies an operand exression.
+.RE
+.RE
+.PP
+The function `tctdbqrysetorder' is used in order to set the order of a query object.
+.PP
+.RS
+.br
+\fBvoid tctdbqrysetorder(TDBQRY *\fIqry\fB, const char *\fIname\fB, int \fItype\fB);\fR
+.RS
+`\fIqry\fR' specifies the query object.
+.RE
+.RS
+`\fIname\fR' specifies the name of a column.  An empty string means the primary key.
+.RE
+.RS
+`\fItype\fR' specifies the order type: `TDBQOSTRASC' for string ascending, `TDBQOSTRDESC' for string descending, `TDBQONUMASC' for number ascending, `TDBQONUMDESC' for number descending.
+.RE
+.RE
+.PP
+The function `tctdbqrysetlimit' is used in order to set the limit number of records of the result of a query object.
+.PP
+.RS
+.br
+\fBvoid tctdbqrysetlimit(TDBQRY *\fIqry\fB, int \fImax\fB, int \fIskip\fB);\fR
+.RS
+`\fIqry\fR' specifies the query object.
+.RE
+.RS
+`\fImax\fR' specifies the maximum number of records of the result.  If it is negative, no limit is specified.
+.RE
+.RS
+`\fIskip\fR' specifies the number of skipped records of the result.  If it is not more than 0, no record is skipped.
+.RE
+.RE
+.PP
+The function `tctdbqrysearch' is used in order to execute the search of a query object.
+.PP
+.RS
+.br
+\fBTCLIST *tctdbqrysearch(TDBQRY *\fIqry\fB);\fR
+.RS
+`\fIqry\fR' specifies the query object.
+.RE
+.RS
+The return value is a list object of the primary keys of the corresponding records.  This function does never fail.  It returns an empty list even if no record corresponds.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tctdbqrysearchout' is used in order to remove each record corresponding to a query object.
+.PP
+.RS
+.br
+\fBbool tctdbqrysearchout(TDBQRY *\fIqry\fB);\fR
+.RS
+`\fIqry\fR' specifies the query object of the database connected as a writer.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RE
+.PP
+The function `tctdbqryproc' is used in order to process each record corresponding to a query object.
+.PP
+.RS
+.br
+\fBbool tctdbqryproc(TDBQRY *\fIqry\fB, TDBQRYPROC \fIproc\fB, void *\fIop\fB);\fR
+.RS
+`\fIqry\fR' specifies the query object of the database connected as a writer.
+.RE
+.RS
+`\fIproc\fR' specifies the pointer to the iterator function called for each record.  It receives four parameters.  The first parameter is the pointer to the region of the primary key.  The second parameter is the size of the region of the primary key.  The third parameter is a map object containing columns.  The fourth parameter is the pointer to the optional opaque object.  It returns flags of the post treatment by bitwise-or: `TDBQPPUT' to modify the record, `TDBQPOUT' to remove the record, `TDBQPSTOP' to stop the iteration.
+.RE
+.RS
+`\fIop\fR' specifies an arbitrary pointer to be given as a parameter of the iterator function.  If it is not needed, `NULL' can be specified.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RE
+.PP
+The function `tctdbqryhint' is used in order to get the hint string of a query object.
+.PP
+.RS
+.br
+\fBconst char *tctdbqryhint(TDBQRY *\fIqry\fB);\fR
+.RS
+`\fIqry\fR' specifies the query object.
+.RE
+.RS
+The return value is the hint string.
+.RE
+.RE
+.PP
+The function `tctdbmetasearch' is used in order to retrieve records with multiple query objects and get the set of the result.
+.PP
+.RS
+.br
+\fBTCLIST *tctdbmetasearch(TDBQRY **\fIqrys\fB, int \fInum\fB, int \fItype\fB);\fR
+.RS
+`\fIqrys\fR' specifies an array of the query objects.
+.RE
+.RS
+`\fInum\fR' specifies the number of elements of the array.
+.RE
+.RS
+`\fItype\fR' specifies a set operation type: `TDBMSUNION' for the union set, `TDBMSISECT' for the intersection set, `TDBMSDIFF' for the difference set.
+.RE
+.RS
+The return value is a list object of the primary keys of the corresponding records.  This function does never fail.  It returns an empty list even if no record corresponds.
+.RE
+.RS
+If the first query object has the order setting, the result array is sorted by the order.  Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.
+.RE
+.RE
+
+.SH SEE ALSO
+.PP
+.BR tcttest (1),
+.BR tctmttest (1),
+.BR tctmgr (1),
+.BR tokyocabinet (3)
diff --git a/tcejdb/man/tctmgr.1 b/tcejdb/man/tctmgr.1
new file mode 100644 (file)
index 0000000..b58068d
--- /dev/null
@@ -0,0 +1,140 @@
+.TH "TCTMGR" 1 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tctmgr \- the command line utility of the table database API
+
+.SH DESCRIPTION
+.PP
+The command `\fBtctmgr\fR' is a utility for test and debugging of the table database API and its applications.  `\fIpath\fR' specifies the path of a database file.  `\fIbnum\fR' specifies the number of buckets.  `\fIapow\fR' specifies the power of the alignment.  `\fIfpow\fR' specifies the power of the free block pool.  `\fIpkey\fR' specifies the primary key of a record.  `\fIcols\fR' specifies the names and the values of a record alternately.  `\fIname\fR' specifies the name of a column.  `\fIop\fR' specifies an operator.  `\fIexpr\fR' specifies the condition expression.  `\fIfile\fR' specifies the input file.
+.PP
+.RS
+.br
+\fBtctmgr create \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fIpath\fB \fR[\fB\fIbnum\fB \fR[\fB\fIapow\fB \fR[\fB\fIfpow\fB\fR]\fB\fR]\fB\fR]\fB\fR
+.RS
+Create a database file.
+.RE
+.br
+\fBtctmgr inform \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fIpath\fB\fR
+.RS
+Print miscellaneous information to the standard output.
+.RE
+.br
+\fBtctmgr put \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-sx\fR]\fB \fR[\fB\-dk\fR|\fB\-dc\fR|\fB\-dai\fR|\fB\-dad\fR]\fB \fIpath\fB \fIpkey\fB \fR[\fB\fIcols\fB ...\fR]\fB\fR
+.RS
+Store a record.
+.RE
+.br
+\fBtctmgr out \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-sx\fR]\fB \fIpath\fB \fIpkey\fB\fR
+.RS
+Remove a record.
+.RE
+.br
+\fBtctmgr get \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-sx\fR]\fB \fR[\fB\-px\fR]\fB \fR[\fB\-pz\fR]\fB \fIpath\fB \fIpkey\fB\fR
+.RS
+Print the value of a record.
+.RE
+.br
+\fBtctmgr list \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-m \fInum\fB\fR]\fB \fR[\fB\-pv\fR]\fB \fR[\fB\-px\fR]\fB \fR[\fB\-fm \fIstr\fB\fR]\fB \fIpath\fB\fR
+.RS
+Print the primary keys of all records, separated by line feeds.
+.RE
+.br
+\fBtctmgr search \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-ord \fIname\fB \fItype\fB\fR]\fB \fR[\fB\-m \fInum\fB\fR]\fB \fR[\fB\-sk \fInum\fB\fR]\fB \fR[\fB\-kw\fR]\fB \fR[\fB\-pv\fR]\fB \fR[\fB\-px\fR]\fB \fR[\fB\-ph\fR]\fB \fR[\fB\-bt \fInum\fB\fR]\fB \fR[\fB\-rm\fR]\fB \fR[\fB\-ms \fItype\fB\fR]\fB \fIpath\fB \fR[\fB\fIname\fB \fIop\fB \fIexpr\fB ...\fR]\fB\fR
+.RS
+Print records matching conditions, separated by line feeds.
+.RE
+.br
+\fBtctmgr optimize \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-tz\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-df\fR]\fB \fIpath\fB \fR[\fB\fIbnum\fB \fR[\fB\fIapow\fB \fR[\fB\fIfpow\fB\fR]\fB\fR]\fB\fR]\fB\fR
+.RS
+Optimize a database file.
+.RE
+.br
+\fBtctmgr setindex \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-it \fItype\fB\fR]\fB \fIpath\fB \fIname\fB\fR
+.RS
+Set the index of a column.
+.RE
+.br
+\fBtctmgr importtsv \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-sc\fR]\fB \fIpath\fB \fR[\fB\fIfile\fB\fR]\fB\fR
+.RS
+Store records of TSV in each line of a file.
+.RE
+.br
+\fBtctmgr version\fR
+.RS
+Print the version information of Tokyo Cabinet.
+.RE
+.RE
+.PP
+Options feature the following.
+.PP
+.RS
+\fB\-tl\fR : enable the option `TDBTLARGE'.
+.br
+\fB\-td\fR : enable the option `TDBTDEFLATE'.
+.br
+\fB\-tb\fR : enable the option `TDBTBZIP'.
+.br
+\fB\-tt\fR : enable the option `TDBTTCBS'.
+.br
+\fB\-tx\fR : enable the option `TDBTEXCODEC'.
+.br
+\fB\-nl\fR : enable the option `TDBNOLCK'.
+.br
+\fB\-nb\fR : enable the option `TDBLCKNB'.
+.br
+\fB\-sx\fR : the input data is evaluated as a hexadecimal data string.
+.br
+\fB\-dk\fR : use the function `tctdbputkeep' instead of `tctdbput'.
+.br
+\fB\-dc\fR : use the function `tctdbputcat' instead of `tctdbput'.
+.br
+\fB\-dai\fR : use the function `tctdbaddint' instead of `tctdbput'.
+.br
+\fB\-dad\fR : use the function `tctdbadddouble' instead of `tctdbput'.
+.br
+\fB\-px\fR : the output data is converted into a hexadecimal data string.
+.br
+\fB\-pz\fR : do not append line feed at the end of the output.
+.br
+\fB\-m \fInum\fR\fR : specify the maximum number of the output.
+.br
+\fB\-pv\fR : print values of records also.
+.br
+\fB\-fm \fIstr\fR\fR : specify the prefix of keys.
+.br
+\fB\-ord \fIname\fR \fItype\fR\fR : specify the order of the result.
+.br
+\fB\-sk \fInum\fR\fR : specify the number of skipped records.
+.br
+\fB\-kw\fR : print KWIC string.
+.br
+\fB\-ph\fR : print hint information also.
+.br
+\fB\-bt\fR : specify the number of benchmark tests.
+.br
+\fB\-rm\fR : remove every record in the result.
+.br
+\fB\-ms \fItype\fR\fR : specify the set operation of meta search.
+.br
+\fB\-tz\fR : enable the option `UINT8_MAX'.
+.br
+\fB\-df\fR : perform defragmentation only.
+.br
+\fB\-it \fItype\fR\fR : specify the index type among "lexical", "decimal", "token", "qgram", and "void".
+.br
+\fB\-cd\fR : create the number index instead of the string index.
+.br
+\fB\-cv\fR : remove the existing index.
+.br
+\fB\-sc\fR : normalize keys as lower cases.
+.br
+.RE
+.PP
+The operator of the `search' subcommand is one of "STREQ", "STRINC", "STRBW", "STREW", "STRAND", "STROR", "STROREQ", "STRRX", "NUMEQ", "NUMGT", "NUMGE", "NUMLT", "NUMLE", "NUMBT", "NUMOREQ", "FTSPH", "FTSAND", "FTSOR", and "FTSEX".  If "~" preposes each operator, the logical meaning is reversed.  If "+" preposes each operator, no index is used for the operator.  The type of the `\-ord' option is one of "STRASC", "STRDESC", "NUMASC", and "NUMDESC".  The type of the `\-ms' option is one of "UNION", "ISECT", and "DIFF".  This command returns 0 on success, another on failure.
+
+.SH SEE ALSO
+.PP
+.BR tcttest (1),
+.BR tctmttest (1),
+.BR tctdb (3),
+.BR tokyocabinet (3)
diff --git a/tcejdb/man/tctmttest.1 b/tcejdb/man/tctmttest.1
new file mode 100644 (file)
index 0000000..da36f03
--- /dev/null
@@ -0,0 +1,92 @@
+.TH "TCTMTTEST" 1 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tctmttest \- test cases of the table database API
+
+.SH DESCRIPTION
+.PP
+The command `\fBtctmttest\fR' is a utility for facility test under multi\-thread situation.  This command is used in the following format.  `\fIpath\fR' specifies the path of a database file.  `\fItnum\fR' specifies the number of running threads.  `\fIrnum\fR' specifies the number of iterations.  `\fIbnum\fR' specifies the number of buckets.  `\fIapow\fR' specifies the power of the alignment.  `\fIfpow\fR' specifies the power of the free block pool.
+.PP
+.RS
+.br
+\fBtctmttest write \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-rc \fInum\fB\fR]\fB \fR[\fB\-lc \fInum\fB\fR]\fB \fR[\fB\-nc \fInum\fB\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-ip\fR]\fB \fR[\fB\-is\fR]\fB \fR[\fB\-in\fR]\fB \fR[\fB\-it\fR]\fB \fR[\fB\-if\fR]\fB \fR[\fB\-ix\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-rnd\fR]\fB \fIpath\fB \fItnum\fB \fIrnum\fB \fR[\fB\fIbnum\fB \fR[\fB\fIapow\fB \fR[\fB\fIfpow\fB\fR]\fB\fR]\fB\fR]\fB\fR
+.RS
+Store records with columns "str", "num", "type", and "flag".
+.RE
+.br
+\fBtctmttest read \fR[\fB\-rc \fInum\fB\fR]\fB \fR[\fB\-lc \fInum\fB\fR]\fB \fR[\fB\-nc \fInum\fB\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-rnd\fR]\fB \fIpath\fB \fItnum\fB\fR
+.RS
+Retrieve all records of the database above.
+.RE
+.br
+\fBtctmttest remove \fR[\fB\-rc \fInum\fB\fR]\fB \fR[\fB\-lc \fInum\fB\fR]\fB \fR[\fB\-nc \fInum\fB\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-rnd\fR]\fB \fIpath\fB \fItnum\fB\fR
+.RS
+Remove all records of the database above.
+.RE
+.br
+\fBtctmttest wicked \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fIpath\fB \fItnum\fB \fIrnum\fB\fR
+.RS
+Perform updating operations selected at random.
+.RE
+.br
+\fBtctmttest typical \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-rc \fInum\fB\fR]\fB \fR[\fB\-lc \fInum\fB\fR]\fB \fR[\fB\-nc \fInum\fB\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-rr \fInum\fB\fR]\fB \fIpath\fB \fItnum\fB \fIrnum\fB \fR[\fB\fIbnum\fB \fR[\fB\fIapow\fB \fR[\fB\fIfpow\fB\fR]\fB\fR]\fB\fR
+.RS
+Perform typical operations selected at random.
+.RE
+.RE
+.PP
+Options feature the following.
+.PP
+.RS
+\fB\-tl\fR : enable the option `TDBTLARGE'.
+.br
+\fB\-td\fR : enable the option `TDBTDEFLATE'.
+.br
+\fB\-tb\fR : enable the option `TDBTBZIP'.
+.br
+\fB\-tt\fR : enable the option `TDBTTCBS'.
+.br
+\fB\-tx\fR : enable the option `TDBTEXCODEC'.
+.br
+\fB\-rc \fInum\fR\fR : specify the number of cached records.
+.br
+\fB\-lc \fInum\fR\fR : specify the number of cached leaf pages.
+.br
+\fB\-nc \fInum\fR\fR : specify the number of cached non\-leaf pages.
+.br
+\fB\-xm \fInum\fR\fR : specify the size of the extra mapped memory.
+.br
+\fB\-df \fInum\fR\fR : specify the unit step number of auto defragmentation.
+.br
+\fB\-ip\fR : create the number index for the primary key.
+.br
+\fB\-is\fR : create the string index for the column "str".
+.br
+\fB\-in\fR : create the number index for the column "num".
+.br
+\fB\-it\fR : create the string index for the column "type".
+.br
+\fB\-if\fR : create the token inverted index for the column "flag".
+.br
+\fB\-ix\fR : create the q\-gram inverted index for the column "text".
+.br
+\fB\-nl\fR : enable the option `TDBNOLCK'.
+.br
+\fB\-nb\fR : enable the option `TDBLCKNB'.
+.br
+\fB\-rnd\fR : select keys at random.
+.br
+\fB\-nc\fR : omit the comparison test.
+.br
+\fB\-rr \fInum\fR\fR : specify the ratio of reading operation by percentage.
+.br
+.RE
+.PP
+This command returns 0 on success, another on failure.
+
+.SH SEE ALSO
+.PP
+.BR tcttest (1),
+.BR tctmgr (1),
+.BR tctdb (3),
+.BR tokyocabinet (3)
diff --git a/tcejdb/man/tctree.3 b/tcejdb/man/tctree.3
new file mode 100644 (file)
index 0000000..03538a8
--- /dev/null
@@ -0,0 +1 @@
+.so man3/tcutil.3
diff --git a/tcejdb/man/tcttest.1 b/tcejdb/man/tcttest.1
new file mode 100644 (file)
index 0000000..3ec3503
--- /dev/null
@@ -0,0 +1,105 @@
+.TH "TCTTEST" 1 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tcttest \- test cases of the table database API
+
+.SH DESCRIPTION
+.PP
+The command `\fBtcttest\fR' is a utility for facility test and performance test.  This command is used in the following format.  `\fIpath\fR' specifies the path of a database file.  `\fIrnum\fR' specifies the number of iterations.  `\fIbnum\fR' specifies the number of buckets.  `\fIapow\fR' specifies the power of the alignment.  `\fIfpow\fR' specifies the power of the free block pool.
+.PP
+.RS
+.br
+\fBtcttest write \fR[\fB\-mt\fR]\fB \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-rc \fInum\fB\fR]\fB \fR[\fB\-lc \fInum\fB\fR]\fB \fR[\fB\-nc \fInum\fB\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-ip\fR]\fB \fR[\fB\-is\fR]\fB \fR[\fB\-in\fR]\fB \fR[\fB\-it\fR]\fB \fR[\fB\-if\fR]\fB \fR[\fB\-ix\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-rnd\fR]\fB \fIpath\fB \fIrnum\fB \fR[\fB\fIbnum\fB \fR[\fB\fIapow\fB \fR[\fB\fIfpow\fB\fR]\fB\fR]\fB\fR]\fB\fR
+.RS
+Store records with columns "str", "num", "type", and "flag".
+.RE
+.br
+\fBtcttest read \fR[\fB\-mt\fR]\fB \fR[\fB\-rc \fInum\fB\fR]\fB \fR[\fB\-lc \fInum\fB\fR]\fB \fR[\fB\-nc \fInum\fB\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-rnd\fR]\fB \fIpath\fB\fR
+.RS
+Retrieve all records of the database above.
+.RE
+.br
+\fBtcttest remove \fR[\fB\-mt\fR]\fB \fR[\fB\-rc \fInum\fB\fR]\fB \fR[\fB\-lc \fInum\fB\fR]\fB \fR[\fB\-nc \fInum\fB\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-rnd\fR]\fB \fIpath\fB\fR
+.RS
+Remove all records of the database above.
+.RE
+.br
+\fBtcttest rcat \fR[\fB\-mt\fR]\fB \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-rc \fInum\fB\fR]\fB \fR[\fB\-lc \fInum\fB\fR]\fB \fR[\fB\-nc \fInum\fB\fR]\fB \fR[\fB\-xm \fInum\fB\fR]\fB \fR[\fB\-df \fInum\fB\fR]\fB \fR[\fB\-ip\fR]\fB \fR[\fB\-is\fR]\fB \fR[\fB\-in\fR]\fB \fR[\fB\-it\fR]\fB \fR[\fB\-if\fR]\fB \fR[\fB\-ix\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fR[\fB\-pn \fInum\fB\fR]\fB \fR[\fB\-dai\fR|\fB\-dad\fR|\fB\-rl\fR|\fB\-ru\fR]\fB \fIpath\fB \fIrnum\fB \fR[\fB\fIbnum\fB \fR[\fB\fIapow\fB \fR[\fB\fIfpow\fB\fR]\fB\fR]\fB\fR]\fB\fR
+.RS
+Store records with partway duplicated keys using concatenate mode.
+.RE
+.br
+\fBtcttest misc \fR[\fB\-mt\fR]\fB \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fIpath\fB \fIrnum\fB\fR
+.RS
+Perform miscellaneous test of various operations.
+.RE
+.br
+\fBtcttest wicked \fR[\fB\-mt\fR]\fB \fR[\fB\-tl\fR]\fB \fR[\fB\-td\fR|\fB\-tb\fR|\fB\-tt\fR|\fB\-tx\fR]\fB \fR[\fB\-nl\fR|\fB\-nb\fR]\fB \fIpath\fB \fIrnum\fB\fR
+.RS
+Perform updating operations selected at random.
+.RE
+.RE
+.PP
+Options feature the following.
+.PP
+.RS
+\fB\-mt\fR : call the function `tctdbsetmutex'.
+.br
+\fB\-tl\fR : enable the option `TDBTLARGE'.
+.br
+\fB\-td\fR : enable the option `TDBTDEFLATE'.
+.br
+\fB\-tb\fR : enable the option `TDBTBZIP'.
+.br
+\fB\-tt\fR : enable the option `TDBTTCBS'.
+.br
+\fB\-tx\fR : enable the option `TDBTEXCODEC'.
+.br
+\fB\-rc \fInum\fR\fR : specify the number of cached records.
+.br
+\fB\-lc \fInum\fR\fR : specify the number of cached leaf pages.
+.br
+\fB\-nc \fInum\fR\fR : specify the number of cached non\-leaf pages.
+.br
+\fB\-xm \fInum\fR\fR : specify the size of the extra mapped memory.
+.br
+\fB\-df \fInum\fR\fR : specify the unit step number of auto defragmentation.
+.br
+\fB\-ip\fR : create the number index for the primary key.
+.br
+\fB\-is\fR : create the string index for the column "str".
+.br
+\fB\-in\fR : create the number index for the column "num".
+.br
+\fB\-it\fR : create the string index for the column "type".
+.br
+\fB\-if\fR : create the token inverted index for the column "flag".
+.br
+\fB\-ix\fR : create the q\-gram inverted index for the column "text".
+.br
+\fB\-nl\fR : enable the option `TDBNOLCK'.
+.br
+\fB\-nb\fR : enable the option `TDBLCKNB'.
+.br
+\fB\-rnd\fR : select keys at random.
+.br
+\fB\-pn \fInum\fR\fR : specify the number of patterns.
+.br
+\fB\-dai\fR : use the function `tctdbaddint' instead of `tctdbputcat'.
+.br
+\fB\-dad\fR : use the function `tctdbadddouble' instead of `tctdbputcat'.
+.br
+\fB\-rl\fR : set the length of values at random.
+.br
+\fB\-ru\fR : select update operations at random.
+.br
+.RE
+.PP
+This command returns 0 on success, another on failure.
+
+.SH SEE ALSO
+.PP
+.BR tctmttest (1),
+.BR tctmgr (1),
+.BR tctdb (3),
+.BR tokyocabinet (3)
diff --git a/tcejdb/man/tcucodec.1 b/tcejdb/man/tcucodec.1
new file mode 100644 (file)
index 0000000..b4091c9
--- /dev/null
@@ -0,0 +1,162 @@
+.TH "TCUCODEC" 1 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tcucodec \- popular encoders and decoders
+
+.SH DESCRIPTION
+.PP
+The command `\fBtcucodec\fR' is a tool to use encoding and decoding features.  This command is used in the following format.  `\fIfile\fR' specifies a input file.  If it is omitted, the standard input is read.
+.PP
+.RS
+.br
+\fBtcucodec url \fR[\fB\-d\fR]\fB \fR[\fB\-br\fR]\fB \fR[\fB\-rs \fIbase\fB\fR]\fB \fR[\fB\fIfile\fB\fR]\fB\fR
+.RS
+Perform URL encoding and its decoding.
+.RE
+.br
+\fBtcucodec base \fR[\fB\-d\fR]\fB \fR[\fB\fIfile\fB\fR]\fB\fR
+.RS
+Perform Base64 encoding and its decoding.
+.RE
+.br
+\fBtcucodec quote \fR[\fB\-d\fR]\fB \fR[\fB\fIfile\fB\fR]\fB\fR
+.RS
+Perform quoted\-printable encoding and its decoding.
+.RE
+.br
+\fBtcucodec mime \fR[\fB\-d\fR]\fB \fR[\fB\-en \fIname\fB\fR]\fB \fR[\fB\-q\fR]\fB \fR[\fB\-on\fR]\fB \fR[\fB\-hd\fR]\fB \fR[\fB\-bd\fR]\fB \fR[\fB\-part \fInum\fB\fR]\fB \fR[\fB\fIfile\fB\fR]\fB\fR
+.RS
+Perform MIME encoding and its decoding.
+.RE
+.br
+\fBtcucodec hex \fR[\fB\-d\fR]\fB \fR[\fB\fIfile\fB\fR]\fB\fR
+.RS
+Perform hexadecimal encoding and its decoding.
+.RE
+.br
+\fBtcucodec pack \fR[\fB\-d\fR]\fB \fR[\fB\-bwt\fR]\fB \fR[\fB\fIfile\fB\fR]\fB\fR
+.RS
+Perform Packbits encoding and its decoding.
+.RE
+.br
+\fBtcucodec tcbs \fR[\fB\-d\fR]\fB \fR[\fB\fIfile\fB\fR]\fB\fR
+.RS
+Perform TCBS encoding and its decoding.
+.RE
+.br
+\fBtcucodec zlib \fR[\fB\-d\fR]\fB \fR[\fB\-gz\fR]\fB \fR[\fB\fIfile\fB\fR]\fB\fR
+.RS
+Perform ZLIB encoding and its decoding.
+.RE
+.br
+\fBtcucodec bzip \fR[\fB\-d\fR]\fB \fR[\fB\fIfile\fB\fR]\fB\fR
+.RS
+Perform BZIP2 encoding and its decoding.
+.RE
+.br
+\fBtcucodec xml \fR[\fB\-d\fR]\fB \fR[\fB\-br\fR]\fB \fR[\fB\fIfile\fB\fR]\fB\fR
+.RS
+Process XML.  By default, escape meta characters.
+.RE
+.br
+\fBtcucodec cstr \fR[\fB\-d\fR]\fB \fR[\fB\-js\fR]\fB \fR[\fB\fIfile\fB\fR]\fB\fR
+.RS
+Perform C\-string escaping and its unescaping.
+.RE
+.br
+\fBtcucodec ucs \fR[\fB\-d\fR]\fB \fR[\fB\-un\fR]\fB \fR[\fB\-kw \fIstr\fB\fR]\fB \fR[\fB\fIfile\fB\fR]\fB\fR
+.RS
+Convert UTF\-8 string into UCS\-2 array.
+.RE
+.br
+\fBtcucodec hash \fR[\fB\-crc\fR]\fB \fR[\fB\-ch \fInum\fB\fR]\fB \fR[\fB\fIfile\fB\fR]\fB\fR
+.RS
+Calculate the hash value.  By default, use MD5 function.
+.RE
+.br
+\fBtcucodec cipher \fR[\fB\-key \fIstr\fB\fR]\fB \fR[\fB\fIfile\fB\fR]\fB\fR
+.RS
+Perform stream cipher and its decipher.
+.RE
+.br
+\fBtcucodec date \fR[\fB\-ds \fIstr\fB\fR]\fB \fR[\fB\-jl \fInum\fB\fR]\fB \fR[\fB\-wf\fR]\fB \fR[\fB\-rf\fR]\fB\fR
+.RS
+Process date string.  By default, print the current UNIX time.
+.RE
+.br
+\fBtcucodec tmpl \fR[\fB\-var \fIname\fB \fIvalue\fB\fR]\fB \fR[\fB\fIfile\fB\fR]\fB\fR
+.RS
+Perform template serialization.
+.RE
+.br
+\fBtcucodec conf \fR[\fB\-v\fR|\fB\-i\fR|\fB\-l\fR|\fB\-p\fR]\fB\fR
+.RS
+Print some configurations.
+.RE
+.RE
+.PP
+Options feature the following.
+.PP
+.RS
+\fB\-d\fR : perform decoding (unescaping), not encoding (escaping).
+.br
+\fB\-br\fR : break up URL or XML into elements.
+.br
+\fB\-rs \fIbase\fR\fR : specify the base URL and resolve the relative URL.
+.br
+\fB\-en \fIname\fR\fR : specify the input encoding, which is UTF\-8 by default.
+.br
+\fB\-q\fR : use quoted\-printable encoding, which is Base64 by default.
+.br
+\fB\-on\fR : output the charset name when decoding.
+.br
+\fB\-bd\fR : perform MIME parsing and output the body.
+.br
+\fB\-hd\fR : perform MIME parsing and output the headers.
+.br
+\fB\-part \fInum\fR\fR : perform MIME parsing and output the specified part.
+.br
+\fB\-bwt\fR : convert by BWT as preprocessing.
+.br
+\fB\-gz\fR : use GZIP format.
+.br
+\fB\-crc\fR : use CRC32 function.
+.br
+\fB\-js\fR : use JSON compatible format.
+.br
+\fB\-un\fR : perform UCS normalization.
+.br
+\fB\-kw \fIstr\fR\fR : generate KWIC string.
+.br
+\fB\-ch \fInum\fR\fR : use consistent hashing function.
+.br
+\fB\-key \fIstr\fR\fR : specify the cipher key.
+.br
+\fB\-ds \fIstr\fR\fR : specify the time.
+.br
+\fB\-jl \fInum\fR\fR : specify the jet lag.
+.br
+\fB\-wf\fR : format the output in W3CDTF.
+.br
+\fB\-rf\fR : format the output in RFC 1123 format.
+.br
+\fB\-var \fIname\fR \fIvalue\fR\fR : specify a template variable.
+.br
+\fB\-v\fR : show the version number of Tokyo Cabinet.
+.br
+\fB\-i\fR : show options to include the headers of Tokyo Cabinet.
+.br
+\fB\-l\fR : show options to link the library of Tokyo Cabinet.
+.br
+\fB\-p\fR : show the directory path of the commands of Tokyo Cabinet.
+.br
+.RE
+.PP
+This command returns 0 on success, another on failure.
+
+.SH SEE ALSO
+.PP
+.BR tcutest (1),
+.BR tcumttest (1),
+.BR tcutil (3),
+.BR tokyocabinet (3)
diff --git a/tcejdb/man/tcumttest.1 b/tcejdb/man/tcumttest.1
new file mode 100644 (file)
index 0000000..8a8493a
--- /dev/null
@@ -0,0 +1,41 @@
+.TH "TCUMTTEST" 1 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tcutest \- test cases of the on-memory database API
+
+.SH DESCRIPTION
+.PP
+The command `\fBtcumttest\fR' is a utility for facility test under multi\-thread situation.  This command is used in the following format.  `\fIrnum\fR' specifies the number of iterations.  `\fIbnum\fR' specifies the number of buckets.
+.PP
+.RS
+.br
+\fBtcumttest combo \fR[\fB\-rnd\fR]\fB \fItnum\fB \fIrnum\fB \fR[\fB\fIbnum\fB\fR]\fB\fR
+.RS
+Peform storing, retrieving, and removing in turn.
+.RE
+.br
+\fBtcumttest typical \fR[\fB\-nc\fR]\fB \fR[\fB\-rr \fInum\fB\fR]\fB \fItnum\fB \fIrnum\fB \fR[\fB\fIbnum\fB\fR]\fB\fR
+.RS
+Perform typical operations selected at random.
+.RE
+.RE
+.PP
+Options feature the following.
+.PP
+.RS
+\fB\-rnd\fR : select keys at random.
+.br
+\fB\-nc\fR : omit the comparison test.
+.br
+\-rr \fInum\fR : specifiy the ratio of reading operation by percentage.
+.br
+.RE
+.PP
+This command returns 0 on success, another on failure.
+
+.SH SEE ALSO
+.PP
+.BR tcutest (1),
+.BR tcucodec (1),
+.BR tcutil (3),
+.BR tokyocabinet (3)
diff --git a/tcejdb/man/tcutest.1 b/tcejdb/man/tcutest.1
new file mode 100644 (file)
index 0000000..fecb5ff
--- /dev/null
@@ -0,0 +1,81 @@
+.TH "TCUTEST" 1 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tcutest \- test cases of the utility API
+
+.SH DESCRIPTION
+.PP
+The command `\fBtcutest\fR' is a utility for facility test and performance test.  This command is used in the following format.  `\fIrnum\fR' specifies the number of iterations.  `\fIanum\fR' specifies the initial number of elements of array.  `\fIbnum\fR' specifies the number of buckets.
+.PP
+.RS
+.br
+\fBtcutest xstr \fIrnum\fB\fR
+.RS
+Perform test of extensible string.
+.RE
+.br
+\fBtcutest list \fR[\fB\-rd\fR]\fB \fIrnum\fB \fR[\fB\fIanum\fB\fR]\fB\fR
+.RS
+Perform test of array list.
+.RE
+.br
+\fBtcutest map \fR[\fB\-rd\fR]\fB \fR[\fB\-tr\fR]\fB \fR[\fB\-rnd\fR]\fB \fR[\fB\-dk\fR|\fB\-dc\fR|\fB\-dai\fR|\fB\-dad\fR|\fB\-dpr\fR]\fB \fIrnum\fB \fR[\fB\fIbnum\fB\fR]\fB\fR
+.RS
+Perform test of hash map.
+.RE
+.br
+\fBtcutest tree \fR[\fB\-rd\fR]\fB \fR[\fB\-tr\fR]\fB \fR[\fB\-rnd\fR]\fB \fR[\fB\-dk\fR|\fB\-dc\fR|\fB\-dai\fR|\fB\-dad\fR|\fB\-dpr\fR]\fB \fIrnum\fB\fR
+.RS
+Perform test of ordered tree.
+.RE
+.br
+\fBtcutest mdb \fR[\fB\-rd\fR]\fB \fR[\fB\-tr\fR]\fB \fR[\fB\-rnd\fR]\fB \fR[\fB\-dk\fR|\fB\-dc\fR|\fB\-dai\fR|\fB\-dad\fR|\fB\-dpr\fR]\fB \fIrnum\fB \fR[\fB\fIbnum\fB\fR]\fB\fR
+.RS
+Perform test of on\-memory hash database.
+.RE
+.br
+\fBtcutest ndb \fR[\fB\-rd\fR]\fB \fR[\fB\-tr\fR]\fB \fR[\fB\-rnd\fR]\fB \fR[\fB\-dk\fR|\fB\-dc\fR|\fB\-dai\fR|\fB\-dad\fR|\fB\-dpr\fR]\fB \fIrnum\fB\fR
+.RS
+Perform test of on\-memory tree database.
+.RE
+.br
+\fBtcutest misc \fIrnum\fB\fR
+.RS
+Perform test of miscellaneous routines.
+.RE
+.br
+\fBtcutest wicked \fIrnum\fB\fR
+.RS
+Perform updating operations of list and map selected at random.
+.RE
+.RE
+.PP
+Options feature the following.
+.PP
+.RS
+\fB\-rd\fR : perform the reading test also.
+.br
+\fB\-tr\fR : perform the iterator test also.
+.br
+\fB\-rnd\fR : select keys at random.
+.br
+\fB\-dk\fR : use the function `tcxxxputkeep' instead of `tcxxxput'.
+.br
+\fB\-dc\fR : use the function `tcxxxputcat' instead of `tcxxxput'.
+.br
+\fB\-dai\fR : use the function `tcxxxaddint' instead of `tcxxxput'.
+.br
+\fB\-dad\fR : use the function `tcxxxadddouble' instead of `tcxxxput'.
+.br
+\fB\-dpr\fR : use the function `tcxxxputproc' instead of `tcxxxput'.
+.br
+.RE
+.PP
+This command returns 0 on success, another on failure.
+
+.SH SEE ALSO
+.PP
+.BR tcumttest (1),
+.BR tcucodec (1),
+.BR tcutil (3),
+.BR tokyocabinet (3)
diff --git a/tcejdb/man/tcutil.3 b/tcejdb/man/tcutil.3
new file mode 100644 (file)
index 0000000..be1313e
--- /dev/null
@@ -0,0 +1,4518 @@
+.TH "TCUTIL" 3 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tcutil \- the utility API
+
+.SH DESCRIPTION
+.PP
+The utility API is a set of routines to handle records on memory easily.  Especially, extensible string, array list, hash map, and ordered tree are useful.
+.PP
+To use the utility API, include `\fBtcutil.h\fR' and related standard header files.  Usually, write the following description near the front of a source file.
+.PP
+.RS
+.br
+\fB#include <tcutil.h>\fR
+.br
+\fB#include <stdlib.h>\fR
+.br
+\fB#include <time.h>\fR
+.br
+\fB#include <stdbool.h>\fR
+.br
+\fB#include <stdint.h>\fR
+.RE
+.PP
+Objects whose type is pointer to `\fBTCXSTR\fR' are used for extensible string.  An extensible string object is created with the function `\fBtcxstrnew\fR' and is deleted with the function `\fBtcxstrdel\fR'.  Objects whose type is pointer to `\fBTCLIST\fR' are used for array list.  A list object is created with the function `\fBtclistnew\fR' and is deleted with the function `\fBtclistdel\fR'.  Objects whose type is pointer to `\fBTCMAP\fR' are used for hash map.  A map object is created with the function `\fBtcmapnew\fR' and is deleted with the function `\fBtcmapdel\fR'.  Objects whose type is pointer to `\fBTCTREE\fR' are used for ordered tree.  A tree object is created with the function `\fBtctreenew\fR' and is deleted with the function `\fBtctreedel\fR'.  To avoid memory leak, it is important to delete every object when it is no longer in use.
+
+.SH API OF BASIC UTILITIES
+.PP
+The constant `tcversion' is the string containing the version information.
+.PP
+.RS
+.br
+\fBextern const char *tcversion;\fR
+.RE
+.PP
+The variable `tcfatalfunc' is the pointer to the call back function for handling a fatal error.
+.PP
+.RS
+.br
+\fBextern void (*tcfatalfunc)(const char *);\fR
+.RS
+The argument specifies the error message.
+.RE
+.RS
+The initial value of this variable is `NULL'.  If the value is `NULL', the default function is called when a fatal error occurs.  A fatal error occurs when memory allocation is failed.
+.RE
+.RE
+.PP
+The function `tcmalloc' is used in order to allocate a region on memory.
+.PP
+.RS
+.br
+\fBvoid *tcmalloc(size_t \fIsize\fB);\fR
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+The return value is the pointer to the allocated region.
+.RE
+.RS
+This function handles failure of memory allocation implicitly.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tccalloc' is used in order to allocate a nullified region on memory.
+.PP
+.RS
+.br
+\fBvoid *tccalloc(size_t \fInmemb\fB, size_t \fIsize\fB);\fR
+.RS
+`\fInmemb\fR' specifies the number of elements.
+.RE
+.RS
+`\fIsize\fR' specifies the size of each element.
+.RE
+.RS
+The return value is the pointer to the allocated nullified region.
+.RE
+.RS
+This function handles failure of memory allocation implicitly.  Because the region of the return value is allocated with the `calloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcrealloc' is used in order to re\-allocate a region on memory.
+.PP
+.RS
+.br
+\fBvoid *tcrealloc(void *\fIptr\fB, size_t \fIsize\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+The return value is the pointer to the re\-allocated region.
+.RE
+.RS
+This function handles failure of memory allocation implicitly.  Because the region of the return value is allocated with the `realloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcmemdup' is used in order to duplicate a region on memory.
+.PP
+.RS
+.br
+\fBvoid *tcmemdup(const void *\fIptr\fB, size_t \fIsize\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+The return value is the pointer to the allocated region of the duplicate.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcstrdup' is used in order to duplicate a string on memory.
+.PP
+.RS
+.br
+\fBchar *tcstrdup(const void *\fIstr\fB);\fR
+.RS
+`\fIstr\fR' specifies the string.
+.RE
+.RS
+The return value is the allocated string equivalent to the specified string.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcfree' is used in order to free a region on memory.
+.PP
+.RS
+.br
+\fBvoid tcfree(void *\fIptr\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region.  If it is `NULL', this function has no effect.
+.RE
+.RS
+Although this function is just a wrapper of `free' call, this is useful in applications using another package of the `malloc' series.
+.RE
+.RE
+
+.SH API OF EXTENSIBLE STRING
+.PP
+The function `tcxstrnew' is used in order to create an extensible string object.
+.PP
+.RS
+.br
+\fBTCXSTR *tcxstrnew(void);\fR
+.RS
+The return value is the new extensible string object.
+.RE
+.RE
+.PP
+The function `tcxstrnew2' is used in order to create an extensible string object from a character string.
+.PP
+.RS
+.br
+\fBTCXSTR *tcxstrnew2(const char *\fIstr\fB);\fR
+.RS
+`\fIstr\fR' specifies the string of the initial content.
+.RE
+.RS
+The return value is the new extensible string object containing the specified string.
+.RE
+.RE
+.PP
+The function `tcxstrnew3' is used in order to create an extensible string object with the initial allocation size.
+.PP
+.RS
+.br
+\fBTCXSTR *tcxstrnew3(int \fIasiz\fB);\fR
+.RS
+`\fIasiz\fR' specifies the initial allocation size.
+.RE
+.RS
+The return value is the new extensible string object.
+.RE
+.RE
+.PP
+The function `tcxstrdup' is used in order to copy an extensible string object.
+.PP
+.RS
+.br
+\fBTCXSTR *tcxstrdup(const TCXSTR *\fIxstr\fB);\fR
+.RS
+`\fIxstr\fR' specifies the extensible string object.
+.RE
+.RS
+The return value is the new extensible string object equivalent to the specified object.
+.RE
+.RE
+.PP
+The function `tcxstrdel' is used in order to delete an extensible string object.
+.PP
+.RS
+.br
+\fBvoid tcxstrdel(TCXSTR *\fIxstr\fB);\fR
+.RS
+`\fIxstr\fR' specifies the extensible string object.
+.RE
+.RS
+Note that the deleted object and its derivatives can not be used anymore.
+.RE
+.RE
+.PP
+The function `tcxstrcat' is used in order to concatenate a region to the end of an extensible string object.
+.PP
+.RS
+.br
+\fBvoid tcxstrcat(TCXSTR *\fIxstr\fB, const void *\fIptr\fB, int \fIsize\fB);\fR
+.RS
+`\fIxstr\fR' specifies the extensible string object.
+.RE
+.RS
+`\fIptr\fR' specifies the pointer to the region to be appended.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RE
+.PP
+The function `tcxstrcat2' is used in order to concatenate a character string to the end of an extensible string object.
+.PP
+.RS
+.br
+\fBvoid tcxstrcat2(TCXSTR *\fIxstr\fB, const char *\fIstr\fB);\fR
+.RS
+`\fIxstr\fR' specifies the extensible string object.
+.RE
+.RS
+`\fIstr\fR' specifies the string to be appended.
+.RE
+.RE
+.PP
+The function `tcxstrptr' is used in order to get the pointer of the region of an extensible string object.
+.PP
+.RS
+.br
+\fBconst void *tcxstrptr(const TCXSTR *\fIxstr\fB);\fR
+.RS
+`\fIxstr\fR' specifies the extensible string object.
+.RE
+.RS
+The return value is the pointer of the region of the object.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.
+.RE
+.RE
+.PP
+The function `tcxstrsize' is used in order to get the size of the region of an extensible string object.
+.PP
+.RS
+.br
+\fBint tcxstrsize(const TCXSTR *\fIxstr\fB);\fR
+.RS
+`\fIxstr\fR' specifies the extensible string object.
+.RE
+.RS
+The return value is the size of the region of the object.
+.RE
+.RE
+.PP
+The function `tcxstrclear' is used in order to clear an extensible string object.
+.PP
+.RS
+.br
+\fBvoid tcxstrclear(TCXSTR *\fIxstr\fB);\fR
+.RS
+`\fIxstr\fR' specifies the extensible string object.
+.RE
+.RS
+The internal buffer of the object is cleared and the size is set zero.
+.RE
+.RE
+.PP
+The function `tcxstrprintf' is used in order to perform formatted output into an extensible string object.
+.PP
+.RS
+.br
+\fBvoid tcxstrprintf(TCXSTR *\fIxstr\fB, const char *\fIformat\fB, ...);\fR
+.RS
+`\fIxstr\fR' specifies the extensible string object.
+.RE
+.RS
+`\fIformat\fR' specifies the printf\-like format string.  The conversion character `%' can be used with such flag characters as `s', `d', `o', `u', `x', `X', `c', `e', `E', `f', `g', `G', `@', `?', `b', and `%'.  `@' works as with `s' but escapes meta characters of XML.  `?' works as with `s' but escapes meta characters of URL.  `b' converts an integer to the string as binary numbers.  The other conversion character work as with each original.
+.RE
+.RS
+The other arguments are used according to the format string.
+.RE
+.RE
+.PP
+The function `tcsprintf' is used in order to allocate a formatted string on memory.
+.PP
+.RS
+.br
+\fBchar *tcsprintf(const char *\fIformat\fB, ...);\fR
+.RS
+`\fIformat\fR' specifies the printf\-like format string.  The conversion character `%' can be used with such flag characters as `s', `d', `o', `u', `x', `X', `c', `e', `E', `f', `g', `G', `@', `?', `b', and `%'.  `@' works as with `s' but escapes meta characters of XML.  `?' works as with `s' but escapes meta characters of URL.  `b' converts an integer to the string as binary numbers.  The other conversion character work as with each original.
+.RE
+.RS
+The other arguments are used according to the format string.
+.RE
+.RS
+The return value is the pointer to the region of the result string.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+
+.SH API OF ARRAY LIST
+.PP
+The function `tclistnew' is used in order to create a list object.
+.PP
+.RS
+.br
+\fBTCLIST *tclistnew(void);\fR
+.RS
+The return value is the new list object.
+.RE
+.RE
+.PP
+The function `tclistnew2' is used in order to create a list object with expecting the number of elements.
+.PP
+.RS
+.br
+\fBTCLIST *tclistnew2(int \fIanum\fB);\fR
+.RS
+`\fIanum\fR' specifies the number of elements expected to be stored in the list.
+.RE
+.RS
+The return value is the new list object.
+.RE
+.RE
+.PP
+The function `tclistnew3' is used in order to create a list object with initial string elements.
+.PP
+.RS
+.br
+\fBTCLIST *tclistnew3(const char *\fIstr\fB, ...);\fR
+.RS
+`\fIstr\fR' specifies the string of the first element.
+.RE
+.RS
+The other arguments are other elements.  They should be trailed by a `NULL' argument.
+.RE
+.RS
+The return value is the new list object.
+.RE
+.RE
+.PP
+The function `tclistdup' is used in order to copy a list object.
+.PP
+.RS
+.br
+\fBTCLIST *tclistdup(const TCLIST *\fIlist\fB);\fR
+.RS
+`\fIlist\fR' specifies the list object.
+.RE
+.RS
+The return value is the new list object equivalent to the specified object.
+.RE
+.RE
+.PP
+The function `tclistdel' is used in order to delete a list object.
+.PP
+.RS
+.br
+\fBvoid tclistdel(TCLIST *\fIlist\fB);\fR
+.RS
+`\fIlist\fR' specifies the list object.
+.RE
+.RS
+Note that the deleted object and its derivatives can not be used anymore.
+.RE
+.RE
+.PP
+The function `tclistnum' is used in order to get the number of elements of a list object.
+.PP
+.RS
+.br
+\fBint tclistnum(const TCLIST *\fIlist\fB);\fR
+.RS
+`\fIlist\fR' specifies the list object.
+.RE
+.RS
+The return value is the number of elements of the list.
+.RE
+.RE
+.PP
+The function `tclistval' is used in order to get the pointer to the region of an element of a list object.
+.PP
+.RS
+.br
+\fBconst void *tclistval(const TCLIST *\fIlist\fB, int \fIindex\fB, int *\fIsp\fB);\fR
+.RS
+`\fIlist\fR' specifies the list object.
+.RE
+.RS
+`\fIindex\fR' specifies the index of the element.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+The return value is the pointer to the region of the value.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  If `index' is equal to or more than the number of elements, the return value is `NULL'.
+.RE
+.RE
+.PP
+The function `tclistval2' is used in order to get the string of an element of a list object.
+.PP
+.RS
+.br
+\fBconst char *tclistval2(const TCLIST *\fIlist\fB, int \fIindex\fB);\fR
+.RS
+`\fIlist\fR' specifies the list object.
+.RE
+.RS
+`\fIindex\fR' specifies the index of the element.
+.RE
+.RS
+The return value is the string of the value.
+.RE
+.RS
+If `index' is equal to or more than the number of elements, the return value is `NULL'.
+.RE
+.RE
+.PP
+The function `tclistpush' is used in order to add an element at the end of a list object.
+.PP
+.RS
+.br
+\fBvoid tclistpush(TCLIST *\fIlist\fB, const void *\fIptr\fB, int \fIsize\fB);\fR
+.RS
+`\fIlist\fR' specifies the list object.
+.RE
+.RS
+`\fIptr\fR' specifies the pointer to the region of the new element.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RE
+.PP
+The function `tclistpush2' is used in order to add a string element at the end of a list object.
+.PP
+.RS
+.br
+\fBvoid tclistpush2(TCLIST *\fIlist\fB, const char *\fIstr\fB);\fR
+.RS
+`\fIlist\fR' specifies the list object.
+.RE
+.RS
+`\fIstr\fR' specifies the string of the new element.
+.RE
+.RE
+.PP
+The function `tclistpop' is used in order to remove an element of the end of a list object.
+.PP
+.RS
+.br
+\fBvoid *tclistpop(TCLIST *\fIlist\fB, int *\fIsp\fB);\fR
+.RS
+`\fIlist\fR' specifies the list object.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+The return value is the pointer to the region of the removed element.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  If the list is empty, the return value is `NULL'.
+.RE
+.RE
+.PP
+The function `tclistpop2' is used in order to remove a string element of the end of a list object.
+.PP
+.RS
+.br
+\fBchar *tclistpop2(TCLIST *\fIlist\fB);\fR
+.RS
+`\fIlist\fR' specifies the list object.
+.RE
+.RS
+The return value is the string of the removed element.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  If the list is empty, the return value is `NULL'.
+.RE
+.RE
+.PP
+The function `tclistunshift' is used in order to add an element at the top of a list object.
+.PP
+.RS
+.br
+\fBvoid tclistunshift(TCLIST *\fIlist\fB, const void *\fIptr\fB, int \fIsize\fB);\fR
+.RS
+`\fIlist\fR' specifies the list object.
+.RE
+.RS
+`\fIptr\fR' specifies the pointer to the region of the new element.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RE
+.PP
+The function `tclistunshift2' is used in order to add a string element at the top of a list object.
+.PP
+.RS
+.br
+\fBvoid tclistunshift2(TCLIST *\fIlist\fB, const char *\fIstr\fB);\fR
+.RS
+`\fIlist\fR' specifies the list object.
+.RE
+.RS
+`\fIstr\fR' specifies the string of the new element.
+.RE
+.RE
+.PP
+The function `tclistshift' is used in order to remove an element of the top of a list object.
+.PP
+.RS
+.br
+\fBvoid *tclistshift(TCLIST *\fIlist\fB, int *\fIsp\fB);\fR
+.RS
+`\fIlist\fR' specifies the list object.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+The return value is the pointer to the region of the removed element.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  If the list is empty, the return value is `NULL'.
+.RE
+.RE
+.PP
+The function `tclistshift2' is used in order to remove a string element of the top of a list object.
+.PP
+.RS
+.br
+\fBchar *tclistshift2(TCLIST *\fIlist\fB);\fR
+.RS
+`\fIlist\fR' specifies the list object.
+.RE
+.RS
+The return value is the string of the removed element.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  If the list is empty, the return value is `NULL'.
+.RE
+.RE
+.PP
+The function `tclistinsert' is used in order to add an element at the specified location of a list object.
+.PP
+.RS
+.br
+\fBvoid tclistinsert(TCLIST *\fIlist\fB, int \fIindex\fB, const void *\fIptr\fB, int \fIsize\fB);\fR
+.RS
+`\fIlist\fR' specifies the list object.
+.RE
+.RS
+`\fIindex\fR' specifies the index of the new element.
+.RE
+.RS
+`\fIptr\fR' specifies the pointer to the region of the new element.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+If `index' is equal to or more than the number of elements, this function has no effect.
+.RE
+.RE
+.PP
+The function `tclistinsert2' is used in order to add a string element at the specified location of a list object.
+.PP
+.RS
+.br
+\fBvoid tclistinsert2(TCLIST *\fIlist\fB, int \fIindex\fB, const char *\fIstr\fB);\fR
+.RS
+`\fIlist\fR' specifies the list object.
+.RE
+.RS
+`\fIindex\fR' specifies the index of the new element.
+.RE
+.RS
+`\fIstr\fR' specifies the string of the new element.
+.RE
+.RS
+If `index' is equal to or more than the number of elements, this function has no effect.
+.RE
+.RE
+.PP
+The function `tclistremove' is used in order to remove an element at the specified location of a list object.
+.PP
+.RS
+.br
+\fBvoid *tclistremove(TCLIST *\fIlist\fB, int \fIindex\fB, int *\fIsp\fB);\fR
+.RS
+`\fIlist\fR' specifies the list object.
+.RE
+.RS
+`\fIindex\fR' specifies the index of the element to be removed.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+The return value is the pointer to the region of the removed element.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  If `index' is equal to or more than the number of elements, no element is removed and the return value is `NULL'.
+.RE
+.RE
+.PP
+The function `tclistremove2' is used in order to remove a string element at the specified location of a list object.
+.PP
+.RS
+.br
+\fBchar *tclistremove2(TCLIST *\fIlist\fB, int \fIindex\fB);\fR
+.RS
+`\fIlist\fR' specifies the list object.
+.RE
+.RS
+`\fIindex\fR' specifies the index of the element to be removed.
+.RE
+.RS
+The return value is the string of the removed element.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  If `index' is equal to or more than the number of elements, no element is removed and the return value is `NULL'.
+.RE
+.RE
+.PP
+The function `tclistover' is used in order to overwrite an element at the specified location of a list object.
+.PP
+.RS
+.br
+\fBvoid tclistover(TCLIST *\fIlist\fB, int \fIindex\fB, const void *\fIptr\fB, int \fIsize\fB);\fR
+.RS
+`\fIlist\fR' specifies the list object.
+.RE
+.RS
+`\fIindex\fR' specifies the index of the element to be overwritten.
+.RE
+.RS
+`\fIptr\fR' specifies the pointer to the region of the new content.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the new content.
+.RE
+.RS
+If `index' is equal to or more than the number of elements, this function has no effect.
+.RE
+.RE
+.PP
+The function `tclistover2' is used in order to overwrite a string element at the specified location of a list object.
+.PP
+.RS
+.br
+\fBvoid tclistover2(TCLIST *\fIlist\fB, int \fIindex\fB, const char *\fIstr\fB);\fR
+.RS
+`\fIlist\fR' specifies the list object.
+.RE
+.RS
+`\fIindex\fR' specifies the index of the element to be overwritten.
+.RE
+.RS
+`\fIstr\fR' specifies the string of the new content.
+.RE
+.RS
+If `index' is equal to or more than the number of elements, this function has no effect.
+.RE
+.RE
+.PP
+The function `tclistsort' is used in order to sort elements of a list object in lexical order.
+.PP
+.RS
+.br
+\fBvoid tclistsort(TCLIST *\fIlist\fB);\fR
+.RS
+`\fIlist\fR' specifies the list object.
+.RE
+.RE
+.PP
+The function `tclistlsearch' is used in order to search a list object for an element using liner search.
+.PP
+.RS
+.br
+\fBint tclistlsearch(const TCLIST *\fIlist\fB, const void *\fIptr\fB, int \fIsize\fB);\fR
+.RS
+`\fIlist\fR' specifies the list object.
+.RE
+.RS
+`\fIptr\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+The return value is the index of a corresponding element or \-1 if there is no corresponding element.
+.RE
+.RS
+If two or more elements correspond, the former returns.
+.RE
+.RE
+.PP
+The function `tclistbsearch' is used in order to search a list object for an element using binary search.
+.PP
+.RS
+.br
+\fBint tclistbsearch(const TCLIST *\fIlist\fB, const void *\fIptr\fB, int \fIsize\fB);\fR
+.RS
+`\fIlist\fR' specifies the list object.  It should be sorted in lexical order.
+.RE
+.RS
+`\fIptr\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+The return value is the index of a corresponding element or \-1 if there is no corresponding element.
+.RE
+.RS
+If two or more elements correspond, which returns is not defined.
+.RE
+.RE
+.PP
+The function `tclistclear' is used in order to clear a list object.
+.PP
+.RS
+.br
+\fBvoid tclistclear(TCLIST *\fIlist\fB);\fR
+.RS
+`\fIlist\fR' specifies the list object.
+.RE
+.RS
+All elements are removed.
+.RE
+.RE
+.PP
+The function `tclistdump' is used in order to serialize a list object into a byte array.
+.PP
+.RS
+.br
+\fBvoid *tclistdump(const TCLIST *\fIlist\fB, int *\fIsp\fB);\fR
+.RS
+`\fIlist\fR' specifies the list object.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+The return value is the pointer to the region of the result serial region.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tclistload' is used in order to create a list object from a serialized byte array.
+.PP
+.RS
+.br
+\fBTCLIST *tclistload(const void *\fIptr\fB, int \fIsize\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region of serialized byte array.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+The return value is a new list object.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.
+.RE
+.RE
+
+.SH API OF HASH MAP
+.PP
+The function `tcmapnew' is used in order to create a map object.
+.PP
+.RS
+.br
+\fBTCMAP *tcmapnew(void);\fR
+.RS
+The return value is the new map object.
+.RE
+.RE
+.PP
+The function `tcmapnew2' is used in order to create a map object with specifying the number of the buckets.
+.PP
+.RS
+.br
+\fBTCMAP *tcmapnew2(uint32_t \fIbnum\fB);\fR
+.RS
+`\fIbnum\fR' specifies the number of the buckets.
+.RE
+.RS
+The return value is the new map object.
+.RE
+.RE
+.PP
+The function `tcmapnew3' is used in order to create a map object with initial string elements.
+.PP
+.RS
+.br
+\fBTCMAP *tcmapnew3(const char *\fIstr\fB, ...);\fR
+.RS
+`\fIstr\fR' specifies the string of the first element.
+.RE
+.RS
+The other arguments are other elements.  They should be trailed by a `NULL' argument.
+.RE
+.RS
+The return value is the new map object.
+.RE
+.RS
+The key and the value of each record are situated one after the other.
+.RE
+.RE
+.PP
+The function `tcmapdup' is used in order to copy a map object.
+.PP
+.RS
+.br
+\fBTCMAP *tcmapdup(const TCMAP *\fImap\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+The return value is the new map object equivalent to the specified object.
+.RE
+.RE
+.PP
+The function `tcmapdel' is used in order to delete a map object.
+.PP
+.RS
+.br
+\fBvoid tcmapdel(TCMAP *\fImap\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+Note that the deleted object and its derivatives can not be used anymore.
+.RE
+.RE
+.PP
+The function `tcmapput' is used in order to store a record into a map object.
+.PP
+.RS
+.br
+\fBvoid tcmapput(TCMAP *\fImap\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.
+.RE
+.RS
+If a record with the same key exists in the map, it is overwritten.
+.RE
+.RE
+.PP
+The function `tcmapput2' is used in order to store a string record into a map object.
+.PP
+.RS
+.br
+\fBvoid tcmapput2(TCMAP *\fImap\fB, const char *\fIkstr\fB, const char *\fIvstr\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If a record with the same key exists in the map, it is overwritten.
+.RE
+.RE
+.PP
+The function `tcmapputkeep' is used in order to store a new record into a map object.
+.PP
+.RS
+.br
+\fBbool tcmapputkeep(TCMAP *\fImap\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the map, this function has no effect.
+.RE
+.RE
+.PP
+The function `tcmapputkeep2' is used in order to store a new string record into a map object.
+.PP
+.RS
+.br
+\fBbool tcmapputkeep2(TCMAP *\fImap\fB, const char *\fIkstr\fB, const char *\fIvstr\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the map, this function has no effect.
+.RE
+.RE
+.PP
+The function `tcmapputcat' is used in order to concatenate a value at the end of the value of the existing record in a map object.
+.PP
+.RS
+.br
+\fBvoid tcmapputcat(TCMAP *\fImap\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.
+.RE
+.RS
+If there is no corresponding record, a new record is created.
+.RE
+.RE
+.PP
+The function `tcmapputcat2' is used in order to concatenate a string value at the end of the value of the existing record in a map object.
+.PP
+.RS
+.br
+\fBvoid tcmapputcat2(TCMAP *\fImap\fB, const char *\fIkstr\fB, const char *\fIvstr\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If there is no corresponding record, a new record is created.
+.RE
+.RE
+.PP
+The function `tcmapout' is used in order to remove a record of a map object.
+.PP
+.RS
+.br
+\fBbool tcmapout(TCMAP *\fImap\fB, const void *\fIkbuf\fB, int \fIksiz\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+If successful, the return value is true.  False is returned when no record corresponds to the specified key.
+.RE
+.RE
+.PP
+The function `tcmapout2' is used in order to remove a string record of a map object.
+.PP
+.RS
+.br
+\fBbool tcmapout2(TCMAP *\fImap\fB, const char *\fIkstr\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+If successful, the return value is true.  False is returned when no record corresponds to the specified key.
+.RE
+.RE
+.PP
+The function `tcmapget' is used in order to retrieve a record in a map object.
+.PP
+.RS
+.br
+\fBconst void *tcmapget(const TCMAP *\fImap\fB, const void *\fIkbuf\fB, int \fIksiz\fB, int *\fIsp\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned when no record corresponds.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.
+.RE
+.RE
+.PP
+The function `tcmapget2' is used in order to retrieve a string record in a map object.
+.PP
+.RS
+.br
+\fBconst char *tcmapget2(const TCMAP *\fImap\fB, const char *\fIkstr\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+If successful, the return value is the string of the value of the corresponding record.  `NULL' is returned when no record corresponds.
+.RE
+.RE
+.PP
+The function `tcmapmove' is used in order to move a record to the edge of a map object.
+.PP
+.RS
+.br
+\fBbool tcmapmove(TCMAP *\fImap\fB, const void *\fIkbuf\fB, int \fIksiz\fB, bool \fIhead\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of a key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIhead\fR' specifies the destination which is the head if it is true or the tail if else.
+.RE
+.RS
+If successful, the return value is true.  False is returned when no record corresponds to the specified key.
+.RE
+.RE
+.PP
+The function `tcmapmove2' is used in order to move a string record to the edge of a map object.
+.PP
+.RS
+.br
+\fBbool tcmapmove2(TCMAP *\fImap\fB, const char *\fIkstr\fB, bool \fIhead\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of a key.
+.RE
+.RS
+`\fIhead\fR' specifies the destination which is the head if it is true or the tail if else.
+.RE
+.RS
+If successful, the return value is true.  False is returned when no record corresponds to the specified key.
+.RE
+.RE
+.PP
+The function `tcmapiterinit' is used in order to initialize the iterator of a map object.
+.PP
+.RS
+.br
+\fBvoid tcmapiterinit(TCMAP *\fImap\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+The iterator is used in order to access the key of every record stored in the map object.
+.RE
+.RE
+.PP
+The function `tcmapiternext' is used in order to get the next key of the iterator of a map object.
+.PP
+.RS
+.br
+\fBconst void *tcmapiternext(TCMAP *\fImap\fB, int *\fIsp\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record can be fetched from the iterator.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  The order of iteration is assured to be the same as the stored order.
+.RE
+.RE
+.PP
+The function `tcmapiternext2' is used in order to get the next key string of the iterator of a map object.
+.PP
+.RS
+.br
+\fBconst char *tcmapiternext2(TCMAP *\fImap\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record can be fetched from the iterator.
+.RE
+.RS
+The order of iteration is assured to be the same as the stored order.
+.RE
+.RE
+.PP
+The function `tcmaprnum' is used in order to get the number of records stored in a map object.
+.PP
+.RS
+.br
+\fBuint64_t tcmaprnum(const TCMAP *\fImap\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+The return value is the number of the records stored in the map object.
+.RE
+.RE
+.PP
+The function `tcmapmsiz' is used in order to get the total size of memory used in a map object.
+.PP
+.RS
+.br
+\fBuint64_t tcmapmsiz(const TCMAP *\fImap\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+The return value is the total size of memory used in a map object.
+.RE
+.RE
+.PP
+The function `tcmapkeys' is used in order to create a list object containing all keys in a map object.
+.PP
+.RS
+.br
+\fBTCLIST *tcmapkeys(const TCMAP *\fImap\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+The return value is the new list object containing all keys in the map object.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcmapvals' is used in order to create a list object containing all values in a map object.
+.PP
+.RS
+.br
+\fBTCLIST *tcmapvals(const TCMAP *\fImap\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+The return value is the new list object containing all values in the map object.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcmapaddint' is used in order to add an integer to a record in a map object.
+.PP
+.RS
+.br
+\fBint tcmapaddint(TCMAP *\fImap\fB, const void *\fIkbuf\fB, int \fIksiz\fB, int \fInum\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fInum\fR' specifies the additional value.
+.RE
+.RS
+The return value is the summation value.
+.RE
+.RS
+If the corresponding record exists, the value is treated as an integer and is added to.  If no record corresponds, a new record of the additional value is stored.
+.RE
+.RE
+.PP
+The function `tcmapadddouble' is used in order to add a real number to a record in a map object.
+.PP
+.RS
+.br
+\fBdouble tcmapadddouble(TCMAP *\fImap\fB, const void *\fIkbuf\fB, int \fIksiz\fB, double \fInum\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fInum\fR' specifies the additional value.
+.RE
+.RS
+The return value is the summation value.
+.RE
+.RS
+If the corresponding record exists, the value is treated as a real number and is added to.  If no record corresponds, a new record of the additional value is stored.
+.RE
+.RE
+.PP
+The function `tcmapclear' is used in order to clear a map object.
+.PP
+.RS
+.br
+\fBvoid tcmapclear(TCMAP *\fImap\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+All records are removed.
+.RE
+.RE
+.PP
+The function `tcmapcutfront' is used in order to remove front records of a map object.
+.PP
+.RS
+.br
+\fBvoid tcmapcutfront(TCMAP *\fImap\fB, int \fInum\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+`\fInum\fR' specifies the number of records to be removed.
+.RE
+.RE
+.PP
+The function `tcmapdump' is used in order to serialize a map object into a byte array.
+.PP
+.RS
+.br
+\fBvoid *tcmapdump(const TCMAP *\fImap\fB, int *\fIsp\fB);\fR
+.RS
+`\fImap\fR' specifies the map object.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+The return value is the pointer to the region of the result serial region.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcmapload' is used in order to create a map object from a serialized byte array.
+.PP
+.RS
+.br
+\fBTCMAP *tcmapload(const void *\fIptr\fB, int \fIsize\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region of serialized byte array.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+The return value is a new map object.
+.RE
+.RS
+Because the object of the return value is created with the function `tcmapnew', it should be deleted with the function `tcmapdel' when it is no longer in use.
+.RE
+.RE
+
+.SH API OF ORDERED TREE
+.PP
+The function `tctreenew' is used in order to create a tree object.
+.PP
+.RS
+.br
+\fBTCTREE *tctreenew(void);\fR
+.RS
+The return value is the new tree object.
+.RE
+.RE
+.PP
+The function `tctreenew2' is used in order to create a tree object with specifying the custom comparison function.
+.PP
+.RS
+.br
+\fBTCTREE *tctreenew2(TCCMP \fIcmp\fB, void *\fIcmpop\fB);\fR
+.RS
+`\fIcmp\fR' specifies the pointer to the custom comparison function.  It receives five parameters.  The first parameter is the pointer to the region of one key.  The second parameter is the size of the region of one key.  The third parameter is the pointer to the region of the other key.  The fourth parameter is the size of the region of the other key.  The fifth parameter is the pointer to the optional opaque object.  It returns positive if the former is big, negative if the latter is big, 0 if both are equivalent.
+.RE
+.RS
+`\fIcmpop\fR' specifies an arbitrary pointer to be given as a parameter of the comparison function.  If it is not needed, `NULL' can be specified.
+.RE
+.RS
+The return value is the new tree object.
+.RE
+.RS
+The default comparison function compares keys of two records by lexical order.  The functions `tccmplexical' (dafault), `tccmpdecimal', `tccmpint32', and `tccmpint64' are built\-in.
+.RE
+.RE
+.PP
+The function `tctreedup' is used in order to copy a tree object.
+.PP
+.RS
+.br
+\fBTCTREE *tctreedup(const TCTREE *\fItree\fB);\fR
+.RS
+`\fItree\fR' specifies the tree object.
+.RE
+.RS
+The return value is the new tree object equivalent to the specified object.
+.RE
+.RE
+.PP
+The function `tctreedel' is used in order to delete a tree object.
+.PP
+.RS
+.br
+\fBvoid tctreedel(TCTREE *\fItree\fB);\fR
+.RS
+`tree' specifies the tree object.
+.RE
+.RS
+Note that the deleted object and its derivatives can not be used anymore.
+.RE
+.RE
+.PP
+The function `tctreeput' is used in order to store a record into a tree object.
+.PP
+.RS
+.br
+\fBvoid tctreeput(TCTREE *\fItree\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fItree\fR' specifies the tree object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.
+.RE
+.RS
+If a record with the same key exists in the tree, it is overwritten.
+.RE
+.RE
+.PP
+The function `tctreeput2' is used in order to store a string record into a tree object.
+.PP
+.RS
+.br
+\fBvoid tctreeput2(TCTREE *\fItree\fB, const char *\fIkstr\fB, const char *\fIvstr\fB);\fR
+.RS
+`\fItree\fR' specifies the tree object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If a record with the same key exists in the tree, it is overwritten.
+.RE
+.RE
+.PP
+The function `tctreeputkeep' is used in order to store a new record into a tree object.
+.PP
+.RS
+.br
+\fBbool tctreeputkeep(TCTREE *\fItree\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fItree\fR' specifies the tree object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the tree, this function has no effect.
+.RE
+.RE
+.PP
+The function `tctreeputkeep2' is used in order to store a new string record into a tree object.
+.PP
+.RS
+.br
+\fBbool tctreeputkeep2(TCTREE *\fItree\fB, const char *\fIkstr\fB, const char *\fIvstr\fB);\fR
+.RS
+`\fItree\fR' specifies the tree object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the tree, this function has no effect.
+.RE
+.RE
+.PP
+The function `tctreeputcat' is used in order to concatenate a value at the end of the value of the existing record in a tree object.
+.PP
+.RS
+.br
+\fBvoid tctreeputcat(TCTREE *\fItree\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fItree\fR' specifies the tree object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.
+.RE
+.RS
+If there is no corresponding record, a new record is created.
+.RE
+.RE
+.PP
+The function `tctreeputcat2' is used in order to concatenate a string value at the end of the value of the existing record in a tree object.
+.PP
+.RS
+.br
+\fBvoid tctreeputcat2(TCTREE *\fItree\fB, const char *\fIkstr\fB, const char *\fIvstr\fB);\fR
+.RS
+`\fItree\fR' specifies the tree object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If there is no corresponding record, a new record is created.
+.RE
+.RE
+.PP
+The function `tctreeout' is used in order to remove a record of a tree object.
+.PP
+.RS
+.br
+\fBbool tctreeout(TCTREE *\fItree\fB, const void *\fIkbuf\fB, int \fIksiz\fB);\fR
+.RS
+`\fItree\fR' specifies the tree object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+If successful, the return value is true.  False is returned when no record corresponds to the specified key.
+.RE
+.RE
+.PP
+The function `tctreeout2' is used in order to remove a string record of a tree object.
+.PP
+.RS
+.br
+\fBbool tctreeout2(TCTREE *\fItree\fB, const char *\fIkstr\fB);\fR
+.RS
+`\fItree\fR' specifies the tree object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+If successful, the return value is true.  False is returned when no record corresponds to the specified key.
+.RE
+.RE
+.PP
+The function `tctreeget' is used in order to retrieve a record in a tree object.
+.PP
+.RS
+.br
+\fBconst void *tctreeget(TCTREE *\fItree\fB, const void *\fIkbuf\fB, int \fIksiz\fB, int *\fIsp\fB);\fR
+.RS
+`\fItree\fR' specifies the tree object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned when no record corresponds.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.
+.RE
+.RE
+.PP
+The function `tctreeget2' is used in order to retrieve a string record in a tree object.
+.PP
+.RS
+.br
+\fBconst char *tctreeget2(TCTREE *\fItree\fB, const char *\fIkstr\fB);\fR
+.RS
+`\fItree\fR' specifies the tree object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+If successful, the return value is the string of the value of the corresponding record.  `NULL' is returned when no record corresponds.
+.RE
+.RE
+.PP
+The function `tctreeiterinit' is used in order to initialize the iterator of a tree object.
+.PP
+.RS
+.br
+\fBvoid tctreeiterinit(TCTREE *\fItree\fB);\fR
+.RS
+`\fItree\fR' specifies the tree object.
+.RE
+.RS
+The iterator is used in order to access the key of every record stored in the tree object.
+.RE
+.RE
+.PP
+The function `tctreeiternext' is used in order to get the next key of the iterator of a tree object.
+.PP
+.RS
+.br
+\fBconst void *tctreeiternext(TCTREE *\fItree\fB, int *\fIsp\fB);\fR
+.RS
+`\fItree\fR' specifies the tree object.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record can be fetched from the iterator.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  The order of iteration is assured to be ascending of the keys.
+.RE
+.RE
+.PP
+The function `tctreeiternext2' is used in order to get the next key string of the iterator of a tree object.
+.PP
+.RS
+.br
+\fBconst char *tctreeiternext2(TCTREE *\fItree\fB);\fR
+.RS
+`\fItree\fR' specifies the tree object.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record can be fetched from the iterator.
+.RE
+.RS
+The order of iteration is assured to be ascending of the keys.
+.RE
+.RE
+.PP
+The function `tctreernum' is used in order to get the number of records stored in a tree object.
+.PP
+.RS
+.br
+\fBuint64_t tctreernum(const TCTREE *\fItree\fB);\fR
+.RS
+`\fItree\fR' specifies the tree object.
+.RE
+.RS
+The return value is the number of the records stored in the tree object.
+.RE
+.RE
+.PP
+The function `tctreemsiz' is used in order to get the total size of memory used in a tree object.
+.PP
+.RS
+.br
+\fBuint64_t tctreemsiz(const TCTREE *\fItree\fB);\fR
+.RS
+`\fItree\fR' specifies the tree object.
+.RE
+.RS
+The return value is the total size of memory used in a tree object.
+.RE
+.RE
+.PP
+The function `tctreekeys' is used in order to create a list object containing all keys in a tree object.
+.PP
+.RS
+.br
+\fBTCLIST *tctreekeys(const TCTREE *\fItree\fB);\fR
+.RS
+`\fItree\fR' specifies the tree object.
+.RE
+.RS
+The return value is the new list object containing all keys in the tree object.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tctreevals' is used in order to create a list object containing all values in a tree object.
+.PP
+.RS
+.br
+\fBTCLIST *tctreevals(const TCTREE *\fItree\fB);\fR
+.RS
+`\fItree\fR' specifies the tree object.
+.RE
+.RS
+The return value is the new list object containing all values in the tree object.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tctreeaddint' is used in order to add an integer to a record in a tree object.
+.PP
+.RS
+.br
+\fBint tctreeaddint(TCTREE *\fItree\fB, const void *\fIkbuf\fB, int \fIksiz\fB, int \fInum\fB);\fR
+.RS
+`\fItree\fR' specifies the tree object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fInum\fR' specifies the additional value.
+.RE
+.RS
+The return value is the summation value.
+.RE
+.RS
+If the corresponding record exists, the value is treated as an integer and is added to.  If no record corresponds, a new record of the additional value is stored.
+.RE
+.RE
+.PP
+The function `tctreeadddouble' is used in order to add a real number to a record in a tree object.
+.PP
+.RS
+.br
+\fBdouble tctreeadddouble(TCTREE *\fItree\fB, const void *\fIkbuf\fB, int \fIksiz\fB, double \fInum\fB);\fR
+.RS
+`\fItree\fR' specifies the tree object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fInum\fR' specifies the additional value.
+.RE
+.RS
+The return value is the summation value.
+.RE
+.RS
+If the corresponding record exists, the value is treated as a real number and is added to.  If no record corresponds, a new record of the additional value is stored.
+.RE
+.RE
+.PP
+The function `tctreeclear' is used in order to clear a tree object.
+.PP
+.RS
+.br
+\fBvoid tctreeclear(TCTREE *\fItree\fB);\fR
+.RS
+`\fItree\fR' specifies the tree object.
+.RE
+.RS
+All records are removed.
+.RE
+.RE
+.PP
+The function `tctreecutfringe' is used in order to remove fringe records of a tree object.
+.PP
+.RS
+.br
+\fBvoid tctreecutfringe(TCTREE *\fItree\fB, int \fInum\fB);\fR
+.RS
+`\fItree\fR' specifies the tree object.
+.RE
+.RS
+`\fInum\fR' specifies the number of records to be removed.
+.RE
+.RE
+.PP
+The function `tctreedump' is used in order to serialize a tree object into a byte array.
+.PP
+.RS
+.br
+\fBvoid *tctreedump(const TCTREE *\fItree\fB, int *\fIsp\fB);\fR
+.RS
+`\fItree\fR' specifies the tree object.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+The return value is the pointer to the region of the result serial region.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tctreeload' is used in order to create a tree object from a serialized byte array.
+.PP
+.RS
+.br
+\fBTCTREE *tctreeload(const void *\fIptr\fB, int \fIsize\fB, TCCMP \fIcmp\fB, void *\fIcmpop\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region of serialized byte array.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+`\fIcmp\fR' specifies the pointer to the custom comparison function.
+.RE
+.RS
+`\fIcmpop\fR' specifies an arbitrary pointer to be given as a parameter of the comparison function.
+.RE
+.RS
+If it is not needed, `NULL' can be specified.
+.RE
+.RS
+The return value is a new tree object.
+.RE
+.RS
+Because the object of the return value is created with the function `tctreenew', it should be deleted with the function `tctreedel' when it is no longer in use.
+.RE
+.RE
+
+.SH API OF ON\-MEMORY HASH DATABASE
+.PP
+The function `tcmdbnew' is used in order to create an on\-memory hash database object.
+.PP
+.RS
+.br
+\fBTCMDB *tcmdbnew(void);\fR
+.RS
+The return value is the new on\-memory hash database object.
+.RE
+.RS
+The object can be shared by plural threads because of the internal mutex.
+.RE
+.RE
+.PP
+The function `tcmdbnew2' is used in order to create an on\-memory hash database object with specifying the number of the buckets.
+.PP
+.RS
+.br
+\fBTCMDB *tcmdbnew2(uint32_t \fIbnum\fB);\fR
+.RS
+`\fIbnum\fR' specifies the number of the buckets.
+.RE
+.RS
+The return value is the new on\-memory hash database object.
+.RE
+.RS
+The object can be shared by plural threads because of the internal mutex.
+.RE
+.RE
+.PP
+The function `tcmdbdel' is used in order to delete an on\-memory hash database object.
+.PP
+.RS
+.br
+\fBvoid tcmdbdel(TCMDB *\fImdb\fB);\fR
+.RS
+`\fImdb\fR' specifies the on\-memory hash database object.
+.RE
+.RE
+.PP
+The function `tcmdbput' is used in order to store a record into an on\-memory hash database object.
+.PP
+.RS
+.br
+\fBvoid tcmdbput(TCMDB *\fImdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fImdb\fR' specifies the on\-memory hash database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.
+.RE
+.RS
+If a record with the same key exists in the database, it is overwritten.
+.RE
+.RE
+.PP
+The function `tcmdbput2' is used in order to store a string record into an on\-memory hash database object.
+.PP
+.RS
+.br
+\fBvoid tcmdbput2(TCMDB *\fImdb\fB, const char *\fIkstr\fB, const char *\fIvstr\fB);\fR
+.RS
+`\fImdb\fR' specifies the on\-memory hash database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If a record with the same key exists in the database, it is overwritten.
+.RE
+.RE
+.PP
+The function `tcmdbputkeep' is used in order to store a new record into an on\-memory hash database object.
+.PP
+.RS
+.br
+\fBbool tcmdbputkeep(TCMDB *\fImdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fImdb\fR' specifies the on\-memory hash database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, this function has no effect.
+.RE
+.RE
+.PP
+The function `tcmdbputkeep2' is used in order to store a new string record into an on\-memory hash database object.
+.PP
+.RS
+.br
+\fBbool tcmdbputkeep2(TCMDB *\fImdb\fB, const char *\fIkstr\fB, const char *\fIvstr\fB);\fR
+.RS
+`\fImdb\fR' specifies the on\-memory hash database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, this function has no effect.
+.RE
+.RE
+.PP
+The function `tcmdbputcat' is used in order to concatenate a value at the end of the existing record in an on\-memory hash database.
+.PP
+.RS
+.br
+\fBvoid tcmdbputcat(TCMDB *\fImdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fImdb\fR' specifies the on\-memory hash database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.
+.RE
+.RS
+If there is no corresponding record, a new record is created.
+.RE
+.RE
+.PP
+The function `tcmdbputcat2' is used in order to concatenate a string at the end of the existing record in an on\-memory hash database.
+.PP
+.RS
+.br
+\fBvoid tcmdbputcat2(TCMDB *\fImdb\fB, const char *\fIkstr\fB, const char *\fIvstr\fB);\fR
+.RS
+`\fImdb\fR' specifies the on\-memory hash database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If there is no corresponding record, a new record is created.
+.RE
+.RE
+.PP
+The function `tcmdbout' is used in order to remove a record of an on\-memory hash database object.
+.PP
+.RS
+.br
+\fBbool tcmdbout(TCMDB *\fImdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB);\fR
+.RS
+`\fImdb\fR' specifies the on\-memory hash database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+If successful, the return value is true.  False is returned when no record corresponds to the specified key.
+.RE
+.RE
+.PP
+The function `tcmdbout2' is used in order to remove a string record of an on\-memory hash database object.
+.PP
+.RS
+.br
+\fBbool tcmdbout2(TCMDB *\fImdb\fB, const char *\fIkstr\fB);\fR
+.RS
+`\fImdb\fR' specifies the on\-memory hash database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+If successful, the return value is true.  False is returned when no record corresponds to the specified key.
+.RE
+.RE
+.PP
+The function `tcmdbget' is used in order to retrieve a record in an on\-memory hash database object.
+.PP
+.RS
+.br
+\fBvoid *tcmdbget(TCMDB *\fImdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, int *\fIsp\fB);\fR
+.RS
+`\fImdb\fR' specifies the on\-memory hash database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned when no record corresponds.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcmdbget2' is used in order to retrieve a string record in an on\-memory hash database object.
+.PP
+.RS
+.br
+\fBchar *tcmdbget2(TCMDB *\fImdb\fB, const char *\fIkstr\fB);\fR
+.RS
+`\fImdb\fR' specifies the on\-memory hash database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+If successful, the return value is the string of the value of the corresponding record.  `NULL' is returned when no record corresponds.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcmdbvsiz' is used in order to get the size of the value of a record in an on\-memory hash database object.
+.PP
+.RS
+.br
+\fBint tcmdbvsiz(TCMDB *\fImdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB);\fR
+.RS
+`\fImdb\fR' specifies the on\-memory hash database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+If successful, the return value is the size of the value of the corresponding record, else, it is \-1.
+.RE
+.RE
+.PP
+The function `tcmdbvsiz2' is used in order to get the size of the value of a string record in an on\-memory hash database object.
+.PP
+.RS
+.br
+\fBint tcmdbvsiz2(TCMDB *\fImdb\fB, const char *\fIkstr\fB);\fR
+.RS
+`\fImdb\fR' specifies the on\-memory hash database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+If successful, the return value is the size of the value of the corresponding record, else, it is \-1.
+.RE
+.RE
+.PP
+The function `tcmdbiterinit' is used in order to initialize the iterator of an on\-memory hash database object.
+.PP
+.RS
+.br
+\fBvoid tcmdbiterinit(TCMDB *\fImdb\fB);\fR
+.RS
+`\fImdb\fR' specifies the on\-memory hash database object.
+.RE
+.RS
+The iterator is used in order to access the key of every record stored in the on\-memory hash database.
+.RE
+.RE
+.PP
+The function `tcmdbiternext' is used in order to get the next key of the iterator of an on\-memory hash database object.
+.PP
+.RS
+.br
+\fBvoid *tcmdbiternext(TCMDB *\fImdb\fB, int *\fIsp\fB);\fR
+.RS
+`\fImdb\fR' specifies the on\-memory hash database object.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return
+.RE
+.RS
+value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record can be fetched from the iterator.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  The order of iteration is assured to be the same as the stored order.
+.RE
+.RE
+.PP
+The function `tcmdbiternext2' is used in order to get the next key string of the iterator of an on\-memory hash database object.
+.PP
+.RS
+.br
+\fBchar *tcmdbiternext2(TCMDB *\fImdb\fB);\fR
+.RS
+`\fImdb\fR' specifies the on\-memory hash database object.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record can be fetched from the iterator.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  The order of iteration is assured to be the same as the stored order.
+.RE
+.RE
+.PP
+The function `tcmdbfwmkeys' is used in order to get forward matching keys in an on\-memory hash database object.
+.PP
+.RS
+.br
+\fBTCLIST *tcmdbfwmkeys(TCMDB *\fImdb\fB, const void *\fIpbuf\fB, int \fIpsiz\fB, int \fImax\fB);\fR
+.RS
+`\fImdb\fR' specifies the on\-memory hash database object.
+.RE
+.RS
+`\fIpbuf\fR' specifies the pointer to the region of the prefix.
+.RE
+.RS
+`\fIpsiz\fR' specifies the size of the region of the prefix.
+.RE
+.RS
+`\fImax\fR' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.
+.RE
+.RS
+The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.
+.RE
+.RE
+.PP
+The function `tcmdbfwmkeys2' is used in order to get forward matching string keys in an on\-memory hash database object.
+.PP
+.RS
+.br
+\fBTCLIST *tcmdbfwmkeys2(TCMDB *\fImdb\fB, const char *\fIpstr\fB, int \fImax\fB);\fR
+.RS
+`\fImdb\fR' specifies the on\-memory hash database object.
+.RE
+.RS
+`\fIpstr\fR' specifies the string of the prefix.
+.RE
+.RS
+`\fImax\fR' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.
+.RE
+.RS
+The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.  Note that this function may be very slow because every key in the database is scanned.
+.RE
+.RE
+.PP
+The function `tcmdbrnum' is used in order to get the number of records stored in an on\-memory hash database object.
+.PP
+.RS
+.br
+\fBuint64_t tcmdbrnum(TCMDB *\fImdb\fB);\fR
+.RS
+`\fImdb\fR' specifies the on\-memory hash database object.
+.RE
+.RS
+The return value is the number of the records stored in the database.
+.RE
+.RE
+.PP
+The function `tcmdbmsiz' is used in order to get the total size of memory used in an on\-memory hash database object.
+.PP
+.RS
+.br
+\fBuint64_t tcmdbmsiz(TCMDB *\fImdb\fB);\fR
+.RS
+`\fImdb\fR' specifies the on\-memory hash database object.
+.RE
+.RS
+The return value is the total size of memory used in the database.
+.RE
+.RE
+.PP
+The function `tcmdbaddint' is used in order to add an integer to a record in an on\-memory hash database object.
+.PP
+.RS
+.br
+\fBint tcmdbaddint(TCMDB *\fImdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, int \fInum\fB);\fR
+.RS
+`\fImdb\fR' specifies the on\-memory hash database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fInum\fR' specifies the additional value.
+.RE
+.RS
+The return value is the summation value.
+.RE
+.RS
+If the corresponding record exists, the value is treated as an integer and is added to.  If no record corresponds, a new record of the additional value is stored.
+.RE
+.RE
+.PP
+The function `tcmdbadddouble' is used in order to add a real number to a record in an on\-memory hash database object.
+.PP
+.RS
+.br
+\fBdouble tcmdbadddouble(TCMDB *\fImdb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, double \fInum\fB);\fR
+.RS
+`\fImdb\fR' specifies the on\-memory hash database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fInum\fR' specifies the additional value.
+.RE
+.RS
+The return value is the summation value.
+.RE
+.RS
+If the corresponding record exists, the value is treated as a real number and is added to.  If no record corresponds, a new record of the additional value is stored.
+.RE
+.RE
+.PP
+The function `tcmdbvanish' is used in order to clear an on\-memory hash database object.
+.PP
+.RS
+.br
+\fBvoid tcmdbvanish(TCMDB *\fImdb\fB);\fR
+.RS
+`\fImdb\fR' specifies the on\-memory hash database object.
+.RE
+.RS
+All records are removed.
+.RE
+.RE
+.PP
+The function `tcmdbcutfront' is used in order to remove front records of an on\-memory hash database object.
+.PP
+.RS
+.br
+\fBvoid tcmdbcutfront(TCMDB *\fImdb\fB, int \fInum\fB);\fR
+.RS
+`\fImdb\fR' specifies the on\-memory hash database object.
+.RE
+.RS
+`\fInum\fR' specifies the number of records to be removed.
+.RE
+.RE
+
+.SH API OF ON\-MEMORY TREE DATABASE
+.PP
+The function `tcndbnew' is used in order to create an on\-memory tree database object.
+.PP
+.RS
+.br
+\fBTCNDB *tcndbnew(void);\fR
+.RS
+The return value is the new on\-memory tree database object.
+.RE
+.RS
+The object can be shared by plural threads because of the internal mutex.
+.RE
+.RE
+.PP
+The function `tcndbnew2' is used in order to create an on\-memory tree database object with specifying the custom comparison function.
+.PP
+.RS
+.br
+\fBTCNDB *tcndbnew2(TCCMP \fIcmp\fB, void *\fIcmpop\fB);\fR
+.RS
+`\fIcmp\fR' specifies the pointer to the custom comparison function.
+.RE
+.RS
+`\fIcmpop\fR' specifies an arbitrary pointer to be given as a parameter of the comparison function.  If it is not needed, `NULL' can be specified.
+.RE
+.RS
+The return value is the new on\-memory tree database object.
+.RE
+.RS
+The default comparison function compares keys of two records by lexical order.  The functions `tccmplexical' (dafault), `tccmpdecimal', `tccmpint32', and `tccmpint64' are built\-in.  The object can be shared by plural threads because of the internal mutex.
+.RE
+.RE
+.PP
+The function `tcndbdel' is used in order to delete an on\-memory tree database object.
+.PP
+.RS
+.br
+\fBvoid tcndbdel(TCNDB *\fIndb\fB);\fR
+.RS
+`\fIndb\fR' specifies the on\-memory tree database object.
+.RE
+.RE
+.PP
+The function `tcndbput' is used in order to store a record into an on\-memory tree database object.
+.PP
+.RS
+.br
+\fBvoid tcndbput(TCNDB *\fIndb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fIndb\fR' specifies the on\-memory tree database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.
+.RE
+.RS
+If a record with the same key exists in the database, it is overwritten.
+.RE
+.RE
+.PP
+The function `tcndbput2' is used in order to store a string record into an on\-memory tree database object.
+.PP
+.RS
+.br
+\fBvoid tcndbput2(TCNDB *\fIndb\fB, const char *\fIkstr\fB, const char *\fIvstr\fB);\fR
+.RS
+`\fIndb\fR' specifies the on\-memory tree database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If a record with the same key exists in the database, it is overwritten.
+.RE
+.RE
+.PP
+The function `tcndbputkeep' is used in order to store a new record into an on\-memory tree database object.
+.PP
+.RS
+.br
+\fBbool tcndbputkeep(TCNDB *\fIndb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fIndb\fR' specifies the on\-memory tree database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, this function has no effect.
+.RE
+.RE
+.PP
+The function `tcndbputkeep2' is used in order to store a new string record into an on\-memory tree database object.
+.PP
+.RS
+.br
+\fBbool tcndbputkeep2(TCNDB *\fIndb\fB, const char *\fIkstr\fB, const char *\fIvstr\fB);\fR
+.RS
+`\fIndb\fR' specifies the on\-memory tree database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RS
+If a record with the same key exists in the database, this function has no effect.
+.RE
+.RE
+.PP
+The function `tcndbputcat' is used in order to concatenate a value at the end of the existing record in an on\-memory tree database.
+.PP
+.RS
+.br
+\fBvoid tcndbputcat(TCNDB *\fIndb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, const void *\fIvbuf\fB, int \fIvsiz\fB);\fR
+.RS
+`\fIndb\fR' specifies the on\-memory tree database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIvbuf\fR' specifies the pointer to the region of the value.
+.RE
+.RS
+`\fIvsiz\fR' specifies the size of the region of the value.
+.RE
+.RS
+If there is no corresponding record, a new record is created.
+.RE
+.RE
+.PP
+The function `tcndbputcat2' is used in order to concatenate a string at the end of the existing record in an on\-memory tree database.
+.PP
+.RS
+.br
+\fBvoid tcndbputcat2(TCNDB *\fIndb\fB, const char *\fIkstr\fB, const char *\fIvstr\fB);\fR
+.RS
+`\fIndb\fR' specifies the on\-memory tree database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+`\fIvstr\fR' specifies the string of the value.
+.RE
+.RS
+If there is no corresponding record, a new record is created.
+.RE
+.RE
+.PP
+The function `tcndbout' is used in order to remove a record of an on\-memory tree database object.
+.PP
+.RS
+.br
+\fBbool tcndbout(TCNDB *\fIndb\fB, const void *\fIkbuf\fB, int \fIksiz\fB);\fR
+.RS
+`\fIndb\fR' specifies the on\-memory tree database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+If successful, the return value is true.  False is returned when no record corresponds to the specified key.
+.RE
+.RE
+.PP
+The function `tcndbout2' is used in order to remove a string record of an on\-memory tree database object.
+.PP
+.RS
+.br
+\fBbool tcndbout2(TCNDB *\fIndb\fB, const char *\fIkstr\fB);\fR
+.RS
+`\fIndb\fR' specifies the on\-memory tree database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+If successful, the return value is true.  False is returned when no record corresponds to the specified key.
+.RE
+.RE
+.PP
+The function `tcndbget' is used in order to retrieve a record in an on\-memory tree database object.
+.PP
+.RS
+.br
+\fBvoid *tcndbget(TCNDB *\fIndb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, int *\fIsp\fB);\fR
+.RS
+`\fIndb\fR' specifies the on\-memory tree database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the value of the corresponding record.  `NULL' is returned when no record corresponds.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcndbget2' is used in order to retrieve a string record in an on\-memory tree database object.
+.PP
+.RS
+.br
+\fBchar *tcndbget2(TCNDB *\fIndb\fB, const char *\fIkstr\fB);\fR
+.RS
+`\fIndb\fR' specifies the on\-memory tree database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+If successful, the return value is the string of the value of the corresponding record.  `NULL' is returned when no record corresponds.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcndbvsiz' is used in order to get the size of the value of a record in an on\-memory tree database object.
+.PP
+.RS
+.br
+\fBint tcndbvsiz(TCNDB *\fIndb\fB, const void *\fIkbuf\fB, int \fIksiz\fB);\fR
+.RS
+`\fIndb\fR' specifies the on\-memory tree database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+If successful, the return value is the size of the value of the corresponding record, else, it is \-1.
+.RE
+.RE
+.PP
+The function `tcndbvsiz2' is used in order to get the size of the value of a string record in an on\-memory tree database object.
+.PP
+.RS
+.br
+\fBint tcndbvsiz2(TCNDB *\fIndb\fB, const char *\fIkstr\fB);\fR
+.RS
+`\fIndb\fR' specifies the on\-memory tree database object.
+.RE
+.RS
+`\fIkstr\fR' specifies the string of the key.
+.RE
+.RS
+If successful, the return value is the size of the value of the corresponding record, else, it is \-1.
+.RE
+.RE
+.PP
+The function `tcndbiterinit' is used in order to initialize the iterator of an on\-memory tree database object.
+.PP
+.RS
+.br
+\fBvoid tcndbiterinit(TCNDB *\fIndb\fB);\fR
+.RS
+`\fIndb\fR' specifies the on\-memory tree database object.
+.RE
+.RS
+The iterator is used in order to access the key of every record stored in the on\-memory database.
+.RE
+.RE
+.PP
+The function `tcndbiternext' is used in order to get the next key of the iterator of an on\-memory tree database object.
+.PP
+.RS
+.br
+\fBvoid *tcndbiternext(TCNDB *\fIndb\fB, int *\fIsp\fB);\fR
+.RS
+`\fIndb\fR' specifies the on\-memory tree database object.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record can be fetched from the iterator.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  The order of iteration is assured to be the same as the stored order.
+.RE
+.RE
+.PP
+The function `tcndbiternext2' is used in order to get the next key string of the iterator of an on\-memory tree database object.
+.PP
+.RS
+.br
+\fBchar *tcndbiternext2(TCNDB *\fIndb\fB);\fR
+.RS
+`\fIndb\fR' specifies the on\-memory tree database object.
+.RE
+.RS
+If successful, the return value is the pointer to the region of the next key, else, it is `NULL'.  `NULL' is returned when no record can be fetched from the iterator.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.  The order of iteration is assured to be the same as the stored order.
+.RE
+.RE
+.PP
+The function `tcndbfwmkeys' is used in order to get forward matching keys in an on\-memory tree database object.
+.PP
+.RS
+.br
+\fBTCLIST *tcndbfwmkeys(TCNDB *\fIndb\fB, const void *\fIpbuf\fB, int \fIpsiz\fB, int \fImax\fB);\fR
+.RS
+`\fIndb\fR' specifies the on\-memory tree database object.
+.RE
+.RS
+`\fIpbuf\fR' specifies the pointer to the region of the prefix.
+.RE
+.RS
+`\fIpsiz\fR' specifies the size of the region of the prefix.
+.RE
+.RS
+`\fImax\fR' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.
+.RE
+.RS
+The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcndbfwmkeys2' is used in order to get forward matching string keys in an on\-memory tree database object.
+.PP
+.RS
+.br
+\fBTCLIST *tcndbfwmkeys2(TCNDB *\fIndb\fB, const char *\fIpstr\fB, int \fImax\fB);\fR
+.RS
+`\fIndb\fR' specifies the on\-memory tree database object.
+.RE
+.RS
+`\fIpstr\fR' specifies the string of the prefix.
+.RE
+.RS
+`\fImax\fR' specifies the maximum number of keys to be fetched.  If it is negative, no limit is specified.
+.RE
+.RS
+The return value is a list object of the corresponding keys.  This function does never fail.  It returns an empty list even if no key corresponds.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcndbrnum' is used in order to get the number of records stored in an on\-memory tree database object.
+.PP
+.RS
+.br
+\fBuint64_t tcndbrnum(TCNDB *\fIndb\fB);\fR
+.RS
+`\fIndb\fR' specifies the on\-memory tree database object.
+.RE
+.RS
+The return value is the number of the records stored in the database.
+.RE
+.RE
+.PP
+The function `tcndbmsiz' is used in order to get the total size of memory used in an on\-memory tree database object.
+.PP
+.RS
+.br
+\fBuint64_t tcndbmsiz(TCNDB *\fIndb\fB);\fR
+.RS
+`\fIndb\fR' specifies the on\-memory tree database object.
+.RE
+.RS
+The return value is the total size of memory used in the database.
+.RE
+.RE
+.PP
+The function `tcndbaddint' is used in order to add an integer to a record in an on\-memory tree database object.
+.PP
+.RS
+.br
+\fBint tcndbaddint(TCNDB *\fIndb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, int \fInum\fB);\fR
+.RS
+`\fIndb\fR' specifies the on\-memory tree database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fInum\fR' specifies the additional value.
+.RE
+.RS
+The return value is the summation value.
+.RE
+.RS
+If the corresponding record exists, the value is treated as an integer and is added to.  If no record corresponds, a new record of the additional value is stored.
+.RE
+.RE
+.PP
+The function `tcndbadddouble' is used in order to add a real number to a record in an on\-memory tree database object.
+.PP
+.RS
+.br
+\fBdouble tcndbadddouble(TCNDB *\fIndb\fB, const void *\fIkbuf\fB, int \fIksiz\fB, double \fInum\fB);\fR
+.RS
+`\fIndb\fR' specifies the on\-memory tree database object.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the key.
+.RE
+.RS
+`\fInum\fR' specifies the additional value.
+.RE
+.RS
+The return value is the summation value.
+.RE
+.RS
+If the corresponding record exists, the value is treated as a real number and is added to.  If no record corresponds, a new record of the additional value is stored.
+.RE
+.RE
+.PP
+The function `tcndbvanish' is used in order to clear an on\-memory tree database object.
+.PP
+.RS
+.br
+\fBvoid tcndbvanish(TCNDB *\fIndb\fB);\fR
+.RS
+`\fIndb\fR' specifies the on\-memory tree database object.
+.RE
+.RS
+All records are removed.
+.RE
+.RE
+.PP
+The function `tcndbcutfringe' is used in order to remove fringe records of an on\-memory tree database object.
+.PP
+.RS
+.br
+\fBvoid tcndbcutfringe(TCNDB *\fIndb\fB, int \fInum\fB);\fR
+.RS
+`\fIndb\fR' specifies the on\-memory tree database object.
+.RE
+.RS
+`\fInum\fR' specifies the number of records to be removed.
+.RE
+.RE
+
+.SH API OF MEMORY POOL
+.PP
+The function `tcmpoolnew' is used in order to create a memory pool object.
+.PP
+.RS
+.br
+\fBTCMPOOL *tcmpoolnew(void);\fR
+.RS
+The return value is the new memory pool object.
+.RE
+.RE
+.PP
+The function `tcmpooldel' is used in order to delete a memory pool object.
+.PP
+.RS
+.br
+\fBvoid tcmpooldel(TCMPOOL *\fImpool\fB);\fR
+.RS
+`\fImpool\fR' specifies the memory pool object.
+.RE
+.RS
+Note that the deleted object and its derivatives can not be used anymore.
+.RE
+.RE
+.PP
+The function `tcmpoolpush' is used in order to relegate an arbitrary object to a memory pool object.
+.PP
+.RS
+.br
+\fBvoid *tcmpoolpush(TCMPOOL *\fImpool\fB, void *\fIptr\fB, void (*\fIdel\fB)(void *));\fR
+.RS
+`\fImpool\fR' specifies the memory pool object.
+.RE
+.RS
+`\fIptr\fR' specifies the pointer to the object to be relegated.  If it is `NULL', this function has no effect.
+.RE
+.RS
+`\fIdel\fR' specifies the pointer to the function to delete the object.
+.RE
+.RS
+The return value is the pointer to the given object.
+.RE
+.RS
+This function assures that the specified object is deleted when the memory pool object is deleted.
+.RE
+.RE
+.PP
+The function `tcmpoolpushptr' is used in order to relegate an allocated region to a memory pool object.
+.PP
+.RS
+.br
+\fBvoid *tcmpoolpushptr(TCMPOOL *\fImpool\fB, void *\fIptr\fB);\fR
+.RS
+`\fImpool\fR' specifies the memory pool object.
+.RE
+.RS
+`\fIptr\fR' specifies the pointer to the region to be relegated.  If it is `NULL', this function has no effect.
+.RE
+.RS
+The return value is the pointer to the given object.
+.RE
+.RS
+This function assures that the specified region is released when the memory pool object is deleted.
+.RE
+.RE
+.PP
+The function `tcmpoolpushxstr' is used in order to relegate an extensible string object to a memory pool object.
+.PP
+.RS
+.br
+\fBTCXSTR *tcmpoolpushxstr(TCMPOOL *\fImpool\fB, TCXSTR *\fIxstr\fB);\fR
+.RS
+`\fImpool\fR' specifies the memory pool object.
+.RE
+.RS
+`\fIxstr\fR' specifies the extensible string object.  If it is `NULL', this function has no effect.
+.RE
+.RS
+The return value is the pointer to the given object.
+.RE
+.RS
+This function assures that the specified object is deleted when the memory pool object is deleted.
+.RE
+.RE
+.PP
+The function `tcmpoolpushlist' is used in order to relegate a list object to a memory pool object.
+.PP
+.RS
+.br
+\fBTCLIST *tcmpoolpushlist(TCMPOOL *\fImpool\fB, TCLIST *\fIlist\fB);\fR
+.RS
+`\fImpool\fR' specifies the memory pool object.
+.RE
+.RS
+`\fIlist\fR' specifies the list object.  If it is `NULL', this function has no effect.
+.RE
+.RS
+The return value is the pointer to the given object.
+.RE
+.RS
+This function assures that the specified object is deleted when the memory pool object is deleted.
+.RE
+.RE
+.PP
+The function `tcmpoolpushmap' is used in order to relegate a map object to a memory pool object.
+.PP
+.RS
+.br
+\fBTCMAP *tcmpoolpushmap(TCMPOOL *\fImpool\fB, TCMAP *\fImap\fB);\fR
+.RS
+`\fImpool\fR' specifies the memory pool object.
+.RE
+.RS
+`\fImap\fR' specifies the map object.  If it is `NULL', this function has no effect.
+.RE
+.RS
+The return value is the pointer to the given object.
+.RE
+.RS
+This function assures that the specified object is deleted when the memory pool object is deleted.
+.RE
+.RE
+.PP
+The function `tcmpoolpushtree' is used in order to relegate a tree object to a memory pool object.
+.PP
+.RS
+.br
+\fBTCTREE *tcmpoolpushtree(TCMPOOL *\fImpool\fB, TCTREE *\fItree\fB);\fR
+.RS
+`\fImpool\fR' specifies the memory pool object.
+.RE
+.RS
+`\fItree\fR' specifies the tree object.  If it is `NULL', this function has no effect.
+.RE
+.RS
+The return value is the pointer to the given object.
+.RE
+.RS
+This function assures that the specified object is deleted when the memory pool object is deleted.
+.RE
+.RE
+.PP
+The function `tcmpoolmalloc' is used in order to allocate a region relegated to a memory pool object.
+.PP
+.RS
+.br
+\fBvoid *tcmpoolmalloc(TCMPOOL *\fImpool\fB, size_t \fIsize\fB);\fR
+.RS
+`\fImpool\fR' specifies the memory pool object.
+.RE
+.RS
+The return value is the pointer to the allocated region under the memory pool.
+.RE
+.RE
+.PP
+The function `tcmpoolxstrnew' is used in order to create an extensible string object relegated to a memory pool object.
+.PP
+.RS
+.br
+\fBTCXSTR *tcmpoolxstrnew(TCMPOOL *\fImpool\fB);\fR
+.RS
+The return value is the new extensible string object under the memory pool.
+.RE
+.RE
+.PP
+The function `tcmpoollistnew' is used in order to create a list object relegated to a memory pool object.
+.PP
+.RS
+.br
+\fBTCLIST *tcmpoollistnew(TCMPOOL *\fImpool\fB);\fR
+.RS
+The return value is the new list object under the memory pool.
+.RE
+.RE
+.PP
+The function `tcmpoolmapnew' is used in order to create a map object relegated to a memory pool object.
+.PP
+.RS
+.br
+\fBTCMAP *tcmpoolmapnew(TCMPOOL *\fImpool\fB);\fR
+.RS
+The return value is the new map object under the memory pool.
+.RE
+.RE
+.PP
+The function `tcmpooltreenew' is used in order to create a tree object relegated to a memory pool object.
+.PP
+.RS
+.br
+\fBTCTREE *tcmpooltreenew(TCMPOOL *\fImpool\fB);\fR
+.RS
+The return value is the new tree object under the memory pool.
+.RE
+.RE
+.PP
+The function `tcmpoolpop' is used in order to remove the most recently installed cleanup handler of a memory pool object.
+.PP
+.RS
+.br
+\fBvoid tcmpoolpop(TCMPOOL *\fImpool\fB, bool \fIexe\fB);\fR
+.RS
+`\fImpool\fR' specifies the memory pool object.
+.RE
+.RS
+`\fIexe\fR' specifies whether to execute the destructor of the removed handler.
+.RE
+.RE
+.PP
+The function `tcmpoolclear' is used in order to remove all cleanup handler of a memory pool object.
+.PP
+.RS
+.br
+\fBvoid tcmpoolclear(TCMPOOL *\fImpool\fB, bool \fIexe\fB);\fR
+.RS
+`\fImpool\fR' specifies the memory pool object.
+.RE
+.RS
+`\fIexe\fR' specifies whether to execute the destructors of the removed handlers.
+.RE
+.RE
+.PP
+The function `tcmpoolglobal' is used in order to get the global memory pool object.
+.PP
+.RS
+.br
+\fBTCMPOOL *tcmpoolglobal(void);\fR
+.RS
+The return value is the global memory pool object.
+.RE
+.RS
+The global memory pool object is a singleton and assured to be deleted when the process is terminating normally.
+.RE
+.RE
+
+.SH API OF MISCELLANEOUS UTILITIES
+.PP
+The function `tclmax' is used in order to get the larger value of two integers.
+.PP
+.RS
+.br
+\fBlong tclmax(long \fIa\fB, long \fIb\fB);\fR
+.RS
+`\fIa\fR' specifies an integer.
+.RE
+.RS
+`\fIb\fR' specifies the other integer.
+.RE
+.RS
+The return value is the larger value of the two.
+.RE
+.RE
+.PP
+The function `tclmin' is used in order to get the lesser value of two integers.
+.PP
+.RS
+.br
+\fBlong tclmin(long \fIa\fB, long \fIb\fB);\fR
+.RS
+`\fIa\fR' specifies an integer.
+.RE
+.RS
+`\fIb\fR' specifies the other integer.
+.RE
+.RS
+The return value is the lesser value of the two.
+.RE
+.RE
+.PP
+The function `tclrand' is used in order to get a random number as long integer based on uniform distribution.
+.PP
+.RS
+.br
+\fBunsigned long tclrand(void);\fR
+.RS
+The return value is the random number between 0 and `ULONG_MAX'.
+.RE
+.RS
+This function uses the random number source device and generates a real random number if possible.
+.RE
+.RE
+.PP
+The function `tcdrand' is used in order to get a random number as double decimal based on uniform distribution.
+.PP
+.RS
+.br
+\fBdouble tcdrand(void);\fR
+.RS
+The return value is the random number equal to or greater than 0, and less than 1.0.
+.RE
+.RS
+This function uses the random number source device and generates a real random number if possible.
+.RE
+.RE
+.PP
+The function `tcdrandnd' is used in order to get a random number as double decimal based on normal distribution.
+.PP
+.RS
+.br
+\fBdouble tcdrandnd(double \fIavg\fB, double \fIsd\fB);\fR
+.RS
+`\fIavg\fR' specifies the average.
+.RE
+.RS
+`\fIsd\fR' specifies the standard deviation.
+.RE
+.RS
+The return value is the random number.
+.RE
+.RS
+This function uses the random number source device and generates a real random number if possible.
+.RE
+.RE
+.PP
+The function `tcstricmp' is used in order to compare two strings with case insensitive evaluation.
+.PP
+.RS
+.br
+\fBint tcstricmp(const char *\fIastr\fB, const char *\fIbstr\fB);\fR
+.RS
+`\fIastr\fR' specifies a string.
+.RE
+.RS
+`\fIbstr\fR' specifies of the other string.
+.RE
+.RS
+The return value is positive if the former is big, negative if the latter is big, 0 if both are equivalent.
+.RE
+.RE
+.PP
+The function `tcstrfwm' is used in order to check whether a string begins with a key.
+.PP
+.RS
+.br
+\fBbool tcstrfwm(const char *\fIstr\fB, const char *\fIkey\fB);\fR
+.RS
+`\fIstr\fR' specifies the target string.
+.RE
+.RS
+`\fIkey\fR' specifies the forward matching key string.
+.RE
+.RS
+The return value is true if the target string begins with the key, else, it is false.
+.RE
+.RE
+.PP
+The function `tcstrifwm' is used in order to check whether a string begins with a key with case insensitive evaluation.
+.PP
+.RS
+.br
+\fBbool tcstrifwm(const char *\fIstr\fB, const char *\fIkey\fB);\fR
+.RS
+`\fIstr\fR' specifies the target string.
+.RE
+.RS
+`\fIkey\fR' specifies the forward matching key string.
+.RE
+.RS
+The return value is true if the target string begins with the key, else, it is false.
+.RE
+.RE
+.PP
+The function `tcstrbwm' is used in order to check whether a string ends with a key.
+.PP
+.RS
+.br
+\fBbool tcstrbwm(const char *\fIstr\fB, const char *\fIkey\fB);\fR
+.RS
+`\fIstr\fR' specifies the target string.
+.RE
+.RS
+`\fIkey\fR' specifies the backward matching key string.
+.RE
+.RS
+The return value is true if the target string ends with the key, else, it is false.
+.RE
+.RE
+.PP
+The function `tcstribwm' is used in order to check whether a string ends with a key with case insensitive evaluation.
+.PP
+.RS
+.br
+\fBbool tcstribwm(const char *\fIstr\fB, const char *\fIkey\fB);\fR
+.RS
+`\fIstr\fR' specifies the target string.
+.RE
+.RS
+`\fIkey\fR' specifies the backward matching key string.
+.RE
+.RS
+The return value is true if the target string ends with the key, else, it is false.
+.RE
+.RE
+.PP
+The function `tcstrdist' is used in order to calculate the edit distance of two strings.
+.PP
+.RS
+.br
+\fBint tcstrdist(const char *\fIastr\fB, const char *\fIbstr\fB);\fR
+.RS
+`\fIastr\fR' specifies a string.
+.RE
+.RS
+`\fIbstr\fR' specifies of the other string.
+.RE
+.RS
+The return value is the edit distance which is known as the Levenshtein distance.  The cost is calculated by byte.
+.RE
+.RE
+.PP
+The function `tcstrdistutf' is used in order to calculate the edit distance of two UTF\-8 strings.
+.PP
+.RS
+.br
+\fBint tcstrdistutf(const char *\fIastr\fB, const char *\fIbstr\fB);\fR
+.RS
+`\fIastr\fR' specifies a string.
+.RE
+.RS
+`\fIbstr\fR' specifies of the other string.
+.RE
+.RS
+The return value is the edit distance which is known as the Levenshtein distance.  The cost is calculated by Unicode character.
+.RE
+.RE
+.PP
+The function `tcstrtoupper' is used in order to convert the letters of a string into upper case.
+.PP
+.RS
+.br
+\fBchar *tcstrtoupper(char *\fIstr\fB);\fR
+.RS
+`\fIstr\fR' specifies the string to be converted.
+.RE
+.RS
+The return value is the string itself.
+.RE
+.RE
+.PP
+The function `tcstrtolower' is used in order to convert the letters of a string into lower case.
+.PP
+.RS
+.br
+\fBchar *tcstrtolower(char *\fIstr\fB);\fR
+.RS
+`\fIstr\fR' specifies the string to be converted.
+.RE
+.RS
+The return value is the string itself.
+.RE
+.RE
+.PP
+The function `tcstrtrim' is used in order to cut space characters at head or tail of a string.
+.PP
+.RS
+.br
+\fBchar *tcstrtrim(char *\fIstr\fB);\fR
+.RS
+`\fIstr\fR' specifies the string to be converted.
+.RE
+.RS
+The return value is the string itself.
+.RE
+.RE
+.PP
+The function `tcstrsqzspc' is used in order to squeeze space characters in a string and trim it.
+.PP
+.RS
+.br
+\fBchar *tcstrsqzspc(char *\fIstr\fB);\fR
+.RS
+`\fIstr\fR' specifies the string to be converted.
+.RE
+.RS
+The return value is the string itself.
+.RE
+.RE
+.PP
+The function `tcstrsubchr' is used in order to substitute characters in a string.
+.PP
+.RS
+.br
+\fBchar *tcstrsubchr(char *\fIstr\fB, const char *\fIrstr\fB, const char *\fIsstr\fB);\fR
+.RS
+`\fIstr\fR' specifies the string to be converted.
+.RE
+.RS
+`\fIrstr\fR' specifies the string containing characters to be replaced.
+.RE
+.RS
+`\fIsstr\fR' specifies the string containing characters to be substituted.
+.RE
+.RS
+If the substitute string is shorter then the replacement string, corresponding characters are removed.
+.RE
+.RE
+.PP
+The function `tcstrcntutf' is used in order to count the number of characters in a string of UTF\-8.
+.PP
+.RS
+.br
+\fBint tcstrcntutf(const char *\fIstr\fB);\fR
+.RS
+`\fIstr\fR' specifies the string of UTF\-8.
+.RE
+.RS
+The return value is the number of characters in the string.
+.RE
+.RE
+.PP
+The function `tcstrcututf' is used in order to cut a string of UTF\-8 at the specified number of characters.
+.PP
+.RS
+.br
+\fBchar *tcstrcututf(char *\fIstr\fB, int \fInum\fB);\fR
+.RS
+`\fIstr\fR' specifies the string of UTF\-8.
+.RE
+.RS
+`\fInum\fR' specifies the number of characters to be kept.
+.RE
+.RS
+The return value is the string itself.
+.RE
+.RE
+.PP
+The function `tcstrutftoucs' is used in order to convert a UTF\-8 string into a UCS\-2 array.
+.PP
+.RS
+.br
+\fBvoid tcstrutftoucs(const char *\fIstr\fB, uint16_t *\fIary\fB, int *\fInp\fB);\fR
+.RS
+`\fIstr\fR' specifies the UTF\-8 string.
+.RE
+.RS
+`\fIary\fR' specifies the pointer to the region into which the result UCS\-2 codes are written.  The size of the buffer should be sufficient.
+.RE
+.RS
+`\fInp\fR' specifies the pointer to a variable into which the number of elements of the result array is assigned.
+.RE
+.RE
+.PP
+The function `tcstrucstoutf' is used in order to convert a UCS\-2 array into a UTF\-8 string.
+.PP
+.RS
+.br
+\fBint tcstrucstoutf(const uint16_t *\fIary\fB, int \fInum\fB, char *\fIstr\fB);\fR
+.RS
+`\fIary\fR' specifies the array of UCS\-2 codes.
+.RE
+.RS
+`\fInum\fR' specifies the number of the array.
+.RE
+.RS
+`\fIstr\fR' specifies the pointer to the region into which the result UTF\-8 string is written.  The size of the buffer should be sufficient.
+.RE
+.RS
+The return value is the length of the result string.
+.RE
+.RE
+.PP
+The function `tcstrsplit' is used in order to create a list object by splitting a string.
+.PP
+.RS
+.br
+\fBTCLIST *tcstrsplit(const char *\fIstr\fB, const char *\fIdelims\fB);\fR
+.RS
+`\fIstr\fR' specifies the source string.
+.RE
+.RS
+`\fIdelims\fR' specifies a string containing delimiting characters.
+.RE
+.RS
+The return value is a list object of the split elements.
+.RE
+.RS
+If two delimiters are successive, it is assumed that an empty element is between the two.  Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcstrjoin' is used in order to create a string by joining all elements of a list object.
+.PP
+.RS
+.br
+\fBchar *tcstrjoin(const TCLIST *\fIlist\fB, char \fIdelim\fB);\fR
+.RS
+`\fIlist\fR' specifies a list object.
+.RE
+.RS
+`\fIdelim\fR' specifies a delimiting character.
+.RE
+.RS
+The return value is the result string.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcatoi' is used in order to convert a string to an integer.
+.PP
+.RS
+.br
+\fBint64_t tcatoi(const char *\fIstr\fB);\fR
+.RS
+`\fIstr\fR' specifies the string.
+.RE
+.RS
+The return value is the integer.  If the string does not contain numeric expression, 0 is returned.
+.RE
+.RS
+This function is equivalent to `atoll' except that it does not depend on the locale.
+.RE
+.RE
+.PP
+The function `tcatoix' is used in order to convert a string with a metric prefix to an integer.
+.PP
+.RS
+.br
+\fBint64_t tcatoix(const char *\fIstr\fB);\fR
+.RS
+`\fIstr\fR' specifies the string, which can be trailed by a binary metric prefix.  "K", "M", "G", "T", "P", and "E" are supported.  They are case\-insensitive.
+.RE
+.RS
+The return value is the integer.  If the string does not contain numeric expression, 0 is returned.  If the integer overflows the domain, `INT64_MAX' or `INT64_MIN' is returned according to the sign.
+.RE
+.RE
+.PP
+The function `tcatof' is used in order to convert a string to a real number.
+.PP
+.RS
+.br
+\fBdouble tcatof(const char *\fIstr\fB);\fR
+.RS
+`\fIstr\fR' specifies the string.
+.RE
+.RS
+The return value is the real number.  If the string does not contain numeric expression, 0.0 is returned.
+.RE
+.RS
+This function is equivalent to `atof' except that it does not depend on the locale.
+.RE
+.RE
+.PP
+The function `tcregexmatch' is used in order to check whether a string matches a regular expression.
+.PP
+.RS
+.br
+\fBbool tcregexmatch(const char *\fIstr\fB, const char *\fIregex\fB);\fR
+.RS
+`\fIstr\fR' specifies the target string.
+.RE
+.RS
+`\fIregex\fR' specifies the regular expression string.  If it begins with `*', the trailing substring is used as a case\-insensitive regular expression.
+.RE
+.RS
+The return value is true if matching is success, else, it is false.
+.RE
+.RE
+.PP
+The function `tcregexreplace' is used in order to replace each substring matching a regular expression string.
+.PP
+.RS
+.br
+\fBchar *tcregexreplace(const char *\fIstr\fB, const char *\fIregex\fB, const char *\fIalt\fB);\fR
+.RS
+`\fIstr\fR' specifies the target string.
+.RE
+.RS
+`\fIregex\fR' specifies the regular expression string for substrings.  If it begins with `*', the trailing substring is used as a case\-insensitive regular expression.
+.RE
+.RS
+`\fIalt\fR' specifies the alternative string with which each substrings is replaced.  Each `&' in the string is replaced with the matched substring.  Each `\' in the string escapes the following character.  Special escapes "\1" through "\9" referring to the corresponding matching sub\-expressions in the regular expression string are supported.
+.RE
+.RS
+The return value is a new converted string.  Even if the regular expression is invalid, a copy of the original string is returned.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcmd5hash' is used in order to get the MD5 hash value of a serial object.
+.PP
+.RS
+.br
+\fBvoid tcmd5hash(const void *\fIptr\fB, int \fIsize\fB, char *\fIbuf\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+`\fIbuf\fR' specifies the pointer to the region into which the result string is written.  The size of the buffer should be equal to or more than 48 bytes.
+.RE
+.RE
+.PP
+The function `tcarccipher' is used in order to cipher or decipher a serial object with the Arcfour stream cipher.
+.PP
+.RS
+.br
+\fBvoid tcarccipher(const void *\fIptr\fB, int \fIsize\fB, const void *\fIkbuf\fB, int \fIksiz\fB, void *\fIobuf\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+`\fIkbuf\fR' specifies the pointer to the region of the cipher key.
+.RE
+.RS
+`\fIksiz\fR' specifies the size of the region of the cipher key.
+.RE
+.RS
+`\fIobuf\fR' specifies the pointer to the region into which the result data is written.  The size of the buffer should be equal to or more than the input region.
+.RE
+.RE
+.PP
+The function `tctime' is used in order to get the time of day in seconds.
+.PP
+.RS
+.br
+\fBdouble tctime(void);\fR
+.RS
+The return value is the time of day in seconds.  The accuracy is in microseconds.
+.RE
+.RE
+.PP
+The function `tccalendar' is used in order to get the Gregorian calendar of a time.
+.PP
+.RS
+.br
+\fBvoid tccalendar(int64_t \fIt\fB, int \fIjl\fB, int *\fIyearp\fB, int *\fImonp\fB, int *\fIdayp\fB, int *\fIhourp\fB, int *\fIminp\fB, int *\fIsecp\fB);\fR
+.RS
+`\fIt\fR' specifies the source time in seconds from the epoch.  If it is `INT64_MAX', the current time is specified.
+.RE
+.RS
+`\fIjl\fR' specifies the jet lag of a location in seconds.  If it is `INT_MAX', the local jet lag is specified.
+.RE
+.RS
+`\fIyearp\fR' specifies the pointer to a variable to which the year is assigned.  If it is `NULL', it is not used.
+.RE
+.RS
+`\fImonp\fR' specifies the pointer to a variable to which the month is assigned.  If it is `NULL', it is not used.  1 means January and 12 means December.
+.RE
+.RS
+`\fIdayp\fR' specifies the pointer to a variable to which the day of the month is assigned.  If it is `NULL', it is not used.
+.RE
+.RS
+`\fIhourp\fR' specifies the pointer to a variable to which the hours is assigned.  If it is `NULL', it is not used.
+.RE
+.RS
+`\fIminp\fR' specifies the pointer to a variable to which the minutes is assigned.  If it is `NULL', it is not used.
+.RE
+.RS
+`\fIsecp\fR' specifies the pointer to a variable to which the seconds is assigned.  If it is `NULL', it is not used.
+.RE
+.RE
+.PP
+The function `tcdatestrwww' is used in order to format a date as a string in W3CDTF.
+.PP
+.RS
+.br
+\fBvoid tcdatestrwww(int64_t \fIt\fB, int \fIjl\fB, char *\fIbuf\fB);\fR
+.RS
+`\fIt\fR' specifies the source time in seconds from the epoch.  If it is `INT64_MAX', the current time is specified.
+.RE
+.RS
+`\fIjl\fR' specifies the jet lag of a location in seconds.  If it is `INT_MAX', the local jet lag is specified.
+.RE
+.RS
+`\fIbuf\fR' specifies the pointer to the region into which the result string is written.  The size of the buffer should be equal to or more than 48 bytes.
+.RE
+.RS
+W3CDTF represents a date as "YYYY\-MM\-DDThh:mm:ddTZD".
+.RE
+.RE
+.PP
+The function `tcdatestrhttp' is used in order to format a date as a string in RFC 1123 format.
+.PP
+.RS
+.br
+\fBvoid tcdatestrhttp(int64_t \fIt\fB, int \fIjl\fB, char *\fIbuf\fB);\fR
+.RS
+`\fIt\fR' specifies the source time in seconds from the epoch.  If it is `INT64_MAX', the current time is specified.
+.RE
+.RS
+`\fIjl\fR' specifies the jet lag of a location in seconds.  If it is `INT_MAX', the local jet lag is specified.
+.RE
+.RS
+`\fIbuf\fR' specifies the pointer to the region into which the result string is written.  The size of the buffer should be equal to or more than 48 bytes.
+.RE
+.RS
+RFC 1123 format represents a date as "Wdy, DD\-Mon\-YYYY hh:mm:dd TZD".
+.RE
+.RE
+.PP
+The function `tcstrmktime' is used in order to get the time value of a date string.
+.PP
+.RS
+.br
+\fBint64_t tcstrmktime(const char *\fIstr\fB);\fR
+.RS
+`\fIstr\fR' specifies the date string in decimal, hexadecimal, W3CDTF, or RFC 822 (1123).  Decimal can be trailed by "s" for in seconds, "m" for in minutes, "h" for in hours, and "d" for in days.
+.RE
+.RS
+The return value is the time value of the date or `INT64_MIN' if the format is invalid.
+.RE
+.RE
+.PP
+The function `tcjetlag' is used in order to get the jet lag of the local time.
+.PP
+.RS
+.br
+\fBint tcjetlag(void);\fR
+.RS
+The return value is the jet lag of the local time in seconds.
+.RE
+.RE
+.PP
+The function `tcdayofweek' is used in order to get the day of week of a date.
+.PP
+.RS
+.br
+\fBint tcdayofweek(int \fIyear\fB, int \fImon\fB, int \fIday\fB);\fR
+.RS
+`\fIyear\fR' specifies the year of a date.
+.RE
+.RS
+`\fImon\fR' specifies the month of the date.
+.RE
+.RS
+`\fIday\fR' specifies the day of the date.
+.RE
+.RS
+The return value is the day of week of the date.  0 means Sunday and 6 means Saturday.
+.RE
+.RE
+
+.SH API OF FILESYSTEM UTILITIES
+.PP
+The function `tcrealpath' is used in order to get the canonicalized absolute path of a file.
+.PP
+.RS
+.br
+\fBchar *tcrealpath(const char *\fIpath\fB);\fR
+.RS
+`\fIpath\fR' specifies the path of the file.
+.RE
+.RS
+The return value is the canonicalized absolute path of a file, or `NULL' if the path is invalid.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcstatfile' is used in order to get the status information of a file.
+.PP
+.RS
+.br
+\fBbool tcstatfile(const char *\fIpath\fB, bool *\fIisdirp\fB, int64_t *\fIsizep\fB, int64_t *\fImtimep\fB);\fR
+.RS
+`\fIpath\fR' specifies the path of the file.
+.RE
+.RS
+`\fIisdirp\fR' specifies the pointer to a variable into which whether the file is a directory is assigned.  If it is `NULL', it is ignored.
+.RE
+.RS
+`\fIsizep\fR' specifies the pointer to a variable into which the size of the file is assigned.  If it is `NULL', it is ignored.
+.RE
+.RS
+`\fIntimep\fR' specifies the pointer to a variable into which the size of the file is assigned.  If it is `NULL', it is ignored.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RE
+.PP
+The function `tcreadfile' is used in order to read whole data of a file.
+.PP
+.RS
+.br
+\fBvoid *tcreadfile(const char *\fIpath\fB, int \fIlimit\fB, int *\fIsp\fB);\fR
+.RS
+`\fIpath\fR' specifies the path of the file.  If it is `NULL', the standard input is specified.
+.RE
+.RS
+`\fIlimit\fR' specifies the limiting size of reading data.  If it is not more than 0, the limitation is not specified.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.  If it is `NULL', it is not used.
+.RE
+.RS
+The return value is the pointer to the allocated region of the read data, or `NULL' if the file could not be opened.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when when is no longer in use.
+.RE
+.RE
+.PP
+The function `tcreadfilelines' is used in order to read every line of a file.
+.PP
+.RS
+.br
+\fBTCLIST *tcreadfilelines(const char *\fIpath\fB);\fR
+.RS
+`\fIpath\fR' specifies the path of the file.  If it is `NULL', the standard input is specified.
+.RE
+.RS
+The return value is a list object of every lines if successful, else it is `NULL'.
+.RE
+.RS
+Line separators are cut out.  Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcwritefile' is used in order to write data into a file.
+.PP
+.RS
+.br
+\fBbool tcwritefile(const char *\fIpath\fB, const void *\fIptr\fB, int \fIsize\fB);\fR
+.RS
+`\fIpath\fR' specifies the path of the file.  If it is `NULL', the standard output is specified.
+.RE
+.RS
+`\fIptr\fR' specifies the pointer to the data region.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+If successful, the return value is true, else, it is false.
+.RE
+.RE
+.PP
+The function `tccopyfile' is used in order to copy a file.
+.PP
+.RS
+.br
+\fBbool tccopyfile(const char *\fIsrc\fB, const char *\fIdest\fB);\fR
+.RS
+`\fIsrc\fR' specifies the path of the source file.
+.RE
+.RS
+`\fIdest\fR' specifies the path of the destination file.
+.RE
+.RS
+The return value is true if successful, else, it is false.
+.RE
+.RS
+If the destination file exists, it is overwritten.
+.RE
+.RE
+.PP
+The function `tcreaddir' is used in order to read names of files in a directory.
+.PP
+.RS
+.br
+\fBTCLIST *tcreaddir(const char *\fIpath\fB);\fR
+.RS
+`\fIpath\fR' specifies the path of the directory.
+.RE
+.RS
+The return value is a list object of names if successful, else it is `NULL'.
+.RE
+.RS
+Links to the directory itself and to the parent directory are ignored.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcglobpat' is used in order to expand a pattern into a list of matched paths.
+.PP
+.RS
+.br
+\fBTCLIST *tcglobpat(const char *\fIpattern\fB);\fR
+.RS
+`\fIpattern\fR' specifies the matching pattern.
+.RE
+.RS
+The return value is a list object of matched paths.  If no path is matched, an empty list is returned.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcremovelink' is used in order to remove a file or a directory and its sub ones recursively.
+.PP
+.RS
+.br
+\fBbool tcremovelink(const char *\fIpath\fB);\fR
+.RS
+`\fIpath\fR' specifies the path of the link.
+.RE
+.RS
+If successful, the return value is true, else, it is false.  False is returned when the link does not exist or the permission is denied.
+.RE
+.RE
+.PP
+The function `tcwrite' is used in order to write data into a file.
+.PP
+.RS
+.br
+\fBbool tcwrite(int \fIfd\fB, const void *\fIbuf\fB, size_t \fIsize\fB);\fR
+.RS
+`\fIfd\fR' specifies the file descriptor.
+.RE
+.RS
+`\fIbuf\fR' specifies the buffer to be written.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the buffer.
+.RE
+.RS
+The return value is true if successful, else, it is false.
+.RE
+.RE
+.PP
+The function `tcread' is used in order to read data from a file.
+.PP
+.RS
+.br
+\fBbool tcread(int \fIfd\fB, void *\fIbuf\fB, size_t \fIsize\fB);\fR
+.RS
+`\fIfd\fR' specifies the file descriptor.
+.RE
+.RS
+`\fIbuf\fR' specifies the buffer to store into.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the buffer.
+.RE
+.RS
+The return value is true if successful, else, it is false.
+.RE
+.RE
+.PP
+The function `tclock' is used in order to lock a file.
+.PP
+.RS
+.br
+\fBbool tclock(int \fIfd\fB, bool \fIex\fB, bool \fInb\fB);\fR
+.RS
+`\fIfd\fR' specifies the file descriptor.
+.RE
+.RS
+`\fIex\fR' specifies whether an exclusive lock or a shared lock is performed.
+.RE
+.RS
+`\fInb\fR' specifies whether to request with non\-blocking.
+.RE
+.RS
+The return value is true if successful, else, it is false.
+.RE
+.RE
+.PP
+The function `tcunlock' is used in order to unlock a file.
+.PP
+.RS
+.br
+\fBbool tcunlock(int \fIfd\fB);\fR
+.RS
+`\fIfd\fR' specifies the file descriptor.
+.RE
+.RS
+The return value is true if successful, else, it is false.
+.RE
+.RE
+.PP
+The function `tcsystem' is used in order to execute a shell command.
+.PP
+.RS
+.br
+\fBint tcsystem(const char **\fIargs\fB, int \fIanum\fB);\fR
+.RS
+`\fIargs\fR' specifies an array of the command name and its arguments.
+.RE
+.RS
+`\fIanum\fR' specifies the number of elements of the array.
+.RE
+.RS
+The return value is the exit code of the command or `INT_MAX' on failure.
+.RE
+.RS
+The command name and the arguments are quoted and meta characters are escaped.
+.RE
+.RE
+
+.SH API OF ENCODING UTILITIES
+.PP
+The function `tcurlencode' is used in order to encode a serial object with URL encoding.
+.PP
+.RS
+.br
+\fBchar *tcurlencode(const char *\fIptr\fB, int \fIsize\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+The return value is the result string.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if when is no longer in use.
+.RE
+.RE
+.PP
+The function `tcurldecode' is used in order to decode a string encoded with URL encoding.
+.PP
+.RS
+.br
+\fBchar *tcurldecode(const char *\fIstr\fB, int *\fIsp\fB);\fR
+.RS
+`\fIstr\fR' specifies the encoded string.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to a variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+The return value is the pointer to the region of the result.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcurlbreak' is used in order to break up a URL into elements.
+.PP
+.RS
+.br
+\fBTCMAP *tcurlbreak(const char *\fIstr\fB);\fR
+.RS
+`\fIstr\fR' specifies the URL string.
+.RE
+.RS
+The return value is the map object whose keys are the name of elements.  The key "self" indicates the URL itself.  The key "scheme" indicates the scheme.  The key "host" indicates the host of the server.  The key "port" indicates the port number of the server.  The key "authority" indicates the authority information.  The key "path" indicates the path of the resource.  The key "file" indicates the file name without the directory section.  The key "query" indicates the query string.  The key "fragment" indicates the fragment string.
+.RE
+.RS
+Supported schema are HTTP, HTTPS, FTP, and FILE.  Absolute URL and relative URL are supported.  Because the object of the return value is created with the function `tcmapnew', it should be deleted with the function `tcmapdel' when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcurlresolve' is used in order to resolve a relative URL with an absolute URL.
+.PP
+.RS
+.br
+\fBchar *tcurlresolve(const char *\fIbase\fB, const char *\fItarget\fB);\fR
+.RS
+`\fIbase\fR' specifies the absolute URL of the base location.
+.RE
+.RS
+`\fItarget\fR' specifies the URL to be resolved.
+.RE
+.RS
+The return value is the resolved URL.  If the target URL is relative, a new URL of relative location from the base location is returned.  Else, a copy of the target URL is returned.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcbaseencode' is used in order to encode a serial object with Base64 encoding.
+.PP
+.RS
+.br
+\fBchar *tcbaseencode(const char *\fIptr\fB, int \fIsize\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+The return value is the result string.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if when is no longer in use.
+.RE
+.RE
+.PP
+The function `tcbasedecode' is used in order to decode a string encoded with Base64 encoding.
+.PP
+.RS
+.br
+\fBchar *tcbasedecode(const char *\fIstr\fB, int *\fIsp\fB);\fR
+.RS
+`\fIstr\fR' specifies the encoded string.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to a variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+The return value is the pointer to the region of the result.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcquoteencode' is used in order to encode a serial object with Quoted\-printable encoding.
+.PP
+.RS
+.br
+\fBchar *tcquoteencode(const char *\fIptr\fB, int \fIsize\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+The return value is the result string.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if when is no longer in use.
+.RE
+.RE
+.PP
+The function `tcquotedecode' is used in order to decode a string encoded with Quoted\-printable encoding.
+.PP
+.RS
+.br
+\fBchar *tcquotedecode(const char *\fIstr\fB, int *\fIsp\fB);\fR
+.RS
+`\fIstr\fR' specifies the encoded string.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to a variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+The return value is the pointer to the region of the result.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcmimeencode' is used in order to encode a string with MIME encoding.
+.PP
+.RS
+.br
+\fBchar *tcmimeencode(const char *\fIstr\fB, const char *\fIencname\fB, bool \fIbase\fB);\fR
+.RS
+`\fIstr\fR' specifies the string.
+.RE
+.RS
+`\fIencname\fR' specifies the string of the name of the character encoding.
+.RE
+.RS
+`\fIbase\fR' specifies whether to use Base64 encoding.  If it is false, Quoted\-printable is used.
+.RE
+.RS
+The return value is the result string.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcmimedecode' is used in order to decode a string encoded with MIME encoding.
+.PP
+.RS
+.br
+\fBchar *tcmimedecode(const char *\fIstr\fB, char *\fIenp\fB);\fR
+.RS
+`\fIstr\fR' specifies the encoded string.
+.RE
+.RS
+`\fIenp\fR' specifies the pointer to the region into which the name of encoding is written.  If it is `NULL', it is not used.  The size of the buffer should be equal to or more than 32 bytes.
+.RE
+.RS
+The return value is the result string.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcmimebreak' is used in order to split a string of MIME into headers and the body.
+.PP
+.RS
+.br
+\fBchar *tcmimebreak(const char *\fIptr\fB, int \fIsize\fB, TCMAP *\fIheaders\fB, int *\fIsp\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region of MIME data.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+`\fIheaders\fR' specifies a map object to store headers.  If it is `NULL', it is not used.  Each key of the map is an uncapitalized header name.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+The return value is the pointer to the region of the body data.
+.RE
+.RS
+If the content type is defined, the header map has the key "TYPE" specifying the type.  If the character encoding is defined, the key "CHARSET" indicates the encoding name.  If the boundary string of multipart is defined, the key "BOUNDARY" indicates the string.  If the content disposition is defined, the key "DISPOSITION" indicates the direction.  If the file name is defined, the key "FILENAME" indicates the name.  If the attribute name is defined, the key "NAME" indicates the name.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcmimeparts' is used in order to split multipart data of MIME into its parts.
+.PP
+.RS
+.br
+\fBTCLIST *tcmimeparts(const char *\fIptr\fB, int \fIsize\fB, const char *\fIboundary\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region of multipart data of MIME.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+`\fIboundary\fR' specifies the boundary string.
+.RE
+.RS
+The return value is a list object.  Each element of the list is the data of a part.
+.RE
+.RS
+Because the object of the return value is created with the function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tchexencode' is used in order to encode a serial object with hexadecimal encoding.
+.PP
+.RS
+.br
+\fBchar *tchexencode(const char *\fIptr\fB, int \fIsize\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+The return value is the result string.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if when is no longer in use.
+.RE
+.RE
+.PP
+The function `tchexdecode' is used in order to decode a string encoded with hexadecimal encoding.
+.PP
+.RS
+.br
+\fBchar *tchexdecode(const char *\fIstr\fB, int *\fIsp\fB);\fR
+.RS
+`\fIstr\fR' specifies the encoded string.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to a variable into which the size of the region of the return
+.RE
+.RS
+value is assigned.
+.RE
+.RS
+The return value is the pointer to the region of the result.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcpackencode' is used in order to compress a serial object with Packbits encoding.
+.PP
+.RS
+.br
+\fBchar *tcpackencode(const char *\fIptr\fB, int \fIsize\fB, int *\fIsp\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the result object, else, it is `NULL'.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcpackdecode' is used in order to decompress a serial object compressed with Packbits encoding.
+.PP
+.RS
+.br
+\fBchar *tcpackdecode(const char *\fIptr\fB, int \fIsize\fB, int *\fIsp\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to a variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the result object, else, it is `NULL'.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcbsencode' is used in order to compress a serial object with TCBS encoding.
+.PP
+.RS
+.br
+\fBchar *tcbsencode(const char *\fIptr\fB, int \fIsize\fB, int *\fIsp\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the result object, else, it is `NULL'.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcbsdecode' is used in order to decompress a serial object compressed with TCBS encoding.
+.PP
+.RS
+.br
+\fBchar *tcbsdecode(const char *\fIptr\fB, int \fIsize\fB, int *\fIsp\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to a variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the result object, else, it is `NULL'.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcdeflate' is used in order to compress a serial object with Deflate encoding.
+.PP
+.RS
+.br
+\fBchar *tcdeflate(const char *\fIptr\fB, int \fIsize\fB, int *\fIsp\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the result object, else, it is `NULL'.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcinflate' is used in order to decompress a serial object compressed with Deflate encoding.
+.PP
+.RS
+.br
+\fBchar *tcinflate(const char *\fIptr\fB, int \fIsize\fB, int *\fIsp\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to a variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the result object, else, it is `NULL'.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcgzipencode' is used in order to compress a serial object with GZIP encoding.
+.PP
+.RS
+.br
+\fBchar *tcgzipencode(const char *\fIptr\fB, int \fIsize\fB, int *\fIsp\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the result object, else, it is `NULL'.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcgzipdecode' is used in order to decompress a serial object compressed with GZIP encoding.
+.PP
+.RS
+.br
+\fBchar *tcgzipdecode(const char *\fIptr\fB, int \fIsize\fB, int *\fIsp\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to a variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the result object, else, it is `NULL'.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcgetcrc' is used in order to get the CRC32 checksum of a serial object.
+.PP
+.RS
+.br
+\fBunsigned int tcgetcrc(const char *\fIptr\fB, int \fIsize\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+The return value is the CRC32 checksum of the object.
+.RE
+.RE
+.PP
+The function `tcbzipencode' is used in order to compress a serial object with BZIP2 encoding.
+.PP
+.RS
+.br
+\fBchar *tcbzipencode(const char *\fIptr\fB, int \fIsize\fB, int *\fIsp\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to the variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the result object, else, it is `NULL'.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcbzipdecode' is used in order to decompress a serial object compressed with BZIP2 encoding.
+.PP
+.RS
+.br
+\fBchar *tcbzipdecode(const char *\fIptr\fB, int \fIsize\fB, int *\fIsp\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to a variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+If successful, the return value is the pointer to the result object, else, it is `NULL'.
+.RE
+.RS
+Because an additional zero code is appended at the end of the region of the return value, the return value can be treated as a character string.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcberencode' is used in order to encode an array of nonnegative integers with BER encoding.
+.PP
+.RS
+.br
+\fBchar *tcberencode(const unsigned int *\fIary\fB, int \fIanum\fB, int *\fIsp\fB);\fR
+.RS
+`\fIary\fR' specifies the pointer to the array of nonnegative integers.
+.RE
+.RS
+`\fIanum\fR' specifies the size of the array.
+.RE
+.RS
+`\fIsp\fR' specifies the pointer to a variable into which the size of the region of the return value is assigned.
+.RE
+.RS
+The return value is the pointer to the region of the result.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if when is no longer in use.
+.RE
+.RE
+.PP
+The function `tcberdecode' is used in order to decode a serial object encoded with BER encoding.
+.PP
+.RS
+.br
+\fBunsigned int *tcberdecode(const char *\fIptr\fB, int \fIsize\fB, int *\fInp\fB);\fR
+.RS
+`\fIptr\fR' specifies the pointer to the region.
+.RE
+.RS
+`\fIsize\fR' specifies the size of the region.
+.RE
+.RS
+`\fInp\fR' specifies the pointer to a variable into which the number of elements of the return value is assigned.
+.RE
+.RS
+The return value is the pointer to the array of the result.
+.RE
+.RS
+Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call if when is no longer in use.
+.RE
+.RE
+.PP
+The function `tcxmlescape' is used in order to escape meta characters in a string with the entity references of XML.
+.PP
+.RS
+.br
+\fBchar *tcxmlescape(const char *\fIstr\fB);\fR
+.RS
+`\fIstr\fR' specifies the string.
+.RE
+.RS
+The return value is the pointer to the escaped string.
+.RE
+.RS
+This function escapes only `&', `<', `>', and `"'.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+.PP
+The function `tcxmlunescape' is used in order to unescape entity references in a string of XML.
+.PP
+.RS
+.br
+\fBchar *tcxmlunescape(const char *\fIstr\fB);\fR
+.RS
+`\fIstr\fR' specifies the string.
+.RE
+.RS
+The return value is the unescaped string.
+.RE
+.RS
+This function restores only `&amp;', `&lt;', `&gt;', and `&quot;'.  Because the region of the return value is allocated with the `malloc' call, it should be released with the `free' call when it is no longer in use.
+.RE
+.RE
+
+.SH SEE ALSO
+.PP
+.BR tcutest (1),
+.BR tcucodec (1),
+.BR tokyocabinet (3)
diff --git a/tcejdb/man/tcxstr.3 b/tcejdb/man/tcxstr.3
new file mode 100644 (file)
index 0000000..03538a8
--- /dev/null
@@ -0,0 +1 @@
+.so man3/tcutil.3
diff --git a/tcejdb/man/tokyocabinet.3 b/tcejdb/man/tokyocabinet.3
new file mode 100644 (file)
index 0000000..19d4494
--- /dev/null
@@ -0,0 +1,132 @@
+.TH "TOKYOCABINET" 3 "2012-08-18" "Man Page" "Tokyo Cabinet"
+
+.SH NAME
+tokyocabinet \- a modern implementation of DBM
+
+.SH INTRODUCTION
+.PP
+Tokyo Cabinet is a library of routines for managing a database.  The database is a simple data file containing records, each is a pair of a key and a value.  Every key and value is serial bytes with variable length.  Both binary data and character string can be used as a key and a value.  There is neither concept of data tables nor data types.  Records are organized in hash table, B+ tree, or fixed\-length array.
+.PP
+As for database of hash table, each key must be unique within a database, so it is impossible to store two or more records with a key overlaps.  The following access methods are provided to the database: storing a record with a key and a value, deleting a record by a key, retrieving a record by a key.  Moreover, traversal access to every key are provided, although the order is arbitrary.  These access methods are similar to ones of DBM (or its followers: NDBM and GDBM) library defined in the UNIX standard.  Tokyo Cabinet is an alternative for DBM because of its higher performance.
+.PP
+As for database of B+ tree, records whose keys are duplicated can be stored.  Access methods of storing, deleting, and retrieving are provided as with the database of hash table.  Records are stored in order by a comparison function assigned by a user.  It is possible to access each record with the cursor in ascending or descending order.  According to this mechanism, forward matching search for strings and range search for integers are realized.
+.PP
+As for database of fixed\-length array, records are stored with unique natural numbers.  It is impossible to store two or more records with a key overlaps.  Moreover, the length of each record is limited by the specified length.  Provided operations are the same as ones of hash database.
+.PP
+Table database is also provided as a variant of hash database.  Each record is identified by the primary key and has a set of named columns.  Although there is no concept of data schema, it is possible to search for records with complex conditions efficiently by using indices of arbitrary columns.
+.PP
+Tokyo Cabinet is written in the C language, and provided as API of C, Perl, Ruby, Java, and Lua.  Tokyo Cabinet is available on platforms which have API conforming to C99 and POSIX.  Tokyo Cabinet is a free software licensed under the GNU Lesser General Public License.
+
+.SH THE DINOSAUR WING OF THE DBM FORKS
+.PP
+Tokyo Cabinet is developed as the successor of GDBM and QDBM on the following purposes.  They are achieved and Tokyo Cabinet replaces conventional DBM products.
+.PP
+.RS
+improves space efficiency : smaller size of database file.
+.br
+improves time efficiency : faster processing speed.
+.br
+improves parallelism : higher performance in multi\-thread environment.
+.br
+improves usability : simplified API.
+.br
+improves robustness : database file is not corrupted even under catastrophic situation.
+.br
+supports 64\-bit architecture : enormous memory space and database file are available.
+.br
+.RE
+.PP
+As with QDBM, the following three restrictions of traditional DBM: a process can handle only one database, the size of a key and a value is bounded, a database file is sparse, are cleared.  Moreover, the following three restrictions of QDBM: the size of a database file is limited to 2GB, environments with different byte orders can not share a database file, only one thread can search a database at the same time, are cleared.
+.PP
+Tokyo Cabinet runs very fast.  For example, elapsed time to store 1 million records is 0.7 seconds for hash database, and 1.6 seconds for B+ tree database.  Moreover, the size of database of Tokyo Cabinet is very small.  For example, overhead for a record is 16 bytes for hash database, and 5 bytes for B+ tree database.  Furthermore, scalability of Tokyo Cabinet is great.  The database size can be up to 8EB (9.22e18 bytes).
+
+.SH EFFECTIVE IMPLEMENTATION OF HASH DATABASE
+.PP
+Tokyo Cabinet uses hash algorithm to retrieve records.  If a bucket array has sufficient number of elements, the time complexity of retrieval is "O(1)".  That is, time required for retrieving a record is constant, regardless of the scale of a database.  It is also the same about storing and deleting.  Collision of hash values is managed by separate chaining.  Data structure of the chains is binary search tree.  Even if a bucket array has unusually scarce elements, the time complexity of retrieval is "O(log n)".
+.PP
+Tokyo Cabinet attains improvement in retrieval by loading RAM with the whole of a bucket array.  If a bucket array is on RAM, it is possible to access a region of a target record by about one path of file operations.  A bucket array saved in a file is not read into RAM with the `read' call but directly mapped to RAM with the `mmap' call.  Therefore, preparation time on connecting to a database is very short, and two or more processes can share the same memory map.
+.PP
+If the number of elements of a bucket array is about half of records stored within a database, although it depends on characteristic of the input, the probability of collision of hash values is about 56.7% (36.8% if the same, 21.3% if twice, 11.5% if four times, 6.0% if eight times).  In such case, it is possible to retrieve a record by two or less paths of file operations.  If it is made into a performance index, in order to handle a database containing one million of records, a bucket array with half a million of elements is needed.  The size of each element is 4 bytes.  That is, if 2M bytes of RAM is available, a database containing one million records can be handled.
+.PP
+Traditional DBM provides two modes of the storing operations: "insert" and "replace".  In the case a key overlaps an existing record, the insert mode keeps the existing value, while the replace mode transposes it to the specified value.  In addition to the two modes, Tokyo Cabinet provides "concatenate" mode.  In the mode, the specified value is concatenated at the end of the existing value and stored.  This feature is useful when adding an element to a value as an array.
+.PP
+Generally speaking, while succession of updating, fragmentation of available regions occurs, and the size of a database grows rapidly.  Tokyo Cabinet deal with this problem by coalescence of dispensable regions and reuse of them.  When overwriting a record with a value whose size is greater than the existing one, it is necessary to remove the region to another position of the file.  Because the time complexity of the operation depends on the size of the region of a record, extending values successively is inefficient.  However, Tokyo Cabinet deal with this problem by alignment.  If increment can be put in padding, it is not necessary to remove the region.
+.PP
+The "free block pool" to reuse dispensable regions efficiently is also implemented.  It keeps a list of dispensable regions and reuse the "best fit" region, that is the smallest region in the list, when a new block is requested.  Because fragmentation is inevitable even then, two kinds of optimization (defragmentation) mechanisms are implemented.  The first is called static optimization which deploys all records into another file and then writes them back to the original file at once.  The second is called dynamic optimization which gathers up dispensable regions by replacing the locations of records and dispensable regions gradually.
+
+.SH USEFUL IMPLEMENTATION OF B+ TREE DATABASE
+.PP
+Although B+ tree database is slower than hash database, it features ordering access to each record.  The order can be assigned by users.  Records of B+ tree are sorted and arranged in logical pages.  Sparse index organized in B tree that is multiway balanced tree are maintained for each page.  Thus, the time complexity of retrieval and so on is "O(log n)".  Cursor is provided to access each record in order.  The cursor can jump to a position specified by a key and can step forward or backward from the current position.  Because each page is arranged as double linked list, the time complexity of stepping cursor is "O(1)".
+.PP
+B+ tree database is implemented, based on above hash database.  Because each page of B+ tree is stored as each record of hash database, B+ tree database inherits efficiency of storage management of hash database.  Because the header of each record is smaller and alignment of each page is adjusted according to the page size, in most cases, the size of database file is cut by half compared to one of hash database.  Although operation of many pages are required to update B+ tree, QDBM expedites the process by caching pages and reducing file operations.  In most cases, because whole of the sparse index is cached on memory, it is possible to retrieve a record by one or less path of file operations.
+.PP
+Each pages of B+ tree can be stored with compressed.  Two compression method; Deflate of ZLIB and Block Sorting of BZIP2, are supported.  Because each record in a page has similar patterns, high efficiency of compression is expected due to the Lempel\-Ziv or the BWT algorithms.  In case handling text data, the size of a database is reduced to about 25%.  If the scale of a database is large and disk I/O is the bottleneck, featuring compression makes the processing speed improved to a large extent.
+
+.SH NAIVE IMPLEMENTATION OF FIXED\-LENGTH DATABASE
+.PP
+Fixed\-length database has restrictions that each key should be a natural number and that the length of each value is limited.  However, time efficiency and space efficiency are higher than the other data structures as long as the use case is within the restriction.
+.PP
+Because the whole region of the database is mapped on memory by the `mmap' call and referred as a multidimensional array, the overhead related to the file I/O is minimized.  Due to this simple structure, fixed\-length database works faster than hash database, and its concurrency in multi\-thread environment is prominent.
+.PP
+The size of the database is proportional to the range of keys and the limit size of each value.  That is, the smaller the range of keys is or the smaller the length of each value is, the higher the space efficiency is.  For example, if the maximum key is 1000000 and the limit size of the value is 100 bytes, the size of the database will be about 100MB.  Because regions around referred records are only loaded on the RAM, you can increase the size of the database to the size of the virtual memory.
+
+.SH FLEXIBLE IMPLEMENTATION OF TABLE DATABASE
+.PP
+Table database does not express simple key/value structure but expresses a structure like a table of relational database.  Each record is identified by the primary key and has a set of multiple columns named with arbitrary strings.  For example, a stuff in your company can be expressed by a record identified by the primary key of the employee ID number and structured by columns of his name, division, salary, and so on.  Unlike relational database, table database does not need to define any data schema and can contain records of various structures different from each other.
+.PP
+Table database supports query functions with not only the primary key but also with conditions about arbitrary columns.  Each column condition is composed of the name of a column and a condition expression.  Operators of full matching, forward matching, regular expression matching, and so on are provided for the string type.  Operators of full matching, range matching and so on are provided for the number type.  Operators for tag search and full-text search are also provided.  A query can contain multiple conditions for logical intersection.  Search by multiple queries for logical union is also available.  The order of the result set can be specified as the ascending or descending order of strings or numbers.
+.PP
+You can create indices for arbitrary columns to improve performance of search and sorting.  Although columns do not have data types, indices have types for strings or numbers.  Inverted indices for space separated tokens and character N-gram tokens are also supported.  The query optimizer uses indices in suitable way according to each query.  Indices are implemented as different files of B+ tree database.
+
+.SH PRACTICAL FUNCTIONALITY
+.PP
+Databases on the filesystem feature transaction mechanisms.  It is possible to commit a series of operations between the beginning and the end of the transaction in a lump, or to abort the transaction and perform rollback to the state before the transaction.  Two isolation levels are supported; serializable and read uncommitted.  Durability is secured by write ahead logging and shadow paging.
+.PP
+Tokyo Cabinet provides two modes to connect to a database: "reader" and "writer".  A reader can perform retrieving but neither storing nor deleting.  A writer can perform all access methods.  Exclusion control between processes is performed when connecting to a database by file locking.  While a writer is connected to a database, neither readers nor writers can be connected.  While a reader is connected to a database, other readers can be connect, but writers can not.  According to this mechanism, data consistency is guaranteed with simultaneous connections in multitasking environment.
+.PP
+Functions of API of Tokyo cabinet are reentrant and available in multi\-thread environment.  Discrete database object can be operated in parallel entirely.  For simultaneous operations of the same database object, read\-write lock is used for exclusion control.  That is, while a writing thread is operating the database, other reading threads and writing threads are blocked.  However, while a reading thread is operating the database, reading threads are not blocked.  The locking granularity of hash database and fixed\-length database is per record, and that of the other databases is per file.
+
+.SH SIMPLE BUT VARIOUS INTERFACES
+.PP
+Tokyo Cabinet provides simple API based on the object oriented design.  Every operation for database is encapsulated and published as lucid methods as `open' (connect), `close' (disconnect), `put' (insert), `out' (remove), `get' (retrieve), and so on.  Because the three of hash, B+ tree, and fixed\-length array database APIs are very similar with each other, porting an application from one to the other is easy.  Moreover, the abstract API is provided to handle these databases with the same interface.  Applications of the abstract API can determine the type of the database in runtime.
+.PP
+The utility API is also provided.  Such fundamental data structure as list and map are included.  And, some useful features; memory pool, string processing, encoding, are also included.
+.PP
+Six kinds of API; the utility API, the hash database API, the B+ tree database API, the fixed\-length database API, the table database API, and the abstract database API, are provided for the C language.  Command line interfaces are also provided corresponding to each API.  They are useful for prototyping, test, and debugging.  Except for C, Tokyo Cabinet provides APIs for Perl, Ruby, Java, and Lua.  APIs for other languages will hopefully be provided by third party.
+.PP
+In cases that multiple processes access a database at the same time or some processes access a database on a remote host, the remote service is useful.  The remote service is composed of a database server and its access library.  Applications can access the database server by using the remote database API.  The server implements HTTP and the memcached protocol partly so that client programs on almost all platforms can access the server easily.
+
+.SH HOW TO USE THE LIBRARY
+.PP
+Tokyo Cabinet provides API of the C language and it is available by programs conforming to the C89 (ANSI C) standard or the C99 standard.  As the header files of Tokyo Cabinet are provided as `\fBtcutil.h\fR', `\fBtchdb.h\fR', and `\fBtcbdb.h\fR', applications should include one or more of them accordingly to use the API.  As the library is provided as `\fBlibtokyocabinet.a\fR' and `\fBlibtokyocabinet.so\fR' and they depends `\fBlibz.so\fR', `\fBlibrt.so\fR', `\fBlibpthread.so\fR', `\fBlibm.so\fR', and `\fBlibc.so\fR', linker options `\fB\-ltokyocabinet\fR', `\fB\-lz\fR', `\fB\-lbz2\fR', `\fB\-lrt\fR', `\fB\-lpthread\fR', `\fB\-lm\fR', and `\fB\-lc\fR' are required for build command.  A typical build command is the following.
+.PP
+.RS
+gcc \-I/usr/local/include tc_example.c \-o tc_example \\
+.br
+  \-L/usr/local/lib \-ltokyocabinet \-lz \-lbz2 \-lrt \-lpthread \-lm \-lc
+.RE
+.PP
+You can also use Tokyo Cabinet in programs written in C++.  Because each header is wrapped in C linkage (`\fBextern "C"\fR' block), you can simply include them into your C++ programs.
+
+.SH LICENSE
+.PP
+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.
+.PP
+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.
+.PP
+You should have received a copy of the GNU Lesser General Public License along with Tokyo Cabinet (See the file `\fBCOPYING\fR'); if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111\-1307 USA.
+.PP
+Tokyo Cabinet was written by FAL Labs.  You can contact the author by e\-mail to `\fBinfo@fallabs.com\fR'.
+
+.SH SEE ALSO
+.PP
+.BR tcutil (3),
+.BR tchdb (3),
+.BR tcbdb (3),
+.BR tcfdb (3),
+.BR tctdb (3),
+.BR tcadb (3)
+.PP
+Please see
+.I http://1978th.net/tokyocabinet/
+for detail.
diff --git a/tcejdb/md5.c b/tcejdb/md5.c
new file mode 100644 (file)
index 0000000..4502bca
--- /dev/null
@@ -0,0 +1,381 @@
+/*
+  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));
+}
diff --git a/tcejdb/md5.h b/tcejdb/md5.h
new file mode 100644 (file)
index 0000000..c65cc4f
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+  Copyright (C) 1999, 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.h,v 1.4 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.h 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 Removed support for non-ANSI compilers; removed
+        references to Ghostscript; clarified derivation from RFC 1321;
+        now handles byte order either statically or dynamically.
+  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
+        added conditionalization for C++ compilation from Martin
+        Purschke <purschke@bnl.gov>.
+  1999-05-03 lpd Original version.
+ */
+
+#ifndef md5_INCLUDED
+#  define md5_INCLUDED
+
+/*
+ * This package supports both compile-time and run-time determination of CPU
+ * byte order.  If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be
+ * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is
+ * defined as non-zero, the code will be compiled to run only on big-endian
+ * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to
+ * run on either big- or little-endian CPUs, but will run slightly less
+ * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.
+ */
+
+
+/*
+ * hack to avoid conflict of application symbol names
+ */
+#define md5_init      _tc_md5_init
+#define md5_append    _tc_md5_append
+#define md5_finish    _tc_md5_finish
+
+
+
+typedef unsigned char md5_byte_t; /* 8-bit byte */
+typedef unsigned int md5_word_t; /* 32-bit word */
+
+/* Define the state of the MD5 Algorithm. */
+typedef struct md5_state_s {
+    md5_word_t count[2];        /* message length in bits, lsw first */
+    md5_word_t abcd[4];         /* digest buffer */
+    md5_byte_t buf[64];         /* accumulate block */
+} md5_state_t;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/* Initialize the algorithm. */
+void md5_init(md5_state_t *pms);
+
+/* Append a string to the message. */
+void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);
+
+/* Finish the message and return the digest. */
+void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
+
+#ifdef __cplusplus
+}  /* end extern "C" */
+#endif
+
+#endif /* md5_INCLUDED */
diff --git a/tcejdb/myconf.c b/tcejdb/myconf.c
new file mode 100644 (file)
index 0000000..2b95457
--- /dev/null
@@ -0,0 +1,493 @@
+/*************************************************************************************************
+ * 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
diff --git a/tcejdb/myconf.h b/tcejdb/myconf.h
new file mode 100644 (file)
index 0000000..807829f
--- /dev/null
@@ -0,0 +1,575 @@
+/*************************************************************************************************
+ * 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.
+ *************************************************************************************************/
+
+
+#ifndef _MYCONF_H                        // duplication check
+#define _MYCONF_H
+
+
+
+/*************************************************************************************************
+ * system discrimination
+ *************************************************************************************************/
+
+
+#if defined(__linux__)
+
+#define _SYS_LINUX_
+#define TCSYSNAME   "Linux"
+
+#elif defined(__FreeBSD__)
+
+#define _SYS_FREEBSD_
+#define TCSYSNAME   "FreeBSD"
+
+#elif defined(__NetBSD__)
+
+#define _SYS_NETBSD_
+#define TCSYSNAME   "NetBSD"
+
+#elif defined(__OpenBSD__)
+
+#define _SYS_OPENBSD_
+#define TCSYSNAME   "OpenBSD"
+
+#elif defined(__sun__) || defined(__sun)
+
+#define _SYS_SUNOS_
+#define TCSYSNAME   "SunOS"
+
+#elif defined(__hpux)
+
+#define _SYS_HPUX_
+#define TCSYSNAME   "HP-UX"
+
+#elif defined(__osf)
+
+#define _SYS_TRU64_
+#define TCSYSNAME   "Tru64"
+
+#elif defined(_AIX)
+
+#define _SYS_AIX_
+#define TCSYSNAME   "AIX"
+
+#elif defined(__APPLE__) && defined(__MACH__)
+
+#define _SYS_MACOSX_
+#define TCSYSNAME   "Mac OS X"
+
+#elif defined(_MSC_VER)
+
+#define _SYS_MSVC_
+#define TCSYSNAME   "Windows (VC++)"
+
+#elif defined(_WIN32)
+
+#define _SYS_MINGW_
+#define TCSYSNAME   "Windows (MinGW)"
+
+#elif defined(__CYGWIN__)
+
+#define _SYS_CYGWIN_
+#define TCSYSNAME   "Windows (Cygwin)"
+
+#else
+
+#define _SYS_GENERIC_
+#define TCSYSNAME   "Generic"
+
+#endif
+
+
+
+/*************************************************************************************************
+ * common settings
+ *************************************************************************************************/
+
+#ifdef __cplusplus
+#define EJDB_EXTERN_C_START extern "C" {
+#define EJDB_EXTERN_C_END }
+#else
+#define EJDB_EXTERN_C_START
+#define EJDB_EXTERN_C_END
+#endif
+
+#ifdef __GNUC__
+#define EJDB_INLINE static __inline__
+#define EJDB_EXPORT
+#else
+#define EJDB_INLINE static
+#ifdef EJDB_STATIC_BUILD
+#define EJDB_EXPORT
+#elif defined(MONGO_DLL_BUILD)
+#define EJDB_EXPORT __declspec(dllexport)
+#else
+#define EJDB_EXPORT __declspec(dllimport)
+#endif
+#endif
+
+
+#if defined(NDEBUG)
+#define TCDODEBUG(TC_expr) \
+  do { \
+  } while(false)
+#else
+#define TCDODEBUG(TC_expr) \
+  do { \
+    TC_expr; \
+  } while(false)
+#endif
+
+#define TCSWAB16(TC_num) \
+  ( \
+   ((TC_num & 0x00ffU) << 8) | \
+   ((TC_num & 0xff00U) >> 8) \
+  )
+
+#define TCSWAB32(TC_num) \
+  ( \
+   ((TC_num & 0x000000ffUL) << 24) | \
+   ((TC_num & 0x0000ff00UL) << 8) | \
+   ((TC_num & 0x00ff0000UL) >> 8) | \
+   ((TC_num & 0xff000000UL) >> 24) \
+  )
+
+#define TCSWAB64(TC_num) \
+  ( \
+   ((TC_num & 0x00000000000000ffULL) << 56) | \
+   ((TC_num & 0x000000000000ff00ULL) << 40) | \
+   ((TC_num & 0x0000000000ff0000ULL) << 24) | \
+   ((TC_num & 0x00000000ff000000ULL) << 8) | \
+   ((TC_num & 0x000000ff00000000ULL) >> 8) | \
+   ((TC_num & 0x0000ff0000000000ULL) >> 24) | \
+   ((TC_num & 0x00ff000000000000ULL) >> 40) | \
+   ((TC_num & 0xff00000000000000ULL) >> 56) \
+  )
+
+#if defined(_MYBIGEND) || defined(_MYSWAB)
+#define TCBIGEND       1
+#define TCHTOIS(TC_num)   TCSWAB16(TC_num)
+#define TCHTOIL(TC_num)   TCSWAB32(TC_num)
+#define TCHTOILL(TC_num)  TCSWAB64(TC_num)
+#define TCITOHS(TC_num)   TCSWAB16(TC_num)
+#define TCITOHL(TC_num)   TCSWAB32(TC_num)
+#define TCITOHLL(TC_num)  TCSWAB64(TC_num)
+#else
+#define TCBIGEND       0
+#define TCHTOIS(TC_num)   (TC_num)
+#define TCHTOIL(TC_num)   (TC_num)
+#define TCHTOILL(TC_num)  (TC_num)
+#define TCITOHS(TC_num)   (TC_num)
+#define TCITOHL(TC_num)   (TC_num)
+#define TCITOHLL(TC_num)  (TC_num)
+#endif
+
+#if defined(_MYNOUBC)
+#define TCUBCACHE      0
+#elif defined(_SYS_LINUX_) || defined(_SYS_FREEBSD_) || defined(_SYS_NETBSD_) || \
+  defined(_SYS_MACOSX_) || defined(_SYS_SUNOS_)
+#define TCUBCACHE      1
+#else
+#define TCUBCACHE      0
+#endif
+
+#if defined(_MYNOZLIB)
+#define TCUSEZLIB      0
+#else
+#define TCUSEZLIB      1
+#endif
+
+#if defined(_MYNOBZIP)
+#define TCUSEBZIP      0
+#else
+#define TCUSEBZIP      1
+#endif
+
+#if defined(_MYEXLZMA)
+#define TCUSEEXLZMA    1
+#else
+#define TCUSEEXLZMA    0
+#endif
+
+#if defined(_MYEXLZO)
+#define TCUSEEXLZO     1
+#else
+#define TCUSEEXLZO     0
+#endif
+
+#if defined(_MYNOPTHREAD)
+#define TCUSEPTHREAD   0
+#else
+#define TCUSEPTHREAD   1
+#endif
+
+#if defined(_MYMICROYIELD)
+#define TCMICROYIELD   1
+#else
+#define TCMICROYIELD   0
+#endif
+
+#define MYMALLOC       malloc
+#define MYCALLOC       calloc
+#define MYREALLOC      realloc
+#define MYFREE         free
+
+
+
+/*************************************************************************************************
+ * general headers
+ *************************************************************************************************/
+
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <float.h>
+#include <limits.h>
+#include <locale.h>
+#include <math.h>
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <string.h>
+#include <time.h>
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <unistd.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/time.h>
+#include <sys/times.h>
+#include <sys/wait.h>
+#include <sys/resource.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <regex.h>
+#include <glob.h>
+
+#if TCUSEPTHREAD
+#include <pthread.h>
+#if defined(_POSIX_PRIORITY_SCHEDULING)
+#include <sched.h>
+#endif
+#endif
+
+
+
+/*************************************************************************************************
+ * miscellaneous hacks
+ *************************************************************************************************/
+
+
+#if defined(__GNUC__)
+#define _alignof(TC_a) ((size_t)__alignof__(TC_a))
+#else
+#define _alignof(TC_a) sizeof(TC_a)
+#endif
+#define _issigned(TC_a)  ((TC_a)-1 < 1 ? true : false)
+#define _maxof(TC_a) \
+ ((TC_a)(sizeof(TC_a) == sizeof(int64_t) ? _issigned(TC_a) ? INT64_MAX : UINT64_MAX : \
+   sizeof(TC_a) == sizeof(int32_t) ? _issigned(TC_a) ? INT32_MAX : UINT32_MAX : \
+   sizeof(TC_a) == sizeof(int16_t) ? _issigned(TC_a) ? INT16_MAX : UINT16_MAX : \
+   _issigned(TC_a) ? INT8_MAX : UINT8_MAX))
+
+#if defined(_SYS_FREEBSD_) || defined(_SYS_NETBSD_) || defined(_SYS_OPENBSD_)
+#define nan(TC_a)      strtod("nan", NULL)
+#define nanl(TC_a)     ((long double)strtod("nan", NULL))
+#endif
+
+#if ! defined(PATH_MAX)
+#if defined(MAXPATHLEN)
+#define PATH_MAX       MAXPATHLEN
+#else
+#define PATH_MAX       4096
+#endif
+#endif
+#if ! defined(NAME_MAX)
+#define NAME_MAX       255
+#endif
+
+extern int _tc_dummy_cnt;
+
+int _tc_dummyfunc(void);
+
+int _tc_dummyfuncv(int a, ...);
+
+
+
+/*************************************************************************************************
+ * notation of filesystems
+ *************************************************************************************************/
+
+
+#define MYPATHCHR       '/'
+#define MYPATHSTR       "/"
+#define MYEXTCHR        '.'
+#define MYEXTSTR        "."
+#define MYCDIRSTR       "."
+#define MYPDIRSTR       ".."
+
+
+
+/*************************************************************************************************
+ * for ZLIB
+ *************************************************************************************************/
+
+
+enum {
+  _TCZMZLIB,
+  _TCZMRAW,
+  _TCZMGZIP
+};
+
+
+extern char *(*_tc_deflate)(const char *, int, int *, int);
+
+extern char *(*_tc_inflate)(const char *, int, int *, int);
+
+extern unsigned int (*_tc_getcrc)(const char *, int);
+
+
+
+/*************************************************************************************************
+ * for BZIP2
+ *************************************************************************************************/
+
+
+extern char *(*_tc_bzcompress)(const char *, int, int *);
+
+extern char *(*_tc_bzdecompress)(const char *, int, int *);
+
+
+
+/*************************************************************************************************
+ * for test of custom codec functions
+ *************************************************************************************************/
+
+
+void *_tc_recencode(const void *ptr, int size, int *sp, void *op);
+
+void *_tc_recdecode(const void *ptr, int size, int *sp, void *op);
+
+
+
+/*************************************************************************************************
+ * for POSIX thread disability
+ *************************************************************************************************/
+
+
+#if ! TCUSEPTHREAD
+
+#define pthread_t                        intptr_t
+
+#define pthread_once_t                   intptr_t
+#undef PTHREAD_ONCE_INIT
+#define PTHREAD_ONCE_INIT                0
+#define pthread_once(TC_a, TC_b)         _tc_dummyfuncv((intptr_t)(TC_a), (TC_b))
+
+#define pthread_mutexattr_t              intptr_t
+#undef PTHREAD_MUTEX_RECURSIVE
+#define PTHREAD_MUTEX_RECURSIVE          0
+#define pthread_mutexattr_init(TC_a)     _tc_dummyfuncv((intptr_t)(TC_a))
+#define pthread_mutexattr_destroy(TC_a)  _tc_dummyfuncv((intptr_t)(TC_a))
+#define pthread_mutexattr_settype(TC_a, TC_b)  _tc_dummyfuncv((intptr_t)(TC_a), (TC_b))
+
+#define pthread_mutex_t                  intptr_t
+#undef PTHREAD_MUTEX_INITIALIZER
+#define PTHREAD_MUTEX_INITIALIZER        0
+#define pthread_mutex_init(TC_a, TC_b)   _tc_dummyfuncv((intptr_t)(TC_a), (TC_b))
+#define pthread_mutex_destroy(TC_a)      _tc_dummyfuncv((intptr_t)(TC_a))
+#define pthread_mutex_lock(TC_a)         _tc_dummyfuncv((intptr_t)(TC_a))
+#define pthread_mutex_unlock(TC_a)       _tc_dummyfuncv((intptr_t)(TC_a))
+
+#define pthread_rwlock_t                 intptr_t
+#undef PTHREAD_RWLOCK_INITIALIZER
+#define PTHREAD_RWLOCK_INITIALIZER       0
+#define pthread_rwlock_init(TC_a, TC_b)  _tc_dummyfuncv((intptr_t)(TC_a), (TC_b))
+#define pthread_rwlock_destroy(TC_a)     _tc_dummyfuncv((intptr_t)(TC_a))
+#define pthread_rwlock_rdlock(TC_a)      _tc_dummyfuncv((intptr_t)(TC_a))
+#define pthread_rwlock_wrlock(TC_a)      _tc_dummyfuncv((intptr_t)(TC_a))
+#define pthread_rwlock_unlock(TC_a)      _tc_dummyfuncv((intptr_t)(TC_a))
+
+#define pthread_key_t                    intptr_t
+#define pthread_key_create(TC_a, TC_b)   _tc_dummyfuncv((intptr_t)(TC_a), (TC_b))
+#define pthread_key_delete(TC_a)         _tc_dummyfuncv((intptr_t)(TC_a))
+#define pthread_setspecific(TC_a, TC_b)  _tc_dummyfuncv((intptr_t)(TC_a))
+#define pthread_getspecific(TC_a)        _tc_dummyfuncv((intptr_t)(TC_a))
+
+#define pthread_create(TC_th, TC_attr, TC_func, TC_arg) \
+  (*(TC_th) = 0, (TC_func)(TC_arg), 0)
+#define pthread_join(TC_th, TC_rv)       (*(TC_rv) = NULL, 0)
+#define pthread_detach(TC_th)            0
+#define sched_yield()                    _tc_dummyfunc()
+
+#endif
+
+#if TCUSEPTHREAD && TCMICROYIELD
+#define TCTESTYIELD() \
+  do { \
+    if(((++_tc_dummy_cnt) & (0x20 - 1)) == 0){ \
+      sched_yield(); \
+      if(_tc_dummy_cnt > 0x1000) _tc_dummy_cnt = (uint32_t)time(NULL) % 0x1000; \
+    } \
+  } while(false)
+#undef assert
+#define assert(TC_expr) \
+  do { \
+    if(!(TC_expr)){ \
+      fprintf(stderr, "assertion failed: %s\n", #TC_expr); \
+      abort(); \
+    } \
+    TCTESTYIELD(); \
+  } while(false)
+#define if(TC_cond) \
+  if((((++_tc_dummy_cnt) & (0x100 - 1)) != 0 || (sched_yield() * 0) == 0) && (TC_cond))
+#define while(TC_cond) \
+  while((((++_tc_dummy_cnt) & (0x100 - 1)) != 0 || (sched_yield() * 0) == 0) && (TC_cond))
+#else
+#define TCTESTYIELD() \
+  do { \
+  } while(false)
+#endif
+
+#if !defined(_POSIX_PRIORITY_SCHEDULING) && TCUSEPTHREAD
+#define sched_yield()                    usleep(1000 * 20)
+#endif
+
+
+
+/*************************************************************************************************
+ * utilities for implementation
+ *************************************************************************************************/
+
+
+#define TCNUMBUFSIZ    32                // size of a buffer for a number
+
+/* set a buffer for a variable length number */
+#define TCSETVNUMBUF(TC_len, TC_buf, TC_num) \
+  do { \
+    int _TC_num = (TC_num); \
+    if(_TC_num == 0){ \
+      ((signed char *)(TC_buf))[0] = 0; \
+      (TC_len) = 1; \
+    } else { \
+      (TC_len) = 0; \
+      while(_TC_num > 0){ \
+        int _TC_rem = _TC_num & 0x7f; \
+        _TC_num >>= 7; \
+        if(_TC_num > 0){ \
+          ((signed char *)(TC_buf))[(TC_len)] = -_TC_rem - 1; \
+        } else { \
+          ((signed char *)(TC_buf))[(TC_len)] = _TC_rem; \
+        } \
+        (TC_len)++; \
+      } \
+    } \
+  } while(false)
+
+/* set a buffer for a variable length number of 64-bit */
+#define TCSETVNUMBUF64(TC_len, TC_buf, TC_num) \
+  do { \
+    long long int _TC_num = (TC_num); \
+    if(_TC_num == 0){ \
+      ((signed char *)(TC_buf))[0] = 0; \
+      (TC_len) = 1; \
+    } else { \
+      (TC_len) = 0; \
+      while(_TC_num > 0){ \
+        int _TC_rem = _TC_num & 0x7f; \
+        _TC_num >>= 7; \
+        if(_TC_num > 0){ \
+          ((signed char *)(TC_buf))[(TC_len)] = -_TC_rem - 1; \
+        } else { \
+          ((signed char *)(TC_buf))[(TC_len)] = _TC_rem; \
+        } \
+        (TC_len)++; \
+      } \
+    } \
+  } while(false)
+
+/* read a variable length buffer */
+#define TCREADVNUMBUF(TC_buf, TC_num, TC_step) \
+  do { \
+    TC_num = 0; \
+    int _TC_base = 1; \
+    int _TC_i = 0; \
+    while(true){ \
+      if(((signed char *)(TC_buf))[_TC_i] >= 0){ \
+        TC_num += ((signed char *)(TC_buf))[_TC_i] * _TC_base; \
+        break; \
+      } \
+      TC_num += _TC_base * (((signed char *)(TC_buf))[_TC_i] + 1) * -1; \
+      _TC_base <<= 7; \
+      _TC_i++; \
+    } \
+    (TC_step) = _TC_i + 1; \
+  } while(false)
+
+/* read a variable length buffer */
+#define TCREADVNUMBUF64(TC_buf, TC_num, TC_step) \
+  do { \
+    TC_num = 0; \
+    long long int _TC_base = 1; \
+    int _TC_i = 0; \
+    while(true){ \
+      if(((signed char *)(TC_buf))[_TC_i] >= 0){ \
+        TC_num += ((signed char *)(TC_buf))[_TC_i] * _TC_base; \
+        break; \
+      } \
+      TC_num += _TC_base * (((signed char *)(TC_buf))[_TC_i] + 1) * -1; \
+      _TC_base <<= 7; \
+      _TC_i++; \
+    } \
+    (TC_step) = _TC_i + 1; \
+  } while(false)
+
+/* calculate the size of a buffer for a variable length number */
+#define TCCALCVNUMSIZE(TC_num) \
+  ((TC_num) < 0x80 ? 1 : (TC_num) < 0x4000 ? 2 : (TC_num) < 0x200000 ? 3 : \
+   (TC_num) < 0x10000000 ? 4 : 5)
+
+/* compare keys of two records by lexical order */
+#define TCCMPLEXICAL(TC_rv, TC_aptr, TC_asiz, TC_bptr, TC_bsiz) \
+  do { \
+    (TC_rv) = 0; \
+    int _TC_min = (TC_asiz) < (TC_bsiz) ? (TC_asiz) : (TC_bsiz); \
+    for(int _TC_i = 0; _TC_i < _TC_min; _TC_i++){ \
+      if(((unsigned char *)(TC_aptr))[_TC_i] != ((unsigned char *)(TC_bptr))[_TC_i]){ \
+        (TC_rv) = ((unsigned char *)(TC_aptr))[_TC_i] - ((unsigned char *)(TC_bptr))[_TC_i]; \
+        break; \
+      } \
+    } \
+    if((TC_rv) == 0) (TC_rv) = (TC_asiz) - (TC_bsiz); \
+  } while(false)
+
+
+//TODO fixit
+#define TCCMPLEXICAL2(TC_rv, TC_s1, TC_s2) \
+    TC_rv = strcmp(TC_s1, TC_s2);
+
+
+#endif                                   // duplication check
+
+
+// END OF FILE
diff --git a/tcejdb/nbproject/Package-Default.bash b/tcejdb/nbproject/Package-Default.bash
new file mode 100644 (file)
index 0000000..7b327f6
--- /dev/null
@@ -0,0 +1,75 @@
+#!/bin/bash -x
+
+#
+# Generated - do not edit!
+#
+
+# Macros
+TOP=`pwd`
+CND_PLATFORM=GNU-Linux-x86
+CND_CONF=Default
+CND_DISTDIR=dist
+CND_BUILDDIR=build
+NBTMPDIR=${CND_BUILDDIR}/${CND_CONF}/${CND_PLATFORM}/tmp-packaging
+TMPDIRNAME=tmp-packaging
+OUTPUT_PATH=libtcejdb.so.9.11.0
+OUTPUT_BASENAME=libtcejdb.so.9.11.0
+PACKAGE_TOP_DIR=tcejdb/
+
+# Functions
+function checkReturnCode
+{
+    rc=$?
+    if [ $rc != 0 ]
+    then
+        exit $rc
+    fi
+}
+function makeDirectory
+# $1 directory path
+# $2 permission (optional)
+{
+    mkdir -p "$1"
+    checkReturnCode
+    if [ "$2" != "" ]
+    then
+      chmod $2 "$1"
+      checkReturnCode
+    fi
+}
+function copyFileToTmpDir
+# $1 from-file path
+# $2 to-file path
+# $3 permission
+{
+    cp "$1" "$2"
+    checkReturnCode
+    if [ "$3" != "" ]
+    then
+        chmod $3 "$2"
+        checkReturnCode
+    fi
+}
+
+# Setup
+cd "${TOP}"
+mkdir -p ${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/package
+rm -rf ${NBTMPDIR}
+mkdir -p ${NBTMPDIR}
+
+# Copy files and create directories and links
+cd "${TOP}"
+makeDirectory "${NBTMPDIR}/tcejdb/bin"
+copyFileToTmpDir "${OUTPUT_PATH}" "${NBTMPDIR}/${PACKAGE_TOP_DIR}bin/${OUTPUT_BASENAME}" 0755
+
+
+# Generate tar file
+cd "${TOP}"
+rm -f ${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/package/tcejdb.tar
+cd ${NBTMPDIR}
+tar -vcf ../../../../${CND_DISTDIR}/${CND_CONF}/${CND_PLATFORM}/package/tcejdb.tar *
+checkReturnCode
+
+# Cleanup
+cd "${TOP}"
+rm -rf ${NBTMPDIR}
diff --git a/tcejdb/nbproject/configurations.xml b/tcejdb/nbproject/configurations.xml
new file mode 100644 (file)
index 0000000..1f502fa
--- /dev/null
@@ -0,0 +1,305 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configurationDescriptor version="84">
+  <logicalFolder name="root" displayName="root" projectFiles="true" kind="ROOT">
+    <df name="tcejdb" root=".">
+      <df name="autom4te.cache">
+        <in>output.0</in>
+        <in>requests</in>
+        <in>traces.0</in>
+      </df>
+      <df name="bros">
+        <in>bdbtest.c</in>
+        <in>cdbtest.c</in>
+        <in>cmpsqltctest.c</in>
+        <in>gdbmtest.c</in>
+        <in>maptest.cc</in>
+        <in>ndbmtest.c</in>
+        <in>qdbmtest.c</in>
+        <in>sdbmtest.c</in>
+        <in>sqltest.c</in>
+        <in>tctest.c</in>
+        <in>tdbtest.c</in>
+      </df>
+      <df name="doc">
+      </df>
+      <df name="lab">
+      </df>
+      <df name="man">
+      </df>
+      <df name="samples">
+        <df name="sample1">
+          <in>sample1.c</in>
+        </df>
+      </df>
+      <df name="testejdb">
+        <in>t1.c</in>
+        <in>t2.c</in>
+        <in>t3.c</in>
+      </df>
+      <in>bson.c</in>
+      <in>bson.h</in>
+      <in>ejdb.c</in>
+      <in>ejdb.h</in>
+      <in>ejdb_private.h</in>
+      <in>ejdbutl.h</in>
+      <in>encoding.c</in>
+      <in>encoding.h</in>
+      <in>md5.c</in>
+      <in>md5.h</in>
+      <in>myconf.c</in>
+      <in>myconf.h</in>
+      <in>numbers.c</in>
+      <in>tcadb.c</in>
+      <in>tcadb.h</in>
+      <in>tcamgr.c</in>
+      <in>tcamttest.c</in>
+      <in>tcatest.c</in>
+      <in>tcawmgr.c</in>
+      <in>tcbdb.c</in>
+      <in>tcbdb.h</in>
+      <in>tcbmgr.c</in>
+      <in>tcbmttest.c</in>
+      <in>tcbtest.c</in>
+      <in>tcfdb.c</in>
+      <in>tcfdb.h</in>
+      <in>tcfmgr.c</in>
+      <in>tcfmttest.c</in>
+      <in>tcftest.c</in>
+      <in>tchdb.c</in>
+      <in>tchdb.h</in>
+      <in>tchmgr.c</in>
+      <in>tchmttest.c</in>
+      <in>tchtest.c</in>
+      <in>tctdb.c</in>
+      <in>tctdb.h</in>
+      <in>tctmgr.c</in>
+      <in>tctmttest.c</in>
+      <in>tcttest.c</in>
+      <in>tcucodec.c</in>
+      <in>tcumttest.c</in>
+      <in>tcutest.c</in>
+      <in>tcutil.c</in>
+      <in>tcutil.h</in>
+      <in>timsort-impl.h</in>
+      <in>timsort.c</in>
+    </df>
+    <logicalFolder name="ExternalFiles"
+                   displayName="Important Files"
+                   projectFiles="false"
+                   kind="IMPORTANT_FILES_FOLDER">
+      <itemPath>Makefile</itemPath>
+      <itemPath>configure</itemPath>
+    </logicalFolder>
+  </logicalFolder>
+  <sourceFolderFilter>^(nbproject)$</sourceFolderFilter>
+  <sourceRootList>
+    <Elem>.</Elem>
+  </sourceRootList>
+  <projectmakefile>Makefile</projectmakefile>
+  <confs>
+    <conf name="Default" type="0">
+      <toolsSet>
+        <remote-sources-mode>LOCAL_SOURCES</remote-sources-mode>
+        <compilerSet>default</compilerSet>
+      </toolsSet>
+      <codeAssistance>
+      </codeAssistance>
+      <makefileType>
+        <makeTool>
+          <buildCommandWorkingDir>.</buildCommandWorkingDir>
+          <buildCommand>${MAKE} -f Makefile</buildCommand>
+          <cleanCommand>${MAKE} -f Makefile clean</cleanCommand>
+          <executablePath>libtcejdb.so.9.11.0</executablePath>
+          <cTool>
+            <incDir>
+              <pElem>../../../../include</pElem>
+              <pElem>.</pElem>
+              <pElem>/usr/local/include</pElem>
+            </incDir>
+            <preprocessorList>
+              <Elem>_GNU_SOURCE=1</Elem>
+              <Elem>_REENTRANT</Elem>
+              <Elem>_TC_APPINC="-I/usr/local/include"</Elem>
+              <Elem>_TC_APPLIBS="-L/usr/local/lib -ltokyocabinet -lbz2 -lz -lrt -lpthread -lm -lc "</Elem>
+              <Elem>_TC_BINDIR="/usr/local/bin"</Elem>
+              <Elem>_TC_INCLUDEDIR="/usr/local/include"</Elem>
+              <Elem>_TC_LIBDIR="/usr/local/lib"</Elem>
+              <Elem>_TC_LIBEXECDIR="/usr/local/libexec"</Elem>
+              <Elem>_TC_PREFIX="/usr/local"</Elem>
+              <Elem>__EXTENSIONS__</Elem>
+            </preprocessorList>
+          </cTool>
+        </makeTool>
+      </makefileType>
+      <item path="bros/bdbtest.c" ex="true" tool="0" flavor2="0">
+      </item>
+      <item path="bros/cdbtest.c" ex="true" tool="0" flavor2="0">
+      </item>
+      <item path="bros/cmpsqltctest.c" ex="true" tool="0" flavor2="0">
+      </item>
+      <item path="bros/gdbmtest.c" ex="true" tool="0" flavor2="0">
+      </item>
+      <item path="bros/maptest.cc" ex="true" tool="1" flavor2="0">
+      </item>
+      <item path="bros/ndbmtest.c" ex="true" tool="0" flavor2="0">
+      </item>
+      <item path="bros/qdbmtest.c" ex="true" tool="0" flavor2="0">
+      </item>
+      <item path="bros/sdbmtest.c" ex="true" tool="0" flavor2="0">
+      </item>
+      <item path="bros/sqltest.c" ex="true" tool="0" flavor2="0">
+      </item>
+      <item path="bros/tctest.c" ex="true" tool="0" flavor2="0">
+      </item>
+      <item path="bros/tdbtest.c" ex="true" tool="0" flavor2="0">
+      </item>
+      <item path="bson.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="ejdb.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+          <preprocessorList>
+            <Elem>_TC_APPLIBS="-L/usr/local/lib -ltcejdb -lbz2 -lz -lrt -lpthread -lm -lc "</Elem>
+          </preprocessorList>
+        </cTool>
+      </item>
+      <item path="encoding.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="md5.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="myconf.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="numbers.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="samples/sample1/sample1.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tcadb.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tcamgr.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tcamttest.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tcatest.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tcawmgr.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tcbdb.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tcbmgr.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tcbmttest.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tcbtest.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tcfdb.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tcfmgr.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tcfmttest.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tcftest.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tchdb.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tchmgr.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tchmttest.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tchtest.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tctdb.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tctmgr.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tctmttest.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tcttest.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tcucodec.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tcumttest.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tcutest.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="tcutil.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="testejdb/t1.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="testejdb/t2.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="testejdb/t3.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+      <item path="timsort.c" ex="false" tool="0" flavor2="2">
+        <cTool>
+        </cTool>
+      </item>
+    </conf>
+  </confs>
+</configurationDescriptor>
diff --git a/tcejdb/nbproject/project.xml b/tcejdb/nbproject/project.xml
new file mode 100644 (file)
index 0000000..66bf57a
--- /dev/null
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://www.netbeans.org/ns/project/1">
+    <type>org.netbeans.modules.cnd.makeproject</type>
+    <configuration>
+        <data xmlns="http://www.netbeans.org/ns/make-project/1">
+            <name>tcejdb</name>
+            <c-extensions>c</c-extensions>
+            <cpp-extensions>cc</cpp-extensions>
+            <header-extensions>h</header-extensions>
+            <sourceEncoding>UTF-8</sourceEncoding>
+            <make-dep-projects/>
+            <sourceRootList>
+                <sourceRootElem>.</sourceRootElem>
+            </sourceRootList>
+            <confList>
+                <confElem>
+                    <name>Default</name>
+                    <type>0</type>
+                </confElem>
+            </confList>
+        </data>
+    </configuration>
+</project>
diff --git a/tcejdb/numbers.c b/tcejdb/numbers.c
new file mode 100644 (file)
index 0000000..72ddea4
--- /dev/null
@@ -0,0 +1,128 @@
+
+/*    Copyright 2009-2012 10gen Inc.
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+/* all the numbers that fit in a 4 byte string */
+const char bson_numstrs[1000][4] = {
+    "0",  "1",  "2",  "3",  "4",  "5",  "6",  "7",  "8",  "9",
+    "10", "11", "12", "13", "14", "15", "16", "17", "18", "19",
+    "20", "21", "22", "23", "24", "25", "26", "27", "28", "29",
+    "30", "31", "32", "33", "34", "35", "36", "37", "38", "39",
+    "40", "41", "42", "43", "44", "45", "46", "47", "48", "49",
+    "50", "51", "52", "53", "54", "55", "56", "57", "58", "59",
+    "60", "61", "62", "63", "64", "65", "66", "67", "68", "69",
+    "70", "71", "72", "73", "74", "75", "76", "77", "78", "79",
+    "80", "81", "82", "83", "84", "85", "86", "87", "88", "89",
+    "90", "91", "92", "93", "94", "95", "96", "97", "98", "99",
+
+    "100", "101", "102", "103", "104", "105", "106", "107", "108", "109",
+    "110", "111", "112", "113", "114", "115", "116", "117", "118", "119",
+    "120", "121", "122", "123", "124", "125", "126", "127", "128", "129",
+    "130", "131", "132", "133", "134", "135", "136", "137", "138", "139",
+    "140", "141", "142", "143", "144", "145", "146", "147", "148", "149",
+    "150", "151", "152", "153", "154", "155", "156", "157", "158", "159",
+    "160", "161", "162", "163", "164", "165", "166", "167", "168", "169",
+    "170", "171", "172", "173", "174", "175", "176", "177", "178", "179",
+    "180", "181", "182", "183", "184", "185", "186", "187", "188", "189",
+    "190", "191", "192", "193", "194", "195", "196", "197", "198", "199",
+
+    "200", "201", "202", "203", "204", "205", "206", "207", "208", "209",
+    "210", "211", "212", "213", "214", "215", "216", "217", "218", "219",
+    "220", "221", "222", "223", "224", "225", "226", "227", "228", "229",
+    "230", "231", "232", "233", "234", "235", "236", "237", "238", "239",
+    "240", "241", "242", "243", "244", "245", "246", "247", "248", "249",
+    "250", "251", "252", "253", "254", "255", "256", "257", "258", "259",
+    "260", "261", "262", "263", "264", "265", "266", "267", "268", "269",
+    "270", "271", "272", "273", "274", "275", "276", "277", "278", "279",
+    "280", "281", "282", "283", "284", "285", "286", "287", "288", "289",
+    "290", "291", "292", "293", "294", "295", "296", "297", "298", "299",
+
+    "300", "301", "302", "303", "304", "305", "306", "307", "308", "309",
+    "310", "311", "312", "313", "314", "315", "316", "317", "318", "319",
+    "320", "321", "322", "323", "324", "325", "326", "327", "328", "329",
+    "330", "331", "332", "333", "334", "335", "336", "337", "338", "339",
+    "340", "341", "342", "343", "344", "345", "346", "347", "348", "349",
+    "350", "351", "352", "353", "354", "355", "356", "357", "358", "359",
+    "360", "361", "362", "363", "364", "365", "366", "367", "368", "369",
+    "370", "371", "372", "373", "374", "375", "376", "377", "378", "379",
+    "380", "381", "382", "383", "384", "385", "386", "387", "388", "389",
+    "390", "391", "392", "393", "394", "395", "396", "397", "398", "399",
+
+    "400", "401", "402", "403", "404", "405", "406", "407", "408", "409",
+    "410", "411", "412", "413", "414", "415", "416", "417", "418", "419",
+    "420", "421", "422", "423", "424", "425", "426", "427", "428", "429",
+    "430", "431", "432", "433", "434", "435", "436", "437", "438", "439",
+    "440", "441", "442", "443", "444", "445", "446", "447", "448", "449",
+    "450", "451", "452", "453", "454", "455", "456", "457", "458", "459",
+    "460", "461", "462", "463", "464", "465", "466", "467", "468", "469",
+    "470", "471", "472", "473", "474", "475", "476", "477", "478", "479",
+    "480", "481", "482", "483", "484", "485", "486", "487", "488", "489",
+    "490", "491", "492", "493", "494", "495", "496", "497", "498", "499",
+
+    "500", "501", "502", "503", "504", "505", "506", "507", "508", "509",
+    "510", "511", "512", "513", "514", "515", "516", "517", "518", "519",
+    "520", "521", "522", "523", "524", "525", "526", "527", "528", "529",
+    "530", "531", "532", "533", "534", "535", "536", "537", "538", "539",
+    "540", "541", "542", "543", "544", "545", "546", "547", "548", "549",
+    "550", "551", "552", "553", "554", "555", "556", "557", "558", "559",
+    "560", "561", "562", "563", "564", "565", "566", "567", "568", "569",
+    "570", "571", "572", "573", "574", "575", "576", "577", "578", "579",
+    "580", "581", "582", "583", "584", "585", "586", "587", "588", "589",
+    "590", "591", "592", "593", "594", "595", "596", "597", "598", "599",
+
+    "600", "601", "602", "603", "604", "605", "606", "607", "608", "609",
+    "610", "611", "612", "613", "614", "615", "616", "617", "618", "619",
+    "620", "621", "622", "623", "624", "625", "626", "627", "628", "629",
+    "630", "631", "632", "633", "634", "635", "636", "637", "638", "639",
+    "640", "641", "642", "643", "644", "645", "646", "647", "648", "649",
+    "650", "651", "652", "653", "654", "655", "656", "657", "658", "659",
+    "660", "661", "662", "663", "664", "665", "666", "667", "668", "669",
+    "670", "671", "672", "673", "674", "675", "676", "677", "678", "679",
+    "680", "681", "682", "683", "684", "685", "686", "687", "688", "689",
+    "690", "691", "692", "693", "694", "695", "696", "697", "698", "699",
+
+    "700", "701", "702", "703", "704", "705", "706", "707", "708", "709",
+    "710", "711", "712", "713", "714", "715", "716", "717", "718", "719",
+    "720", "721", "722", "723", "724", "725", "726", "727", "728", "729",
+    "730", "731", "732", "733", "734", "735", "736", "737", "738", "739",
+    "740", "741", "742", "743", "744", "745", "746", "747", "748", "749",
+    "750", "751", "752", "753", "754", "755", "756", "757", "758", "759",
+    "760", "761", "762", "763", "764", "765", "766", "767", "768", "769",
+    "770", "771", "772", "773", "774", "775", "776", "777", "778", "779",
+    "780", "781", "782", "783", "784", "785", "786", "787", "788", "789",
+    "790", "791", "792", "793", "794", "795", "796", "797", "798", "799",
+
+    "800", "801", "802", "803", "804", "805", "806", "807", "808", "809",
+    "810", "811", "812", "813", "814", "815", "816", "817", "818", "819",
+    "820", "821", "822", "823", "824", "825", "826", "827", "828", "829",
+    "830", "831", "832", "833", "834", "835", "836", "837", "838", "839",
+    "840", "841", "842", "843", "844", "845", "846", "847", "848", "849",
+    "850", "851", "852", "853", "854", "855", "856", "857", "858", "859",
+    "860", "861", "862", "863", "864", "865", "866", "867", "868", "869",
+    "870", "871", "872", "873", "874", "875", "876", "877", "878", "879",
+    "880", "881", "882", "883", "884", "885", "886", "887", "888", "889",
+    "890", "891", "892", "893", "894", "895", "896", "897", "898", "899",
+
+    "900", "901", "902", "903", "904", "905", "906", "907", "908", "909",
+    "910", "911", "912", "913", "914", "915", "916", "917", "918", "919",
+    "920", "921", "922", "923", "924", "925", "926", "927", "928", "929",
+    "930", "931", "932", "933", "934", "935", "936", "937", "938", "939",
+    "940", "941", "942", "943", "944", "945", "946", "947", "948", "949",
+    "950", "951", "952", "953", "954", "955", "956", "957", "958", "959",
+    "960", "961", "962", "963", "964", "965", "966", "967", "968", "969",
+    "970", "971", "972", "973", "974", "975", "976", "977", "978", "979",
+    "980", "981", "982", "983", "984", "985", "986", "987", "988", "989",
+    "990", "991", "992", "993", "994", "995", "996", "997", "998", "999",
+};
diff --git a/tcejdb/samples/sample1/Makefile b/tcejdb/samples/sample1/Makefile
new file mode 100644 (file)
index 0000000..90769f3
--- /dev/null
@@ -0,0 +1,13 @@
+
+CC=gcc
+CPPFLAGS = -I. -I../../../ -D_GNU_SOURCE=1
+CFLAGS = -std=c99 -Wall -fPIC -pedantic -O2
+LDFLAGS = -L../../
+LIBS =
+
+sample1 : sample1.o
+       $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) -ltcejdb $(LIBS)
+
+run : sample1
+       export LD_LIBRARY_PATH=../.. && ./sample1
+
diff --git a/tcejdb/samples/sample1/sample1.c b/tcejdb/samples/sample1/sample1.c
new file mode 100644 (file)
index 0000000..426467b
--- /dev/null
@@ -0,0 +1,72 @@
+#include <tcejdb/ejdb.h>
+#include <locale.h>
+
+static EJDB* jb;
+
+int main() {
+    setlocale(LC_ALL, "en_US.UTF-8");
+    jb = ejdbnew();
+    if (!ejdbopen(jb, "addressbook", JDBOWRITER | JDBOCREAT | JDBOTRUNC)) {
+        return 1;
+    }
+
+    //Get or create collection 'contacts'
+    EJCOLL *coll = ejdbcreatecoll(jb, "contacts", NULL);
+
+    bson bsrec;
+    bson_oid_t oid;
+
+    //One record
+    bson_init(&bsrec);
+    bson_append_string(&bsrec, "name", "John Travolta");
+    bson_append_string(&bsrec, "phone", "333-222-333");
+    bson_append_int(&bsrec, "age", 58);
+    bson_finish(&bsrec);
+    ejdbsavebson(coll, &bsrec, &oid);
+    fprintf(stderr, "\nSaved Travolta");
+    bson_destroy(&bsrec);
+
+
+    //Another record
+    bson_init(&bsrec);
+    bson_append_string(&bsrec, "name", "Bruce Willis");
+    bson_append_string(&bsrec, "phone", "222-333-222");
+    bson_append_int(&bsrec, "age", 57);
+    bson_finish(&bsrec);
+    ejdbsavebson(coll, &bsrec, &oid);
+    fprintf(stderr, "\nSaved Bruce Willis");
+    bson_destroy(&bsrec);
+
+
+    //Now select one record.
+    //Query: {'name' : {'$begin' : 'Bru'}} //Name starts with 'Bru' string
+    bson bq1;
+    bson_init_as_query(&bq1);
+    bson_append_start_object(&bq1, "name");
+    bson_append_string(&bq1, "$begin", "Bru");
+    bson_append_finish_object(&bq1);
+    bson_finish(&bq1);
+    EJQ* q1 = ejdbcreatequery(jb, &bq1, NULL, 0, NULL);
+
+    uint32_t count;
+    TCLIST *res = ejdbqrysearch(coll, q1, &count, 0, NULL);
+    fprintf(stderr, "\n\nRecords found: %d\n", count);
+
+    for (int i = 0; i < TCLISTNUM(res); ++i) {
+        void *bsdata = TCLISTVALPTR(res, i);
+        bson_print_raw(stderr, bsdata, 0);
+    }
+    fprintf(stderr, "\n");
+
+    //Dispose result set
+    tclistdel(res);
+
+    //Dispose query
+    ejdbquerydel(q1);
+    bson_destroy(&bq1);
+
+    //Close database
+    ejdbclose(jb);
+    ejdbdel(jb);
+    return 0;
+}
diff --git a/tcejdb/tcadb.c b/tcejdb/tcadb.c
new file mode 100644 (file)
index 0000000..9777d66
--- /dev/null
@@ -0,0 +1,4338 @@
+/*************************************************************************************************
+ * 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
diff --git a/tcejdb/tcadb.h b/tcejdb/tcadb.h
new file mode 100644 (file)
index 0000000..616bdac
--- /dev/null
@@ -0,0 +1,548 @@
+/*************************************************************************************************
+ * 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.
+ *************************************************************************************************/
+
+
+#ifndef _TCADB_H                         /* duplication check */
+#define _TCADB_H
+
+#if defined(__cplusplus)
+#define __TCADB_CLINKAGEBEGIN extern "C" {
+#define __TCADB_CLINKAGEEND }
+#else
+#define __TCADB_CLINKAGEBEGIN
+#define __TCADB_CLINKAGEEND
+#endif
+__TCADB_CLINKAGEBEGIN
+
+
+#include <tcutil.h>
+#include <tchdb.h>
+#include <tcbdb.h>
+#include <tcfdb.h>
+#include <tctdb.h>
+
+
+
+/*************************************************************************************************
+ * API
+ *************************************************************************************************/
+
+
+typedef struct {                         /* type of structure for an abstract database */
+  int omode;                             /* open mode */
+  TCMDB *mdb;                            /* on-memory hash database object */
+  TCNDB *ndb;                            /* on-memory tree database object */
+  TCHDB *hdb;                            /* hash database object */
+  TCBDB *bdb;                            /* B+ tree database object */
+  TCFDB *fdb;                            /* fixed-length databae object */
+  TCTDB *tdb;                            /* table database object */
+  int64_t capnum;                        /* capacity number of records */
+  int64_t capsiz;                        /* capacity size of using memory */
+  uint32_t capcnt;                       /* count for capacity check */
+  BDBCUR *cur;                           /* cursor of B+ tree */
+  void *skel;                            /* skeleton database */
+} TCADB;
+
+enum {                                   /* enumeration for open modes */
+  ADBOVOID,                              /* not opened */
+  ADBOMDB,                               /* on-memory hash database */
+  ADBONDB,                               /* on-memory tree database */
+  ADBOHDB,                               /* hash database */
+  ADBOBDB,                               /* B+ tree database */
+  ADBOFDB,                               /* fixed-length database */
+  ADBOTDB,                               /* table database */
+  ADBOSKEL                               /* skeleton database */
+};
+
+
+/* Create an abstract database object.
+   The return value is the new abstract database object. */
+TCADB *tcadbnew(void);
+
+
+/* Delete an abstract database object.
+   `adb' specifies the abstract database object. */
+void tcadbdel(TCADB *adb);
+
+
+/* Open an abstract database.
+   `adb' specifies the abstract database object.
+   `name' specifies the name of the database.  If it is "*", the database will be an on-memory
+   hash database.  If it is "+", the database will be an on-memory tree database.  If its suffix
+   is ".tch", the database will be a hash database.  If its suffix is ".tcb", the database will
+   be a B+ tree database.  If its suffix is ".tcf", the database will be a fixed-length database.
+   If its suffix is ".tct", the database will be a table database.  Otherwise, this function
+   fails.  Tuning parameters can trail the name, separated by "#".  Each parameter is composed of
+   the name and the value, separated by "=".  On-memory hash database supports "bnum", "capnum",
+   and "capsiz".  On-memory tree database supports "capnum" and "capsiz".  Hash database supports
+   "mode", "bnum", "apow", "fpow", "opts", "rcnum", "xmsiz", and "dfunit".  B+ tree database
+   supports "mode", "lmemb", "nmemb", "bnum", "apow", "fpow", "opts", "lcnum", "ncnum", "xmsiz",
+   and "dfunit".  Fixed-length database supports "mode", "width", and "limsiz".  Table database
+   supports "mode", "bnum", "apow", "fpow", "opts", "rcnum", "lcnum", "ncnum", "xmsiz", "dfunit",
+   and "idx".
+   If successful, the return value is true, else, it is false.
+   The tuning parameter "capnum" specifies the capacity number of records.  "capsiz" specifies
+   the capacity size of using memory.  Records spilled the capacity are removed by the storing
+   order.  "mode" can contain "w" of writer, "r" of reader, "c" of creating, "t" of truncating,
+   "e" of no locking, and "f" of non-blocking lock.  The default mode is relevant to "wc".
+   "opts" can contains "l" of large option, "d" of Deflate option, "b" of BZIP2 option, and "t"
+   of TCBS option.  "idx" specifies the column name of an index and its type separated by ":".
+   For example, "casket.tch#bnum=1000000#opts=ld" means that the name of the database file is
+   "casket.tch", and the bucket number is 1000000, and the options are large and Deflate. */
+bool tcadbopen(TCADB *adb, const char *name);
+
+
+/* Close an abstract database object.
+   `adb' specifies the abstract database object.
+   If successful, the return value is true, else, it is false.
+   Update of a database is assured to be written when the database is closed.  If a writer opens
+   a database but does not close it appropriately, the database will be broken. */
+bool tcadbclose(TCADB *adb);
+
+
+/* Store a record into an abstract database object.
+   `adb' specifies the abstract 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.
+   If a record with the same key exists in the database, it is overwritten. */
+bool tcadbput(TCADB *adb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Store a string record into an abstract object.
+   `adb' specifies the abstract database object.
+   `kstr' specifies the string of the key.
+   `vstr' specifies the string of the value.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the database, it is overwritten. */
+bool tcadbput2(TCADB *adb, const char *kstr, const char *vstr);
+
+
+/* Store a new record into an abstract database object.
+   `adb' specifies the abstract 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.
+   If a record with the same key exists in the database, this function has no effect. */
+bool tcadbputkeep(TCADB *adb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Store a new string record into an abstract database object.
+   `adb' specifies the abstract database object.
+   `kstr' specifies the string of the key.
+   `vstr' specifies the string of the value.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the database, this function has no effect. */
+bool tcadbputkeep2(TCADB *adb, const char *kstr, const char *vstr);
+
+
+/* Concatenate a value at the end of the existing record in an abstract database object.
+   `adb' specifies the abstract 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.
+   If there is no corresponding record, a new record is created. */
+bool tcadbputcat(TCADB *adb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Concatenate a string value at the end of the existing record in an abstract database object.
+   `adb' specifies the abstract database object.
+   `kstr' specifies the string of the key.
+   `vstr' specifies the string of the value.
+   If successful, the return value is true, else, it is false.
+   If there is no corresponding record, a new record is created. */
+bool tcadbputcat2(TCADB *adb, const char *kstr, const char *vstr);
+
+
+/* Remove a record of an abstract database object.
+   `adb' specifies the abstract 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. */
+bool tcadbout(TCADB *adb, const void *kbuf, int ksiz);
+
+
+/* Remove a string record of an abstract database object.
+   `adb' specifies the abstract database object.
+   `kstr' specifies the string of the key.
+   If successful, the return value is true, else, it is false. */
+bool tcadbout2(TCADB *adb, const char *kstr);
+
+
+/* Retrieve a record in an abstract database object.
+   `adb' specifies the abstract 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.  `NULL' is returned if no record corresponds.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when
+   it is no longer in use. */
+void *tcadbget(TCADB *adb, const void *kbuf, int ksiz, int *sp);
+
+
+/* Retrieve a string record in an abstract database object.
+   `adb' specifies the abstract database object.
+   `kstr' specifies the string of the key.
+   If successful, the return value is the string of the value of the corresponding record.
+   `NULL' is returned if no record corresponds.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+char *tcadbget2(TCADB *adb, const char *kstr);
+
+
+/* Get the size of the value of a record in an abstract database object.
+   `adb' specifies the abstract 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. */
+int tcadbvsiz(TCADB *adb, const void *kbuf, int ksiz);
+
+
+/* Get the size of the value of a string record in an abstract database object.
+   `adb' specifies the abstract database object.
+   `kstr' specifies the string of the key.
+   If successful, the return value is the size of the value of the corresponding record, else,
+   it is -1. */
+int tcadbvsiz2(TCADB *adb, const char *kstr);
+
+
+/* Initialize the iterator of an abstract database object.
+   `adb' specifies the abstract database object.
+   If successful, the return value is true, else, it is false.
+   The iterator is used in order to access the key of every record stored in a database. */
+bool tcadbiterinit(TCADB *adb);
+
+
+/* Get the next key of the iterator of an abstract database object.
+   `adb' specifies the abstract 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'.  `NULL' is returned when no record is to be get out of the iterator.
+   Because an additional zero code is appended at the end of the region of the return value, the
+   return value can be treated as a character string.  Because the region of the return value is
+   allocated with the `malloc' call, it should be released with the `free' call when it is no
+   longer in use.  It is possible to access every record by iteration of calling this function.
+   It is allowed to update or remove records whose keys are fetched while the iteration.
+   However, it is not assured if updating the database is occurred while the iteration.  Besides,
+   the order of this traversal access method is arbitrary, so it is not assured that the order of
+   storing matches the one of the traversal access. */
+void *tcadbiternext(TCADB *adb, int *sp);
+
+
+/* Get the next key string of the iterator of an abstract database object.
+   `adb' specifies the abstract database object.
+   If successful, the return value is the string of the next key, else, it is `NULL'.  `NULL' is
+   returned when no record is to be get out of the iterator.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use.  It is possible to access every
+   record by iteration of calling this function.  However, it is not assured if updating the
+   database is occurred while the iteration.  Besides, the order of this traversal access method
+   is arbitrary, so it is not assured that the order of storing matches the one of the traversal
+   access. */
+char *tcadbiternext2(TCADB *adb);
+
+
+/* Get forward matching keys in an abstract database object.
+   `adb' specifies the abstract 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.  This function does never fail.
+   It returns an empty list even if no key corresponds.
+   Because the object of the return value is created with the function `tclistnew', it should be
+   deleted with the function `tclistdel' when it is no longer in use.  Note that this function
+   may be very slow because every key in the database is scanned. */
+TCLIST *tcadbfwmkeys(TCADB *adb, const void *pbuf, int psiz, int max);
+
+
+/* Get forward matching string keys in an abstract database object.
+   `adb' specifies the abstract database object.
+   `pstr' specifies the string 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.  This function does never fail.
+   It returns an empty list even if no key corresponds.
+   Because the object of the return value is created with the function `tclistnew', it should be
+   deleted with the function `tclistdel' when it is no longer in use.  Note that this function
+   may be very slow because every key in the database is scanned. */
+TCLIST *tcadbfwmkeys2(TCADB *adb, const char *pstr, int max);
+
+
+/* Add an integer to a record in an abstract database object.
+   `adb' specifies the abstract 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'.
+   If the corresponding record exists, the value is treated as an integer and is added to.  If no
+   record corresponds, a new record of the additional value is stored. */
+int tcadbaddint(TCADB *adb, const void *kbuf, int ksiz, int num);
+
+
+/* Add a real number to a record in an abstract database object.
+   `adb' specifies the abstract 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.
+   If the corresponding record exists, the value is treated as a real number and is added to.  If
+   no record corresponds, a new record of the additional value is stored. */
+double tcadbadddouble(TCADB *adb, const void *kbuf, int ksiz, double num);
+
+
+/* Synchronize updated contents of an abstract database object with the file and the device.
+   `adb' specifies the abstract database object.
+   If successful, the return value is true, else, it is false. */
+bool tcadbsync(TCADB *adb);
+
+
+/* Optimize the storage of an abstract database object.
+   `adb' specifies the abstract database object.
+   `params' specifies the string of the tuning parameters, which works as with the tuning
+   of parameters the function `tcadbopen'.  If it is `NULL', it is not used.
+   If successful, the return value is true, else, it is false.
+   This function is useful to reduce the size of the database storage with data fragmentation by
+   successive updating. */
+bool tcadboptimize(TCADB *adb, const char *params);
+
+
+/* Remove all records of an abstract database object.
+   `adb' specifies the abstract database object.
+   If successful, the return value is true, else, it is false. */
+bool tcadbvanish(TCADB *adb);
+
+
+/* Copy the database file of an abstract database object.
+   `adb' specifies the abstract database object.
+   `path' specifies the path of the destination file.  If it begins with `@', the trailing
+   substring is executed as a command line.
+   If successful, the return value is true, else, it is false.  False is returned if the executed
+   command returns non-zero code.
+   The database file is assured to be kept synchronized and not modified while the copying or
+   executing operation is in progress.  So, this function is useful to create a backup file of
+   the database file. */
+bool tcadbcopy(TCADB *adb, const char *path);
+
+
+/* Begin the transaction of an abstract database object.
+   `adb' specifies the abstract database object.
+   If successful, the return value is true, else, it is false.
+   The database is locked by the thread while the transaction so that only one transaction can be
+   activated with a database object at the same time.  Thus, the serializable isolation level is
+   assumed if every database operation is performed in the transaction.  All updated regions are
+   kept track of by write ahead logging while the transaction.  If the database is closed during
+   transaction, the transaction is aborted implicitly. */
+bool tcadbtranbegin(TCADB *adb);
+
+
+/* Commit the transaction of an abstract database object.
+   `adb' specifies the abstract database object.
+   If successful, the return value is true, else, it is false.
+   Update in the transaction is fixed when it is committed successfully. */
+bool tcadbtrancommit(TCADB *adb);
+
+
+/* Abort the transaction of an abstract database object.
+   `adb' specifies the abstract database object.
+   If successful, the return value is true, else, it is false.
+   Update in the transaction is discarded when it is aborted.  The state of the database is
+   rollbacked to before transaction. */
+bool tcadbtranabort(TCADB *adb);
+
+
+/* Get the file path of an abstract database object.
+   `adb' specifies the abstract database object.
+   The return value is the path of the database file or `NULL' if the object does not connect to
+   any database.  "*" stands for on-memory hash database.  "+" stands for on-memory tree
+   database. */
+const char *tcadbpath(TCADB *adb);
+
+
+/* Get the number of records of an abstract database object.
+   `adb' specifies the abstract database object.
+   The return value is the number of records or 0 if the object does not connect to any database
+   instance. */
+uint64_t tcadbrnum(TCADB *adb);
+
+
+/* Get the size of the database of an abstract database object.
+   `adb' specifies the abstract database object.
+   The return value is the size of the database or 0 if the object does not connect to any
+   database instance. */
+uint64_t tcadbsize(TCADB *adb);
+
+
+/* Call a versatile function for miscellaneous operations of an abstract database object.
+   `adb' specifies the abstract database object.
+   `name' specifies the name of the function.  All databases support "put", "out", "get",
+   "putlist", "outlist", "getlist", and "getpart".  "put" is to store a record.  It receives a
+   key and a value, and returns an empty list.  "out" is to remove a record.  It receives a key,
+   and returns an empty list.  "get" is to retrieve a record.  It receives a key, and returns a
+   list of the values.  "putlist" is to store records.  It receives keys and values one after the
+   other, and returns an empty list.  "outlist" is to remove records.  It receives keys, and
+   returns an empty list.  "getlist" is to retrieve records.  It receives keys, and returns keys
+   and values of corresponding records one after the other.  "getpart" is to retrieve the partial
+   value of a record.  It receives a key, the offset of the region, and the length of the region.
+   `args' specifies a list object containing arguments.
+   If successful, the return value is a list object of the result.  `NULL' is returned on failure.
+   Because the object of the return value is created with the function `tclistnew', it
+   should be deleted with the function `tclistdel' when it is no longer in use. */
+TCLIST *tcadbmisc(TCADB *adb, const char *name, const TCLIST *args);
+
+
+
+/*************************************************************************************************
+ * features for experts
+ *************************************************************************************************/
+
+
+typedef struct {                         /* type of structure for a extra database skeleton */
+  void *opq;                             /* opaque pointer */
+  void (*del)(void *);                   /* destructor */
+  bool (*open)(void *, const char *);
+  bool (*close)(void *);
+  bool (*put)(void *, const void *, int, const void *, int);
+  bool (*putkeep)(void *, const void *, int, const void *, int);
+  bool (*putcat)(void *, const void *, int, const void *, int);
+  bool (*out)(void *, const void *, int);
+  void *(*get)(void *, const void *, int, int *);
+  int (*vsiz)(void *, const void *, int);
+  bool (*iterinit)(void *);
+  void *(*iternext)(void *, int *);
+  TCLIST *(*fwmkeys)(void *, const void *, int, int);
+  int (*addint)(void *, const void *, int, int);
+  double (*adddouble)(void *, const void *, int, double);
+  bool (*sync)(void *);
+  bool (*optimize)(void *, const char *);
+  bool (*vanish)(void *);
+  bool (*copy)(void *, const char *);
+  bool (*tranbegin)(void *);
+  bool (*trancommit)(void *);
+  bool (*tranabort)(void *);
+  const char *(*path)(void *);
+  uint64_t (*rnum)(void *);
+  uint64_t (*size)(void *);
+  TCLIST *(*misc)(void *, const char *, const TCLIST *);
+  bool (*putproc)(void *, const void *, int, const void *, int, TCPDPROC, void *);
+  bool (*foreach)(void *, TCITER, void *);
+} ADBSKEL;
+
+/* type of the pointer to a mapping function.
+   `map' specifies the pointer to the destination manager.
+   `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. */
+typedef bool (*ADBMAPPROC)(void *map, const char *kbuf, int ksiz, const char *vbuf, int vsiz,
+                           void *op);
+
+
+/* Set an extra database sleleton to an abstract database object.
+   `adb' specifies the abstract database object.
+   `skel' specifies the extra database skeleton.
+   If successful, the return value is true, else, it is false. */
+bool tcadbsetskel(TCADB *adb, ADBSKEL *skel);
+
+
+/* Set the multiple database skeleton to an abstract database object.
+   `adb' specifies the abstract database object.
+   `num' specifies the number of inner databases.
+   If successful, the return value is true, else, it is false. */
+bool tcadbsetskelmulti(TCADB *adb, int num);
+
+
+/* Get the open mode of an abstract database object.
+   `adb' specifies the abstract database object.
+   The return value is `ADBOVOID' for not opened database, `ADBOMDB' for on-memory hash database,
+  `ADBONDB' for on-memory tree database, `ADBOHDB' for hash database, `ADBOBDB' for B+ tree
+  database, `ADBOFDB' for fixed-length database, `ADBOTDB' for table database. */
+int tcadbomode(TCADB *adb);
+
+
+/* Get the concrete database object of an abstract database object.
+   `adb' specifies the abstract database object.
+   The return value is the concrete database object depend on the open mode or 0 if the object
+   does not connect to any database instance. */
+void *tcadbreveal(TCADB *adb);
+
+
+/* Store a record into an abstract database object with a duplication handler.
+   `adb' specifies the abstract 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
+   it is not needed, `NULL' can be specified.
+   If successful, the return value is true, else, it is false.
+   This function does not work for the table database. */
+bool tcadbputproc(TCADB *adb, const void *kbuf, int ksiz, const void *vbuf, int vsiz,
+                  TCPDPROC proc, void *op);
+
+
+/* Process each record atomically of an abstract database object.
+   `adb' specifies the abstract 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
+   it is not needed, `NULL' can be specified.
+   If successful, the return value is true, else, it is false. */
+bool tcadbforeach(TCADB *adb, TCITER iter, void *op);
+
+
+/* Map records of an abstract database object into another B+ tree database.
+   `adb' specifies the abstract database object.
+   `keys' specifies a list object of the keys of the target records.  If it is `NULL', every
+   record is processed.
+   `bdb' specifies the B+ tree database object into which records emitted by the mapping function
+   are stored.
+   `proc' specifies the pointer to the mapping function called for each record.
+   `op' specifies specifies the pointer to the optional opaque object for the mapping function.
+   `csiz' specifies the size of the cache to sort emitted records.  If it is negative, the
+   default size is specified.  The default size is 268435456.
+   If successful, the return value is true, else, it is false. */
+bool tcadbmapbdb(TCADB *adb, TCLIST *keys, TCBDB *bdb, ADBMAPPROC proc, void *op, int64_t csiz);
+
+
+/* Emit records generated by the mapping function into the result map.
+   `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. */
+bool tcadbmapbdbemit(void *map, const char *kbuf, int ksiz, const char *vbuf, int vsiz);
+
+
+
+__TCADB_CLINKAGEEND
+#endif                                   /* duplication check */
+
+
+/* END OF FILE */
diff --git a/tcejdb/tcamgr.c b/tcejdb/tcamgr.c
new file mode 100644 (file)
index 0000000..3677559
--- /dev/null
@@ -0,0 +1,1027 @@
+/*************************************************************************************************
+ * The command line utility of the abstract database API
+ *                                                               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 <tcbdb.h>
+#include <tcadb.h>
+#include "myconf.h"
+
+
+/* global variables */
+const char *g_progname;                  // program name
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+static void usage(void);
+static void printerr(TCADB *adb);
+static int sepstrtochr(const char *str);
+static char *strtozsv(const char *str, int sep, int *sp);
+static int printdata(const char *ptr, int size, bool px, int sep);
+static void setskeltran(ADBSKEL *skel);
+static bool mapbdbproc(void *map, const char *kbuf, int ksiz, const char *vbuf, int vsiz,
+                       void *op);
+static int runcreate(int argc, char **argv);
+static int runinform(int argc, char **argv);
+static int runput(int argc, char **argv);
+static int runout(int argc, char **argv);
+static int runget(int argc, char **argv);
+static int runlist(int argc, char **argv);
+static int runoptimize(int argc, char **argv);
+static int runmisc(int argc, char **argv);
+static int runmap(int argc, char **argv);
+static int runversion(int argc, char **argv);
+static int proccreate(const char *name);
+static int procinform(const char *name);
+static int procput(const char *name, const char *kbuf, int ksiz, const char *vbuf, int vsiz,
+                   int dmode);
+static int procout(const char *name, const char *kbuf, int ksiz);
+static int procget(const char *name, const char *kbuf, int ksiz, int sep, bool px, bool pz);
+static int proclist(const char *name, int sep, int max, bool pv, bool px, const char *fmstr);
+static int procoptimize(const char *name, const char *params);
+static int procmisc(const char *name, const char *func, const TCLIST *args, int sep, bool px);
+static int procmap(const char *name, const char *dest, const char *fmstr);
+static int procversion(void);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  g_progname = argv[0];
+  if(argc < 2) usage();
+  int rv = 0;
+  if(!strcmp(argv[1], "create")){
+    rv = runcreate(argc, argv);
+  } else if(!strcmp(argv[1], "inform")){
+    rv = runinform(argc, argv);
+  } else if(!strcmp(argv[1], "put")){
+    rv = runput(argc, argv);
+  } else if(!strcmp(argv[1], "out")){
+    rv = runout(argc, argv);
+  } else if(!strcmp(argv[1], "get")){
+    rv = runget(argc, argv);
+  } else if(!strcmp(argv[1], "list")){
+    rv = runlist(argc, argv);
+  } else if(!strcmp(argv[1], "optimize")){
+    rv = runoptimize(argc, argv);
+  } else if(!strcmp(argv[1], "misc")){
+    rv = runmisc(argc, argv);
+  } else if(!strcmp(argv[1], "map")){
+    rv = runmap(argc, argv);
+  } else if(!strcmp(argv[1], "version") || !strcmp(argv[1], "--version")){
+    rv = runversion(argc, argv);
+  } else {
+    usage();
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+static void usage(void){
+  fprintf(stderr, "%s: the command line utility of the abstract database API\n", g_progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s create name\n", g_progname);
+  fprintf(stderr, "  %s inform name\n", g_progname);
+  fprintf(stderr, "  %s put [-sx] [-sep chr] [-dk|-dc|-dai|-dad] name key value\n", g_progname);
+  fprintf(stderr, "  %s out [-sx] [-sep chr] name key\n", g_progname);
+  fprintf(stderr, "  %s get [-sx] [-sep chr] [-px] [-pz] name key\n", g_progname);
+  fprintf(stderr, "  %s list [-sep chr] [-m num] [-pv] [-px] [-fm str] name\n", g_progname);
+  fprintf(stderr, "  %s optimize name [params]\n", g_progname);
+  fprintf(stderr, "  %s misc [-sx] [-sep chr] [-px] name func [arg...]\n", g_progname);
+  fprintf(stderr, "  %s map [-fm str] name dest\n", g_progname);
+  fprintf(stderr, "  %s version\n", g_progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* get the character of separation string */
+static int sepstrtochr(const char *str){
+  if(!strcmp(str, "\\t")) return '\t';
+  if(!strcmp(str, "\\r")) return '\r';
+  if(!strcmp(str, "\\n")) return '\n';
+  return *(unsigned char *)str;
+}
+
+
+/* encode a string as a zero separaterd string */
+static char *strtozsv(const char *str, int sep, int *sp){
+  int size = strlen(str);
+  char *buf = tcmemdup(str, size);
+  for(int i = 0; i < size; i++){
+    if(buf[i] == sep) buf[i] = '\0';
+  }
+  *sp = size;
+  return buf;
+}
+
+
+/* print error information */
+static void printerr(TCADB *adb){
+  const char *path = tcadbpath(adb);
+  fprintf(stderr, "%s: %s: error\n", g_progname, path ? path : "-");
+}
+
+
+/* print record data */
+static int printdata(const char *ptr, int size, bool px, int sep){
+  int len = 0;
+  while(size-- > 0){
+    if(px){
+      if(len > 0) putchar(' ');
+      len += printf("%02X", *(unsigned char *)ptr);
+    } else if(sep > 0){
+      if(*ptr == '\0'){
+        putchar(sep);
+      } else {
+        putchar(*ptr);
+      }
+      len++;
+    } else {
+      putchar(*ptr);
+      len++;
+    }
+    ptr++;
+  }
+  return len;
+}
+
+
+/* set the transparent skeleton database */
+static void setskeltran(ADBSKEL *skel){
+  memset(skel, 0, sizeof(*skel));
+  skel->opq = tcadbnew();
+  skel->del = (void (*)(void *))tcadbdel;
+  skel->open = (bool (*)(void *, const char *))tcadbopen;
+  skel->close = (bool (*)(void *))tcadbclose;
+  skel->put = (bool (*)(void *, const void *, int, const void *, int))tcadbput;
+  skel->putkeep = (bool (*)(void *, const void *, int, const void *, int))tcadbputkeep;
+  skel->putcat = (bool (*)(void *, const void *, int, const void *, int))tcadbputcat;
+  skel->out = (bool (*)(void *, const void *, int))tcadbout;
+  skel->get = (void *(*)(void *, const void *, int, int *))tcadbget;
+  skel->vsiz = (int (*)(void *, const void *, int))tcadbvsiz;
+  skel->iterinit = (bool (*)(void *))tcadbiterinit;
+  skel->iternext = (void *(*)(void *, int *))tcadbiternext;
+  skel->fwmkeys = (TCLIST *(*)(void *, const void *, int, int))tcadbfwmkeys;
+  skel->addint = (int (*)(void *, const void *, int, int))tcadbaddint;
+  skel->adddouble = (double (*)(void *, const void *, int, double))tcadbadddouble;
+  skel->sync = (bool (*)(void *))tcadbsync;
+  skel->optimize = (bool (*)(void *, const char *))tcadboptimize;
+  skel->vanish = (bool (*)(void *))tcadbvanish;
+  skel->copy = (bool (*)(void *, const char *))tcadbcopy;
+  skel->tranbegin = (bool (*)(void *))tcadbtranbegin;
+  skel->trancommit = (bool (*)(void *))tcadbtrancommit;
+  skel->tranabort = (bool (*)(void *))tcadbtranabort;
+  skel->path = (const char *(*)(void *))tcadbpath;
+  skel->rnum = (uint64_t (*)(void *))tcadbrnum;
+  skel->size = (uint64_t (*)(void *))tcadbsize;
+  skel->misc = (TCLIST *(*)(void *, const char *, const TCLIST *))tcadbmisc;
+  skel->putproc =
+    (bool (*)(void *, const void *, int, const void *, int, TCPDPROC, void *))tcadbputproc;
+  skel->foreach = (bool (*)(void *, TCITER, void *))tcadbforeach;
+}
+
+
+/* mapping function */
+static bool mapbdbproc(void *map, const char *kbuf, int ksiz, const char *vbuf, int vsiz,
+                       void *op){
+  bool err = false;
+  if(!tcadbmapbdbemit(map, kbuf, ksiz, vbuf, vsiz)) err = true;
+  return !err;
+}
+
+
+/* parse arguments of create command */
+static int runcreate(int argc, char **argv){
+  char *name = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name) usage();
+  int rv = proccreate(name);
+  return rv;
+}
+
+
+/* parse arguments of inform command */
+static int runinform(int argc, char **argv){
+  char *name = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name) usage();
+  name = tcsprintf("%s#mode=r", name);
+  int rv = procinform(name);
+  tcfree(name);
+  return rv;
+}
+
+
+/* parse arguments of put command */
+static int runput(int argc, char **argv){
+  char *name = NULL;
+  char *key = NULL;
+  char *value = NULL;
+  int dmode = 0;
+  bool sx = false;
+  int sep = -1;
+  for(int i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-dk")){
+        dmode = -1;
+      } else if(!strcmp(argv[i], "-dc")){
+        dmode = 1;
+      } else if(!strcmp(argv[i], "-dai")){
+        dmode = 10;
+      } else if(!strcmp(argv[i], "-dad")){
+        dmode = 11;
+      } else if(!strcmp(argv[i], "-sx")){
+        sx = true;
+      } else if(!strcmp(argv[i], "-sep")){
+        if(++i >= argc) usage();
+        sep = sepstrtochr(argv[i]);
+      } else {
+        usage();
+      }
+    } else if(!name){
+      name = argv[i];
+    } else if(!key){
+      key = argv[i];
+    } else if(!value){
+      value = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !key || !value) usage();
+  char *kbuf, *vbuf;
+  int ksiz, vsiz;
+  if(sx){
+    kbuf = tchexdecode(key, &ksiz);
+    vbuf = tchexdecode(value, &vsiz);
+  } else if(sep > 0){
+    kbuf = strtozsv(key, sep, &ksiz);
+    vbuf = strtozsv(value, sep, &vsiz);
+  } else {
+    ksiz = strlen(key);
+    kbuf = tcmemdup(key, ksiz);
+    vsiz = strlen(value);
+    vbuf = tcmemdup(value, vsiz);
+  }
+  int rv = procput(name, kbuf, ksiz, vbuf, vsiz, dmode);
+  tcfree(vbuf);
+  tcfree(kbuf);
+  return rv;
+}
+
+
+/* parse arguments of out command */
+static int runout(int argc, char **argv){
+  char *name = NULL;
+  char *key = NULL;
+  bool sx = false;
+  int sep = -1;
+  for(int i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-sx")){
+        sx = true;
+      } else if(!strcmp(argv[i], "-sep")){
+        if(++i >= argc) usage();
+        sep = sepstrtochr(argv[i]);
+      } else {
+        usage();
+      }
+    } else if(!name){
+      name = argv[i];
+    } else if(!key){
+      key = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !key) usage();
+  int ksiz;
+  char *kbuf;
+  if(sx){
+    kbuf = tchexdecode(key, &ksiz);
+  } else if(sep > 0){
+    kbuf = strtozsv(key, sep, &ksiz);
+  } else {
+    ksiz = strlen(key);
+    kbuf = tcmemdup(key, ksiz);
+  }
+  int rv = procout(name, kbuf, ksiz);
+  tcfree(kbuf);
+  return rv;
+}
+
+
+/* parse arguments of get command */
+static int runget(int argc, char **argv){
+  char *name = NULL;
+  char *key = NULL;
+  bool sx = false;
+  int sep = -1;
+  bool px = false;
+  bool pz = false;
+  for(int i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-sx")){
+        sx = true;
+      } else if(!strcmp(argv[i], "-sep")){
+        if(++i >= argc) usage();
+        sep = sepstrtochr(argv[i]);
+      } else if(!strcmp(argv[i], "-px")){
+        px = true;
+      } else if(!strcmp(argv[i], "-pz")){
+        pz = true;
+      } else {
+        usage();
+      }
+    } else if(!name){
+      name = argv[i];
+    } else if(!key){
+      key = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !key) usage();
+  int ksiz;
+  char *kbuf;
+  if(sx){
+    kbuf = tchexdecode(key, &ksiz);
+  } else if(sep > 0){
+    kbuf = strtozsv(key, sep, &ksiz);
+  } else {
+    ksiz = strlen(key);
+    kbuf = tcmemdup(key, ksiz);
+  }
+  name = tcsprintf("%s#mode=r", name);
+  int rv = procget(name, kbuf, ksiz, sep, px, pz);
+  tcfree(name);
+  tcfree(kbuf);
+  return rv;
+}
+
+
+/* parse arguments of list command */
+static int runlist(int argc, char **argv){
+  char *name = NULL;
+  int sep = -1;
+  int max = -1;
+  bool pv = false;
+  bool px = false;
+  char *fmstr = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-sep")){
+        if(++i >= argc) usage();
+        sep = sepstrtochr(argv[i]);
+      } else if(!strcmp(argv[i], "-m")){
+        if(++i >= argc) usage();
+        max = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-pv")){
+        pv = true;
+      } else if(!strcmp(argv[i], "-px")){
+        px = true;
+      } else if(!strcmp(argv[i], "-fm")){
+        if(++i >= argc) usage();
+        fmstr = argv[i];
+      } else {
+        usage();
+      }
+    } else if(!name){
+      name = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name) usage();
+  name = tcsprintf("%s#mode=r", name);
+  int rv = proclist(name, sep, max, pv, px, fmstr);
+  tcfree(name);
+  return rv;
+}
+
+
+/* parse arguments of optimize command */
+static int runoptimize(int argc, char **argv){
+  char *name = NULL;
+  char *params = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!params){
+      params = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name) usage();
+  int rv = procoptimize(name, params);
+  return rv;
+}
+
+
+/* parse arguments of misc command */
+static int runmisc(int argc, char **argv){
+  char *name = NULL;
+  char *func = NULL;
+  TCLIST *args = tcmpoollistnew(tcmpoolglobal());
+  bool sx = false;
+  int sep = -1;
+  bool px = false;
+  for(int i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-sx")){
+        sx = true;
+      } else if(!strcmp(argv[i], "-sep")){
+        if(++i >= argc) usage();
+        sep = sepstrtochr(argv[i]);
+      } else if(!strcmp(argv[i], "-px")){
+        px = true;
+      } else {
+        usage();
+      }
+    } else if(!name){
+      name = argv[i];
+    } else if(!func){
+      func = argv[i];
+    } else {
+      if(sx){
+        int size;
+        char *buf = tchexdecode(argv[i], &size);
+        tclistpush(args, buf, size);
+        tcfree(buf);
+      } else if(sep > 0){
+        int size;
+        char *buf = strtozsv(argv[i], sep, &size);
+        tclistpush(args, buf, size);
+        tcfree(buf);
+      } else {
+        tclistpush2(args, argv[i]);
+      }
+    }
+  }
+  if(!name || !func) usage();
+  int rv = procmisc(name, func, args, sep, px);
+  return rv;
+}
+
+
+/* parse arguments of map command */
+static int runmap(int argc, char **argv){
+  char *name = NULL;
+  char *dest = NULL;
+  char *fmstr = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-fm")){
+        if(++i >= argc) usage();
+        fmstr = argv[i];
+      } else {
+        usage();
+      }
+    } else if(!name){
+      name = argv[i];
+    } else if(!dest){
+      dest = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !dest) usage();
+  name = tcsprintf("%s#mode=r", name);
+  int rv = procmap(name, dest, fmstr);
+  tcfree(name);
+  return rv;
+}
+
+
+/* parse arguments of version command */
+static int runversion(int argc, char **argv){
+  int rv = procversion();
+  return rv;
+}
+
+
+/* perform create command */
+static int proccreate(const char *name){
+  TCADB *adb = tcadbnew();
+  ADBSKEL skel;
+  if(*name == '@'){
+    setskeltran(&skel);
+    if(!tcadbsetskel(adb, &skel)){
+      printerr(adb);
+      skel.del(skel.opq);
+      tcadbdel(adb);
+      return 1;
+    }
+    name++;
+  } else if(*name == '%'){
+    if(!tcadbsetskelmulti(adb, 8)){
+      printerr(adb);
+      tcadbdel(adb);
+      return 1;
+    }
+    name++;
+  }
+  if(!tcadbopen(adb, name)){
+    printerr(adb);
+    tcadbdel(adb);
+    return 1;
+  }
+  bool err = false;
+  if(!tcadbclose(adb)){
+    printerr(adb);
+    err = true;
+  }
+  tcadbdel(adb);
+  return err ? 1 : 0;
+}
+
+
+/* perform inform command */
+static int procinform(const char *name){
+  TCADB *adb = tcadbnew();
+  ADBSKEL skel;
+  if(*name == '@'){
+    setskeltran(&skel);
+    if(!tcadbsetskel(adb, &skel)){
+      printerr(adb);
+      skel.del(skel.opq);
+      tcadbdel(adb);
+      return 1;
+    }
+    name++;
+  } else if(*name == '%'){
+    if(!tcadbsetskelmulti(adb, 8)){
+      printerr(adb);
+      tcadbdel(adb);
+      return 1;
+    }
+    name++;
+  }
+  if(!tcadbopen(adb, name)){
+    printerr(adb);
+    tcadbdel(adb);
+    return 1;
+  }
+  bool err = false;
+  const char *path = tcadbpath(adb);
+  if(!path) path = "(unknown)";
+  printf("path: %s\n", path);
+  const char *type = "(unknown)";
+  switch(tcadbomode(adb)){
+    case ADBOVOID: type = "not opened"; break;
+    case ADBOMDB: type = "on-memory hash database"; break;
+    case ADBONDB: type = "on-memory tree database"; break;
+    case ADBOHDB: type = "hash database"; break;
+    case ADBOBDB: type = "B+ tree database"; break;
+    case ADBOFDB: type = "fixed-length database"; break;
+    case ADBOTDB: type = "table database"; break;
+    case ADBOSKEL: type = "skeleton database"; break;
+  }
+  printf("database type: %s\n", type);
+  printf("record number: %llu\n", (unsigned long long)tcadbrnum(adb));
+  printf("size: %llu\n", (unsigned long long)tcadbsize(adb));
+  if(!tcadbclose(adb)){
+    printerr(adb);
+    err = true;
+  }
+  tcadbdel(adb);
+  return err ? 1 : 0;
+}
+
+
+/* perform put command */
+static int procput(const char *name, const char *kbuf, int ksiz, const char *vbuf, int vsiz,
+                   int dmode){
+  TCADB *adb = tcadbnew();
+  ADBSKEL skel;
+  if(*name == '@'){
+    setskeltran(&skel);
+    if(!tcadbsetskel(adb, &skel)){
+      printerr(adb);
+      skel.del(skel.opq);
+      tcadbdel(adb);
+      return 1;
+    }
+    name++;
+  } else if(*name == '%'){
+    if(!tcadbsetskelmulti(adb, 8)){
+      printerr(adb);
+      tcadbdel(adb);
+      return 1;
+    }
+    name++;
+  }
+  if(!tcadbopen(adb, name)){
+    printerr(adb);
+    tcadbdel(adb);
+    return 1;
+  }
+  bool err = false;
+  int inum;
+  double dnum;
+  switch(dmode){
+    case -1:
+      if(!tcadbputkeep(adb, kbuf, ksiz, vbuf, vsiz)){
+        printerr(adb);
+        err = true;
+      }
+      break;
+    case 1:
+      if(!tcadbputcat(adb, kbuf, ksiz, vbuf, vsiz)){
+        printerr(adb);
+        err = true;
+      }
+      break;
+    case 10:
+      inum = tcadbaddint(adb, kbuf, ksiz, tcatoi(vbuf));
+      if(inum == INT_MIN){
+        printerr(adb);
+        err = true;
+      } else {
+        printf("%d\n", inum);
+      }
+      break;
+    case 11:
+      dnum = tcadbadddouble(adb, kbuf, ksiz, tcatof(vbuf));
+      if(isnan(dnum)){
+        printerr(adb);
+        err = true;
+      } else {
+        printf("%.6f\n", dnum);
+      }
+      break;
+    default:
+      if(!tcadbput(adb, kbuf, ksiz, vbuf, vsiz)){
+        printerr(adb);
+        err = true;
+      }
+      break;
+  }
+  if(!tcadbclose(adb)){
+    if(!err) printerr(adb);
+    err = true;
+  }
+  tcadbdel(adb);
+  return err ? 1 : 0;
+}
+
+
+/* perform out command */
+static int procout(const char *name, const char *kbuf, int ksiz){
+  TCADB *adb = tcadbnew();
+  ADBSKEL skel;
+  if(*name == '@'){
+    setskeltran(&skel);
+    if(!tcadbsetskel(adb, &skel)){
+      printerr(adb);
+      skel.del(skel.opq);
+      tcadbdel(adb);
+      return 1;
+    }
+    name++;
+  } else if(*name == '%'){
+    if(!tcadbsetskelmulti(adb, 8)){
+      printerr(adb);
+      tcadbdel(adb);
+      return 1;
+    }
+    name++;
+  }
+  if(!tcadbopen(adb, name)){
+    printerr(adb);
+    tcadbdel(adb);
+    return 1;
+  }
+  bool err = false;
+  if(!tcadbout(adb, kbuf, ksiz)){
+    printerr(adb);
+    err = true;
+  }
+  if(!tcadbclose(adb)){
+    if(!err) printerr(adb);
+    err = true;
+  }
+  tcadbdel(adb);
+  return err ? 1 : 0;
+}
+
+
+/* perform get command */
+static int procget(const char *name, const char *kbuf, int ksiz, int sep, bool px, bool pz){
+  TCADB *adb = tcadbnew();
+  ADBSKEL skel;
+  if(*name == '@'){
+    setskeltran(&skel);
+    if(!tcadbsetskel(adb, &skel)){
+      printerr(adb);
+      skel.del(skel.opq);
+      tcadbdel(adb);
+      return 1;
+    }
+    name++;
+  } else if(*name == '%'){
+    if(!tcadbsetskelmulti(adb, 8)){
+      printerr(adb);
+      tcadbdel(adb);
+      return 1;
+    }
+    name++;
+  }
+  if(!tcadbopen(adb, name)){
+    printerr(adb);
+    tcadbdel(adb);
+    return 1;
+  }
+  bool err = false;
+  int vsiz;
+  char *vbuf = tcadbget(adb, kbuf, ksiz, &vsiz);
+  if(vbuf){
+    printdata(vbuf, vsiz, px, sep);
+    if(!pz) putchar('\n');
+    tcfree(vbuf);
+  } else {
+    printerr(adb);
+    err = true;
+  }
+  if(!tcadbclose(adb)){
+    if(!err) printerr(adb);
+    err = true;
+  }
+  tcadbdel(adb);
+  return err ? 1 : 0;
+}
+
+
+/* perform list command */
+static int proclist(const char *name, int sep, int max, bool pv, bool px, const char *fmstr){
+  TCADB *adb = tcadbnew();
+  ADBSKEL skel;
+  if(*name == '@'){
+    setskeltran(&skel);
+    if(!tcadbsetskel(adb, &skel)){
+      printerr(adb);
+      skel.del(skel.opq);
+      tcadbdel(adb);
+      return 1;
+    }
+    name++;
+  } else if(*name == '%'){
+    if(!tcadbsetskelmulti(adb, 8)){
+      printerr(adb);
+      tcadbdel(adb);
+      return 1;
+    }
+    name++;
+  }
+  if(!tcadbopen(adb, name)){
+    printerr(adb);
+    tcadbdel(adb);
+    return 1;
+  }
+  bool err = false;
+  if(fmstr){
+    TCLIST *keys = tcadbfwmkeys2(adb, fmstr, max);
+    for(int i = 0; i < tclistnum(keys); i++){
+      int ksiz;
+      const char *kbuf = tclistval(keys, i, &ksiz);
+      printdata(kbuf, ksiz, px, sep);
+      if(pv){
+        int vsiz;
+        char *vbuf = tcadbget(adb, kbuf, ksiz, &vsiz);
+        if(vbuf){
+          putchar('\t');
+          printdata(vbuf, vsiz, px, sep);
+          tcfree(vbuf);
+        }
+      }
+      putchar('\n');
+    }
+    tclistdel(keys);
+  } else {
+    if(!tcadbiterinit(adb)){
+      printerr(adb);
+      err = true;
+    }
+    int ksiz;
+    char *kbuf;
+    int cnt = 0;
+    while((kbuf = tcadbiternext(adb, &ksiz)) != NULL){
+      printdata(kbuf, ksiz, px, sep);
+      if(pv){
+        int vsiz;
+        char *vbuf = tcadbget(adb, kbuf, ksiz, &vsiz);
+        if(vbuf){
+          putchar('\t');
+          printdata(vbuf, vsiz, px, sep);
+          tcfree(vbuf);
+        }
+      }
+      putchar('\n');
+      tcfree(kbuf);
+      if(max >= 0 && ++cnt >= max) break;
+    }
+  }
+  if(!tcadbclose(adb)){
+    if(!err) printerr(adb);
+    err = true;
+  }
+  tcadbdel(adb);
+  return err ? 1 : 0;
+}
+
+
+/* perform optimize command */
+static int procoptimize(const char *name, const char *params){
+  TCADB *adb = tcadbnew();
+  ADBSKEL skel;
+  if(*name == '@'){
+    setskeltran(&skel);
+    if(!tcadbsetskel(adb, &skel)){
+      printerr(adb);
+      skel.del(skel.opq);
+      tcadbdel(adb);
+      return 1;
+    }
+    name++;
+  } else if(*name == '%'){
+    if(!tcadbsetskelmulti(adb, 8)){
+      printerr(adb);
+      tcadbdel(adb);
+      return 1;
+    }
+    name++;
+  }
+  if(!tcadbopen(adb, name)){
+    printerr(adb);
+    tcadbdel(adb);
+    return 1;
+  }
+  bool err = false;
+  if(!tcadboptimize(adb, params)){
+    printerr(adb);
+    err = true;
+  }
+  if(!tcadbclose(adb)){
+    if(!err) printerr(adb);
+    err = true;
+  }
+  tcadbdel(adb);
+  return err ? 1 : 0;
+}
+
+
+/* perform misc command */
+static int procmisc(const char *name, const char *func, const TCLIST *args, int sep, bool px){
+  TCADB *adb = tcadbnew();
+  ADBSKEL skel;
+  if(*name == '@'){
+    setskeltran(&skel);
+    if(!tcadbsetskel(adb, &skel)){
+      printerr(adb);
+      skel.del(skel.opq);
+      tcadbdel(adb);
+      return 1;
+    }
+    name++;
+  } else if(*name == '%'){
+    if(!tcadbsetskelmulti(adb, 8)){
+      printerr(adb);
+      tcadbdel(adb);
+      return 1;
+    }
+    name++;
+  }
+  if(!tcadbopen(adb, name)){
+    printerr(adb);
+    tcadbdel(adb);
+    return 1;
+  }
+  bool err = false;
+  TCLIST *res = tcadbmisc(adb, func, args);
+  if(res){
+    for(int i = 0; i < tclistnum(res); i++){
+      int rsiz;
+      const char *rbuf = tclistval(res, i, &rsiz);
+      printdata(rbuf, rsiz, px, sep);
+      printf("\n");
+    }
+    tclistdel(res);
+  } else {
+    printerr(adb);
+    err = true;
+  }
+  if(!tcadbclose(adb)){
+    if(!err) printerr(adb);
+    err = true;
+  }
+  tcadbdel(adb);
+  return err ? 1 : 0;
+}
+
+
+/* perform map command */
+static int procmap(const char *name, const char *dest, const char *fmstr){
+  TCADB *adb = tcadbnew();
+  ADBSKEL skel;
+  if(*name == '@'){
+    setskeltran(&skel);
+    if(!tcadbsetskel(adb, &skel)){
+      printerr(adb);
+      skel.del(skel.opq);
+      tcadbdel(adb);
+      return 1;
+    }
+    name++;
+  } else if(*name == '%'){
+    if(!tcadbsetskelmulti(adb, 8)){
+      printerr(adb);
+      tcadbdel(adb);
+      return 1;
+    }
+    name++;
+  }
+  if(!tcadbopen(adb, name)){
+    printerr(adb);
+    tcadbdel(adb);
+    return 1;
+  }
+  bool err = false;
+  TCBDB *bdb = tcbdbnew();
+  if(!tcbdbopen(bdb, dest, BDBOWRITER | BDBOCREAT | BDBOTRUNC)){
+    printerr(adb);
+    tcbdbdel(bdb);
+    tcadbdel(adb);
+    return 1;
+  }
+  if(fmstr){
+    TCLIST *keys = tcadbfwmkeys2(adb, fmstr, -1);
+    if(!tcadbmapbdb(adb, keys, bdb, mapbdbproc, NULL, -1)){
+      printerr(adb);
+      err = true;
+    }
+    tclistdel(keys);
+  } else {
+    if(!tcadbmapbdb(adb, NULL, bdb, mapbdbproc, NULL, -1)){
+      printerr(adb);
+      err = true;
+    }
+  }
+  if(!tcbdbclose(bdb)){
+    printerr(adb);
+    err = true;
+  }
+  tcbdbdel(bdb);
+  if(!tcadbclose(adb)){
+    printerr(adb);
+    err = true;
+  }
+  tcadbdel(adb);
+  return err ? 1 : 0;
+}
+
+
+/* perform version command */
+static int procversion(void){
+  printf("Tokyo Cabinet version %s (%d:%s) for %s\n",
+         tcversion, _TC_LIBVER, _TC_FORMATVER, TCSYSNAME);
+  printf("Copyright (C) 2006-2012 FAL Labs\n");
+  return 0;
+}
+
+
+
+// END OF FILE
diff --git a/tcejdb/tcamttest.c b/tcejdb/tcamttest.c
new file mode 100644 (file)
index 0000000..943c406
--- /dev/null
@@ -0,0 +1,542 @@
+/*************************************************************************************************
+ * The test cases of the abstract database API
+ *                                                               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 <tcadb.h>
+#include "myconf.h"
+
+#define MULDIVNUM      8                 // division number of multiple database
+#define RECBUFSIZ      48                // buffer for records
+
+typedef struct {                         // type of structure for write thread
+  TCADB *adb;
+  int rnum;
+  int id;
+} TARGWRITE;
+
+typedef struct {                         // type of structure for read thread
+  TCADB *adb;
+  int rnum;
+  int id;
+} TARGREAD;
+
+typedef struct {                         // type of structure for remove thread
+  TCADB *adb;
+  int rnum;
+  int id;
+} TARGREMOVE;
+
+
+/* global variables */
+const char *g_progname;                  // program name
+unsigned int g_randseed;                 // random seed
+int g_dbgfd;                             // debugging output
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+static void usage(void);
+static void iprintf(const char *format, ...);
+static void iputchar(int c);
+static void eprint(TCADB *adb, int line, const char *func);
+static void sysprint(void);
+static void setskeltran(ADBSKEL *skel);
+static int runwrite(int argc, char **argv);
+static int runread(int argc, char **argv);
+static int runremove(int argc, char **argv);
+static int procwrite(const char *name, int tnum, int rnum);
+static int procread(const char *name, int tnum);
+static int procremove(const char *name, int tnum);
+static void *threadwrite(void *targ);
+static void *threadread(void *targ);
+static void *threadremove(void *targ);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  g_progname = argv[0];
+  const char *ebuf = getenv("TCRNDSEED");
+  g_randseed = ebuf ? tcatoix(ebuf) : tctime() * 1000;
+  srand(g_randseed);
+  ebuf = getenv("TCDBGFD");
+  g_dbgfd = ebuf ? tcatoix(ebuf) : UINT16_MAX;
+  if(argc < 2) usage();
+  int rv = 0;
+  if(!strcmp(argv[1], "write")){
+    rv = runwrite(argc, argv);
+  } else if(!strcmp(argv[1], "read")){
+    rv = runread(argc, argv);
+  } else if(!strcmp(argv[1], "remove")){
+    rv = runremove(argc, argv);
+  } else {
+    usage();
+  }
+  if(rv != 0){
+    printf("FAILED: TCRNDSEED=%u PID=%d", g_randseed, (int)getpid());
+    for(int i = 0; i < argc; i++){
+      printf(" %s", argv[i]);
+    }
+    printf("\n\n");
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+static void usage(void){
+  fprintf(stderr, "%s: test cases of the abstract database API of Tokyo Cabinet\n", g_progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s write name tnum rnum\n", g_progname);
+  fprintf(stderr, "  %s read name tnum\n", g_progname);
+  fprintf(stderr, "  %s remove name tnum\n", g_progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* print formatted information string and flush the buffer */
+static void iprintf(const char *format, ...){
+  va_list ap;
+  va_start(ap, format);
+  vprintf(format, ap);
+  fflush(stdout);
+  va_end(ap);
+}
+
+
+/* print a character and flush the buffer */
+static void iputchar(int c){
+  putchar(c);
+  fflush(stdout);
+}
+
+
+/* print error message of abstract database */
+static void eprint(TCADB *adb, int line, const char *func){
+  const char *path = adb ? tcadbpath(adb) : NULL;
+  fprintf(stderr, "%s: %s: %d: %s: error\n", g_progname, path ? path : "-", line, func);
+}
+
+
+/* print system information */
+static void sysprint(void){
+  TCMAP *info = tcsysinfo();
+  if(info){
+    tcmapiterinit(info);
+    const char *kbuf;
+    while((kbuf = tcmapiternext2(info)) != NULL){
+      iprintf("sys_%s: %s\n", kbuf, tcmapiterval2(kbuf));
+    }
+    tcmapdel(info);
+  }
+}
+
+
+/* set the transparent skeleton database */
+static void setskeltran(ADBSKEL *skel){
+  memset(skel, 0, sizeof(*skel));
+  skel->opq = tcadbnew();
+  skel->del = (void (*)(void *))tcadbdel;
+  skel->open = (bool (*)(void *, const char *))tcadbopen;
+  skel->close = (bool (*)(void *))tcadbclose;
+  skel->put = (bool (*)(void *, const void *, int, const void *, int))tcadbput;
+  skel->putkeep = (bool (*)(void *, const void *, int, const void *, int))tcadbputkeep;
+  skel->putcat = (bool (*)(void *, const void *, int, const void *, int))tcadbputcat;
+  skel->out = (bool (*)(void *, const void *, int))tcadbout;
+  skel->get = (void *(*)(void *, const void *, int, int *))tcadbget;
+  skel->vsiz = (int (*)(void *, const void *, int))tcadbvsiz;
+  skel->iterinit = (bool (*)(void *))tcadbiterinit;
+  skel->iternext = (void *(*)(void *, int *))tcadbiternext;
+  skel->fwmkeys = (TCLIST *(*)(void *, const void *, int, int))tcadbfwmkeys;
+  skel->addint = (int (*)(void *, const void *, int, int))tcadbaddint;
+  skel->adddouble = (double (*)(void *, const void *, int, double))tcadbadddouble;
+  skel->sync = (bool (*)(void *))tcadbsync;
+  skel->optimize = (bool (*)(void *, const char *))tcadboptimize;
+  skel->vanish = (bool (*)(void *))tcadbvanish;
+  skel->copy = (bool (*)(void *, const char *))tcadbcopy;
+  skel->tranbegin = (bool (*)(void *))tcadbtranbegin;
+  skel->trancommit = (bool (*)(void *))tcadbtrancommit;
+  skel->tranabort = (bool (*)(void *))tcadbtranabort;
+  skel->path = (const char *(*)(void *))tcadbpath;
+  skel->rnum = (uint64_t (*)(void *))tcadbrnum;
+  skel->size = (uint64_t (*)(void *))tcadbsize;
+  skel->misc = (TCLIST *(*)(void *, const char *, const TCLIST *))tcadbmisc;
+  skel->putproc =
+    (bool (*)(void *, const void *, int, const void *, int, TCPDPROC, void *))tcadbputproc;
+  skel->foreach = (bool (*)(void *, TCITER, void *))tcadbforeach;
+}
+
+
+/* parse arguments of write command */
+static int runwrite(int argc, char **argv){
+  char *name = NULL;
+  char *tstr = NULL;
+  char *rstr = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !tstr || !rstr) usage();
+  int tnum = tcatoix(tstr);
+  int rnum = tcatoix(rstr);
+  if(tnum < 1 || rnum < 1) usage();
+  int rv = procwrite(name, tnum, rnum);
+  return rv;
+}
+
+
+/* parse arguments of read command */
+static int runread(int argc, char **argv){
+  char *name = NULL;
+  char *tstr = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !tstr) usage();
+  int tnum = tcatoix(tstr);
+  if(tnum < 1) usage();
+  int rv = procread(name, tnum);
+  return rv;
+}
+
+
+/* parse arguments of remove command */
+static int runremove(int argc, char **argv){
+  char *name = NULL;
+  char *tstr = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !tstr) usage();
+  int tnum = tcatoix(tstr);
+  if(tnum < 1) usage();
+  int rv = procremove(name, tnum);
+  return rv;
+}
+
+
+/* perform write command */
+static int procwrite(const char *name, int tnum, int rnum){
+  iprintf("<Writing Test>\n  seed=%u  name=%s  tnum=%d  rnum=%d\n\n",
+          g_randseed, name, tnum, rnum);
+  bool err = false;
+  double stime = tctime();
+  TCADB *adb = tcadbnew();
+  ADBSKEL skel;
+  if(*name == '@'){
+    setskeltran(&skel);
+    if(!tcadbsetskel(adb, &skel)){
+      eprint(adb, __LINE__, "tcadbsetskel");
+      err = true;
+      skel.del(skel.opq);
+    }
+    name++;
+  } else if(*name == '%'){
+    if(!tcadbsetskelmulti(adb, MULDIVNUM)){
+      eprint(adb, __LINE__, "tcadbsetskelmulti");
+      err = true;
+    }
+    name++;
+  }
+  if(!tcadbopen(adb, name)){
+    eprint(adb, __LINE__, "tcadbopen");
+    err = true;
+  }
+  TARGWRITE targs[tnum];
+  pthread_t threads[tnum];
+  if(tnum == 1){
+    targs[0].adb = adb;
+    targs[0].rnum = rnum;
+    targs[0].id = 0;
+    if(threadwrite(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].adb = adb;
+      targs[i].rnum = rnum;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadwrite, targs + i) != 0){
+        eprint(adb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(adb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcadbrnum(adb));
+  iprintf("size: %llu\n", (unsigned long long)tcadbsize(adb));
+  sysprint();
+  if(!tcadbclose(adb)){
+    eprint(adb, __LINE__, "tcadbclose");
+    err = true;
+  }
+  tcadbdel(adb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform read command */
+static int procread(const char *name, int tnum){
+  iprintf("<Reading Test>\n  seed=%u  name=%s  tnum=%d\n\n", g_randseed, name, tnum);
+  bool err = false;
+  double stime = tctime();
+  TCADB *adb = tcadbnew();
+  ADBSKEL skel;
+  if(*name == '@'){
+    setskeltran(&skel);
+    if(!tcadbsetskel(adb, &skel)){
+      eprint(adb, __LINE__, "tcadbsetskel");
+      err = true;
+      skel.del(skel.opq);
+    }
+    name++;
+  } else if(*name == '%'){
+    if(!tcadbsetskelmulti(adb, MULDIVNUM)){
+      eprint(adb, __LINE__, "tcadbsetskelmulti");
+      err = true;
+    }
+    name++;
+  }
+  if(!tcadbopen(adb, name)){
+    eprint(adb, __LINE__, "tcadbopen");
+    err = true;
+  }
+  int rnum = tcadbrnum(adb) / tnum;
+  TARGREAD targs[tnum];
+  pthread_t threads[tnum];
+  if(tnum == 1){
+    targs[0].adb = adb;
+    targs[0].rnum = rnum;
+    targs[0].id = 0;
+    if(threadread(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].adb = adb;
+      targs[i].rnum = rnum;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadread, targs + i) != 0){
+        eprint(adb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(adb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcadbrnum(adb));
+  iprintf("size: %llu\n", (unsigned long long)tcadbsize(adb));
+  sysprint();
+  if(!tcadbclose(adb)){
+    eprint(adb, __LINE__, "tcadbclose");
+    err = true;
+  }
+  tcadbdel(adb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform remove command */
+static int procremove(const char *name, int tnum){
+  iprintf("<Removing Test>\n  seed=%u  name=%s  tnum=%d\n\n", g_randseed, name, tnum);
+  bool err = false;
+  double stime = tctime();
+  TCADB *adb = tcadbnew();
+  ADBSKEL skel;
+  if(*name == '@'){
+    setskeltran(&skel);
+    if(!tcadbsetskel(adb, &skel)){
+      eprint(adb, __LINE__, "tcadbsetskel");
+      err = true;
+      skel.del(skel.opq);
+    }
+    name++;
+  } else if(*name == '%'){
+    if(!tcadbsetskelmulti(adb, MULDIVNUM)){
+      eprint(adb, __LINE__, "tcadbsetskelmulti");
+      err = true;
+    }
+    name++;
+  }
+  if(!tcadbopen(adb, name)){
+    eprint(adb, __LINE__, "tcadbopen");
+    err = true;
+  }
+  int rnum = tcadbrnum(adb) / tnum;
+  TARGREMOVE targs[tnum];
+  pthread_t threads[tnum];
+  if(tnum == 1){
+    targs[0].adb = adb;
+    targs[0].rnum = rnum;
+    targs[0].id = 0;
+    if(threadremove(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].adb = adb;
+      targs[i].rnum = rnum;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadremove, targs + i) != 0){
+        eprint(adb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(adb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcadbrnum(adb));
+  iprintf("size: %llu\n", (unsigned long long)tcadbsize(adb));
+  sysprint();
+  if(!tcadbclose(adb)){
+    eprint(adb, __LINE__, "tcadbclose");
+    err = true;
+  }
+  tcadbdel(adb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* thread the write function */
+static void *threadwrite(void *targ){
+  TCADB *adb = ((TARGWRITE *)targ)->adb;
+  int rnum = ((TARGWRITE *)targ)->rnum;
+  int id = ((TARGWRITE *)targ)->id;
+  bool err = false;
+  int base = id * rnum;
+  for(int i = 1; i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", base + i + 1);
+    if(!tcadbput(adb, buf, len, buf, len)){
+      eprint(adb, __LINE__, "tcadbput");
+      err = true;
+      break;
+    }
+    if(id == 0 && rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  return err ? "error" : NULL;
+}
+
+
+/* thread the read function */
+static void *threadread(void *targ){
+  TCADB *adb = ((TARGREAD *)targ)->adb;
+  int rnum = ((TARGREAD *)targ)->rnum;
+  int id = ((TARGREAD *)targ)->id;
+  bool err = false;
+  int base = id * rnum;
+  for(int i = 1; i <= rnum && !err; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%08d", base + i + 1);
+    int vsiz;
+    char *vbuf = tcadbget(adb, kbuf, ksiz, &vsiz);
+    if(!vbuf){
+      eprint(adb, __LINE__, "tcadbget");
+      err = true;
+    }
+    tcfree(vbuf);
+    if(id == 0 && rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  return err ? "error" : NULL;
+}
+
+
+/* thread the remove function */
+static void *threadremove(void *targ){
+  TCADB *adb = ((TARGREMOVE *)targ)->adb;
+  int rnum = ((TARGREMOVE *)targ)->rnum;
+  int id = ((TARGREMOVE *)targ)->id;
+  bool err = false;
+  int base = id * rnum;
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%08d", base + i + 1);
+    if(!tcadbout(adb, kbuf, ksiz)){
+      eprint(adb, __LINE__, "tcadbout");
+      err = true;
+      break;
+    }
+    if(id == 0 && rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  return err ? "error" : NULL;
+}
+
+
+
+// END OF FILE
diff --git a/tcejdb/tcatest.c b/tcejdb/tcatest.c
new file mode 100644 (file)
index 0000000..254bba0
--- /dev/null
@@ -0,0 +1,1845 @@
+/*************************************************************************************************
+ * The test cases of the abstract database API
+ *                                                               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 <tcadb.h>
+#include "myconf.h"
+
+#define MULDIVNUM      8                 // division number of multiple database
+#define RECBUFSIZ      48                // buffer for records
+
+
+/* global variables */
+const char *g_progname;                  // program name
+unsigned int g_randseed;                 // random seed
+ADBSKEL g_skeleton;                      // skeleton database
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+static void usage(void);
+static void iprintf(const char *format, ...);
+static void iputchar(int c);
+static void eprint(TCADB *adb, int line, const char *func);
+static void sysprint(void);
+static int myrand(int range);
+static void setskeltran(ADBSKEL *skel);
+static void *pdprocfunccmp(const void *vbuf, int vsiz, int *sp, void *op);
+static bool iterfunc(const void *kbuf, int ksiz, const void *vbuf, int vsiz, void *op);
+static int runwrite(int argc, char **argv);
+static int runread(int argc, char **argv);
+static int runremove(int argc, char **argv);
+static int runrcat(int argc, char **argv);
+static int runmisc(int argc, char **argv);
+static int runwicked(int argc, char **argv);
+static int runcompare(int argc, char **argv);
+static int procwrite(const char *name, int rnum);
+static int procread(const char *name);
+static int procremove(const char *name);
+static int procrcat(const char *name, int rnum);
+static int procmisc(const char *name, int rnum);
+static int procwicked(const char *name, int rnum);
+static int proccompare(const char *name, int tnum, int rnum);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  g_progname = argv[0];
+  const char *ebuf = getenv("TCRNDSEED");
+  g_randseed = ebuf ? tcatoix(ebuf) : tctime() * 1000;
+  srand(g_randseed);
+  if(argc < 2) usage();
+  int rv = 0;
+  if(!strcmp(argv[1], "write")){
+    rv = runwrite(argc, argv);
+  } else if(!strcmp(argv[1], "read")){
+    rv = runread(argc, argv);
+  } else if(!strcmp(argv[1], "remove")){
+    rv = runremove(argc, argv);
+  } else if(!strcmp(argv[1], "rcat")){
+    rv = runrcat(argc, argv);
+  } else if(!strcmp(argv[1], "misc")){
+    rv = runmisc(argc, argv);
+  } else if(!strcmp(argv[1], "wicked")){
+    rv = runwicked(argc, argv);
+  } else if(!strcmp(argv[1], "compare")){
+    rv = runcompare(argc, argv);
+  } else {
+    usage();
+  }
+  if(rv != 0){
+    printf("FAILED: TCRNDSEED=%u PID=%d", g_randseed, (int)getpid());
+    for(int i = 0; i < argc; i++){
+      printf(" %s", argv[i]);
+    }
+    printf("\n\n");
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+static void usage(void){
+  fprintf(stderr, "%s: test cases of the abstract database API of Tokyo Cabinet\n", g_progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s write name rnum\n", g_progname);
+  fprintf(stderr, "  %s read name\n", g_progname);
+  fprintf(stderr, "  %s remove name\n", g_progname);
+  fprintf(stderr, "  %s rcat name rnum\n", g_progname);
+  fprintf(stderr, "  %s misc name rnum\n", g_progname);
+  fprintf(stderr, "  %s wicked name rnum\n", g_progname);
+  fprintf(stderr, "  %s compare name tnum rnum\n", g_progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* print formatted information string and flush the buffer */
+static void iprintf(const char *format, ...){
+  va_list ap;
+  va_start(ap, format);
+  vprintf(format, ap);
+  fflush(stdout);
+  va_end(ap);
+}
+
+
+/* print a character and flush the buffer */
+static void iputchar(int c){
+  putchar(c);
+  fflush(stdout);
+}
+
+
+/* print error message of abstract database */
+static void eprint(TCADB *adb, int line, const char *func){
+  const char *path = adb ? tcadbpath(adb) : NULL;
+  fprintf(stderr, "%s: %s: %d: %s: error\n", g_progname, path ? path : "-", line, func);
+}
+
+
+/* print system information */
+static void sysprint(void){
+  TCMAP *info = tcsysinfo();
+  if(info){
+    tcmapiterinit(info);
+    const char *kbuf;
+    while((kbuf = tcmapiternext2(info)) != NULL){
+      iprintf("sys_%s: %s\n", kbuf, tcmapiterval2(kbuf));
+    }
+    tcmapdel(info);
+  }
+}
+
+
+/* get a random number */
+static int myrand(int range){
+  if(range < 2) return 0;
+  int high = (unsigned int)rand() >> 4;
+  int low = range * (rand() / (RAND_MAX + 1.0));
+  low &= (unsigned int)INT_MAX >> 4;
+  return (high + low) % range;
+}
+
+
+/* set the transparent skeleton database */
+static void setskeltran(ADBSKEL *skel){
+  memset(skel, 0, sizeof(*skel));
+  skel->opq = tcadbnew();
+  skel->del = (void (*)(void *))tcadbdel;
+  skel->open = (bool (*)(void *, const char *))tcadbopen;
+  skel->close = (bool (*)(void *))tcadbclose;
+  skel->put = (bool (*)(void *, const void *, int, const void *, int))tcadbput;
+  skel->putkeep = (bool (*)(void *, const void *, int, const void *, int))tcadbputkeep;
+  skel->putcat = (bool (*)(void *, const void *, int, const void *, int))tcadbputcat;
+  skel->out = (bool (*)(void *, const void *, int))tcadbout;
+  skel->get = (void *(*)(void *, const void *, int, int *))tcadbget;
+  skel->vsiz = (int (*)(void *, const void *, int))tcadbvsiz;
+  skel->iterinit = (bool (*)(void *))tcadbiterinit;
+  skel->iternext = (void *(*)(void *, int *))tcadbiternext;
+  skel->fwmkeys = (TCLIST *(*)(void *, const void *, int, int))tcadbfwmkeys;
+  skel->addint = (int (*)(void *, const void *, int, int))tcadbaddint;
+  skel->adddouble = (double (*)(void *, const void *, int, double))tcadbadddouble;
+  skel->sync = (bool (*)(void *))tcadbsync;
+  skel->optimize = (bool (*)(void *, const char *))tcadboptimize;
+  skel->vanish = (bool (*)(void *))tcadbvanish;
+  skel->copy = (bool (*)(void *, const char *))tcadbcopy;
+  skel->tranbegin = (bool (*)(void *))tcadbtranbegin;
+  skel->trancommit = (bool (*)(void *))tcadbtrancommit;
+  skel->tranabort = (bool (*)(void *))tcadbtranabort;
+  skel->path = (const char *(*)(void *))tcadbpath;
+  skel->rnum = (uint64_t (*)(void *))tcadbrnum;
+  skel->size = (uint64_t (*)(void *))tcadbsize;
+  skel->misc = (TCLIST *(*)(void *, const char *, const TCLIST *))tcadbmisc;
+  skel->putproc =
+    (bool (*)(void *, const void *, int, const void *, int, TCPDPROC, void *))tcadbputproc;
+  skel->foreach = (bool (*)(void *, TCITER, void *))tcadbforeach;
+}
+
+
+/* duplication callback function for comparison */
+static void *pdprocfunccmp(const void *vbuf, int vsiz, int *sp, void *op){
+  switch(*(int *)op % 4){
+    case 1: return NULL;
+    case 2: return (void *)-1;
+    default: break;
+  }
+  *sp = vsiz;
+  return tcmemdup(vbuf, vsiz);
+}
+
+
+/* iterator function */
+static bool iterfunc(const void *kbuf, int ksiz, const void *vbuf, int vsiz, void *op){
+  unsigned int sum = 0;
+  while(--ksiz >= 0){
+    sum += ((char *)kbuf)[ksiz];
+  }
+  while(--vsiz >= 0){
+    sum += ((char *)vbuf)[vsiz];
+  }
+  return myrand(100 + (sum & 0xff)) > 0;
+}
+
+
+/* parse arguments of write command */
+static int runwrite(int argc, char **argv){
+  char *name = NULL;
+  char *rstr = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int rv = procwrite(name, rnum);
+  return rv;
+}
+
+
+/* parse arguments of read command */
+static int runread(int argc, char **argv){
+  char *name = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name) usage();
+  int rv = procread(name);
+  return rv;
+}
+
+
+/* parse arguments of remove command */
+static int runremove(int argc, char **argv){
+  char *name = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name) usage();
+  int rv = procremove(name);
+  return rv;
+}
+
+
+/* parse arguments of rcat command */
+static int runrcat(int argc, char **argv){
+  char *name = NULL;
+  char *rstr = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int rv = procrcat(name, rnum);
+  return rv;
+}
+
+
+/* parse arguments of misc command */
+static int runmisc(int argc, char **argv){
+  char *name = NULL;
+  char *rstr = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int rv = procmisc(name, rnum);
+  return rv;
+}
+
+
+/* parse arguments of wicked command */
+static int runwicked(int argc, char **argv){
+  char *name = NULL;
+  char *rstr = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int rv = procwicked(name, rnum);
+  return rv;
+}
+
+
+/* parse arguments of compare command */
+static int runcompare(int argc, char **argv){
+  char *name = NULL;
+  char *tstr = NULL;
+  char *rstr = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!name && argv[i][0] == '-'){
+      usage();
+    } else if(!name){
+      name = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!name || !tstr || !rstr) usage();
+  int tnum = tcatoix(tstr);
+  int rnum = tcatoix(rstr);
+  if(tnum < 1 || rnum < 1) usage();
+  int rv = proccompare(name, tnum, rnum);
+  return rv;
+}
+
+
+/* perform write command */
+static int procwrite(const char *name, int rnum){
+  iprintf("<Writing Test>\n  seed=%u  name=%s  rnum=%d\n\n", g_randseed, name, rnum);
+  bool err = false;
+  double stime = tctime();
+  TCADB *adb = tcadbnew();
+  ADBSKEL skel;
+  if(*name == '@'){
+    setskeltran(&skel);
+    if(!tcadbsetskel(adb, &skel)){
+      eprint(adb, __LINE__, "tcadbsetskel");
+      err = true;
+      skel.del(skel.opq);
+    }
+    name++;
+  } else if(*name == '%'){
+    if(!tcadbsetskelmulti(adb, MULDIVNUM)){
+      eprint(adb, __LINE__, "tcadbsetskelmulti");
+      err = true;
+    }
+    name++;
+  }
+  if(!tcadbopen(adb, name)){
+    eprint(adb, __LINE__, "tcadbopen");
+    err = true;
+  }
+  for(int i = 1; i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", i);
+    if(!tcadbput(adb, buf, len, buf, len)){
+      eprint(adb, __LINE__, "tcadbput");
+      err = true;
+      break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcadbrnum(adb));
+  iprintf("size: %llu\n", (unsigned long long)tcadbsize(adb));
+  sysprint();
+  if(!tcadbclose(adb)){
+    eprint(adb, __LINE__, "tcadbclose");
+    err = true;
+  }
+  tcadbdel(adb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform read command */
+static int procread(const char *name){
+  iprintf("<Reading Test>\n  seed=%u  name=%s\n\n", g_randseed, name);
+  bool err = false;
+  double stime = tctime();
+  TCADB *adb = tcadbnew();
+  ADBSKEL skel;
+  if(*name == '@'){
+    setskeltran(&skel);
+    if(!tcadbsetskel(adb, &skel)){
+      eprint(adb, __LINE__, "tcadbsetskel");
+      err = true;
+      skel.del(skel.opq);
+    }
+    name++;
+  } else if(*name == '%'){
+    if(!tcadbsetskelmulti(adb, MULDIVNUM)){
+      eprint(adb, __LINE__, "tcadbsetskelmulti");
+      err = true;
+    }
+    name++;
+  }
+  if(!tcadbopen(adb, name)){
+    eprint(adb, __LINE__, "tcadbopen");
+    err = true;
+  }
+  int rnum = tcadbrnum(adb);
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%08d", i);
+    int vsiz;
+    char *vbuf = tcadbget(adb, kbuf, ksiz, &vsiz);
+    if(!vbuf){
+      eprint(adb, __LINE__, "tcadbget");
+      err = true;
+      break;
+    }
+    tcfree(vbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcadbrnum(adb));
+  iprintf("size: %llu\n", (unsigned long long)tcadbsize(adb));
+  sysprint();
+  if(!tcadbclose(adb)){
+    eprint(adb, __LINE__, "tcadbclose");
+    err = true;
+  }
+  tcadbdel(adb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform remove command */
+static int procremove(const char *name){
+  iprintf("<Removing Test>\n  seed=%u  name=%s\n\n", g_randseed, name);
+  bool err = false;
+  double stime = tctime();
+  TCADB *adb = tcadbnew();
+  ADBSKEL skel;
+  if(*name == '@'){
+    setskeltran(&skel);
+    if(!tcadbsetskel(adb, &skel)){
+      eprint(adb, __LINE__, "tcadbsetskel");
+      err = true;
+      skel.del(skel.opq);
+    }
+    name++;
+  } else if(*name == '%'){
+    if(!tcadbsetskelmulti(adb, MULDIVNUM)){
+      eprint(adb, __LINE__, "tcadbsetskelmulti");
+      err = true;
+    }
+    name++;
+  }
+  if(!tcadbopen(adb, name)){
+    eprint(adb, __LINE__, "tcadbopen");
+    err = true;
+  }
+  int rnum = tcadbrnum(adb);
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%08d", i);
+    if(!tcadbout(adb, kbuf, ksiz)){
+      eprint(adb, __LINE__, "tcadbout");
+      err = true;
+      break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcadbrnum(adb));
+  iprintf("size: %llu\n", (unsigned long long)tcadbsize(adb));
+  sysprint();
+  if(!tcadbclose(adb)){
+    eprint(adb, __LINE__, "tcadbclose");
+    err = true;
+  }
+  tcadbdel(adb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform rcat command */
+static int procrcat(const char *name, int rnum){
+  iprintf("<Random Concatenating Test>\n  seed=%u  name=%s  rnum=%d\n\n",
+          g_randseed, name, rnum);
+  int pnum = rnum / 5 + 1;
+  bool err = false;
+  double stime = tctime();
+  TCADB *adb = tcadbnew();
+  ADBSKEL skel;
+  if(*name == '@'){
+    setskeltran(&skel);
+    if(!tcadbsetskel(adb, &skel)){
+      eprint(adb, __LINE__, "tcadbsetskel");
+      err = true;
+      skel.del(skel.opq);
+    }
+    name++;
+  } else if(*name == '%'){
+    if(!tcadbsetskelmulti(adb, MULDIVNUM)){
+      eprint(adb, __LINE__, "tcadbsetskelmulti");
+      err = true;
+    }
+    name++;
+  }
+  if(!tcadbopen(adb, name)){
+    eprint(adb, __LINE__, "tcadbopen");
+    err = true;
+  }
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(pnum) + 1);
+    if(!tcadbputcat(adb, kbuf, ksiz, kbuf, ksiz)){
+      eprint(adb, __LINE__, "tcadbputcat");
+      err = true;
+      break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcadbrnum(adb));
+  iprintf("size: %llu\n", (unsigned long long)tcadbsize(adb));
+  sysprint();
+  if(!tcadbclose(adb)){
+    eprint(adb, __LINE__, "tcadbclose");
+    err = true;
+  }
+  tcadbdel(adb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform misc command */
+static int procmisc(const char *name, int rnum){
+  iprintf("<Miscellaneous Test>\n  seed=%u  name=%s  rnum=%d\n\n", g_randseed, name, rnum);
+  bool err = false;
+  double stime = tctime();
+  TCADB *adb = tcadbnew();
+  ADBSKEL skel;
+  if(*name == '@'){
+    setskeltran(&skel);
+    if(!tcadbsetskel(adb, &skel)){
+      eprint(adb, __LINE__, "tcadbsetskel");
+      err = true;
+      skel.del(skel.opq);
+    }
+    name++;
+  } else if(*name == '%'){
+    if(!tcadbsetskelmulti(adb, MULDIVNUM)){
+      eprint(adb, __LINE__, "tcadbsetskelmulti");
+      err = true;
+    }
+    name++;
+  }
+  if(!tcadbopen(adb, name)){
+    eprint(adb, __LINE__, "tcadbopen");
+    err = true;
+  }
+  iprintf("writing:\n");
+  for(int i = 1; i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", i);
+    if(!tcadbputkeep(adb, buf, len, buf, len)){
+      eprint(adb, __LINE__, "tcadbputkeep");
+      err = true;
+      break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("reading:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%08d", i);
+    int vsiz;
+    char *vbuf = tcadbget(adb, kbuf, ksiz, &vsiz);
+    if(!vbuf){
+      eprint(adb, __LINE__, "tcadbget");
+      err = true;
+      break;
+    } else if(vsiz != ksiz || memcmp(vbuf, kbuf, vsiz)){
+      eprint(adb, __LINE__, "(validation)");
+      err = true;
+      tcfree(vbuf);
+      break;
+    }
+    tcfree(vbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(tcadbrnum(adb) != rnum){
+    eprint(adb, __LINE__, "(validation)");
+    err = true;
+  }
+  iprintf("random writing:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(rnum) + 1);
+    char vbuf[RECBUFSIZ];
+    int vsiz = myrand(RECBUFSIZ);
+    memset(vbuf, '*', vsiz);
+    if(!tcadbput(adb, kbuf, ksiz, vbuf, vsiz)){
+      eprint(adb, __LINE__, "tcadbput");
+      err = true;
+      break;
+    }
+    int rsiz;
+    char *rbuf = tcadbget(adb, kbuf, ksiz, &rsiz);
+    if(!rbuf){
+      eprint(adb, __LINE__, "tcadbget");
+      err = true;
+      break;
+    }
+    if(rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+      eprint(adb, __LINE__, "(validation)");
+      err = true;
+      tcfree(rbuf);
+      break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+    tcfree(rbuf);
+  }
+  iprintf("word writing:\n");
+  const char *words[] = {
+    "a", "A", "bb", "BB", "ccc", "CCC", "dddd", "DDDD", "eeeee", "EEEEEE",
+    "mikio", "hirabayashi", "tokyo", "cabinet", "hyper", "estraier", "19780211", "birth day",
+    "one", "first", "two", "second", "three", "third", "four", "fourth", "five", "fifth",
+    "_[1]_", "uno", "_[2]_", "dos", "_[3]_", "tres", "_[4]_", "cuatro", "_[5]_", "cinco",
+    "[\xe5\xb9\xb3\xe6\x9e\x97\xe5\xb9\xb9\xe9\x9b\x84]", "[\xe9\xa6\xac\xe9\xb9\xbf]", NULL
+  };
+  for(int i = 0; words[i] != NULL; i += 2){
+    const char *kbuf = words[i];
+    int ksiz = strlen(kbuf);
+    const char *vbuf = words[i+1];
+    int vsiz = strlen(vbuf);
+    if(!tcadbputkeep(adb, kbuf, ksiz, vbuf, vsiz)){
+      eprint(adb, __LINE__, "tcadbputkeep");
+      err = true;
+      break;
+    }
+    if(rnum > 250) iputchar('.');
+  }
+  if(rnum > 250) iprintf(" (%08d)\n", (int)(sizeof(words) / sizeof(*words)));
+  iprintf("random erasing:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(rnum));
+    tcadbout(adb, kbuf, ksiz);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("writing:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "[%d]", i);
+    char vbuf[RECBUFSIZ];
+    int vsiz = i % RECBUFSIZ;
+    memset(vbuf, '*', vsiz);
+    if(!tcadbputkeep(adb, kbuf, ksiz, vbuf, vsiz)){
+      eprint(adb, __LINE__, "tcadbputkeep");
+      err = true;
+      break;
+    }
+    if(vsiz < 1){
+      char tbuf[PATH_MAX];
+      for(int j = 0; j < PATH_MAX; j++){
+        tbuf[j] = myrand(0x100);
+      }
+      if(!tcadbput(adb, kbuf, ksiz, tbuf, PATH_MAX)){
+        eprint(adb, __LINE__, "tcadbput");
+        err = true;
+        break;
+      }
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("erasing:\n");
+  for(int i = 1; i <= rnum; i++){
+    if(i % 2 == 1){
+      char kbuf[RECBUFSIZ];
+      int ksiz = sprintf(kbuf, "[%d]", i);
+      if(!tcadbout(adb, kbuf, ksiz)){
+        eprint(adb, __LINE__, "tcadbout");
+        err = true;
+        break;
+      }
+      tcadbout(adb, kbuf, ksiz);
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("checking iterator:\n");
+  if(!tcadbiterinit(adb)){
+    eprint(adb, __LINE__, "tcadbiterinit");
+    err = true;
+  }
+  char *kbuf;
+  int ksiz;
+  int inum = 0;
+  for(int i = 1; (kbuf = tcadbiternext(adb, &ksiz)) != NULL; i++, inum++){
+    int vsiz;
+    char *vbuf = tcadbget(adb, kbuf, ksiz, &vsiz);
+    if(!vbuf){
+      eprint(adb, __LINE__, "tcadbget");
+      err = true;
+      tcfree(kbuf);
+      break;
+    }
+    tcfree(vbuf);
+    tcfree(kbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(rnum > 250) iprintf(" (%08d)\n", inum);
+  if(inum != tcadbrnum(adb)){
+    eprint(adb, __LINE__, "(validation)");
+    err = true;
+  }
+  iprintf("checking versatile functions:\n");
+  TCLIST *args = tclistnew();
+  for(int i = 1; i <= rnum; i++){
+    if(myrand(10) == 0){
+      const char *name;
+      switch(myrand(3)){
+        default: name = "putlist"; break;
+        case 1: name = "outlist"; break;
+        case 2: name = "getlist"; break;
+      }
+      tclistclear(args);
+      for(int j = myrand(4) * 2; j > 0; j--){
+        char abuf[RECBUFSIZ];
+        int asiz = sprintf(abuf, "%d", myrand(rnum) + 1);
+        tclistpush(args, abuf, asiz);
+      }
+      TCLIST *rv = tcadbmisc(adb, name, args);
+      if(rv){
+        tclistdel(rv);
+      } else {
+        eprint(adb, __LINE__, "tcadbmisc");
+        err = true;
+        break;
+      }
+    } else {
+      char kbuf[RECBUFSIZ];
+      int ksiz = sprintf(kbuf, "(%d)", i);
+      tclistpush(args, kbuf, ksiz);
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  tclistdel(args);
+  args = tclistnew2(1);
+  if(myrand(10) == 0){
+    TCLIST *rv = tcadbmisc(adb, "sync", args);
+    if(rv){
+      tclistdel(rv);
+    } else {
+      eprint(adb, __LINE__, "tcadbmisc");
+      err = true;
+    }
+  }
+  if(myrand(10) == 0){
+    TCLIST *rv = tcadbmisc(adb, "optimize", args);
+    if(rv){
+      tclistdel(rv);
+    } else {
+      eprint(adb, __LINE__, "tcadbmisc");
+      err = true;
+    }
+  }
+  if(myrand(10) == 0){
+    TCLIST *rv = tcadbmisc(adb, "vanish", args);
+    if(rv){
+      tclistdel(rv);
+    } else {
+      eprint(adb, __LINE__, "tcadbmisc");
+      err = true;
+    }
+  }
+  tclistdel(args);
+  if(myrand(10) == 0 && !tcadbsync(adb)){
+    eprint(adb, __LINE__, "tcadbsync");
+    err = true;
+  }
+  if(myrand(10) == 0 && !tcadboptimize(adb, NULL)){
+    eprint(adb, __LINE__, "tcadboptimize");
+    err = true;
+  }
+  if(!tcadbvanish(adb)){
+    eprint(adb, __LINE__, "tcadbvanish");
+    err = true;
+  }
+  int omode = tcadbomode(adb);
+  if(omode == ADBOHDB || omode == ADBOBDB || omode == ADBOFDB){
+    TCMAP *map = tcmapnew();
+    iprintf("random writing:\n");
+    for(int i = 1; i <= rnum; i++){
+      char kbuf[RECBUFSIZ];
+      int ksiz = sprintf(kbuf, "%d", myrand(rnum));
+      char vbuf[RECBUFSIZ];
+      int vsiz = sprintf(vbuf, "%d", myrand(rnum));
+      switch(myrand(4)){
+        case 0:
+          if(!tcadbput(adb, kbuf, ksiz, vbuf, vsiz)){
+            eprint(adb, __LINE__, "tcadbput");
+            err = true;
+          }
+          tcmapput(map, kbuf, ksiz, vbuf, vsiz);
+          break;
+        case 1:
+          tcadbputkeep(adb, kbuf, ksiz, vbuf, vsiz);
+          tcmapputkeep(map, kbuf, ksiz, vbuf, vsiz);
+          break;
+        case 2:
+          tcadbputcat(adb, kbuf, ksiz, vbuf, vsiz);
+          tcmapputcat(map, kbuf, ksiz, vbuf, vsiz);
+          break;
+        case 3:
+          tcadbout(adb, kbuf, ksiz);
+          tcmapout(map, kbuf, ksiz);
+          break;
+      }
+      if(rnum > 250 && i % (rnum / 250) == 0){
+        iputchar('.');
+        if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+      }
+    }
+    iprintf("checking transaction commit:\n");
+    if(!tcadbtranbegin(adb)){
+      eprint(adb, __LINE__, "tcadbtranbegin");
+      err = true;
+    }
+    for(int i = 1; i <= rnum; i++){
+      char kbuf[RECBUFSIZ];
+      int ksiz = sprintf(kbuf, "%d", myrand(rnum));
+      char vbuf[RECBUFSIZ];
+      int vsiz = sprintf(vbuf, "[%d]", myrand(rnum));
+      switch(myrand(6)){
+        case 0:
+          if(!tcadbput(adb, kbuf, ksiz, vbuf, vsiz)){
+            eprint(adb, __LINE__, "tcadbput");
+            err = true;
+          }
+          tcmapput(map, kbuf, ksiz, vbuf, vsiz);
+          break;
+        case 1:
+          tcadbputkeep(adb, kbuf, ksiz, vbuf, vsiz);
+          tcmapputkeep(map, kbuf, ksiz, vbuf, vsiz);
+          break;
+        case 2:
+          tcadbputcat(adb, kbuf, ksiz, vbuf, vsiz);
+          tcmapputcat(map, kbuf, ksiz, vbuf, vsiz);
+          break;
+        case 3:
+          tcadbaddint(adb, kbuf, ksiz, 1);
+          tcmapaddint(map, kbuf, ksiz, 1);
+          break;
+        case 4:
+          tcadbadddouble(adb, kbuf, ksiz, 1.0);
+          tcmapadddouble(map, kbuf, ksiz, 1.0);
+          break;
+        case 5:
+          tcadbout(adb, kbuf, ksiz);
+          tcmapout(map, kbuf, ksiz);
+          break;
+      }
+      if(rnum > 250 && i % (rnum / 250) == 0){
+        iputchar('.');
+        if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+      }
+    }
+    if(!tcadbtrancommit(adb)){
+      eprint(adb, __LINE__, "tcadbtrancommit");
+      err = true;
+    }
+    iprintf("checking transaction abort:\n");
+    uint64_t ornum = tcadbrnum(adb);
+    uint64_t osize = tcadbsize(adb);
+    if(!tcadbtranbegin(adb)){
+      eprint(adb, __LINE__, "tcadbtranbegin");
+      err = true;
+    }
+    for(int i = 1; i <= rnum; i++){
+      char kbuf[RECBUFSIZ];
+      int ksiz = sprintf(kbuf, "%d", myrand(rnum));
+      char vbuf[RECBUFSIZ];
+      int vsiz = sprintf(vbuf, "((%d))", myrand(rnum));
+      switch(myrand(6)){
+        case 0:
+          if(!tcadbput(adb, kbuf, ksiz, vbuf, vsiz)){
+            eprint(adb, __LINE__, "tcadbput");
+            err = true;
+          }
+          break;
+        case 1:
+          tcadbputkeep(adb, kbuf, ksiz, vbuf, vsiz);
+          break;
+        case 2:
+          tcadbputcat(adb, kbuf, ksiz, vbuf, vsiz);
+          break;
+        case 3:
+          tcadbaddint(adb, kbuf, ksiz, 1);
+          break;
+        case 4:
+          tcadbadddouble(adb, kbuf, ksiz, 1.0);
+          break;
+        case 5:
+          tcadbout(adb, kbuf, ksiz);
+          break;
+      }
+      if(rnum > 250 && i % (rnum / 250) == 0){
+        iputchar('.');
+        if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+      }
+    }
+    if(!tcadbtranabort(adb)){
+      eprint(adb, __LINE__, "tcadbtranabort");
+      err = true;
+    }
+    iprintf("checking consistency:\n");
+    if(tcadbrnum(adb) != ornum || tcadbsize(adb) != osize || tcadbrnum(adb) != tcmaprnum(map)){
+      eprint(adb, __LINE__, "(validation)");
+      err = true;
+    }
+    inum = 0;
+    tcmapiterinit(map);
+    const char *tkbuf;
+    int tksiz;
+    for(int i = 1; (tkbuf = tcmapiternext(map, &tksiz)) != NULL; i++, inum++){
+      int tvsiz;
+      const char *tvbuf = tcmapiterval(tkbuf, &tvsiz);
+      int rsiz;
+      char *rbuf = tcadbget(adb, tkbuf, tksiz, &rsiz);
+      if(!rbuf || rsiz != tvsiz || memcmp(rbuf, tvbuf, rsiz)){
+        eprint(adb, __LINE__, "(validation)");
+        err = true;
+        tcfree(rbuf);
+        break;
+      }
+      tcfree(rbuf);
+      if(rnum > 250 && i % (rnum / 250) == 0){
+        iputchar('.');
+        if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+      }
+    }
+    if(rnum > 250) iprintf(" (%08d)\n", inum);
+    inum = 0;
+    if(!tcadbiterinit(adb)){
+      eprint(adb, __LINE__, "tcadbiterinit");
+      err = true;
+    }
+    for(int i = 1; (kbuf = tcadbiternext(adb, &ksiz)) != NULL; i++, inum++){
+      int vsiz;
+      char *vbuf = tcadbget(adb, kbuf, ksiz, &vsiz);
+      int rsiz;
+      const char *rbuf = tcmapget(map, kbuf, ksiz, &rsiz);
+      if(!rbuf || rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+        eprint(adb, __LINE__, "(validation)");
+        err = true;
+        tcfree(vbuf);
+        tcfree(kbuf);
+        break;
+      }
+      tcfree(vbuf);
+      tcfree(kbuf);
+      if(rnum > 250 && i % (rnum / 250) == 0){
+        iputchar('.');
+        if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+      }
+    }
+    if(rnum > 250) iprintf(" (%08d)\n", inum);
+    tcmapdel(map);
+    if(!tcadbvanish(adb)){
+      eprint(adb, __LINE__, "tcadbvanish");
+      err = true;
+    }
+  }
+  if(!tcadbput2(adb, "mikio", "hirabayashi")){
+    eprint(adb, __LINE__, "tcadbput2");
+    err = true;
+  }
+  for(int i = 0; i < 10; i++){
+    char buf[RECBUFSIZ];
+    int size = sprintf(buf, "%d", myrand(rnum));
+    if(!tcadbput(adb, buf, size, buf, size)){
+      eprint(adb, __LINE__, "tcadbput");
+      err = true;
+    }
+  }
+  for(int i = myrand(3) + 1; i < PATH_MAX; i = i * 2 + myrand(3)){
+    char vbuf[i];
+    memset(vbuf, '@', i - 1);
+    vbuf[i-1] = '\0';
+    if(!tcadbput2(adb, "mikio", vbuf)){
+      eprint(adb, __LINE__, "tcadbput2");
+      err = true;
+    }
+  }
+  if(!tcadbforeach(adb, iterfunc, NULL)){
+    eprint(adb, __LINE__, "tcadbforeach");
+    err = true;
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcadbrnum(adb));
+  iprintf("size: %llu\n", (unsigned long long)tcadbsize(adb));
+  sysprint();
+  if(!tcadbclose(adb)){
+    eprint(adb, __LINE__, "tcadbclose");
+    err = true;
+  }
+  tcadbdel(adb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform wicked command */
+static int procwicked(const char *name, int rnum){
+  iprintf("<Wicked Writing Test>\n  seed=%u  name=%s  rnum=%d\n\n", g_randseed, name, rnum);
+  bool err = false;
+  double stime = tctime();
+  TCADB *adb = tcadbnew();
+  ADBSKEL skel;
+  if(*name == '@'){
+    setskeltran(&skel);
+    if(!tcadbsetskel(adb, &skel)){
+      eprint(adb, __LINE__, "tcadbsetskel");
+      err = true;
+      skel.del(skel.opq);
+    }
+    name++;
+  } else if(*name == '%'){
+    if(!tcadbsetskelmulti(adb, MULDIVNUM)){
+      eprint(adb, __LINE__, "tcadbsetskelmulti");
+      err = true;
+    }
+    name++;
+  }
+  if(!tcadbopen(adb, name)){
+    eprint(adb, __LINE__, "tcadbopen");
+    err = true;
+  }
+  TCMAP *map = tcmapnew2(rnum / 5);
+  for(int i = 1; i <= rnum && !err; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(rnum) + 1);
+    char vbuf[RECBUFSIZ];
+    int vsiz = myrand(RECBUFSIZ);
+    memset(vbuf, '*', vsiz);
+    vbuf[vsiz] = '\0';
+    char *rbuf;
+    switch(myrand(16)){
+      case 0:
+        iputchar('0');
+        if(!tcadbput(adb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(adb, __LINE__, "tcadbput");
+          err = true;
+        }
+        tcmapput(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 1:
+        iputchar('1');
+        if(!tcadbput2(adb, kbuf, vbuf)){
+          eprint(adb, __LINE__, "tcadbput2");
+          err = true;
+        }
+        tcmapput2(map, kbuf, vbuf);
+        break;
+      case 2:
+        iputchar('2');
+        tcadbputkeep(adb, kbuf, ksiz, vbuf, vsiz);
+        tcmapputkeep(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 3:
+        iputchar('3');
+        tcadbputkeep2(adb, kbuf, vbuf);
+        tcmapputkeep2(map, kbuf, vbuf);
+        break;
+      case 4:
+        iputchar('4');
+        if(!tcadbputcat(adb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(adb, __LINE__, "tcadbputcat");
+          err = true;
+        }
+        tcmapputcat(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 5:
+        iputchar('5');
+        if(!tcadbputcat2(adb, kbuf, vbuf)){
+          eprint(adb, __LINE__, "tcadbputcat2");
+          err = true;
+        }
+        tcmapputcat2(map, kbuf, vbuf);
+        break;
+      case 6:
+        iputchar('6');
+        if(myrand(10) == 0){
+          tcadbout(adb, kbuf, ksiz);
+          tcmapout(map, kbuf, ksiz);
+        }
+        break;
+      case 7:
+        iputchar('7');
+        if(myrand(10) == 0){
+          tcadbout2(adb, kbuf);
+          tcmapout2(map, kbuf);
+        }
+        break;
+      case 8:
+        iputchar('8');
+        if((rbuf = tcadbget(adb, kbuf, ksiz, &vsiz)) != NULL) tcfree(rbuf);
+        break;
+      case 9:
+        iputchar('9');
+        if((rbuf = tcadbget2(adb, kbuf)) != NULL) tcfree(rbuf);
+        break;
+      case 10:
+        iputchar('A');
+        tcadbvsiz(adb, kbuf, ksiz);
+        break;
+      case 11:
+        iputchar('B');
+        tcadbvsiz2(adb, kbuf);
+        break;
+      case 12:
+        iputchar('E');
+        if(myrand(rnum / 50) == 0){
+          if(!tcadbiterinit(adb)){
+            eprint(adb, __LINE__, "tcadbiterinit");
+            err = true;
+          }
+        }
+        for(int j = myrand(rnum) / 1000 + 1; j >= 0; j--){
+          int iksiz;
+          char *ikbuf = tcadbiternext(adb, &iksiz);
+          if(ikbuf) tcfree(ikbuf);
+        }
+        break;
+      default:
+        iputchar('@');
+        if(myrand(10000) == 0) srand((unsigned int)(tctime() * 1000) % UINT_MAX);
+        break;
+    }
+    if(i % 50 == 0) iprintf(" (%08d)\n", i);
+  }
+  if(rnum % 50 > 0) iprintf(" (%08d)\n", rnum);
+  tcadbsync(adb);
+  if(tcadbrnum(adb) != tcmaprnum(map)){
+    eprint(adb, __LINE__, "(validation)");
+    err = true;
+  }
+  for(int i = 1; i <= rnum && !err; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", i - 1);
+    int vsiz;
+    const char *vbuf = tcmapget(map, kbuf, ksiz, &vsiz);
+    int rsiz;
+    char *rbuf = tcadbget(adb, kbuf, ksiz, &rsiz);
+    if(vbuf){
+      iputchar('.');
+      if(!rbuf){
+        eprint(adb, __LINE__, "tcadbget");
+        err = true;
+      } else if(rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+        eprint(adb, __LINE__, "(validation)");
+        err = true;
+      }
+    } else {
+      iputchar('*');
+      if(rbuf){
+        eprint(adb, __LINE__, "(validation)");
+        err = true;
+      }
+    }
+    tcfree(rbuf);
+    if(i % 50 == 0) iprintf(" (%08d)\n", i);
+  }
+  if(rnum % 50 > 0) iprintf(" (%08d)\n", rnum);
+  tcmapiterinit(map);
+  int ksiz;
+  const char *kbuf;
+  for(int i = 1; (kbuf = tcmapiternext(map, &ksiz)) != NULL; i++){
+    iputchar('+');
+    int vsiz;
+    const char *vbuf = tcmapiterval(kbuf, &vsiz);
+    int rsiz;
+    char *rbuf = tcadbget(adb, kbuf, ksiz, &rsiz);
+    if(!rbuf){
+      eprint(adb, __LINE__, "tcadbget");
+      err = true;
+    } else if(rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+      eprint(adb, __LINE__, "(validation)");
+      err = true;
+    }
+    tcfree(rbuf);
+    if(!tcadbout(adb, kbuf, ksiz)){
+      eprint(adb, __LINE__, "tcadbout");
+      err = true;
+    }
+    if(i % 50 == 0) iprintf(" (%08d)\n", i);
+  }
+  int mrnum = tcmaprnum(map);
+  if(mrnum % 50 > 0) iprintf(" (%08d)\n", mrnum);
+  if(tcadbrnum(adb) != 0){
+    eprint(adb, __LINE__, "(validation)");
+    err = true;
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcadbrnum(adb));
+  iprintf("size: %llu\n", (unsigned long long)tcadbsize(adb));
+  sysprint();
+  tcmapdel(map);
+  if(!tcadbclose(adb)){
+    eprint(adb, __LINE__, "tcadbclose");
+    err = true;
+  }
+  tcadbdel(adb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform compare command */
+static int proccompare(const char *name, int tnum, int rnum){
+  iprintf("<Comparison Test>\n  seed=%u  name=%s  tnum=%d  rnum=%d\n\n",
+          g_randseed, name, tnum, rnum);
+  bool err = false;
+  double stime = tctime();
+  char path[PATH_MAX];
+  TCMDB *mdb = tcmdbnew2(rnum / 2);
+  TCNDB *ndb = tcndbnew();
+  TCHDB *hdb = tchdbnew();
+  tchdbsetdbgfd(hdb, UINT16_MAX);
+  int hopts = 0;
+  if(myrand(2) == 1) hopts |= HDBTLARGE;
+  if(myrand(2) == 1) hopts |= HDBTBZIP;
+  if(!tchdbtune(hdb, rnum / 2, -1, -1, hopts)){
+    eprint(NULL, __LINE__, "tchdbtune");
+    err = true;
+  }
+  if(!tchdbsetcache(hdb, rnum / 10)){
+    eprint(NULL, __LINE__, "tchdbsetcache");
+    err = true;
+  }
+  if(!tchdbsetxmsiz(hdb, 4096)){
+    eprint(NULL, __LINE__, "tchdbsetxmsiz");
+    err = true;
+  }
+  if(!tchdbsetdfunit(hdb, 8)){
+    eprint(NULL, __LINE__, "tchdbsetdfunit");
+    err = true;
+  }
+  sprintf(path, "%s.tch", name);
+  int homode = HDBOWRITER | HDBOCREAT | HDBOTRUNC;
+  if(myrand(100) == 1) homode |= HDBOTSYNC;
+  if(!tchdbopen(hdb, path, homode)){
+    eprint(NULL, __LINE__, "tchdbopen");
+    err = true;
+  }
+  TCBDB *bdb = tcbdbnew();
+  tcbdbsetdbgfd(bdb, UINT16_MAX);
+  int bopts = 0;
+  if(myrand(2) == 1) bopts |= BDBTLARGE;
+  if(myrand(2) == 1) bopts |= BDBTBZIP;
+  if(!tcbdbtune(bdb, 5, 5, rnum / 10, -1, -1, bopts)){
+    eprint(NULL, __LINE__, "tcbdbtune");
+    err = true;
+  }
+  if(!tcbdbsetxmsiz(bdb, 4096)){
+    eprint(NULL, __LINE__, "tcbdbsetxmsiz");
+    err = true;
+  }
+  if(!tcbdbsetdfunit(bdb, 8)){
+    eprint(NULL, __LINE__, "tcbdbsetdfunit");
+    err = true;
+  }
+  sprintf(path, "%s.tcb", name);
+  int bomode = BDBOWRITER | BDBOCREAT | BDBOTRUNC;
+  if(myrand(100) == 1) bomode |= BDBOTSYNC;
+  if(!tcbdbopen(bdb, path, bomode)){
+    eprint(NULL, __LINE__, "tcbdbopen");
+    err = true;
+  }
+  BDBCUR *cur = tcbdbcurnew(bdb);
+  TCADB *adb = tcadbnew();
+  switch(myrand(4)){
+    case 0:
+      sprintf(path, "%s.adb.tch#mode=wct#bnum=%d", name, rnum);
+      break;
+    case 1:
+      sprintf(path, "%s.adb.tcb#mode=wct#lmemb=256#nmemb=512", name);
+      break;
+    case 2:
+      sprintf(path, "+");
+      break;
+    default:
+      sprintf(path, "*");
+      break;
+  }
+  if(!tcadbopen(adb, path)){
+    eprint(NULL, __LINE__, "tcbdbopen");
+    err = true;
+  }
+  for(int t = 1; !err && t <= tnum; t++){
+    bool commit = myrand(2) == 0;
+    iprintf("transaction %d (%s):\n", t, commit ? "commit" : "abort");
+    if(!tchdbtranbegin(hdb)){
+      eprint(NULL, __LINE__, "tchdbtranbegin");
+      err = true;
+    }
+    if(!tcbdbtranbegin(bdb)){
+      eprint(NULL, __LINE__, "tcbdbtranbegin");
+      err = true;
+    }
+    if(myrand(tnum) == 0){
+      bool all = myrand(2) == 0;
+      for(int i = 1; !err && i <= rnum; i++){
+        char kbuf[RECBUFSIZ];
+        int ksiz = sprintf(kbuf, "%d", all ? i : myrand(i) + 1);
+        if(!tchdbout(hdb, kbuf, ksiz) && tchdbecode(hdb) != TCENOREC){
+          eprint(NULL, __LINE__, "tchdbout");
+          err = true;
+        }
+        if(!tcbdbout(bdb, kbuf, ksiz) && tcbdbecode(bdb) != TCENOREC){
+          eprint(NULL, __LINE__, "tcbdbout");
+          err = true;
+        }
+        tcadbout(adb, kbuf, ksiz);
+        if(tchdbout(hdb, kbuf, ksiz) || tchdbecode(hdb) != TCENOREC){
+          eprint(NULL, __LINE__, "(validation)");
+          err = true;
+        }
+        if(tcbdbout(bdb, kbuf, ksiz) || tcbdbecode(bdb) != TCENOREC){
+          eprint(NULL, __LINE__, "(validation)");
+          err = true;
+        }
+        if(tcadbout(adb, kbuf, ksiz)){
+          eprint(NULL, __LINE__, "(validation)");
+          err = true;
+        }
+        if(commit){
+          tcmdbout(mdb, kbuf, ksiz);
+          tcndbout(ndb, kbuf, ksiz);
+        }
+        if(rnum > 250 && i % (rnum / 250) == 0){
+          iputchar('.');
+          if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+        }
+      }
+    } else {
+      if(myrand(tnum + 1) == 0){
+        if(!tchdbcacheclear(hdb)){
+          eprint(NULL, __LINE__, "tchdbcacheclear");
+          err = true;
+        }
+        if(!tcbdbcacheclear(bdb)){
+          eprint(NULL, __LINE__, "tcbdbcacheclear");
+          err = true;
+        }
+      }
+      int cldeno = tnum * (rnum / 2) + 1;
+      int act = myrand(7);
+      for(int i = 1; !err && i <= rnum; i++){
+        if(myrand(cldeno) == 0){
+          if(!tchdbcacheclear(hdb)){
+            eprint(NULL, __LINE__, "tchdbcacheclear");
+            err = true;
+          }
+          if(!tcbdbcacheclear(bdb)){
+            eprint(NULL, __LINE__, "tcbdbcacheclear");
+            err = true;
+          }
+        }
+        if(myrand(10) == 0) act = myrand(7);
+        char kbuf[RECBUFSIZ];
+        int ksiz = sprintf(kbuf, "%d", myrand(i) + 1);
+        char vbuf[RECBUFSIZ+256];
+        int vsiz = sprintf(vbuf, "%64d:%d:%d", t, i, myrand(i));
+        switch(act){
+          case 0:
+            if(!tchdbput(hdb, kbuf, ksiz, vbuf, vsiz)){
+              eprint(NULL, __LINE__, "tchdbput");
+              err = true;
+            }
+            if(!tcbdbput(bdb, kbuf, ksiz, vbuf, vsiz)){
+              eprint(NULL, __LINE__, "tcbdbput");
+              err = true;
+            }
+            if(!tcadbput(adb, kbuf, ksiz, vbuf, vsiz)){
+              eprint(NULL, __LINE__, "tcadbput");
+              err = true;
+            }
+            if(commit){
+              tcmdbput(mdb, kbuf, ksiz, vbuf, vsiz);
+              tcndbput(ndb, kbuf, ksiz, vbuf, vsiz);
+            }
+            break;
+          case 1:
+            if(!tchdbputkeep(hdb, kbuf, ksiz, vbuf, vsiz) && tchdbecode(hdb) != TCEKEEP){
+              eprint(NULL, __LINE__, "tchdbputkeep");
+              err = true;
+            }
+            if(!tcbdbputkeep(bdb, kbuf, ksiz, vbuf, vsiz) && tcbdbecode(bdb) != TCEKEEP){
+              eprint(NULL, __LINE__, "tcbdbputkeep");
+              err = true;
+            }
+            tcadbputkeep(adb, kbuf, ksiz, vbuf, vsiz);
+            if(commit){
+              tcmdbputkeep(mdb, kbuf, ksiz, vbuf, vsiz);
+              tcndbputkeep(ndb, kbuf, ksiz, vbuf, vsiz);
+            }
+            break;
+          case 2:
+            if(!tchdbputcat(hdb, kbuf, ksiz, vbuf, vsiz)){
+              eprint(NULL, __LINE__, "tchdbputcat");
+              err = true;
+            }
+            if(!tcbdbputcat(bdb, kbuf, ksiz, vbuf, vsiz)){
+              eprint(NULL, __LINE__, "tcbdbputcat");
+              err = true;
+            }
+            if(!tcadbputcat(adb, kbuf, ksiz, vbuf, vsiz)){
+              eprint(NULL, __LINE__, "tcadbputcat");
+              err = true;
+            }
+            if(commit){
+              tcmdbputcat(mdb, kbuf, ksiz, vbuf, vsiz);
+              tcndbputcat(ndb, kbuf, ksiz, vbuf, vsiz);
+            }
+            break;
+          case 3:
+            if(tchdbaddint(hdb, kbuf, ksiz, 1) == INT_MIN && tchdbecode(hdb) != TCEKEEP){
+              eprint(NULL, __LINE__, "tchdbaddint");
+              err = true;
+            }
+            if(tcbdbaddint(bdb, kbuf, ksiz, 1) == INT_MIN && tcbdbecode(bdb) != TCEKEEP){
+              eprint(NULL, __LINE__, "tchdbaddint");
+              err = true;
+            }
+            tcadbaddint(adb, kbuf, ksiz, 1);
+            if(commit){
+              tcmdbaddint(mdb, kbuf, ksiz, 1);
+              tcndbaddint(ndb, kbuf, ksiz, 1);
+            }
+            break;
+          case 4:
+            if(isnan(tchdbadddouble(hdb, kbuf, ksiz, 1.0)) && tchdbecode(hdb) != TCEKEEP){
+              eprint(NULL, __LINE__, "tchdbadddouble");
+              err = true;
+            }
+            if(isnan(tcbdbadddouble(bdb, kbuf, ksiz, 1.0)) && tcbdbecode(bdb) != TCEKEEP){
+              eprint(NULL, __LINE__, "tchdbadddouble");
+              err = true;
+            }
+            tcadbadddouble(adb, kbuf, ksiz, 1.0);
+            if(commit){
+              tcmdbadddouble(mdb, kbuf, ksiz, 1.0);
+              tcndbadddouble(ndb, kbuf, ksiz, 1.0);
+            }
+            break;
+          case 5:
+            if(myrand(2) == 0){
+              if(!tchdbputproc(hdb, kbuf, ksiz, vbuf, vsiz, pdprocfunccmp, &i) &&
+                 tchdbecode(hdb) != TCEKEEP){
+                eprint(NULL, __LINE__, "tchdbputproc");
+                err = true;
+              }
+              if(!tcbdbputproc(bdb, kbuf, ksiz, vbuf, vsiz, pdprocfunccmp, &i) &&
+                 tcbdbecode(bdb) != TCEKEEP){
+                eprint(NULL, __LINE__, "tcbdbputproc");
+                err = true;
+              }
+              tcadbputproc(adb, kbuf, ksiz, vbuf, vsiz, pdprocfunccmp, &i);
+              if(commit){
+                tcmdbputproc(mdb, kbuf, ksiz, vbuf, vsiz, pdprocfunccmp, &i);
+                tcndbputproc(ndb, kbuf, ksiz, vbuf, vsiz, pdprocfunccmp, &i);
+              }
+            } else {
+              if(!tchdbputproc(hdb, kbuf, ksiz, NULL, 0, pdprocfunccmp, &i) &&
+                 tchdbecode(hdb) != TCEKEEP && tchdbecode(hdb) != TCENOREC){
+                eprint(NULL, __LINE__, "tchdbputproc");
+                err = true;
+              }
+              if(!tcbdbputproc(bdb, kbuf, ksiz, NULL, 0, pdprocfunccmp, &i) &&
+                 tcbdbecode(bdb) != TCEKEEP && tchdbecode(hdb) != TCENOREC){
+                eprint(NULL, __LINE__, "tcbdbputproc");
+                err = true;
+              }
+              tcadbputproc(adb, kbuf, ksiz, NULL, 0, pdprocfunccmp, &i);
+              if(commit){
+                tcmdbputproc(mdb, kbuf, ksiz, NULL, 0, pdprocfunccmp, &i);
+                tcndbputproc(ndb, kbuf, ksiz, NULL, 0, pdprocfunccmp, &i);
+              }
+            }
+            break;
+          default:
+            if(myrand(20) == 0){
+              if(!tcbdbcurjump(cur, kbuf, ksiz) && tcbdbecode(bdb) != TCENOREC){
+                eprint(NULL, __LINE__, "tcbdbcurjump");
+                err = true;
+              }
+              char *cbuf;
+              int csiz;
+              while((cbuf = tcbdbcurkey(cur, &csiz)) != NULL){
+                if(!tchdbout(hdb, cbuf, csiz)){
+                  eprint(NULL, __LINE__, "tchdbout");
+                  err = true;
+                }
+                if(!tcbdbout(bdb, cbuf, csiz)){
+                  eprint(NULL, __LINE__, "tcbdbout");
+                  err = true;
+                }
+                tcadbout(adb, cbuf, csiz);
+                if(commit){
+                  tcmdbout(mdb, cbuf, csiz);
+                  tcndbout(ndb, cbuf, csiz);
+                }
+                tcfree(cbuf);
+                if(myrand(10) == 0) break;
+                switch(myrand(3)){
+                  case 1:
+                    if(!tcbdbcurprev(cur) && tcbdbecode(bdb) != TCENOREC){
+                      eprint(NULL, __LINE__, "tcbdbcurprev");
+                      err = true;
+                    }
+                    break;
+                  case 2:
+                    if(!tcbdbcurnext(cur) && tcbdbecode(bdb) != TCENOREC){
+                      eprint(NULL, __LINE__, "tcbdbcurprev");
+                      err = true;
+                    }
+                    break;
+                }
+              }
+            } else {
+              if(!tchdbout(hdb, kbuf, ksiz) && tchdbecode(hdb) != TCENOREC){
+                eprint(NULL, __LINE__, "tchdbout");
+                err = true;
+              }
+              if(!tcbdbout(bdb, kbuf, ksiz) && tcbdbecode(bdb) != TCENOREC){
+                eprint(NULL, __LINE__, "tcbdbout");
+                err = true;
+              }
+              tcadbout(adb, kbuf, ksiz);
+              if(commit){
+                tcmdbout(mdb, kbuf, ksiz);
+                tcndbout(ndb, kbuf, ksiz);
+              }
+            }
+            break;
+        }
+        if(rnum > 250 && i % (rnum / 250) == 0){
+          iputchar('.');
+          if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+        }
+      }
+    }
+    if(commit){
+      if(!tchdbtrancommit(hdb)){
+        eprint(NULL, __LINE__, "tchdbcommit");
+        err = true;
+      }
+      if(!tcbdbtrancommit(bdb)){
+        eprint(NULL, __LINE__, "tcbdbcommit");
+        err = true;
+      }
+    } else {
+      if(myrand(5) == 0){
+        if(!tchdbclose(hdb)){
+          eprint(NULL, __LINE__, "tchdbclose");
+          err = true;
+        }
+        sprintf(path, "%s.tch", name);
+        if(!tchdbopen(hdb, path, HDBOWRITER)){
+          eprint(NULL, __LINE__, "tchdbopen");
+          err = true;
+        }
+        if(!tcbdbclose(bdb)){
+          eprint(NULL, __LINE__, "tcbdbclose");
+          err = true;
+        }
+        sprintf(path, "%s.tcb", name);
+        if(!tcbdbopen(bdb, path, BDBOWRITER)){
+          eprint(NULL, __LINE__, "tcbdbopen");
+          err = true;
+        }
+      } else {
+        if(!tchdbtranabort(hdb)){
+          eprint(NULL, __LINE__, "tchdbtranabort");
+          err = true;
+        }
+        if(!tcbdbtranabort(bdb)){
+          eprint(NULL, __LINE__, "tcbdbtranabort");
+          err = true;
+        }
+      }
+    }
+  }
+  iprintf("checking consistency of range:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", i);
+    int vsiz;
+    char *vbuf = tcmdbget(mdb, kbuf, ksiz, &vsiz);
+    if(vbuf){
+      int rsiz;
+      char *rbuf = tcndbget(ndb, kbuf, ksiz, &rsiz);
+      if(rbuf){
+        tcfree(rbuf);
+      } else {
+        eprint(NULL, __LINE__, "tcndbget");
+        err = true;
+      }
+      rbuf = tchdbget(hdb, kbuf, ksiz, &rsiz);
+      if(rbuf){
+        tcfree(rbuf);
+      } else {
+        eprint(NULL, __LINE__, "tchdbget");
+        err = true;
+      }
+      rbuf = tcbdbget(bdb, kbuf, ksiz, &rsiz);
+      if(rbuf){
+        tcfree(rbuf);
+      } else {
+        eprint(NULL, __LINE__, "tcbdbget");
+        err = true;
+      }
+      tcfree(vbuf);
+    } else {
+      int rsiz;
+      char *rbuf = tcndbget(ndb, kbuf, ksiz, &rsiz);
+      if(rbuf){
+        eprint(NULL, __LINE__, "tcndbget");
+        tcfree(rbuf);
+        err = true;
+      }
+      rbuf = tchdbget(hdb, kbuf, ksiz, &rsiz);
+      if(rbuf){
+        eprint(NULL, __LINE__, "tchdbget");
+        err = true;
+        tcfree(rbuf);
+      }
+      rbuf = tcbdbget(bdb, kbuf, ksiz, &rsiz);
+      if(rbuf){
+        eprint(NULL, __LINE__, "tcbdbget");
+        err = true;
+        tcfree(rbuf);
+      }
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("checking consistency on memory:\n");
+  if(tchdbrnum(hdb) != tcbdbrnum(bdb)){
+    eprint(NULL, __LINE__, "(validation)");
+    err = true;
+  }
+  int inum = 0;
+  tcmdbiterinit(mdb);
+  char *kbuf;
+  int ksiz;
+  for(int i = 1; (kbuf = tcmdbiternext(mdb, &ksiz)) != NULL; i++, inum++){
+    int vsiz;
+    char *vbuf = tcmdbget(mdb, kbuf, ksiz, &vsiz);
+    int rsiz;
+    char *rbuf = tcndbget(ndb, kbuf, ksiz, &rsiz);
+    if(!vbuf || !rbuf || rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+      eprint(NULL, __LINE__, "tcndbget");
+      err = true;
+    }
+    tcfree(rbuf);
+    rbuf = tchdbget(hdb, kbuf, ksiz, &rsiz);
+    if(!rbuf || rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+      eprint(NULL, __LINE__, "tchdbget");
+      err = true;
+    }
+    tcfree(rbuf);
+    rbuf = tcbdbget(bdb, kbuf, ksiz, &rsiz);
+    if(!rbuf || rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+      eprint(NULL, __LINE__, "tcbdbget");
+      err = true;
+    }
+    tcfree(rbuf);
+    tcfree(vbuf);
+    tcfree(kbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(rnum > 250) iprintf(" (%08d)\n", inum);
+  iprintf("checking consistency of hash:\n");
+  inum = 0;
+  if(!tchdbiterinit(hdb)){
+    eprint(NULL, __LINE__, "tchdbiterinit");
+    err = true;
+  }
+  for(int i = 1; (kbuf = tchdbiternext(hdb, &ksiz)) != NULL; i++, inum++){
+    int vsiz;
+    char *vbuf = tchdbget(hdb, kbuf, ksiz, &vsiz);
+    int rsiz;
+    char *rbuf = tcmdbget(mdb, kbuf, ksiz, &rsiz);
+    if(!vbuf || !rbuf || rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+      eprint(NULL, __LINE__, "(validation)");
+      err = true;
+    }
+    tcfree(rbuf);
+    tcfree(vbuf);
+    tcfree(kbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(rnum > 250) iprintf(" (%08d)\n", inum);
+  iprintf("checking consistency of tree:\n");
+  if(!tcbdbcurfirst(cur) && tcbdbecode(bdb) != TCENOREC){
+    eprint(NULL, __LINE__, "tcbdbcurfirst");
+    err = true;
+  }
+  for(int i = 1; (kbuf = tcbdbcurkey(cur, &ksiz)) != NULL; i++, inum++){
+    int vsiz;
+    char *vbuf = tcbdbget(bdb, kbuf, ksiz, &vsiz);
+    int rsiz;
+    char *rbuf = tcndbget(ndb, kbuf, ksiz, &rsiz);
+    if(!vbuf || !rbuf || rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+      eprint(NULL, __LINE__, "(validation)");
+      err = true;
+    }
+    tcfree(rbuf);
+    tcfree(vbuf);
+    tcfree(kbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+    tcbdbcurnext(cur);
+  }
+  if(rnum > 250) iprintf(" (%08d)\n", inum);
+  if(!tcadbclose(adb)){
+    eprint(NULL, __LINE__, "tcadbclose");
+    err = true;
+  }
+  tcbdbcurdel(cur);
+  if(!tcbdbclose(bdb)){
+    eprint(NULL, __LINE__, "tcbdbclose");
+    err = true;
+  }
+  if(!tchdbclose(hdb)){
+    eprint(NULL, __LINE__, "tcbdbclose");
+    err = true;
+  }
+  tcadbdel(adb);
+  tcmdbdel(mdb);
+  tcndbdel(ndb);
+  tchdbdel(hdb);
+  tcbdbdel(bdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+
+// END OF FILE
diff --git a/tcejdb/tcawmgr.c b/tcejdb/tcawmgr.c
new file mode 100644 (file)
index 0000000..97412e1
--- /dev/null
@@ -0,0 +1,482 @@
+/*************************************************************************************************
+ * The CGI utility of the abstract database API
+ *                                                               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 <tcadb.h>
+#include "myconf.h"
+
+#define MINIBNUM       31                // bucket number of map for trivial use
+#define OUTBUFSIZ      (256*1024)        // size of the output buffer
+#define UPLOADMAX      (256*1024*1024)   // maximum size of the upload data
+#define DEFSHOWNUM     30                // default number of show result
+#define FWMMAX         10000             // maximum number of forward matching
+#define VALWIDTH       80                // maximum width of shown value
+#define PAGETITLE      "Abstract Database Manager"  // page title
+#define DBNAME         "casket"          // name of the database
+
+#define XP(...) tcxstrprintf(obuf, __VA_ARGS__)  // syntex sugar of output setting
+
+typedef struct {                         // type of structure of CGI parameters
+  int action;                            // kind of the action
+  const char *kbuf;                      // key buffer
+  int ksiz;                              // key size
+  const char *vbuf;                      // value buffer
+  int vsiz;                              // value size
+  int num;                               // number per page
+  int page;                              // number of the page
+} PARAMS;
+
+enum {                                   // enumeration for error codes
+  ACTLIST,                               // list records
+  ACTLISTVAL,                            // list records with values
+  ACTPUT,                                // put a record
+  ACTOUT,                                // remove a record
+  ACTGET                                 // get a record
+};
+
+
+
+/* global variables */
+const char *g_scriptname;                // program name
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+static void readparameters(TCMAP *params);
+static void dolist(PARAMS *params, TCADB *db);
+static void doget(PARAMS *params, TCADB *db);
+static void doerror(int code, const char *msg);
+static void sethtmlheader(PARAMS *params, TCXSTR *obuf, TCADB *db);
+static void sethtmlfooter(PARAMS *params, TCXSTR *obuf, TCADB *db);
+static void sethtmlcomform(PARAMS *params, TCXSTR *obuf, TCADB *db);
+static void sethtmlrecval(const char *kbuf, int ksiz, TCXSTR *obuf, TCADB *db);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  g_scriptname = getenv("SCRIPT_NAME");
+  if(!g_scriptname) g_scriptname = argv[0];
+  const char *rp = strrchr(g_scriptname, '/');
+  if(rp) g_scriptname = rp + 1;
+  TCMAP *pmap = tcmapnew2(MINIBNUM);
+  readparameters(pmap);
+  PARAMS params;
+  params.action = ACTLIST;
+  int size;
+  const char *buf = tcmapget(pmap, "action", 6, &size);
+  if(buf) params.action = tcatoix(buf);
+  if(params.action < ACTLIST) params.action = ACTLIST;
+  buf = tcmapget(pmap, "key", 3, &size);
+  if(buf){
+    params.kbuf = buf;
+    params.ksiz = size;
+  } else {
+    params.kbuf = "";
+    params.ksiz = 0;
+  }
+  buf = tcmapget(pmap, "value", 5, &size);
+  if(buf){
+    params.vbuf = buf;
+    params.vsiz = size;
+  } else {
+    params.vbuf = "";
+    params.vsiz = 0;
+  }
+  if(params.ksiz < 1){
+    buf = tcmapget(pmap, "value_filename", 14, &size);
+    if(buf){
+      params.kbuf = buf;
+      params.ksiz = size;
+    }
+  }
+  params.num = 0;
+  buf = tcmapget(pmap, "num", 3, &size);
+  if(buf) params.num = tcatoix(buf);
+  if(params.num < 1) params.num = DEFSHOWNUM;
+  params.page = 1;
+  buf = tcmapget(pmap, "page", 4, &size);
+  if(buf) params.page = tcatoix(buf);
+  if(params.page < 1) params.page = 1;
+  bool wmode;
+  switch(params.action){
+    case ACTPUT:
+    case ACTOUT:
+      wmode = true;
+      break;
+    default:
+      wmode = false;
+      break;
+  }
+  TCADB *db = tcadbnew();
+  char path[strlen(DBNAME)+16];
+  sprintf(path, "%s.tch#mode=%s", DBNAME, wmode ? "w" : "r");
+  if(!tcadbopen(db, path)){
+    sprintf(path, "%s.tcb#mode=%s", DBNAME, wmode ? "w" : "r");
+    if(!tcadbopen(db, path)){
+      sprintf(path, "%s.tcf#mode=%s", DBNAME, wmode ? "w" : "r");
+      tcadbopen(db, path);
+    }
+  }
+  if(tcadbsize(db) > 0){
+    if(wmode) tcadbtranbegin(db);
+    switch(params.action){
+      case ACTLIST:
+      case ACTLISTVAL:
+      case ACTPUT:
+      case ACTOUT:
+        dolist(&params, db);
+        break;
+      case ACTGET:
+        doget(&params, db);
+        break;
+      default:
+        doerror(400, "no such action");
+        break;
+    }
+    if(wmode) tcadbtrancommit(db);
+  } else {
+    doerror(500, "the database file could not be opened");
+  }
+  tcadbdel(db);
+  tcmapdel(pmap);
+  return 0;
+}
+
+
+/* read CGI parameters */
+static void readparameters(TCMAP *params){
+  int maxlen = UPLOADMAX;
+  char *buf = NULL;
+  int len = 0;
+  const char *rp;
+  if((rp = getenv("REQUEST_METHOD")) != NULL && !strcmp(rp, "POST") &&
+     (rp = getenv("CONTENT_LENGTH")) != NULL && (len = tcatoix(rp)) > 0){
+    if(len > maxlen) len = maxlen;
+    buf = tccalloc(len + 1, 1);
+    if(fread(buf, 1, len, stdin) != len){
+      tcfree(buf);
+      buf = NULL;
+    }
+  } else if((rp = getenv("QUERY_STRING")) != NULL){
+    buf = tcstrdup(rp);
+    len = strlen(buf);
+  }
+  if(buf && len > 0) tcwwwformdecode2(buf, len, getenv("CONTENT_TYPE"), params);
+  tcfree(buf);
+}
+
+
+/* perform the list action */
+static void dolist(PARAMS *params, TCADB *db){
+  printf("Content-Type: text/html\r\n");
+  printf("\r\n");
+  TCXSTR *obuf = tcxstrnew3(OUTBUFSIZ);
+  sethtmlheader(params, obuf, db);
+  if(params->action == ACTPUT){
+    XP("<hr />\n");
+    if(params->ksiz < 1){
+      XP("<p>Error: the key should be specified.</p>\n");
+    } else if(tcadbput(db, params->kbuf, params->ksiz, params->vbuf, params->vsiz)){
+      XP("<p>Stored successfully!</p>\n");
+    } else {
+      XP("<p>Error: unknown error.</p>\n");
+    }
+    params->kbuf = "";
+    params->ksiz = 0;
+    params->action = ACTLIST;
+  } else if(params->action == ACTOUT){
+    XP("<hr />\n");
+    if(params->ksiz < 1){
+      XP("<p>Error: the key should be specified.</p>\n");
+    } else if(tcadbout(db, params->kbuf, params->ksiz)){
+      XP("<p>Removed successfully!</p>\n");
+    } else if(tcadbvsiz(db, params->kbuf, params->ksiz) >= 0){
+      XP("<p>Error: unknown error.</p>\n");
+    } else {
+      XP("<p>Error: no such record.</p>\n");
+    }
+    params->kbuf = "";
+    params->ksiz = 0;
+    params->action = ACTLIST;
+  }
+  sethtmlcomform(params, obuf, db);
+  bool isnext = false;
+  XP("<hr />\n");
+  XP("<div id=\"list\">\n");
+  int num = params->num;
+  bool sv = params->action == ACTLISTVAL;
+  if(params->ksiz > 0){
+    TCLIST *keys = tcadbfwmkeys(db, params->kbuf, params->ksiz, FWMMAX);
+    int knum = tclistnum(keys);
+    int skip = params->num * (params->page - 1);
+    int end = skip + params->num;
+    for(int i = skip; i < knum && i < end; i++){
+      int ksiz;
+      const char *kbuf = tclistval(keys, i, &ksiz);
+      XP("<div class=\"record\">\n");
+      XP("<a href=\"%s?action=%d&amp;key=%?\" class=\"key\">%@</a>",
+         g_scriptname, ACTGET, kbuf, kbuf);
+      if(sv) sethtmlrecval(kbuf, ksiz, obuf, db);
+      XP("</div>\n");
+    }
+    tclistdel(keys);
+    isnext = knum > params->num * params->page;
+  } else {
+    tcadbiterinit(db);
+    int ksiz;
+    char *kbuf;
+    int skip = params->num * (params->page - 1);
+    for(int i = 0; i < skip && (kbuf = tcadbiternext(db, &ksiz)) != NULL; i++){
+      tcfree(kbuf);
+    }
+    for(int i = 0; i < num && (kbuf = tcadbiternext(db, &ksiz)) != NULL; i++){
+      XP("<div class=\"record\">\n");
+      XP("<a href=\"%s?action=%d&amp;key=%?\" class=\"key\">%@</a>",
+         g_scriptname, ACTGET, kbuf, kbuf);
+      if(sv) sethtmlrecval(kbuf, ksiz, obuf, db);
+      XP("</div>\n");
+      tcfree(kbuf);
+    }
+    isnext = tcadbrnum(db) > params->num * params->page;
+  }
+  XP("</div>\n");
+  XP("<hr />\n");
+  XP("<form method=\"get\" action=\"%s\">\n", g_scriptname);
+  XP("<div class=\"paging\">\n");
+  if(params->page > 1){
+    XP("<a href=\"%s?action=%d&amp;key=%?&amp;num=%d&amp;page=%d\" class=\"jump\">[PREV]</a>\n",
+       g_scriptname, params->action, params->kbuf, params->num, params->page - 1);
+  } else {
+    XP("<span class=\"void\">[PREV]</span>\n");
+  }
+  if(isnext){
+    XP("<a href=\"%s?action=%d&amp;key=%?&amp;num=%d&amp;page=%d\" class=\"jump\">[NEXT]</a>\n",
+       g_scriptname, params->action, params->kbuf, params->num, params->page + 1);
+  } else {
+    XP("<span class=\"void\">[NEXT]</span>\n");
+  }
+  if(params->action == ACTLIST){
+    XP("<a href=\"%s?action=%d&amp;key=%?&amp;num=%d&amp;page=%d\" class=\"jump\">[VALUE]</a>\n",
+       g_scriptname, ACTLISTVAL, params->kbuf, params->num, params->page);
+  } else {
+    XP("<a href=\"%s?action=%d&amp;key=%?&amp;num=%d&amp;page=%d\" class=\"jump\">[NOVAL]</a>\n",
+       g_scriptname, ACTLIST, params->kbuf, params->num, params->page);
+  }
+  XP("<select name=\"num\">\n");
+  for(int i = 10; i <= 100; i += 10){
+    XP("<option value=\"%d\"%s>%d records</option>\n",
+       i, (i == params->num) ? " selected=\"selected\"" : "", i);
+  }
+  XP("</select>\n");
+  XP("<input type=\"submit\" value=\"go\" />\n");
+  XP("</div>\n");
+  XP("</form>\n");
+  sethtmlfooter(params, obuf, db);
+  fwrite(tcxstrptr(obuf), 1, tcxstrsize(obuf), stdout);
+  tcxstrdel(obuf);
+}
+
+
+/* perform the get action */
+static void doget(PARAMS *params, TCADB *db){
+  static char *types[] = {
+    ".gz", "application/x-gzip", ".bz2", "application/x-bzip2", ".tar", "application/x-tar",
+    ".zip", "application/zip", ".lzh", "application/octet-stream",
+    ".pdf", "application/pdf", ".ps", "application/postscript",
+    ".xml", "application/xml", ".html", "application/html", ".htm", "application/html",
+    ".doc", "application/msword", ".xls", "application/vnd.ms-excel",
+    ".ppt", "application/ms-powerpoint", ".swf", "application/x-shockwave-flash",
+    ".png", "image/png", ".jpg", "image/jpeg", ".jpeg", "image/jpeg", ".gif", "image/gif",
+    ".bmp", "image/bmp", ".tif", "image/tiff", ".tiff", "image/tiff", ".svg", "image/xml+svg",
+    ".au", "audio/basic", ".snd", "audio/basic", ".mid", "audio/midi", ".midi", "audio/midi",
+    ".mp3", "audio/mpeg", ".mp2", "audio/mpeg", ".wav", "audio/x-wav",
+    ".tch", "application/x-tokyocabinet-hash", ".tcb", "application/x-tokyocabinet-btree",
+    NULL
+  };
+  int vsiz;
+  char *vbuf = tcadbget(db, params->kbuf, params->ksiz, &vsiz);
+  if(vbuf){
+    const char *type = "text/plain";
+    for(int i = 0; types[i]; i++){
+      if(tcstribwm(params->kbuf, types[i])){
+        type = types[i+1];
+        break;
+      }
+    }
+    printf("Content-Type: %s\r\n", type);
+    if(!strchr(params->kbuf, '\n') && !strchr(params->kbuf, '\r')){
+      if(!strchr(params->kbuf, ' ') && !strchr(params->kbuf, ';')){
+        printf("Content-Disposition: attachment; filename=%s\r\n", params->kbuf);
+      } else {
+        printf("Content-Disposition: attachment; filename=\"%s\"\r\n", params->kbuf);
+      }
+    }
+    printf("\r\n");
+    fwrite(vbuf, 1, vsiz, stdout);
+    tcfree(vbuf);
+  } else {
+    doerror(404, "no such record");
+  }
+}
+
+
+/* perform the error action */
+static void doerror(int code, const char *msg){
+  printf("Status: %d %s\r\n", code, msg);
+  printf("Content-Type: text/plain\r\n");
+  printf("\r\n");
+  printf("%d: %s\n", code, msg);
+}
+
+
+/* set the header of HTML */
+static void sethtmlheader(PARAMS *params, TCXSTR *obuf, TCADB *db){
+  XP("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+  XP("\n");
+  XP("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\""
+     " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n");
+  XP("\n");
+  XP("<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n");
+  XP("\n");
+  XP("<head>\n");
+  XP("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n");
+  XP("<meta http-equiv=\"Content-Style-Type\" content=\"text/css\" />\n");
+  XP("<title>%@</title>\n", PAGETITLE);
+  XP("<style type=\"text/css\">html { margin: 0em; padding: 0em; }\n");
+  XP("body { margin :0em; padding: 0.5em 1em; background: #eeeeee; color: #111111; }\n");
+  XP("h1 { margin: 3px; padding: 0px; font-size: 125%%; }\n");
+  XP("h1 a { color: #000000; }\n");
+  XP("hr { margin: 0px 0px; height: 1px; border: none;"
+     " background: #999999; color: #999999; }\n");
+  XP("form { margin: 5px; padding: 0px; }\n");
+  XP("#list { margin: 5px; padding: 0px; }\n");
+  XP("p { margin: 5px; padding: 0px; }\n");
+  XP("a { color: #1122ee; text-decoration: none; }\n");
+  XP("a:hover { color: #2288ff; text-decoration: underline; }\n");
+  XP("span.void { color: #888888; }\n");
+  XP("span.value { font-size: 95%%; }\n");
+  XP("i { color: #333333; font-size: 70%%; }\n");
+  XP("</style>\n");
+  XP("</head>\n");
+  XP("\n");
+  XP("<body>\n");
+  XP("<h1><a href=\"%s\">%@</a></h1>\n", g_scriptname, PAGETITLE);
+}
+
+
+/* set the footer of HTML */
+static void sethtmlfooter(PARAMS *params, TCXSTR *obuf, TCADB *db){
+  XP("<hr />\n");
+  XP("<div>record number: %lld</div>\n", (long long)tcadbrnum(db));
+  XP("<div>size: %lld</div>\n", (long long)tcadbsize(db));
+  XP("</body>\n");
+  XP("\n");
+  XP("</html>\n");
+}
+
+
+/* set the common form of HTML */
+static void sethtmlcomform(PARAMS *params, TCXSTR *obuf, TCADB *db){
+  XP("<hr />\n");
+  XP("<form method=\"post\" action=\"%s\">\n", g_scriptname);
+  XP("<div>\n");
+  XP("<input type=\"text\" name=\"key\" value=\"\" size=\"24\" />\n");
+  XP("<input type=\"text\" name=\"value\" value=\"\" size=\"24\" />\n");
+  XP("<input type=\"submit\" value=\"store a new string record\" />\n");
+  XP("<input type=\"hidden\" name=\"action\" value=\"%d\" />\n", ACTPUT);
+  XP("</div>\n");
+  XP("</form>\n");
+  XP("<hr />\n");
+  XP("<form method=\"post\" action=\"%s\" enctype=\"multipart/form-data\">\n", g_scriptname);
+  XP("<div>\n");
+  XP("<input type=\"text\" name=\"key\" value=\"\" size=\"24\" />\n");
+  XP("<input type=\"file\" name=\"value\" size=\"24\" />\n");
+  XP("<input type=\"submit\" value=\"store a new record from a file\" />\n");
+  XP("<input type=\"hidden\" name=\"action\" value=\"%d\" />\n", ACTPUT);
+  XP("</div>\n");
+  XP("</form>\n");
+  XP("<hr />\n");
+  XP("<form method=\"post\" action=\"%s\">\n", g_scriptname);
+  XP("<div>\n");
+  XP("<input type=\"text\" name=\"key\" value=\"\" size=\"24\" />\n");
+  XP("<input type=\"submit\" value=\"remove a record\" />\n");
+  XP("<input type=\"hidden\" name=\"action\" value=\"%d\" />\n", ACTOUT);
+  XP("</div>\n");
+  XP("</form>\n");
+  XP("<hr />\n");
+  XP("<form method=\"post\" action=\"%s\">\n", g_scriptname);
+  XP("<div>\n");
+  XP("<input type=\"text\" name=\"key\" value=\"%@\" size=\"24\" />\n", params->kbuf);
+  XP("<input type=\"submit\" value=\"get the value of a record\" />\n");
+  XP("<input type=\"hidden\" name=\"action\" value=\"%d\" />\n", ACTGET);
+  XP("</div>\n");
+  XP("</form>\n");
+  XP("<hr />\n");
+  XP("<form method=\"post\" action=\"%s\">\n", g_scriptname);
+  XP("<div>\n");
+  XP("<input type=\"text\" name=\"key\" value=\"%@\" size=\"24\" />\n", params->kbuf);
+  XP("<input type=\"submit\" value=\"forward matching list\" />\n");
+  XP("<input type=\"hidden\" name=\"action\" value=\"%d\" />\n", ACTLIST);
+  XP("</div>\n");
+  XP("</form>\n");
+}
+
+
+/* set the value of a record */
+static void sethtmlrecval(const char *kbuf, int ksiz, TCXSTR *obuf, TCADB *db){
+  int vsiz;
+  char *vbuf = tcadbget(db, kbuf, ksiz, &vsiz);
+  if(!vbuf) return;
+  XP(": <span class=\"value\">");
+  bool hex = false;
+  int width = VALWIDTH;
+  for(int j = 0; j < vsiz; j++){
+    int c = ((unsigned char *)vbuf)[j];
+    if(c >= 0x20 && c <= 0x7e){
+      if(hex) tcxstrcat(obuf, " ", 1);
+      switch(c){
+        case '<':
+          tcxstrcat(obuf, "&lt;", 4);
+          break;
+        case '>':
+          tcxstrcat(obuf, "&gt;", 4);
+          break;
+        case '&':
+          tcxstrcat(obuf, "&amp;", 5);
+          break;
+        default:
+          tcxstrcat(obuf, vbuf + j, 1);
+          break;
+      }
+      width--;
+      hex = false;
+    } else {
+      XP(" <i>%02X</i>", c);
+      width -= 2;
+      hex = true;
+    }
+    if(width < 1){
+      XP(" <i>...</i>");
+      break;
+    }
+  }
+  XP("</span>");
+  tcfree(vbuf);
+}
+
+
+
+// END OF FILE
diff --git a/tcejdb/tcbdb.c b/tcejdb/tcbdb.c
new file mode 100644 (file)
index 0000000..371a86d
--- /dev/null
@@ -0,0 +1,4182 @@
+/*************************************************************************************************
+ * 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
diff --git a/tcejdb/tcbdb.h b/tcejdb/tcbdb.h
new file mode 100644 (file)
index 0000000..390a3c8
--- /dev/null
@@ -0,0 +1,1101 @@
+/*************************************************************************************************
+ * 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.
+ *************************************************************************************************/
+
+
+#ifndef _TCBDB_H                         /* duplication check */
+#define _TCBDB_H
+
+#if defined(__cplusplus)
+#define __TCBDB_CLINKAGEBEGIN extern "C" {
+#define __TCBDB_CLINKAGEEND }
+#else
+#define __TCBDB_CLINKAGEBEGIN
+#define __TCBDB_CLINKAGEEND
+#endif
+__TCBDB_CLINKAGEBEGIN
+
+
+#include <tcutil.h>
+#include <tchdb.h>
+
+
+
+/*************************************************************************************************
+ * API
+ *************************************************************************************************/
+
+
+typedef struct {                         /* type of structure for a B+ tree database */
+  void *mmtx;                            /* mutex for method */
+  void *cmtx;                            /* mutex for cache */
+  TCHDB *hdb;                            /* internal database object */
+  char *opaque;                          /* opaque buffer */
+  bool open;                             /* whether the internal database is opened */
+  bool wmode;                            /* whether to be writable */
+  uint32_t lmemb;                        /* number of members in each leaf */
+  uint32_t nmemb;                        /* number of members in each node */
+  uint8_t opts;                          /* options */
+  uint64_t root;                         /* ID number of the root page */
+  uint64_t first;                        /* ID number of the first leaf */
+  uint64_t last;                         /* ID number of the last leaf */
+  uint64_t lnum;                         /* number of leaves */
+  uint64_t nnum;                         /* number of nodes */
+  uint64_t rnum;                         /* number of records */
+  TCMAP *leafc;                          /* cache for leaves */
+  TCMAP *nodec;                          /* cache for nodes */
+  TCCMP cmp;                             /* pointer to the comparison function */
+  void *cmpop;                           /* opaque object for the comparison function */
+  uint32_t lcnum;                        /* maximum number of cached leaves */
+  uint32_t ncnum;                        /* maximum number of cached nodes */
+  uint32_t lsmax;                        /* maximum size of each leaf */
+  uint32_t lschk;                        /* counter for leaf size checking */
+  uint64_t capnum;                       /* capacity number of records */
+  uint64_t *hist;                        /* history array of visited nodes */
+  int hnum;                              /* number of element of the history array */
+  volatile uint64_t hleaf;               /* ID number of the leaf referred by the history */
+  volatile uint64_t lleaf;               /* ID number of the last visited leaf */
+  bool tran;                             /* whether in the transaction */
+  char *rbopaque;                        /* opaque for rollback */
+  volatile uint64_t clock;               /* logical clock */
+  volatile int64_t cnt_saveleaf;         /* tesing counter for leaf save times */
+  volatile int64_t cnt_loadleaf;         /* tesing counter for leaf load times */
+  volatile int64_t cnt_killleaf;         /* tesing counter for leaf kill times */
+  volatile int64_t cnt_adjleafc;         /* tesing counter for node cache adjust times */
+  volatile int64_t cnt_savenode;         /* tesing counter for node save times */
+  volatile int64_t cnt_loadnode;         /* tesing counter for node load times */
+  volatile int64_t cnt_adjnodec;         /* tesing counter for node cache adjust times */
+} TCBDB;
+
+enum {                                   /* enumeration for additional flags */
+  BDBFOPEN = HDBFOPEN,                   /* whether opened */
+  BDBFFATAL = HDBFFATAL                  /* whether with fatal error */
+};
+
+enum {                                   /* enumeration for tuning options */
+  BDBTLARGE = 1 << 0,                    /* use 64-bit bucket array */
+  BDBTDEFLATE = 1 << 1,                  /* compress each page with Deflate */
+  BDBTBZIP = 1 << 2,                     /* compress each record with BZIP2 */
+  BDBTTCBS = 1 << 3,                     /* compress each page with TCBS */
+  BDBTEXCODEC = 1 << 4                   /* compress each record with outer functions */
+};
+
+enum {                                   /* enumeration for open modes */
+  BDBOREADER = 1 << 0,                   /* open as a reader */
+  BDBOWRITER = 1 << 1,                   /* open as a writer */
+  BDBOCREAT = 1 << 2,                    /* writer creating */
+  BDBOTRUNC = 1 << 3,                    /* writer truncating */
+  BDBONOLCK = 1 << 4,                    /* open without locking */
+  BDBOLCKNB = 1 << 5,                    /* lock without blocking */
+  BDBOTSYNC = 1 << 6                     /* synchronize every transaction */
+};
+
+typedef struct {                         /* type of structure for a B+ tree cursor */
+  TCBDB *bdb;                            /* database object */
+  uint64_t clock;                        /* logical clock */
+  uint64_t id;                           /* ID number of the leaf */
+  int32_t kidx;                          /* number of the key */
+  int32_t vidx;                          /* number of the value */
+} BDBCUR;
+
+enum {                                   /* enumeration for cursor put mode */
+  BDBCPCURRENT,                          /* current */
+  BDBCPBEFORE,                           /* before */
+  BDBCPAFTER                             /* after */
+};
+
+
+/* Get the message string corresponding to an error code.
+   `ecode' specifies the error code.
+   The return value is the message string of the error code. */
+const char *tcbdberrmsg(int ecode);
+
+
+/* Create a B+ tree database object.
+   The return value is the new B+ tree database object. */
+TCBDB *tcbdbnew(void);
+
+
+/* Delete a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   If the database is not closed, it is closed implicitly.  Note that the deleted object and its
+   derivatives can not be used anymore. */
+void tcbdbdel(TCBDB *bdb);
+
+
+/* Get the last happened error code of a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   The return value is the last happened error code.
+   The following error codes are defined: `TCESUCCESS' for success, `TCETHREAD' for threading
+   error, `TCEINVALID' for invalid operation, `TCENOFILE' for file not found, `TCENOPERM' for no
+   permission, `TCEMETA' for invalid meta data, `TCERHEAD' for invalid record header, `TCEOPEN'
+   for open error, `TCECLOSE' for close error, `TCETRUNC' for trunc error, `TCESYNC' for sync
+   error, `TCESTAT' for stat error, `TCESEEK' for seek error, `TCEREAD' for read error,
+   `TCEWRITE' for write error, `TCEMMAP' for mmap error, `TCELOCK' for lock error, `TCEUNLINK'
+   for unlink error, `TCERENAME' for rename error, `TCEMKDIR' for mkdir error, `TCERMDIR' for
+   rmdir error, `TCEKEEP' for existing record, `TCENOREC' for no record found, and `TCEMISC' for
+   miscellaneous error. */
+int tcbdbecode(TCBDB *bdb);
+
+
+/* Set mutual exclusion control of a B+ tree database object for threading.
+   `bdb' specifies the B+ tree database object which is not opened.
+   If successful, the return value is true, else, it is false.
+   Note that the mutual exclusion control is needed if the object is shared by plural threads and
+   this function should be called before the database is opened. */
+bool tcbdbsetmutex(TCBDB *bdb);
+
+
+/* Set the custom comparison function of a B+ tree database object.
+   `bdb' specifies the B+ tree database object which is not opened.
+   `cmp' specifies the pointer to the custom comparison function.  It receives five parameters.
+   The first parameter is the pointer to the region of one key.  The second parameter is the size
+   of the region of one key.  The third parameter is the pointer to the region of the other key.
+   The fourth parameter is the size of the region of the other key.  The fifth parameter is the
+   pointer to the optional opaque object.  It returns positive if the former is big, negative if
+   the latter is big, 0 if both are equivalent.
+   `cmpop' specifies an arbitrary pointer to be given as a parameter of the comparison function.
+   If it is not needed, `NULL' can be specified.
+   If successful, the return value is true, else, it is false.
+   The default comparison function compares keys of two records by lexical order.  The functions
+   `tctccmplexical' (dafault), `tctccmpdecimal', `tctccmpint32', and `tctccmpint64' are built-in.
+   Note that the comparison function should be set before the database is opened.  Moreover,
+   user-defined comparison functions should be set every time the database is being opened. */
+bool tcbdbsetcmpfunc(TCBDB *bdb, TCCMP cmp, void *cmpop);
+
+
+/* Set the tuning parameters of a B+ tree database object.
+   `bdb' specifies the B+ tree database object which is not opened.
+   `lmemb' specifies the number of members in each leaf page.  If it is not more than 0, the
+   default value is specified.  The default value is 128.
+   `nmemb' specifies the number of members in each non-leaf page.  If it is not more than 0, the
+   default value is specified.  The default value is 256.
+   `bnum' specifies the number of elements of the bucket array.  If it is not more than 0, the
+   default value is specified.  The default value is 32749.  Suggested size of the bucket array
+   is about from 1 to 4 times of the number of all pages to be stored.
+   `apow' specifies the size of record alignment by power of 2.  If it is negative, the default
+   value is specified.  The default value is 8 standing for 2^8=256.
+   `fpow' specifies the maximum number of elements of the free block pool by power of 2.  If it
+   is negative, the default value is specified.  The default value is 10 standing for 2^10=1024.
+   `opts' specifies options by bitwise-or: `BDBTLARGE' specifies that the size of the database
+   can be larger than 2GB by using 64-bit bucket array, `BDBTDEFLATE' specifies that each page
+   is compressed with Deflate encoding, `BDBTBZIP' specifies that each page is compressed with
+   BZIP2 encoding, `BDBTTCBS' specifies that each page is compressed with TCBS encoding.
+   If successful, the return value is true, else, it is false.
+   Note that the tuning parameters should be set before the database is opened. */
+bool tcbdbtune(TCBDB *bdb, int32_t lmemb, int32_t nmemb,
+               int64_t bnum, int8_t apow, int8_t fpow, uint8_t opts);
+
+
+/* Set the caching parameters of a B+ tree database object.
+   `bdb' specifies the B+ tree database object which is not opened.
+   `lcnum' specifies the maximum number of leaf nodes to be cached.  If it is not more than 0,
+   the default value is specified.  The default value is 1024.
+   `ncnum' specifies the maximum number of non-leaf nodes to be cached.  If it is not more than 0,
+   the default value is specified.  The default value is 512.
+   If successful, the return value is true, else, it is false.
+   Note that the caching parameters should be set before the database is opened. */
+bool tcbdbsetcache(TCBDB *bdb, int32_t lcnum, int32_t ncnum);
+
+
+/* Set the size of the extra mapped memory of a B+ tree database object.
+   `bdb' specifies the B+ tree database object which is not opened.
+   `xmsiz' specifies the size of the extra mapped memory.  If it is not more than 0, the extra
+   mapped memory is disabled.  It is disabled by default.
+   If successful, the return value is true, else, it is false.
+   Note that the mapping parameters should be set before the database is opened. */
+bool tcbdbsetxmsiz(TCBDB *bdb, int64_t xmsiz);
+
+
+/* Set the unit step number of auto defragmentation of a B+ tree database object.
+   `bdb' specifies the B+ tree database object which is not opened.
+   `dfunit' specifie the unit step number.  If it is not more than 0, the auto defragmentation
+   is disabled.  It is disabled by default.
+   If successful, the return value is true, else, it is false.
+   Note that the defragmentation parameter should be set before the database is opened. */
+bool tcbdbsetdfunit(TCBDB *bdb, int32_t dfunit);
+
+
+/* Open a database file and connect a B+ tree database object.
+   `bdb' specifies the B+ tree database object which is not opened.
+   `path' specifies the path of the database file.
+   `omode' specifies the connection mode: `BDBOWRITER' as a writer, `BDBOREADER' as a reader.
+   If the mode is `BDBOWRITER', the following may be added by bitwise-or: `BDBOCREAT', which
+   means it creates a new database if not exist, `BDBOTRUNC', which means it creates a new
+   database regardless if one exists, `BDBOTSYNC', which means every transaction synchronizes
+   updated contents with the device.  Both of `BDBOREADER' and `BDBOWRITER' can be added to by
+   bitwise-or: `BDBONOLCK', which means it opens the database file without file locking, or
+   `BDBOLCKNB', which means locking is performed without blocking.
+   If successful, the return value is true, else, it is false. */
+bool tcbdbopen(TCBDB *bdb, const char *path, int omode);
+
+
+/* Close a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   If successful, the return value is true, else, it is false.
+   Update of a database is assured to be written when the database is closed.  If a writer opens
+   a database but does not close it appropriately, the database will be broken. */
+bool tcbdbclose(TCBDB *bdb);
+
+
+/* Store a record into a B+ tree database object.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   `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.
+   If a record with the same key exists in the database, it is overwritten. */
+bool tcbdbput(TCBDB *bdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Store a string record into a B+ tree database object.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   `kstr' specifies the string of the key.
+   `vstr' specifies the string of the value.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the database, it is overwritten. */
+bool tcbdbput2(TCBDB *bdb, const char *kstr, const char *vstr);
+
+
+/* Store a new record into a B+ tree database object.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   `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.
+   If a record with the same key exists in the database, this function has no effect. */
+bool tcbdbputkeep(TCBDB *bdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Store a new string record into a B+ tree database object.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   `kstr' specifies the string of the key.
+   `vstr' specifies the string of the value.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the database, this function has no effect. */
+bool tcbdbputkeep2(TCBDB *bdb, const char *kstr, const char *vstr);
+
+
+/* Concatenate a value at the end of the existing record in a B+ tree database object.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   `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.
+   If there is no corresponding record, a new record is created. */
+bool tcbdbputcat(TCBDB *bdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Concatenate a string value at the end of the existing record in a B+ tree database object.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   `kstr' specifies the string of the key.
+   `vstr' specifies the string of the value.
+   If successful, the return value is true, else, it is false.
+   If there is no corresponding record, a new record is created. */
+bool tcbdbputcat2(TCBDB *bdb, const char *kstr, const char *vstr);
+
+
+/* Store a record into a B+ tree database object with allowing duplication of keys.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   `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.
+   If a record with the same key exists in the database, the new record is placed after the
+   existing one. */
+bool tcbdbputdup(TCBDB *bdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Store a string record into a B+ tree database object with allowing duplication of keys.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   `kstr' specifies the string of the key.
+   `vstr' specifies the string of the value.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the database, the new record is placed after the
+   existing one. */
+bool tcbdbputdup2(TCBDB *bdb, const char *kstr, const char *vstr);
+
+
+/* Store records into a B+ tree database object with allowing duplication of keys.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   `kbuf' specifies the pointer to the region of the common key.
+   `ksiz' specifies the size of the region of the common key.
+   `vals' specifies a list object containing values.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the database, the new records are placed after the
+   existing one. */
+bool tcbdbputdup3(TCBDB *bdb, const void *kbuf, int ksiz, const TCLIST *vals);
+
+
+/* Remove a record of a B+ tree database object.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   `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.
+   If the key of duplicated records is specified, the first one is selected. */
+bool tcbdbout(TCBDB *bdb, const void *kbuf, int ksiz);
+
+
+/* Remove a string record of a B+ tree database object.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   `kstr' specifies the string of the key.
+   If successful, the return value is true, else, it is false.
+   If the key of duplicated records is specified, the first one is selected. */
+bool tcbdbout2(TCBDB *bdb, const char *kstr);
+
+
+/* Remove records of a B+ tree database object.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   `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.
+   If the key of duplicated records is specified, all of them are removed. */
+bool tcbdbout3(TCBDB *bdb, const void *kbuf, int ksiz);
+
+
+/* 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.  `NULL' is returned if no record corresponds.
+   If the key of duplicated records is specified, the first one is selected.  Because an
+   additional zero code is appended at the end of the region of the return value, the return
+   value can be treated as a character string.  Because the region of the return value is
+   allocated with the `malloc' call, it should be released with the `free' call when it is no
+   longer in use. */
+void *tcbdbget(TCBDB *bdb, const void *kbuf, int ksiz, int *sp);
+
+
+/* Retrieve a string record in a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   `kstr' specifies the string of the key.
+   If successful, the return value is the string of the value of the corresponding record.
+   `NULL' is returned if no record corresponds.
+   If the key of duplicated records is specified, the first one is selected.  Because the region
+   of the return value is allocated with the `malloc' call, it should be released with the `free'
+   call when it is no longer in use. */
+char *tcbdbget2(TCBDB *bdb, const char *kstr);
+
+
+/* Retrieve a record in a B+ tree database object as a volatile buffer.
+   `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.  `NULL' is returned if no record corresponds.
+   If the key of duplicated records is specified, the first one is selected.  Because an
+   additional zero code is appended at the end of the region of the return value, the return
+   value can be treated as a character string.  Because the region of the return value is
+   volatile and it may be spoiled by another operation of the database, the data should be copied
+   into another involatile buffer immediately. */
+const void *tcbdbget3(TCBDB *bdb, const void *kbuf, int ksiz, int *sp);
+
+
+/* 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.
+   `NULL' is returned if no record corresponds.
+   Because the object of the return value is created with the function `tclistnew', it should
+   be deleted with the function `tclistdel' when it is no longer in use. */
+TCLIST *tcbdbget4(TCBDB *bdb, const void *kbuf, int 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. */
+int tcbdbvnum(TCBDB *bdb, const void *kbuf, int ksiz);
+
+
+/* Get the number of records corresponding a string key in a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   `kstr' specifies the string of the key.
+   If successful, the return value is the number of the corresponding records, else, it is 0. */
+int tcbdbvnum2(TCBDB *bdb, const char *kstr);
+
+
+/* Get the size of the value of 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.
+   If successful, the return value is the size of the value of the corresponding record, else,
+   it is -1.
+   If the key of duplicated records is specified, the first one is selected. */
+int tcbdbvsiz(TCBDB *bdb, const void *kbuf, int ksiz);
+
+
+/* Get the size of the value of a string record in a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   `kstr' specifies the string of the key.
+   If successful, the return value is the size of the value of the corresponding record, else,
+   it is -1.
+   If the key of duplicated records is specified, the first one is selected. */
+int tcbdbvsiz2(TCBDB *bdb, const char *kstr);
+
+
+/* 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.  If it is
+   `NULL', the first record is specified.
+   `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.  If it is `NULL',
+   the last record is specified.
+   `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.  If it is negative, no limit is
+   specified.
+   The return value is a list object of the keys of the corresponding records.  This function
+   does never fail.  It returns an empty list even if no record corresponds.
+   Because the object of the return value is created with the function `tclistnew', it should
+   be deleted with the function `tclistdel' when it is no longer in use. */
+TCLIST *tcbdbrange(TCBDB *bdb, const void *bkbuf, int bksiz, bool binc,
+                   const void *ekbuf, int eksiz, bool einc, int max);
+
+
+/* Get string keys of ranged records in a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   `bkstr' specifies the string of the key of the beginning border.  If it is `NULL', the first
+   record is specified.
+   `binc' specifies whether the beginning border is inclusive or not.
+   `ekstr' specifies the string of the key of the ending border.  If it is `NULL', the last
+   record is specified.
+   `einc' specifies whether the ending border is inclusive or not.
+   `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 keys of the corresponding records.  This function
+   does never fail.  It returns an empty list even if no record corresponds.
+   Because the object of the return value is created with the function `tclistnew', it should
+   be deleted with the function `tclistdel' when it is no longer in use. */
+TCLIST *tcbdbrange2(TCBDB *bdb, const char *bkstr, bool binc,
+                    const char *ekstr, bool einc, int max);
+
+
+/* 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.  If it is negative, no limit is
+   specified.
+   The return value is a list object of the corresponding keys.  This function does never fail.
+   It returns an empty list even if no key corresponds.
+   Because the object of the return value is created with the function `tclistnew', it should be
+   deleted with the function `tclistdel' when it is no longer in use. */
+TCLIST *tcbdbfwmkeys(TCBDB *bdb, const void *pbuf, int psiz, int max);
+
+
+/* Get forward matching string keys in a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   `pstr' specifies the string 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.  This function does never fail.
+   It returns an empty list even if no key corresponds.
+   Because the object of the return value is created with the function `tclistnew', it should be
+   deleted with the function `tclistdel' when it is no longer in use. */
+TCLIST *tcbdbfwmkeys2(TCBDB *bdb, const char *pstr, int max);
+
+
+/* Add an integer to a record in a B+ tree database object.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   `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'.
+   If the corresponding record exists, the value is treated as an integer and is added to.  If no
+   record corresponds, a new record of the additional value is stored. */
+int tcbdbaddint(TCBDB *bdb, const void *kbuf, int ksiz, int num);
+
+
+/* Add a real number to a record in a B+ tree database object.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   `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.
+   If the corresponding record exists, the value is treated as a real number and is added to.  If
+   no record corresponds, a new record of the additional value is stored. */
+double tcbdbadddouble(TCBDB *bdb, const void *kbuf, int ksiz, double num);
+
+
+/* Synchronize updated contents of a B+ tree database object with the file and the device.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   If successful, the return value is true, else, it is false.
+   This function is useful when another process connects to the same database file. */
+bool tcbdbsync(TCBDB *bdb);
+
+
+/* Optimize the file of a B+ tree database object.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   `lmemb' specifies the number of members in each leaf page.  If it is not more than 0, the
+   current setting is not changed.
+   `nmemb' specifies the number of members in each non-leaf page.  If it is not more than 0, the
+   current setting is not changed.
+   `bnum' specifies the number of elements of the bucket array.  If it is not more than 0, the
+   default value is specified.  The default value is two times of the number of pages.
+   `apow' specifies the size of record alignment by power of 2.  If it is negative, the current
+   setting is not changed.
+   `fpow' specifies the maximum number of elements of the free block pool by power of 2.  If it
+   is negative, the current setting is not changed.
+   `opts' specifies options by bitwise-or: `BDBTLARGE' specifies that the size of the database
+   can be larger than 2GB by using 64-bit bucket array, `BDBTDEFLATE' specifies that each record
+   is compressed with Deflate encoding, `BDBTBZIP' specifies that each page is compressed with
+   BZIP2 encoding, `BDBTTCBS' specifies that each page is compressed with TCBS encoding.  If it
+   is `UINT8_MAX', the current setting is not changed.
+   If successful, the return value is true, else, it is false.
+   This function is useful to reduce the size of the database file with data fragmentation by
+   successive updating. */
+bool tcbdboptimize(TCBDB *bdb, int32_t lmemb, int32_t nmemb,
+                   int64_t bnum, int8_t apow, int8_t fpow, uint8_t opts);
+
+
+/* Remove all records of a B+ tree database object.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   If successful, the return value is true, else, it is false. */
+bool tcbdbvanish(TCBDB *bdb);
+
+
+/* Copy the database file of a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   `path' specifies the path of the destination file.  If it begins with `@', the trailing
+   substring is executed as a command line.
+   If successful, the return value is true, else, it is false.  False is returned if the executed
+   command returns non-zero code.
+   The database file is assured to be kept synchronized and not modified while the copying or
+   executing operation is in progress.  So, this function is useful to create a backup file of
+   the database file. */
+bool tcbdbcopy(TCBDB *bdb, const char *path);
+
+
+/* Begin the transaction of a B+ tree database object.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   If successful, the return value is true, else, it is false.
+   The database is locked by the thread while the transaction so that only one transaction can be
+   activated with a database object at the same time.  Thus, the serializable isolation level is
+   assumed if every database operation is performed in the transaction.  Because all pages are
+   cached on memory while the transaction, the amount of referred records is limited by the
+   memory capacity.  If the database is closed during transaction, the transaction is aborted
+   implicitly. */
+bool tcbdbtranbegin(TCBDB *bdb);
+
+
+/* Commit the transaction of a B+ tree database object.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   If successful, the return value is true, else, it is false.
+   Update in the transaction is fixed when it is committed successfully. */
+bool tcbdbtrancommit(TCBDB *bdb);
+
+
+/* Abort the transaction of a B+ tree database object.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   If successful, the return value is true, else, it is false.
+   Update in the transaction is discarded when it is aborted.  The state of the database is
+   rollbacked to before transaction. */
+bool tcbdbtranabort(TCBDB *bdb);
+
+
+/* Get the file path of a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   The return value is the path of the database file or `NULL' if the object does not connect to
+   any database file. */
+const char *tcbdbpath(TCBDB *bdb);
+
+
+/* Get the number of records of a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   The return value is the number of records or 0 if the object does not connect to any database
+   file. */
+uint64_t tcbdbrnum(TCBDB *bdb);
+
+
+/* Get the size of the database file of a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   The return value is the size of the database file or 0 if the object does not connect to any
+   database file. */
+uint64_t tcbdbfsiz(TCBDB *bdb);
+
+
+/* Create a cursor object.
+   `bdb' specifies the B+ tree database object.
+   The return value is the new cursor object.
+   Note that the cursor is available only after initialization with the `tcbdbcurfirst' or the
+   `tcbdbcurjump' functions and so on.  Moreover, the position of the cursor will be indefinite
+   when the database is updated after the initialization of the cursor. */
+BDBCUR *tcbdbcurnew(TCBDB *bdb);
+
+
+/* Delete a cursor object.
+   `cur' specifies the cursor object. */
+void tcbdbcurdel(BDBCUR *cur);
+
+
+/* Move a cursor object to the first record.
+   `cur' specifies the cursor object.
+   If successful, the return value is true, else, it is false.  False is returned if there is
+   no record in the database. */
+bool tcbdbcurfirst(BDBCUR *cur);
+
+
+/* Move a cursor object to the last record.
+   `cur' specifies the cursor object.
+   If successful, the return value is true, else, it is false.  False is returned if there is
+   no record in the database. */
+bool tcbdbcurlast(BDBCUR *cur);
+
+
+/* Move a cursor object to the front of 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.
+   If successful, the return value is true, else, it is false.  False is returned if there is
+   no record corresponding the condition.
+   The cursor is set to the first record corresponding the key or the next substitute if
+   completely matching record does not exist. */
+bool tcbdbcurjump(BDBCUR *cur, const void *kbuf, int ksiz);
+
+
+/* Move a cursor object to the front of records corresponding a key string.
+   `cur' specifies the cursor object.
+   `kstr' specifies the string of the key.
+   If successful, the return value is true, else, it is false.  False is returned if there is
+   no record corresponding the condition.
+   The cursor is set to the first record corresponding the key or the next substitute if
+   completely matching record does not exist. */
+bool tcbdbcurjump2(BDBCUR *cur, const char *kstr);
+
+
+/* Move a cursor object to the previous record.
+   `cur' specifies the cursor object.
+   If successful, the return value is true, else, it is false.  False is returned if there is
+   no previous record. */
+bool tcbdbcurprev(BDBCUR *cur);
+
+
+/* Move a cursor object to the next record.
+   `cur' specifies the cursor object.
+   If successful, the return value is true, else, it is false.  False is returned if there is
+   no next record. */
+bool tcbdbcurnext(BDBCUR *cur);
+
+
+/* Insert a record around a cursor object.
+   `cur' specifies the cursor object of writer connection.
+   `vbuf' specifies the pointer to the region of the value.
+   `vsiz' specifies the size of the region of the value.
+   `cpmode' specifies detail adjustment: `BDBCPCURRENT', which means that the value of the
+   current record is overwritten, `BDBCPBEFORE', which means that the new record is inserted
+   before the current record, `BDBCPAFTER', which means that the new record is inserted after the
+   current record.
+   If successful, the return value is true, else, it is false.  False is returned when the cursor
+   is at invalid position.
+   After insertion, the cursor is moved to the inserted record. */
+bool tcbdbcurput(BDBCUR *cur, const void *vbuf, int vsiz, int cpmode);
+
+
+/* Insert a string record around a cursor object.
+   `cur' specifies the cursor object of writer connection.
+   `vstr' specifies the string of the value.
+   `cpmode' specifies detail adjustment: `BDBCPCURRENT', which means that the value of the
+   current record is overwritten, `BDBCPBEFORE', which means that the new record is inserted
+   before the current record, `BDBCPAFTER', which means that the new record is inserted after the
+   current record.
+   If successful, the return value is true, else, it is false.  False is returned when the cursor
+   is at invalid position.
+   After insertion, the cursor is moved to the inserted record. */
+bool tcbdbcurput2(BDBCUR *cur, const char *vstr, int cpmode);
+
+
+/* Remove the record where a cursor object is.
+   `cur' specifies the cursor object of writer connection.
+   If successful, the return value is true, else, it is false.  False is returned when the cursor
+   is at invalid position.
+   After deletion, the cursor is moved to the next record if possible. */
+bool tcbdbcurout(BDBCUR *cur);
+
+
+/* Get the key of the record where the cursor object is.
+   `cur' specifies the cursor 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 key, else, it is `NULL'.
+   `NULL' is returned when the cursor is at invalid position.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when
+   it is no longer in use. */
+void *tcbdbcurkey(BDBCUR *cur, int *sp);
+
+
+/* Get the key string of the record where the cursor object is.
+   `cur' specifies the cursor object.
+   If successful, the return value is the string of the key, else, it is `NULL'.  `NULL' is
+   returned when the cursor is at invalid position.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+char *tcbdbcurkey2(BDBCUR *cur);
+
+
+/* Get the key of the record where the cursor object is, as a volatile buffer.
+   `cur' specifies the cursor 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 key, else, it is `NULL'.
+   `NULL' is returned when the cursor is at invalid position.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return value
+   is volatile and it may be spoiled by another operation of the database, the data should be
+   copied into another involatile buffer immediately. */
+const void *tcbdbcurkey3(BDBCUR *cur, int *sp);
+
+
+/* Get the value of the record where the cursor object is.
+   `cur' specifies the cursor 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 value, else, it is `NULL'.
+   `NULL' is returned when the cursor is at invalid position.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when
+   it is no longer in use. */
+void *tcbdbcurval(BDBCUR *cur, int *sp);
+
+
+/* Get the value string of the record where the cursor object is.
+   `cur' specifies the cursor object.
+   If successful, the return value is the string of the value, else, it is `NULL'.  `NULL' is
+   returned when the cursor is at invalid position.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+char *tcbdbcurval2(BDBCUR *cur);
+
+
+/* Get the value of the record where the cursor object is, as a volatile buffer.
+   `cur' specifies the cursor 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 value, else, it is `NULL'.
+   `NULL' is returned when the cursor is at invalid position.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return value
+   is volatile and it may be spoiled by another operation of the database, the data should be
+   copied into another involatile buffer immediately. */
+const void *tcbdbcurval3(BDBCUR *cur, int *sp);
+
+
+/* Get the key and the value of the record where the cursor object is.
+   `cur' specifies the cursor object.
+   `kxstr' specifies the object into which the key is wrote down.
+   `vxstr' specifies the object into which the value is wrote down.
+   If successful, the return value is true, else, it is false.  False is returned when the cursor
+   is at invalid position. */
+bool tcbdbcurrec(BDBCUR *cur, TCXSTR *kxstr, TCXSTR *vxstr);
+
+
+
+/*************************************************************************************************
+ * features for experts
+ *************************************************************************************************/
+
+
+/* Set the error code of a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   `ecode' specifies the error code.
+   `file' specifies the file name of the code.
+   `line' specifies the line number of the code.
+   `func' specifies the function name of the code. */
+void tcbdbsetecode(TCBDB *bdb, int ecode, const char *filename, int line, const char *func);
+
+
+/* Set the file descriptor for debugging output.
+   `bdb' specifies the B+ tree database object.
+   `fd' specifies the file descriptor for debugging output. */
+void tcbdbsetdbgfd(TCBDB *bdb, int fd);
+
+
+/* Get the file descriptor for debugging output.
+   `bdb' specifies the B+ tree database object.
+   The return value is the file descriptor for debugging output. */
+int tcbdbdbgfd(TCBDB *bdb);
+
+
+/* Check whether mutual exclusion control is set to a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   If mutual exclusion control is set, it is true, else it is false. */
+bool tcbdbhasmutex(TCBDB *bdb);
+
+
+/* Synchronize updating contents on memory of a B+ tree database object.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   `phys' specifies whether to synchronize physically.
+   If successful, the return value is true, else, it is false. */
+bool tcbdbmemsync(TCBDB *bdb, bool phys);
+
+
+/* Get the comparison function of a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   The return value is the pointer to the comparison function. */
+TCCMP tcbdbcmpfunc(TCBDB *bdb);
+
+
+/* Get the opaque object for the comparison function of a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   The return value is the opaque object for the comparison function. */
+void *tcbdbcmpop(TCBDB *bdb);
+
+
+/* Get the maximum number of cached leaf nodes of a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   The return value is the maximum number of cached leaf nodes. */
+uint32_t tcbdblmemb(TCBDB *bdb);
+
+
+/* Get the maximum number of cached non-leaf nodes of a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   The return value is the maximum number of cached non-leaf nodes. */
+uint32_t tcbdbnmemb(TCBDB *bdb);
+
+
+/* Get the number of the leaf nodes of B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   If successful, the return value is the number of the leaf nodes or 0 if the object does not
+   connect to any database file. */
+uint64_t tcbdblnum(TCBDB *bdb);
+
+
+/* Get the number of the non-leaf nodes of B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   If successful, the return value is the number of the non-leaf nodes or 0 if the object does
+   not connect to any database file. */
+uint64_t tcbdbnnum(TCBDB *bdb);
+
+
+/* Get the number of elements of the bucket array of a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   The return value is the number of elements of the bucket array or 0 if the object does not
+   connect to any database file. */
+uint64_t tcbdbbnum(TCBDB *bdb);
+
+
+/* Get the record alignment of a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   The return value is the record alignment or 0 if the object does not connect to any database
+   file. */
+uint32_t tcbdbalign(TCBDB *bdb);
+
+
+/* Get the maximum number of the free block pool of a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   The return value is the maximum number of the free block pool or 0 if the object does not
+   connect to any database file. */
+uint32_t tcbdbfbpmax(TCBDB *bdb);
+
+
+/* Get the inode number of the database file of a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   The return value is the inode number of the database file or 0 if the object does not connect
+   to any database file. */
+uint64_t tcbdbinode(TCBDB *bdb);
+
+
+/* Get the modification time of the database file of a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   The return value is the inode number of the database file or 0 if the object does not connect
+   to any database file. */
+time_t tcbdbmtime(TCBDB *bdb);
+
+
+/* Get the additional flags of a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   The return value is the additional flags. */
+uint8_t tcbdbflags(TCBDB *bdb);
+
+
+/* Get the options of a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   The return value is the options. */
+uint8_t tcbdbopts(TCBDB *bdb);
+
+
+/* Get the pointer to the opaque field of a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   The return value is the pointer to the opaque field whose size is 128 bytes. */
+char *tcbdbopaque(TCBDB *bdb);
+
+
+/* Get the number of used elements of the bucket array of a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   The return value is the number of used elements of the bucket array or 0 if the object does not
+   connect to any database file. */
+uint64_t tcbdbbnumused(TCBDB *bdb);
+
+
+/* Set the maximum size of each leaf node.
+   `bdb' specifies the B+ tree database object which is not opened.
+   `lsmax' specifies the maximum size of each leaf node.  If it is not more than 0, the default
+   value is specified.  The default value is 16386.
+   If successful, the return value is true, else, it is false.
+   Note that the tuning parameters of the database should be set before the database is opened. */
+bool tcbdbsetlsmax(TCBDB *bdb, uint32_t lsmax);
+
+
+/* Set the capacity number of records.
+   `bdb' specifies the B+ tree database object which is not opened.
+   `capnum' specifies the capacity number of records.  If it is not more than 0, the capacity is
+   unlimited.
+   If successful, the return value is true, else, it is false.
+   When the number of records exceeds the capacity, forehand records are removed implicitly.
+   Note that the tuning parameters of the database should be set before the database is opened. */
+bool tcbdbsetcapnum(TCBDB *bdb, uint64_t capnum);
+
+
+/* Set the custom codec functions of a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   `enc' specifies the pointer to the custom encoding function.  It receives four parameters.
+   The first parameter is the pointer to the region.  The second parameter is the size of the
+   region.  The third parameter is the pointer to the variable into which the size of the region
+   of the return value is assigned.  The fourth parameter is the pointer to the optional opaque
+   object.  It returns the pointer to the result object allocated with `malloc' call if
+   successful, else, it returns `NULL'.
+   `encop' specifies an arbitrary pointer to be given as a parameter of the encoding function.
+   If it is not needed, `NULL' can be specified.
+   `dec' specifies the pointer to the custom decoding function.
+   `decop' specifies an arbitrary pointer to be given as a parameter of the decoding function.
+   If it is not needed, `NULL' can be specified.
+   If successful, the return value is true, else, it is false.
+   Note that the custom codec functions should be set before the database is opened and should be
+   set every time the database is being opened. */
+bool tcbdbsetcodecfunc(TCBDB *bdb, TCCODEC enc, void *encop, TCCODEC dec, void *decop);
+
+
+/* Get the unit step number of auto defragmentation of a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   The return value is the unit step number of auto defragmentation. */
+uint32_t tcbdbdfunit(TCBDB *bdb);
+
+
+/* Perform dynamic defragmentation of a B+ tree database object.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   `step' specifie the number of steps.  If it is not more than 0, the whole file is defragmented
+   gradually without keeping a continuous lock.
+   If successful, the return value is true, else, it is false. */
+bool tcbdbdefrag(TCBDB *bdb, int64_t step);
+
+
+/* Clear the cache of a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   If successful, the return value is true, else, it is false. */
+bool tcbdbcacheclear(TCBDB *bdb);
+
+
+/* Store a new record into a B+ tree database object with backward duplication.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   `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.
+   If a record with the same key exists in the database, the new record is placed after the
+   existing one. */
+bool tcbdbputdupback(TCBDB *bdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Store a new string record into a B+ tree database object with backward duplication.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   `kstr' specifies the string of the key.
+   `vstr' specifies the string of the value.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the database, the new record is placed after the
+   existing one. */
+bool tcbdbputdupback2(TCBDB *bdb, const char *kstr, const char *vstr);
+
+
+/* Store a record into a B+ tree database object with a duplication handler.
+   `bdb' specifies the B+ tree database object connected as a writer.
+   `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.  `NULL' means that record addition is
+   ommited if there is no corresponding record.
+   `vsiz' specifies the size of the region of the value.
+   `proc' specifies the pointer to the callback function to process duplication.  It receives
+   four parameters.  The first parameter is the pointer to the region of the value.  The second
+   parameter is the size of the region of the value.  The third parameter is the pointer to the
+   variable into which the size of the region of the return value is assigned.  The fourth
+   parameter is the pointer to the optional opaque object.  It returns the pointer to the result
+   object allocated with `malloc'.  It is released by the caller.  If it is `NULL', the record is
+   not modified.  If it is `(void *)-1', the record is removed.
+   `op' specifies an arbitrary pointer to be given as a parameter of the callback function.  If
+   it is not needed, `NULL' can be specified.
+   If successful, the return value is true, else, it is false.
+   Note that the callback function can not perform any database operation because the function
+   is called in the critical section guarded by the same locks of database operations. */
+bool tcbdbputproc(TCBDB *bdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz,
+                  TCPDPROC proc, void *op);
+
+
+/* Move a cursor object to the rear of 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.
+   If successful, the return value is true, else, it is false.  False is returned if there is
+   no record corresponding the condition.
+   The cursor is set to the last record corresponding the key or the previous substitute if
+   completely matching record does not exist. */
+bool tcbdbcurjumpback(BDBCUR *cur, const void *kbuf, int ksiz);
+
+
+/* Move a cursor object to the rear of records corresponding a key string.
+   `cur' specifies the cursor object.
+   `kstr' specifies the string of the key.
+   If successful, the return value is true, else, it is false.  False is returned if there is
+   no record corresponding the condition.
+   The cursor is set to the last record corresponding the key or the previous substitute if
+   completely matching record does not exist. */
+bool tcbdbcurjumpback2(BDBCUR *cur, const char *kstr);
+
+
+/* Process each record atomically of a B+ tree database object.
+   `bdb' specifies the B+ tree database object.
+   `iter' specifies the pointer to the iterator function called for each record.  It receives
+   five parameters.  The first parameter is the pointer to the region of the key.  The second
+   parameter is the size of the region of the key.  The third parameter is the pointer to the
+   region of the value.  The fourth parameter is the size of the region of the value.  The fifth
+   parameter is the pointer to the optional opaque object.  It returns true to continue iteration
+   or false to stop iteration.
+   `op' specifies an arbitrary pointer to be given as a parameter of the iterator function.  If
+   it is not needed, `NULL' can be specified.
+   If successful, the return value is true, else, it is false.
+   Note that the callback function can not perform any database operation because the function
+   is called in the critical section guarded by the same locks of database operations. */
+bool tcbdbforeach(TCBDB *bdb, TCITER iter, void *op);
+
+
+
+__TCBDB_CLINKAGEEND
+#endif                                   /* duplication check */
+
+
+/* END OF FILE */
diff --git a/tcejdb/tcbmgr.c b/tcejdb/tcbmgr.c
new file mode 100644 (file)
index 0000000..af27ac4
--- /dev/null
@@ -0,0 +1,1020 @@
+/*************************************************************************************************
+ * The command line utility of the B+ tree database API
+ *                                                               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 <tcbdb.h>
+#include "myconf.h"
+
+
+/* global variables */
+const char *g_progname;                  // program name
+int g_dbgfd;                             // debugging output
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+static void usage(void);
+static void printerr(TCBDB *bdb);
+static int printdata(const char *ptr, int size, bool px);
+static char *mygetline(FILE *ifp);
+static int mycmpfunc(const char *aptr, int asiz, const char *bptr, int bsiz, void *op);
+static int runcreate(int argc, char **argv);
+static int runinform(int argc, char **argv);
+static int runput(int argc, char **argv);
+static int runout(int argc, char **argv);
+static int runget(int argc, char **argv);
+static int runlist(int argc, char **argv);
+static int runoptimize(int argc, char **argv);
+static int runimporttsv(int argc, char **argv);
+static int runversion(int argc, char **argv);
+static int proccreate(const char *path, int lmemb, int nmemb,
+                      int bnum, int apow, int fpow, TCCMP cmp, int opts);
+static int procinform(const char *path, int omode);
+static int procput(const char *path, const char *kbuf, int ksiz, const char *vbuf, int vsiz,
+                   TCCMP cmp, int omode, int dmode);
+static int procout(const char *path, const char *kbuf, int ksiz, TCCMP cmp, int omode);
+static int procget(const char *path, const char *kbuf, int ksiz, TCCMP cmp, int omode,
+                   bool px, bool pz);
+static int proclist(const char *path, TCCMP cmp, int omode, int max, bool pv, bool px, bool bk,
+                    const char *jstr, const char *bstr, const char *estr, const char *fmstr);
+static int procoptimize(const char *path, int lmemb, int nmemb,
+                        int bnum, int apow, int fpow, TCCMP cmp, int opts, int omode, bool df);
+static int procimporttsv(const char *path, const char *file, int omode, bool sc);
+static int procversion(void);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  g_progname = argv[0];
+  g_dbgfd = -1;
+  const char *ebuf = getenv("TCDBGFD");
+  if(ebuf) g_dbgfd = tcatoix(ebuf);
+  if(argc < 2) usage();
+  int rv = 0;
+  if(!strcmp(argv[1], "create")){
+    rv = runcreate(argc, argv);
+  } else if(!strcmp(argv[1], "inform")){
+    rv = runinform(argc, argv);
+  } else if(!strcmp(argv[1], "put")){
+    rv = runput(argc, argv);
+  } else if(!strcmp(argv[1], "out")){
+    rv = runout(argc, argv);
+  } else if(!strcmp(argv[1], "get")){
+    rv = runget(argc, argv);
+  } else if(!strcmp(argv[1], "list")){
+    rv = runlist(argc, argv);
+  } else if(!strcmp(argv[1], "optimize")){
+    rv = runoptimize(argc, argv);
+  } else if(!strcmp(argv[1], "importtsv")){
+    rv = runimporttsv(argc, argv);
+  } else if(!strcmp(argv[1], "version") || !strcmp(argv[1], "--version")){
+    rv = runversion(argc, argv);
+  } else {
+    usage();
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+static void usage(void){
+  fprintf(stderr, "%s: the command line utility of the B+ tree database API\n", g_progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s create [-cd|-ci|-cj] [-tl] [-td|-tb|-tt|-tx] path"
+          " [lmemb [nmemb [bnum [apow [fpow]]]]]\n", g_progname);
+  fprintf(stderr, "  %s inform [-nl|-nb] path\n", g_progname);
+  fprintf(stderr, "  %s put [-cd|-ci|-cj] [-nl|-nb] [-sx] [-dk|-dc|-dd|-db|-dai|-dad] path"
+          " key value\n", g_progname);
+  fprintf(stderr, "  %s out [-cd|-ci|-cj] [-nl|-nb] [-sx] path key\n", g_progname);
+  fprintf(stderr, "  %s get [-cd|-ci|-cj] [-nl|-nb] [-sx] [-px] [-pz] path key\n", g_progname);
+  fprintf(stderr, "  %s list [-cd|-ci|-cj] [-nl|-nb] [-m num] [-bk] [-pv] [-px] [-j str]"
+          " [-rb bkey ekey] [-fm str] path\n", g_progname);
+  fprintf(stderr, "  %s optimize [-cd|-ci|-cj] [-tl] [-td|-tb|-tt|-tx] [-tz] [-nl|-nb] [-df]"
+          " path [lmemb [nmemb [bnum [apow [fpow]]]]]\n", g_progname);
+  fprintf(stderr, "  %s importtsv [-nl|-nb] [-sc] path [file]\n", g_progname);
+  fprintf(stderr, "  %s version\n", g_progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* print error information */
+static void printerr(TCBDB *bdb){
+  const char *path = tcbdbpath(bdb);
+  int ecode = tcbdbecode(bdb);
+  fprintf(stderr, "%s: %s: %d: %s\n", g_progname, path ? path : "-", ecode, tcbdberrmsg(ecode));
+}
+
+
+/* print record data */
+static int printdata(const char *ptr, int size, bool px){
+  int len = 0;
+  while(size-- > 0){
+    if(px){
+      if(len > 0) putchar(' ');
+      len += printf("%02X", *(unsigned char *)ptr);
+    } else {
+      putchar(*ptr);
+      len++;
+    }
+    ptr++;
+  }
+  return len;
+}
+
+
+/* read a line from a file descriptor */
+static char *mygetline(FILE *ifp){
+  int len = 0;
+  int blen = 1024;
+  char *buf = tcmalloc(blen + 1);
+  bool end = true;
+  int c;
+  while((c = fgetc(ifp)) != EOF){
+    end = false;
+    if(c == '\0') continue;
+    if(blen <= len){
+      blen *= 2;
+      buf = tcrealloc(buf, blen + 1);
+    }
+    if(c == '\n' || c == '\r') c = '\0';
+    buf[len++] = c;
+    if(c == '\0') break;
+  }
+  if(end){
+    tcfree(buf);
+    return NULL;
+  }
+  buf[len] = '\0';
+  return buf;
+}
+
+
+/* dummy comparison function */
+static int mycmpfunc(const char *aptr, int asiz, const char *bptr, int bsiz, void *op){
+  return 0;
+}
+
+
+/* parse arguments of create command */
+static int runcreate(int argc, char **argv){
+  char *path = NULL;
+  char *lmstr = NULL;
+  char *nmstr = NULL;
+  char *bstr = NULL;
+  char *astr = NULL;
+  char *fstr = NULL;
+  TCCMP cmp = NULL;
+  int opts = 0;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-cd")){
+        cmp = tccmpdecimal;
+      } else if(!strcmp(argv[i], "-ci")){
+        cmp = tccmpint32;
+      } else if(!strcmp(argv[i], "-cj")){
+        cmp = tccmpint64;
+      } else if(!strcmp(argv[i], "-tl")){
+        opts |= BDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= BDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= BDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= BDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= BDBTEXCODEC;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!lmstr){
+      lmstr = argv[i];
+    } else if(!nmstr){
+      nmstr = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else if(!astr){
+      astr = argv[i];
+    } else if(!fstr){
+      fstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int lmemb = lmstr ? tcatoix(lmstr) : -1;
+  int nmemb = nmstr ? tcatoix(nmstr) : -1;
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int apow = astr ? tcatoix(astr) : -1;
+  int fpow = fstr ? tcatoix(fstr) : -1;
+  int rv = proccreate(path, lmemb, nmemb, bnum, apow, fpow, cmp, opts);
+  return rv;
+}
+
+
+/* parse arguments of inform command */
+static int runinform(int argc, char **argv){
+  char *path = NULL;
+  int omode = 0;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= BDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= BDBOLCKNB;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int rv = procinform(path, omode);
+  return rv;
+}
+
+
+/* parse arguments of put command */
+static int runput(int argc, char **argv){
+  char *path = NULL;
+  char *key = NULL;
+  char *value = NULL;
+  TCCMP cmp = NULL;
+  int omode = 0;
+  int dmode = 0;
+  bool sx = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-cd")){
+        cmp = tccmpdecimal;
+      } else if(!strcmp(argv[i], "-ci")){
+        cmp = tccmpint32;
+      } else if(!strcmp(argv[i], "-cj")){
+        cmp = tccmpint64;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= BDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= BDBOLCKNB;
+      } else if(!strcmp(argv[i], "-dk")){
+        dmode = -1;
+      } else if(!strcmp(argv[i], "-dc")){
+        dmode = 1;
+      } else if(!strcmp(argv[i], "-dd")){
+        dmode = 2;
+      } else if(!strcmp(argv[i], "-db")){
+        dmode = 3;
+      } else if(!strcmp(argv[i], "-dai")){
+        dmode = 10;
+      } else if(!strcmp(argv[i], "-dad")){
+        dmode = 11;
+      } else if(!strcmp(argv[i], "-sx")){
+        sx = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!key){
+      key = argv[i];
+    } else if(!value){
+      value = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !key || !value) usage();
+  char *kbuf, *vbuf;
+  int ksiz, vsiz;
+  if(sx){
+    kbuf = tchexdecode(key, &ksiz);
+    vbuf = tchexdecode(value, &vsiz);
+  } else {
+    ksiz = strlen(key);
+    kbuf = tcmemdup(key, ksiz);
+    vsiz = strlen(value);
+    vbuf = tcmemdup(value, vsiz);
+  }
+  int rv = procput(path, kbuf, ksiz, vbuf, vsiz, cmp, omode, dmode);
+  tcfree(vbuf);
+  tcfree(kbuf);
+  return rv;
+}
+
+
+/* parse arguments of out command */
+static int runout(int argc, char **argv){
+  char *path = NULL;
+  char *key = NULL;
+  TCCMP cmp = NULL;
+  int omode = 0;
+  bool sx = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-cd")){
+        cmp = tccmpdecimal;
+      } else if(!strcmp(argv[i], "-ci")){
+        cmp = tccmpint32;
+      } else if(!strcmp(argv[i], "-cj")){
+        cmp = tccmpint64;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= BDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= BDBOLCKNB;
+      } else if(!strcmp(argv[i], "-sx")){
+        sx = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!key){
+      key = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !key) usage();
+  int ksiz;
+  char *kbuf;
+  if(sx){
+    kbuf = tchexdecode(key, &ksiz);
+  } else {
+    ksiz = strlen(key);
+    kbuf = tcmemdup(key, ksiz);
+  }
+  int rv = procout(path, kbuf, ksiz, cmp, omode);
+  tcfree(kbuf);
+  return rv;
+}
+
+
+/* parse arguments of get command */
+static int runget(int argc, char **argv){
+  char *path = NULL;
+  char *key = NULL;
+  TCCMP cmp = NULL;
+  int omode = 0;
+  bool sx = false;
+  bool px = false;
+  bool pz = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-cd")){
+        cmp = tccmpdecimal;
+      } else if(!strcmp(argv[i], "-ci")){
+        cmp = tccmpint32;
+      } else if(!strcmp(argv[i], "-cj")){
+        cmp = tccmpint64;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= BDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= BDBOLCKNB;
+      } else if(!strcmp(argv[i], "-sx")){
+        sx = true;
+      } else if(!strcmp(argv[i], "-px")){
+        px = true;
+      } else if(!strcmp(argv[i], "-pz")){
+        pz = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!key){
+      key = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !key) usage();
+  int ksiz;
+  char *kbuf;
+  if(sx){
+    kbuf = tchexdecode(key, &ksiz);
+  } else {
+    ksiz = strlen(key);
+    kbuf = tcmemdup(key, ksiz);
+  }
+  int rv = procget(path, kbuf, ksiz, cmp, omode, px, pz);
+  tcfree(kbuf);
+  return rv;
+}
+
+
+/* parse arguments of list command */
+static int runlist(int argc, char **argv){
+  char *path = NULL;
+  TCCMP cmp = NULL;
+  int omode = 0;
+  int max = -1;
+  bool pv = false;
+  bool px = false;
+  bool bk = false;
+  char *jstr = NULL;
+  char *bstr = NULL;
+  char *estr = NULL;
+  char *fmstr = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-cd")){
+        cmp = tccmpdecimal;
+      } else if(!strcmp(argv[i], "-ci")){
+        cmp = tccmpint32;
+      } else if(!strcmp(argv[i], "-cj")){
+        cmp = tccmpint64;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= BDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= BDBOLCKNB;
+      } else if(!strcmp(argv[i], "-m")){
+        if(++i >= argc) usage();
+        max = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-bk")){
+        bk = true;
+      } else if(!strcmp(argv[i], "-pv")){
+        pv = true;
+      } else if(!strcmp(argv[i], "-px")){
+        px = true;
+      } else if(!strcmp(argv[i], "-j")){
+        if(++i >= argc) usage();
+        jstr = argv[i];
+      } else if(!strcmp(argv[i], "-rb")){
+        if(++i >= argc) usage();
+        bstr = argv[i];
+        if(++i >= argc) usage();
+        estr = argv[i];
+      } else if(!strcmp(argv[i], "-fm")){
+        if(++i >= argc) usage();
+        fmstr = argv[i];
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int rv = proclist(path, cmp, omode, max, pv, px, bk, jstr, bstr, estr, fmstr);
+  return rv;
+}
+
+
+/* parse arguments of optimize command */
+static int runoptimize(int argc, char **argv){
+  char *path = NULL;
+  char *lmstr = NULL;
+  char *nmstr = NULL;
+  char *bstr = NULL;
+  char *astr = NULL;
+  char *fstr = NULL;
+  TCCMP cmp = NULL;
+  int opts = UINT8_MAX;
+  int omode = 0;
+  bool df = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-cd")){
+        cmp = tccmpdecimal;
+      } else if(!strcmp(argv[i], "-ci")){
+        cmp = tccmpint32;
+      } else if(!strcmp(argv[i], "-cj")){
+        cmp = tccmpint64;
+      } else if(!strcmp(argv[i], "-tl")){
+        if(opts == UINT8_MAX) opts = 0;
+        opts |= BDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        if(opts == UINT8_MAX) opts = 0;
+        opts |= BDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        if(opts == UINT8_MAX) opts = 0;
+        opts |= BDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        if(opts == UINT8_MAX) opts = 0;
+        opts |= BDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        if(opts == UINT8_MAX) opts = 0;
+        opts |= BDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-tz")){
+        if(opts == UINT8_MAX) opts = 0;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= BDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= BDBOLCKNB;
+      } else if(!strcmp(argv[i], "-df")){
+        df = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!lmstr){
+      lmstr = argv[i];
+    } else if(!nmstr){
+      nmstr = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else if(!astr){
+      astr = argv[i];
+    } else if(!fstr){
+      fstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int lmemb = lmstr ? tcatoix(lmstr) : -1;
+  int nmemb = nmstr ? tcatoix(nmstr) : -1;
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int apow = astr ? tcatoix(astr) : -1;
+  int fpow = fstr ? tcatoix(fstr) : -1;
+  int rv = procoptimize(path, lmemb, nmemb, bnum, apow, fpow, cmp, opts, omode, df);
+  return rv;
+}
+
+
+/* parse arguments of importtsv command */
+static int runimporttsv(int argc, char **argv){
+  char *path = NULL;
+  char *file = NULL;
+  int omode = 0;
+  bool sc = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= BDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= BDBOLCKNB;
+      } else if(!strcmp(argv[i], "-sc")){
+        sc = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!file){
+      file = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int rv = procimporttsv(path, file, omode, sc);
+  return rv;
+}
+
+
+/* parse arguments of version command */
+static int runversion(int argc, char **argv){
+  int rv = procversion();
+  return rv;
+}
+
+
+/* perform create command */
+static int proccreate(const char *path, int lmemb, int nmemb,
+                      int bnum, int apow, int fpow, TCCMP cmp, int opts){
+  TCBDB *bdb = tcbdbnew();
+  if(g_dbgfd >= 0) tcbdbsetdbgfd(bdb, g_dbgfd);
+  if(cmp && !tcbdbsetcmpfunc(bdb, cmp, NULL)) printerr(bdb);
+  if(!tcbdbsetcodecfunc(bdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(bdb);
+  if(!tcbdbtune(bdb, lmemb, nmemb, bnum, apow, fpow, opts)){
+    printerr(bdb);
+    tcbdbdel(bdb);
+    return 1;
+  }
+  if(!tcbdbopen(bdb, path, BDBOWRITER | BDBOCREAT | BDBOTRUNC)){
+    printerr(bdb);
+    tcbdbdel(bdb);
+    return 1;
+  }
+  bool err = false;
+  if(!tcbdbclose(bdb)){
+    printerr(bdb);
+    err = true;
+  }
+  tcbdbdel(bdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform inform command */
+static int procinform(const char *path, int omode){
+  TCBDB *bdb = tcbdbnew();
+  if(g_dbgfd >= 0) tcbdbsetdbgfd(bdb, g_dbgfd);
+  tcbdbsetcmpfunc(bdb, mycmpfunc, NULL);
+  tcbdbsetcodecfunc(bdb, _tc_recencode, NULL, _tc_recdecode, NULL);
+  if(!tcbdbopen(bdb, path, BDBOREADER | omode)){
+    printerr(bdb);
+    tcbdbdel(bdb);
+    return 1;
+  }
+  bool err = false;
+  const char *npath = tcbdbpath(bdb);
+  if(!npath) npath = "(unknown)";
+  printf("path: %s\n", npath);
+  printf("database type: btree\n");
+  uint8_t flags = tcbdbflags(bdb);
+  printf("additional flags:");
+  if(flags & BDBFOPEN) printf(" open");
+  if(flags & BDBFFATAL) printf(" fatal");
+  printf("\n");
+  TCCMP cmp = tcbdbcmpfunc(bdb);
+  printf("comparison function: ");
+  if(cmp == tccmplexical){
+    printf("lexical");
+  } else if(cmp == tccmpdecimal){
+    printf("decimal");
+  } else if(cmp == tccmpint32){
+    printf("int32");
+  } else if(cmp == tccmpint64){
+    printf("int64");
+  } else {
+    printf("custom");
+  }
+  printf("\n");
+  printf("max leaf member: %d\n", tcbdblmemb(bdb));
+  printf("max node member: %d\n", tcbdbnmemb(bdb));
+  printf("leaf number: %llu\n", (unsigned long long)tcbdblnum(bdb));
+  printf("node number: %llu\n", (unsigned long long)tcbdbnnum(bdb));
+  printf("bucket number: %llu\n", (unsigned long long)tcbdbbnum(bdb));
+  if(bdb->hdb->cnt_writerec >= 0)
+    printf("used bucket number: %lld\n", (long long)tcbdbbnumused(bdb));
+  printf("alignment: %u\n", tcbdbalign(bdb));
+  printf("free block pool: %u\n", tcbdbfbpmax(bdb));
+  printf("inode number: %lld\n", (long long)tcbdbinode(bdb));
+  char date[48];
+  tcdatestrwww(tcbdbmtime(bdb), INT_MAX, date);
+  printf("modified time: %s\n", date);
+  uint8_t opts = tcbdbopts(bdb);
+  printf("options:");
+  if(opts & BDBTLARGE) printf(" large");
+  if(opts & BDBTDEFLATE) printf(" deflate");
+  if(opts & BDBTBZIP) printf(" bzip");
+  if(opts & BDBTTCBS) printf(" tcbs");
+  if(opts & BDBTEXCODEC) printf(" excodec");
+  printf("\n");
+  printf("record number: %llu\n", (unsigned long long)tcbdbrnum(bdb));
+  printf("file size: %llu\n", (unsigned long long)tcbdbfsiz(bdb));
+  if(!tcbdbclose(bdb)){
+    if(!err) printerr(bdb);
+    err = true;
+  }
+  tcbdbdel(bdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform put command */
+static int procput(const char *path, const char *kbuf, int ksiz, const char *vbuf, int vsiz,
+                   TCCMP cmp, int omode, int dmode){
+  TCBDB *bdb = tcbdbnew();
+  if(g_dbgfd >= 0) tcbdbsetdbgfd(bdb, g_dbgfd);
+  if(cmp && !tcbdbsetcmpfunc(bdb, cmp, NULL)) printerr(bdb);
+  if(!tcbdbsetcodecfunc(bdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(bdb);
+  if(!tcbdbopen(bdb, path, BDBOWRITER | omode)){
+    printerr(bdb);
+    tcbdbdel(bdb);
+    return 1;
+  }
+  bool err = false;
+  int inum;
+  double dnum;
+  switch(dmode){
+    case -1:
+      if(!tcbdbputkeep(bdb, kbuf, ksiz, vbuf, vsiz)){
+        printerr(bdb);
+        err = true;
+      }
+      break;
+    case 1:
+      if(!tcbdbputcat(bdb, kbuf, ksiz, vbuf, vsiz)){
+        printerr(bdb);
+        err = true;
+      }
+      break;
+    case 2:
+      if(!tcbdbputdup(bdb, kbuf, ksiz, vbuf, vsiz)){
+        printerr(bdb);
+        err = true;
+      }
+      break;
+    case 3:
+      if(!tcbdbputdupback(bdb, kbuf, ksiz, vbuf, vsiz)){
+        printerr(bdb);
+        err = true;
+      }
+      break;
+    case 10:
+      inum = tcbdbaddint(bdb, kbuf, ksiz, tcatoi(vbuf));
+      if(inum == INT_MIN){
+        printerr(bdb);
+        err = true;
+      } else {
+        printf("%d\n", inum);
+      }
+      break;
+    case 11:
+      dnum = tcbdbadddouble(bdb, kbuf, ksiz, tcatof(vbuf));
+      if(isnan(dnum)){
+        printerr(bdb);
+        err = true;
+      } else {
+        printf("%.6f\n", dnum);
+      }
+      break;
+    default:
+      if(!tcbdbput(bdb, kbuf, ksiz, vbuf, vsiz)){
+        printerr(bdb);
+        err = true;
+      }
+      break;
+  }
+  if(!tcbdbclose(bdb)){
+    if(!err) printerr(bdb);
+    err = true;
+  }
+  tcbdbdel(bdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform out command */
+static int procout(const char *path, const char *kbuf, int ksiz, TCCMP cmp, int omode){
+  TCBDB *bdb = tcbdbnew();
+  if(g_dbgfd >= 0) tcbdbsetdbgfd(bdb, g_dbgfd);
+  if(cmp && !tcbdbsetcmpfunc(bdb, cmp, NULL)) printerr(bdb);
+  if(!tcbdbsetcodecfunc(bdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(bdb);
+  if(!tcbdbopen(bdb, path, BDBOWRITER | omode)){
+    printerr(bdb);
+    tcbdbdel(bdb);
+    return 1;
+  }
+  bool err = false;
+  if(!tcbdbout(bdb, kbuf, ksiz)){
+    printerr(bdb);
+    err = true;
+  }
+  if(!tcbdbclose(bdb)){
+    if(!err) printerr(bdb);
+    err = true;
+  }
+  tcbdbdel(bdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform get command */
+static int procget(const char *path, const char *kbuf, int ksiz, TCCMP cmp, int omode,
+                   bool px, bool pz){
+  TCBDB *bdb = tcbdbnew();
+  if(g_dbgfd >= 0) tcbdbsetdbgfd(bdb, g_dbgfd);
+  if(cmp && !tcbdbsetcmpfunc(bdb, cmp, NULL)) printerr(bdb);
+  if(!tcbdbsetcodecfunc(bdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(bdb);
+  if(!tcbdbopen(bdb, path, BDBOREADER | omode)){
+    printerr(bdb);
+    tcbdbdel(bdb);
+    return 1;
+  }
+  bool err = false;
+  int vsiz;
+  char *vbuf = tcbdbget(bdb, kbuf, ksiz, &vsiz);
+  if(vbuf){
+    printdata(vbuf, vsiz, px);
+    if(!pz) putchar('\n');
+    tcfree(vbuf);
+  } else {
+    printerr(bdb);
+    err = true;
+  }
+  if(!tcbdbclose(bdb)){
+    if(!err) printerr(bdb);
+    err = true;
+  }
+  tcbdbdel(bdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform list command */
+static int proclist(const char *path, TCCMP cmp, int omode, int max, bool pv, bool px, bool bk,
+                    const char *jstr, const char *bstr, const char *estr, const char *fmstr){
+  TCBDB *bdb = tcbdbnew();
+  if(g_dbgfd >= 0) tcbdbsetdbgfd(bdb, g_dbgfd);
+  if(cmp && !tcbdbsetcmpfunc(bdb, cmp, NULL)) printerr(bdb);
+  if(!tcbdbsetcodecfunc(bdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(bdb);
+  if(!tcbdbopen(bdb, path, BDBOREADER | omode)){
+    printerr(bdb);
+    tcbdbdel(bdb);
+    return 1;
+  }
+  bool err = false;
+  if(bstr || fmstr){
+    TCLIST *keys = fmstr ? tcbdbfwmkeys2(bdb, fmstr, max) :
+      tcbdbrange(bdb, bstr, strlen(bstr), true, estr, strlen(estr), true, max);
+    int cnt = 0;
+    for(int i = 0; i < tclistnum(keys); i++){
+      int ksiz;
+      const char *kbuf = tclistval(keys, i, &ksiz);
+      if(pv){
+        TCLIST *vals = tcbdbget4(bdb, kbuf, ksiz);
+        if(vals){
+          for(int j = 0; j < tclistnum(vals); j++){
+            int vsiz;
+            const char *vbuf = tclistval(vals, j, &vsiz);
+            printdata(kbuf, ksiz, px);
+            putchar('\t');
+            printdata(vbuf, vsiz, px);
+            putchar('\n');
+            if(max >= 0 && ++cnt >= max) break;
+          }
+          tclistdel(vals);
+        }
+      } else {
+        int num = tcbdbvnum(bdb, kbuf, ksiz);
+        for(int j = 0; j < num; j++){
+          printdata(kbuf, ksiz, px);
+          putchar('\n');
+          if(max >= 0 && ++cnt >= max) break;
+        }
+      }
+      if(max >= 0 && cnt >= max) break;
+    }
+    tclistdel(keys);
+  } else {
+    BDBCUR *cur = tcbdbcurnew(bdb);
+    if(bk){
+      if(jstr){
+        if(!tcbdbcurjumpback(cur, jstr, strlen(jstr)) && tcbdbecode(bdb) != TCENOREC){
+          printerr(bdb);
+          err = true;
+        }
+      } else {
+        if(!tcbdbcurlast(cur) && tcbdbecode(bdb) != TCENOREC){
+          printerr(bdb);
+          err = true;
+        }
+      }
+    } else {
+      if(jstr){
+        if(!tcbdbcurjump(cur, jstr, strlen(jstr)) && tcbdbecode(bdb) != TCENOREC){
+          printerr(bdb);
+          err = true;
+        }
+      } else {
+        if(!tcbdbcurfirst(cur) && tcbdbecode(bdb) != TCENOREC){
+          printerr(bdb);
+          err = true;
+        }
+      }
+    }
+    TCXSTR *key = tcxstrnew();
+    TCXSTR *val = tcxstrnew();
+    int cnt = 0;
+    while(tcbdbcurrec(cur, key, val)){
+      printdata(tcxstrptr(key), tcxstrsize(key), px);
+      if(pv){
+        putchar('\t');
+        printdata(tcxstrptr(val), tcxstrsize(val), px);
+      }
+      putchar('\n');
+      if(bk){
+        if(!tcbdbcurprev(cur) && tcbdbecode(bdb) != TCENOREC){
+          printerr(bdb);
+          err = true;
+        }
+      } else {
+        if(!tcbdbcurnext(cur) && tcbdbecode(bdb) != TCENOREC){
+          printerr(bdb);
+          err = true;
+        }
+      }
+      if(max >= 0 && ++cnt >= max) break;
+    }
+    tcxstrdel(val);
+    tcxstrdel(key);
+    tcbdbcurdel(cur);
+  }
+  if(!tcbdbclose(bdb)){
+    if(!err) printerr(bdb);
+    err = true;
+  }
+  tcbdbdel(bdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform optimize command */
+static int procoptimize(const char *path, int lmemb, int nmemb,
+                        int bnum, int apow, int fpow, TCCMP cmp, int opts, int omode, bool df){
+  TCBDB *bdb = tcbdbnew();
+  if(g_dbgfd >= 0) tcbdbsetdbgfd(bdb, g_dbgfd);
+  if(cmp && !tcbdbsetcmpfunc(bdb, cmp, NULL)) printerr(bdb);
+  if(!tcbdbsetcodecfunc(bdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(bdb);
+  if(!tcbdbopen(bdb, path, BDBOWRITER | omode)){
+    printerr(bdb);
+    tcbdbdel(bdb);
+    return 1;
+  }
+  bool err = false;
+  if(df){
+    if(!tcbdbdefrag(bdb, INT64_MAX)){
+      printerr(bdb);
+      err = true;
+    }
+  } else {
+    if(!tcbdboptimize(bdb, lmemb, nmemb, bnum, apow, fpow, opts)){
+      printerr(bdb);
+      err = true;
+    }
+  }
+  if(!tcbdbclose(bdb)){
+    if(!err) printerr(bdb);
+    err = true;
+  }
+  tcbdbdel(bdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform importtsv command */
+static int procimporttsv(const char *path, const char *file, int omode, bool sc){
+  FILE *ifp = file ? fopen(file, "rb") : stdin;
+  if(!ifp){
+    fprintf(stderr, "%s: could not open\n", file ? file : "(stdin)");
+    return 1;
+  }
+  TCBDB *bdb = tcbdbnew();
+  if(g_dbgfd >= 0) tcbdbsetdbgfd(bdb, g_dbgfd);
+  if(!tcbdbsetcodecfunc(bdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(bdb);
+  if(!tcbdbopen(bdb, path, BDBOWRITER | BDBOCREAT | omode)){
+    printerr(bdb);
+    tcbdbdel(bdb);
+    if(ifp != stdin) fclose(ifp);
+    return 1;
+  }
+  bool err = false;
+  char *line;
+  int cnt = 0;
+  while(!err && (line = mygetline(ifp)) != NULL){
+    char *pv = strchr(line, '\t');
+    if(!pv){
+      tcfree(line);
+      continue;
+    }
+    *pv = '\0';
+    if(sc) tcstrutfnorm(line, TCUNSPACE | TCUNLOWER | TCUNNOACC | TCUNWIDTH);
+    if(!tcbdbputdup2(bdb, line, pv + 1)){
+      printerr(bdb);
+      err = true;
+    }
+    tcfree(line);
+    if(cnt > 0 && cnt % 100 == 0){
+      putchar('.');
+      fflush(stdout);
+      if(cnt % 5000 == 0) printf(" (%08d)\n", cnt);
+    }
+    cnt++;
+  }
+  printf(" (%08d)\n", cnt);
+  if(!tcbdbclose(bdb)){
+    if(!err) printerr(bdb);
+    err = true;
+  }
+  tcbdbdel(bdb);
+  if(ifp != stdin) fclose(ifp);
+  return err ? 1 : 0;
+}
+
+
+/* perform version command */
+static int procversion(void){
+  printf("Tokyo Cabinet version %s (%d:%s) for %s\n",
+         tcversion, _TC_LIBVER, _TC_FORMATVER, TCSYSNAME);
+  printf("Copyright (C) 2006-2012 FAL Labs\n");
+  return 0;
+}
+
+
+
+// END OF FILE
diff --git a/tcejdb/tcbmttest.c b/tcejdb/tcbmttest.c
new file mode 100644 (file)
index 0000000..d8bf5c3
--- /dev/null
@@ -0,0 +1,1810 @@
+/*************************************************************************************************
+ * The test cases of the B+ tree database API
+ *                                                               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 <tcbdb.h>
+#include "myconf.h"
+
+#define RECBUFSIZ      48                // buffer for records
+
+typedef struct {                         // type of structure for write thread
+  TCBDB *bdb;
+  int rnum;
+  bool rnd;
+  int id;
+} TARGWRITE;
+
+typedef struct {                         // type of structure for read thread
+  TCBDB *bdb;
+  int rnum;
+  bool wb;
+  bool rnd;
+  int id;
+} TARGREAD;
+
+typedef struct {                         // type of structure for remove thread
+  TCBDB *bdb;
+  int rnum;
+  bool rnd;
+  int id;
+} TARGREMOVE;
+
+typedef struct {                         // type of structure for wicked thread
+  TCBDB *bdb;
+  int rnum;
+  bool nc;
+  int id;
+  TCMAP *map;
+} TARGWICKED;
+
+typedef struct {                         // type of structure for typical thread
+  TCBDB *bdb;
+  int rnum;
+  bool nc;
+  int rratio;
+  int id;
+} TARGTYPICAL;
+
+typedef struct {                         // type of structure for race thread
+  TCBDB *bdb;
+  int rnum;
+  int id;
+} TARGRACE;
+
+
+/* global variables */
+const char *g_progname;                  // program name
+unsigned int g_randseed;                 // random seed
+int g_dbgfd;                             // debugging output
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+static void usage(void);
+static void iprintf(const char *format, ...);
+static void iputchar(int c);
+static void eprint(TCBDB *bdb, int line, const char *func);
+static void mprint(TCBDB *bdb);
+static void sysprint(void);
+static int myrand(int range);
+static int myrandnd(int range);
+static bool iterfunc(const void *kbuf, int ksiz, const void *vbuf, int vsiz, void *op);
+static int runwrite(int argc, char **argv);
+static int runread(int argc, char **argv);
+static int runremove(int argc, char **argv);
+static int runwicked(int argc, char **argv);
+static int runtypical(int argc, char **argv);
+static int runrace(int argc, char **argv);
+static int procwrite(const char *path, int tnum, int rnum, int lmemb, int nmemb,
+                     int bnum, int apow, int fpow, int opts, int xmsiz, int dfunit, int omode,
+                     bool rnd);
+static int procread(const char *path, int tnum, int xmsiz, int dfunit, int omode,
+                    bool wb, bool rnd);
+static int procremove(const char *path, int tnum, int xmsiz, int dfunit, int omode, bool rnd);
+static int procwicked(const char *path, int tnum, int rnum, int opts, int omode, bool nc);
+static int proctypical(const char *path, int tnum, int rnum, int lmemb, int nmemb,
+                       int bnum, int apow, int fpow, int opts, int xmsiz, int dfunit, int omode,
+                       bool nc, int rratio);
+static int procrace(const char *path, int tnum, int rnum, int lmemb, int nmemb,
+                    int bnum, int apow, int fpow, int opts, int xmsiz, int dfunit, int omode);
+static void *threadwrite(void *targ);
+static void *threadread(void *targ);
+static void *threadremove(void *targ);
+static void *threadwicked(void *targ);
+static void *threadtypical(void *targ);
+static void *threadrace(void *targ);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  g_progname = argv[0];
+  const char *ebuf = getenv("TCRNDSEED");
+  g_randseed = ebuf ? tcatoix(ebuf) : tctime() * 1000;
+  srand(g_randseed);
+  ebuf = getenv("TCDBGFD");
+  g_dbgfd = ebuf ? tcatoix(ebuf) : UINT16_MAX;
+  if(argc < 2) usage();
+  int rv = 0;
+  if(!strcmp(argv[1], "write")){
+    rv = runwrite(argc, argv);
+  } else if(!strcmp(argv[1], "read")){
+    rv = runread(argc, argv);
+  } else if(!strcmp(argv[1], "remove")){
+    rv = runremove(argc, argv);
+  } else if(!strcmp(argv[1], "wicked")){
+    rv = runwicked(argc, argv);
+  } else if(!strcmp(argv[1], "typical")){
+    rv = runtypical(argc, argv);
+  } else if(!strcmp(argv[1], "race")){
+    rv = runrace(argc, argv);
+  } else {
+    usage();
+  }
+  if(rv != 0){
+    printf("FAILED: TCRNDSEED=%u PID=%d", g_randseed, (int)getpid());
+    for(int i = 0; i < argc; i++){
+      printf(" %s", argv[i]);
+    }
+    printf("\n\n");
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+static void usage(void){
+  fprintf(stderr, "%s: test cases of the B+ tree database API of Tokyo Cabinet\n", g_progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s write [-tl] [-td|-tb|-tt|-tx] [-xm num] [-df num] [-nl|-nb] [-rnd]"
+          " path tnum rnum [lmemb [nmemb [bnum [apow [fpow]]]]]\n", g_progname);
+  fprintf(stderr, "  %s read [-xm num] [-df num] [-nl|-nb] [-wb] [-rnd] path tnum\n",
+          g_progname);
+  fprintf(stderr, "  %s remove [-xm num] [-df num] [-nl|-nb] [-rnd] path tnum\n", g_progname);
+  fprintf(stderr, "  %s wicked [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] [-nc] path tnum rnum\n",
+          g_progname);
+  fprintf(stderr, "  %s typical [-tl] [-td|-tb|-tt|-tx] [-xm num] [-df num] [-nl|-nb]"
+          " [-nc] [-rr num] path tnum rnum [lmemb [nmemb [bnum [apow [fpow]]]]]\n", g_progname);
+  fprintf(stderr, "  %s race [-tl] [-td|-tb|-tt|-tx] [-xm num] [-df num] [-nl|-nb]"
+          " path tnum rnum [lmemb [nmemb [bnum [apow [fpow]]]]]\n", g_progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* print formatted information string and flush the buffer */
+static void iprintf(const char *format, ...){
+  va_list ap;
+  va_start(ap, format);
+  vprintf(format, ap);
+  fflush(stdout);
+  va_end(ap);
+}
+
+
+/* print a character and flush the buffer */
+static void iputchar(int c){
+  putchar(c);
+  fflush(stdout);
+}
+
+
+/* print error message of hash database */
+static void eprint(TCBDB *bdb, int line, const char *func){
+  const char *path = tcbdbpath(bdb);
+  int ecode = tcbdbecode(bdb);
+  fprintf(stderr, "%s: %s: %d: %s: error: %d: %s\n",
+          g_progname, path ? path : "-", line, func, ecode, tcbdberrmsg(ecode));
+}
+
+
+/* print members of hash database */
+static void mprint(TCBDB *bdb){
+  if(bdb->hdb->cnt_writerec < 0) return;
+  iprintf("max leaf member: %d\n", tcbdblmemb(bdb));
+  iprintf("max node member: %d\n", tcbdbnmemb(bdb));
+  iprintf("leaf number: %lld\n", (long long)tcbdblnum(bdb));
+  iprintf("node number: %lld\n", (long long)tcbdbnnum(bdb));
+  iprintf("bucket number: %lld\n", (long long)tcbdbbnum(bdb));
+  iprintf("used bucket number: %lld\n", (long long)tcbdbbnumused(bdb));
+  iprintf("cnt_saveleaf: %lld\n", (long long)bdb->cnt_saveleaf);
+  iprintf("cnt_loadleaf: %lld\n", (long long)bdb->cnt_loadleaf);
+  iprintf("cnt_killleaf: %lld\n", (long long)bdb->cnt_killleaf);
+  iprintf("cnt_adjleafc: %lld\n", (long long)bdb->cnt_adjleafc);
+  iprintf("cnt_savenode: %lld\n", (long long)bdb->cnt_savenode);
+  iprintf("cnt_loadnode: %lld\n", (long long)bdb->cnt_loadnode);
+  iprintf("cnt_adjnodec: %lld\n", (long long)bdb->cnt_adjnodec);
+  iprintf("cnt_writerec: %lld\n", (long long)bdb->hdb->cnt_writerec);
+  iprintf("cnt_reuserec: %lld\n", (long long)bdb->hdb->cnt_reuserec);
+  iprintf("cnt_moverec: %lld\n", (long long)bdb->hdb->cnt_moverec);
+  iprintf("cnt_readrec: %lld\n", (long long)bdb->hdb->cnt_readrec);
+  iprintf("cnt_searchfbp: %lld\n", (long long)bdb->hdb->cnt_searchfbp);
+  iprintf("cnt_insertfbp: %lld\n", (long long)bdb->hdb->cnt_insertfbp);
+  iprintf("cnt_splicefbp: %lld\n", (long long)bdb->hdb->cnt_splicefbp);
+  iprintf("cnt_dividefbp: %lld\n", (long long)bdb->hdb->cnt_dividefbp);
+  iprintf("cnt_mergefbp: %lld\n", (long long)bdb->hdb->cnt_mergefbp);
+  iprintf("cnt_reducefbp: %lld\n", (long long)bdb->hdb->cnt_reducefbp);
+  iprintf("cnt_appenddrp: %lld\n", (long long)bdb->hdb->cnt_appenddrp);
+  iprintf("cnt_deferdrp: %lld\n", (long long)bdb->hdb->cnt_deferdrp);
+  iprintf("cnt_flushdrp: %lld\n", (long long)bdb->hdb->cnt_flushdrp);
+  iprintf("cnt_adjrecc: %lld\n", (long long)bdb->hdb->cnt_adjrecc);
+  iprintf("cnt_defrag: %lld\n", (long long)bdb->hdb->cnt_defrag);
+  iprintf("cnt_shiftrec: %lld\n", (long long)bdb->hdb->cnt_shiftrec);
+  iprintf("cnt_trunc: %lld\n", (long long)bdb->hdb->cnt_trunc);
+}
+
+
+/* print system information */
+static void sysprint(void){
+  TCMAP *info = tcsysinfo();
+  if(info){
+    tcmapiterinit(info);
+    const char *kbuf;
+    while((kbuf = tcmapiternext2(info)) != NULL){
+      iprintf("sys_%s: %s\n", kbuf, tcmapiterval2(kbuf));
+    }
+    tcmapdel(info);
+  }
+}
+
+
+/* get a random number */
+static int myrand(int range){
+  if(range < 2) return 0;
+  int high = (unsigned int)rand() >> 4;
+  int low = range * (rand() / (RAND_MAX + 1.0));
+  low &= (unsigned int)INT_MAX >> 4;
+  return (high + low) % range;
+}
+
+
+/* get a random number based on normal distribution */
+static int myrandnd(int range){
+  int num = (int)tcdrandnd(range >> 1, range / 10);
+  return (num < 0 || num >= range) ? 0 : num;
+}
+
+
+/* iterator function */
+static bool iterfunc(const void *kbuf, int ksiz, const void *vbuf, int vsiz, void *op){
+  unsigned int sum = 0;
+  while(--ksiz >= 0){
+    sum += ((char *)kbuf)[ksiz];
+  }
+  while(--vsiz >= 0){
+    sum += ((char *)vbuf)[vsiz];
+  }
+  return myrand(100 + (sum & 0xff)) > 0;
+}
+
+
+/* parse arguments of write command */
+static int runwrite(int argc, char **argv){
+  char *path = NULL;
+  char *tstr = NULL;
+  char *rstr = NULL;
+  char *lmstr = NULL;
+  char *nmstr = NULL;
+  char *bstr = NULL;
+  char *astr = NULL;
+  char *fstr = NULL;
+  int opts = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int omode = 0;
+  bool rnd = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-tl")){
+        opts |= BDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= BDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= BDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= BDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= BDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= BDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= BDBOLCKNB;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else if(!lmstr){
+      lmstr = argv[i];
+    } else if(!nmstr){
+      nmstr = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else if(!astr){
+      astr = argv[i];
+    } else if(!fstr){
+      fstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !tstr || !rstr) usage();
+  int tnum = tcatoix(tstr);
+  int rnum = tcatoix(rstr);
+  if(tnum < 1 || rnum < 1) usage();
+  int lmemb = lmstr ? tcatoix(lmstr) : -1;
+  int nmemb = nmstr ? tcatoix(nmstr) : -1;
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int apow = astr ? tcatoix(astr) : -1;
+  int fpow = fstr ? tcatoix(fstr) : -1;
+  int rv = procwrite(path, tnum, rnum, lmemb, nmemb, bnum, apow, fpow, opts, xmsiz, dfunit,
+                     omode, rnd);
+  return rv;
+}
+
+
+/* parse arguments of read command */
+static int runread(int argc, char **argv){
+  char *path = NULL;
+  char *tstr = NULL;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int omode = 0;
+  bool rnd = false;
+  bool wb = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= BDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= BDBOLCKNB;
+      } else if(!strcmp(argv[i], "-wb")){
+        wb = true;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !tstr) usage();
+  int tnum = tcatoix(tstr);
+  if(tnum < 1) usage();
+  int rv = procread(path, tnum, xmsiz, dfunit, omode, wb, rnd);
+  return rv;
+}
+
+
+/* parse arguments of remove command */
+static int runremove(int argc, char **argv){
+  char *path = NULL;
+  char *tstr = NULL;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int omode = 0;
+  bool rnd = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= BDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= BDBOLCKNB;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !tstr) usage();
+  int tnum = tcatoix(tstr);
+  if(tnum < 1) usage();
+  int rv = procremove(path, tnum, xmsiz, dfunit, omode, rnd);
+  return rv;
+}
+
+
+/* parse arguments of wicked command */
+static int runwicked(int argc, char **argv){
+  char *path = NULL;
+  char *tstr = NULL;
+  char *rstr = NULL;
+  int opts = 0;
+  int omode = 0;
+  bool nc = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-tl")){
+        opts |= BDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= BDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= BDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= BDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= BDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= BDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= BDBOLCKNB;
+      } else if(!strcmp(argv[i], "-nc")){
+        nc = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !tstr || !rstr) usage();
+  int tnum = tcatoix(tstr);
+  int rnum = tcatoix(rstr);
+  if(tnum < 1 || rnum < 1) usage();
+  int rv = procwicked(path, tnum, rnum, opts, omode, nc);
+  return rv;
+}
+
+
+/* parse arguments of typical command */
+static int runtypical(int argc, char **argv){
+  char *path = NULL;
+  char *tstr = NULL;
+  char *rstr = NULL;
+  char *lmstr = NULL;
+  char *nmstr = NULL;
+  char *bstr = NULL;
+  char *astr = NULL;
+  char *fstr = NULL;
+  int opts = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int omode = 0;
+  int rratio = -1;
+  bool nc = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-tl")){
+        opts |= BDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= BDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= BDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= BDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= BDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= BDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= BDBOLCKNB;
+      } else if(!strcmp(argv[i], "-nc")){
+        nc = true;
+      } else if(!strcmp(argv[i], "-rr")){
+        if(++i >= argc) usage();
+        rratio = tcatoix(argv[i]);
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else if(!lmstr){
+      lmstr = argv[i];
+    } else if(!nmstr){
+      nmstr = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else if(!astr){
+      astr = argv[i];
+    } else if(!fstr){
+      fstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !tstr || !rstr) usage();
+  int tnum = tcatoix(tstr);
+  int rnum = tcatoix(rstr);
+  if(tnum < 1 || rnum < 1) usage();
+  int lmemb = lmstr ? tcatoix(lmstr) : -1;
+  int nmemb = nmstr ? tcatoix(nmstr) : -1;
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int apow = astr ? tcatoix(astr) : -1;
+  int fpow = fstr ? tcatoix(fstr) : -1;
+  int rv = proctypical(path, tnum, rnum, lmemb, nmemb, bnum, apow, fpow, opts, xmsiz, dfunit,
+                       omode, nc, rratio);
+  return rv;
+}
+
+
+/* parse arguments of race command */
+static int runrace(int argc, char **argv){
+  char *path = NULL;
+  char *tstr = NULL;
+  char *rstr = NULL;
+  char *lmstr = NULL;
+  char *nmstr = NULL;
+  char *bstr = NULL;
+  char *astr = NULL;
+  char *fstr = NULL;
+  int opts = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int omode = 0;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-tl")){
+        opts |= BDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= BDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= BDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= BDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= BDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= BDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= BDBOLCKNB;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else if(!lmstr){
+      lmstr = argv[i];
+    } else if(!nmstr){
+      nmstr = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else if(!astr){
+      astr = argv[i];
+    } else if(!fstr){
+      fstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !tstr || !rstr) usage();
+  int tnum = tcatoix(tstr);
+  int rnum = tcatoix(rstr);
+  if(tnum < 1 || rnum < 1) usage();
+  int lmemb = lmstr ? tcatoix(lmstr) : -1;
+  int nmemb = nmstr ? tcatoix(nmstr) : -1;
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int apow = astr ? tcatoix(astr) : -1;
+  int fpow = fstr ? tcatoix(fstr) : -1;
+  int rv = procrace(path, tnum, rnum, lmemb, nmemb, bnum, apow, fpow, opts, xmsiz, dfunit,
+                    omode);
+  return rv;
+}
+
+
+/* perform write command */
+static int procwrite(const char *path, int tnum, int rnum, int lmemb, int nmemb,
+                     int bnum, int apow, int fpow, int opts, int xmsiz, int dfunit, int omode,
+                     bool rnd){
+  iprintf("<Writing Test>\n  seed=%u  path=%s  tnum=%d  rnum=%d  lmemb=%d  nmemb=%d"
+          "  bnum=%d  apow=%d  fpow=%d  opts=%d  xmsiz=%d  dfunit=%d  omode=%d  rnd=%d\n\n",
+          g_randseed, path, tnum, rnum, lmemb, nmemb, bnum, apow, fpow, opts, xmsiz, dfunit,
+          omode, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCBDB *bdb = tcbdbnew();
+  if(g_dbgfd >= 0) tcbdbsetdbgfd(bdb, g_dbgfd);
+  if(!tcbdbsetmutex(bdb)){
+    eprint(bdb, __LINE__, "tcbdbsetmutex");
+    err = true;
+  }
+  if(!tcbdbsetcodecfunc(bdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(bdb, __LINE__, "tcbdbsetcodecfunc");
+    err = true;
+  }
+  if(!tcbdbtune(bdb, lmemb, nmemb, bnum, apow, fpow, opts)){
+    eprint(bdb, __LINE__, "tcbdbtune");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tcbdbsetxmsiz(bdb, xmsiz)){
+    eprint(bdb, __LINE__, "tcbdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tcbdbsetdfunit(bdb, dfunit)){
+    eprint(bdb, __LINE__, "tcbdbsetdfunit");
+    err = true;
+  }
+  if(!tcbdbopen(bdb, path, BDBOWRITER | BDBOCREAT | BDBOTRUNC | omode)){
+    eprint(bdb, __LINE__, "tcbdbopen");
+    err = true;
+  }
+  TARGWRITE targs[tnum];
+  pthread_t threads[tnum];
+  if(tnum == 1){
+    targs[0].bdb = bdb;
+    targs[0].rnum = rnum;
+    targs[0].rnd = rnd;
+    targs[0].id = 0;
+    if(threadwrite(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].bdb = bdb;
+      targs[i].rnum = rnum;
+      targs[i].rnd = rnd;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadwrite, targs + i) != 0){
+        eprint(bdb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(bdb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcbdbrnum(bdb));
+  iprintf("size: %llu\n", (unsigned long long)tcbdbfsiz(bdb));
+  mprint(bdb);
+  sysprint();
+  if(!tcbdbclose(bdb)){
+    eprint(bdb, __LINE__, "tcbdbclose");
+    err = true;
+  }
+  tcbdbdel(bdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform read command */
+static int procread(const char *path, int tnum, int xmsiz, int dfunit, int omode,
+                    bool wb, bool rnd){
+  iprintf("<Reading Test>\n  seed=%u  path=%s  tnum=%d  xmsiz=%d  dfunit=%d  omode=%d"
+          "  wb=%d  rnd=%d\n\n", g_randseed, path, tnum, xmsiz, dfunit, omode, wb, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCBDB *bdb = tcbdbnew();
+  if(g_dbgfd >= 0) tcbdbsetdbgfd(bdb, g_dbgfd);
+  if(!tcbdbsetmutex(bdb)){
+    eprint(bdb, __LINE__, "tcbdbsetmutex");
+    err = true;
+  }
+  if(!tcbdbsetcodecfunc(bdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(bdb, __LINE__, "tcbdbsetcodecfunc");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tcbdbsetxmsiz(bdb, xmsiz)){
+    eprint(bdb, __LINE__, "tcbdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tcbdbsetdfunit(bdb, dfunit)){
+    eprint(bdb, __LINE__, "tcbdbsetdfunit");
+    err = true;
+  }
+  if(!tcbdbopen(bdb, path, BDBOREADER | omode)){
+    eprint(bdb, __LINE__, "tcbdbopen");
+    err = true;
+  }
+  int rnum = tcbdbrnum(bdb) / tnum;
+  TARGREAD targs[tnum];
+  pthread_t threads[tnum];
+  if(tnum == 1){
+    targs[0].bdb = bdb;
+    targs[0].rnum = rnum;
+    targs[0].wb = wb;
+    targs[0].rnd = rnd;
+    targs[0].id = 0;
+    if(threadread(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].bdb = bdb;
+      targs[i].rnum = rnum;
+      targs[i].wb = wb;
+      targs[i].rnd = rnd;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadread, targs + i) != 0){
+        eprint(bdb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(bdb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcbdbrnum(bdb));
+  iprintf("size: %llu\n", (unsigned long long)tcbdbfsiz(bdb));
+  mprint(bdb);
+  sysprint();
+  if(!tcbdbclose(bdb)){
+    eprint(bdb, __LINE__, "tcbdbclose");
+    err = true;
+  }
+  tcbdbdel(bdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform remove command */
+static int procremove(const char *path, int tnum, int xmsiz, int dfunit, int omode, bool rnd){
+  iprintf("<Removing Test>\n  seed=%u  path=%s  tnum=%d  xmsiz=%d  dfunit=%d  omode=%d"
+          "  rnd=%d\n\n", g_randseed, path, tnum, xmsiz, dfunit, omode, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCBDB *bdb = tcbdbnew();
+  if(g_dbgfd >= 0) tcbdbsetdbgfd(bdb, g_dbgfd);
+  if(!tcbdbsetmutex(bdb)){
+    eprint(bdb, __LINE__, "tcbdbsetmutex");
+    err = true;
+  }
+  if(!tcbdbsetcodecfunc(bdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(bdb, __LINE__, "tcbdbsetcodecfunc");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tcbdbsetxmsiz(bdb, xmsiz)){
+    eprint(bdb, __LINE__, "tcbdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tcbdbsetdfunit(bdb, dfunit)){
+    eprint(bdb, __LINE__, "tcbdbsetdfunit");
+    err = true;
+  }
+  if(!tcbdbopen(bdb, path, BDBOWRITER | omode)){
+    eprint(bdb, __LINE__, "tcbdbopen");
+    err = true;
+  }
+  int rnum = tcbdbrnum(bdb) / tnum;
+  TARGREMOVE targs[tnum];
+  pthread_t threads[tnum];
+  if(tnum == 1){
+    targs[0].bdb = bdb;
+    targs[0].rnum = rnum;
+    targs[0].rnd = rnd;
+    targs[0].id = 0;
+    if(threadremove(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].bdb = bdb;
+      targs[i].rnum = rnum;
+      targs[i].rnd = rnd;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadremove, targs + i) != 0){
+        eprint(bdb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(bdb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcbdbrnum(bdb));
+  iprintf("size: %llu\n", (unsigned long long)tcbdbfsiz(bdb));
+  mprint(bdb);
+  sysprint();
+  if(!tcbdbclose(bdb)){
+    eprint(bdb, __LINE__, "tcbdbclose");
+    err = true;
+  }
+  tcbdbdel(bdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform wicked command */
+static int procwicked(const char *path, int tnum, int rnum, int opts, int omode, bool nc){
+  iprintf("<Writing Test>\n  seed=%u  path=%s  tnum=%d  rnum=%d  opts=%d  omode=%d  nc=%d\n\n",
+          g_randseed, path, tnum, rnum, opts, omode, nc);
+  bool err = false;
+  double stime = tctime();
+  TCBDB *bdb = tcbdbnew();
+  if(g_dbgfd >= 0) tcbdbsetdbgfd(bdb, g_dbgfd);
+  if(!tcbdbsetmutex(bdb)){
+    eprint(bdb, __LINE__, "tcbdbsetmutex");
+    err = true;
+  }
+  if(!tcbdbsetcodecfunc(bdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(bdb, __LINE__, "tcbdbsetcodecfunc");
+    err = true;
+  }
+  if(!tcbdbtune(bdb, 10, 10, rnum / 50, 10, -1, opts)){
+    eprint(bdb, __LINE__, "tcbdbtune");
+    err = true;
+  }
+  if(!tcbdbopen(bdb, path, BDBOWRITER | BDBOCREAT | BDBOTRUNC | omode)){
+    eprint(bdb, __LINE__, "tcbdbopen");
+    err = true;
+  }
+  TARGWICKED targs[tnum];
+  pthread_t threads[tnum];
+  TCMAP *map = tcmapnew();
+  if(tnum == 1){
+    targs[0].bdb = bdb;
+    targs[0].rnum = rnum;
+    targs[0].nc = nc;
+    targs[0].id = 0;
+    targs[0].map = map;
+    if(threadwicked(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].bdb = bdb;
+      targs[i].rnum = rnum;
+      targs[i].nc = nc;
+      targs[i].id = i;
+      targs[i].map = map;
+      if(pthread_create(threads + i, NULL, threadwicked, targs + i) != 0){
+        eprint(bdb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(bdb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  if(!nc){
+    if(!tcbdbsync(bdb)){
+      eprint(bdb, __LINE__, "tcbdbsync");
+      err = true;
+    }
+    if(tcbdbrnum(bdb) != tcmaprnum(map)){
+      eprint(bdb, __LINE__, "(validation)");
+      err = true;
+    }
+    int end = rnum * tnum;
+    for(int i = 1; i <= end && !err; i++){
+      char kbuf[RECBUFSIZ];
+      int ksiz = sprintf(kbuf, "%d", i - 1);
+      int vsiz;
+      const char *vbuf = tcmapget(map, kbuf, ksiz, &vsiz);
+      int rsiz;
+      char *rbuf = tcbdbget(bdb, kbuf, ksiz, &rsiz);
+      if(vbuf){
+        iputchar('.');
+        if(!rbuf){
+          eprint(bdb, __LINE__, "tcbdbget");
+          err = true;
+        } else if(rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+          eprint(bdb, __LINE__, "(validation)");
+          err = true;
+        }
+      } else {
+        iputchar('*');
+        if(rbuf || tcbdbecode(bdb) != TCENOREC){
+          eprint(bdb, __LINE__, "(validation)");
+          err = true;
+        }
+      }
+      tcfree(rbuf);
+      if(i % 50 == 0) iprintf(" (%08d)\n", i);
+    }
+    if(rnum % 50 > 0) iprintf(" (%08d)\n", rnum);
+  }
+  tcmapdel(map);
+  iprintf("record number: %llu\n", (unsigned long long)tcbdbrnum(bdb));
+  iprintf("size: %llu\n", (unsigned long long)tcbdbfsiz(bdb));
+  mprint(bdb);
+  sysprint();
+  if(!tcbdbclose(bdb)){
+    eprint(bdb, __LINE__, "tcbdbclose");
+    err = true;
+  }
+  tcbdbdel(bdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform typical command */
+static int proctypical(const char *path, int tnum, int rnum, int lmemb, int nmemb,
+                       int bnum, int apow, int fpow, int opts, int xmsiz, int dfunit, int omode,
+                       bool nc, int rratio){
+  iprintf("<Typical Access Test>\n  seed=%u  path=%s  tnum=%d  rnum=%d  lmemb=%d  nmemb=%d"
+          "  bnum=%d  apow=%d  fpow=%d  opts=%d  xmsiz=%d  dfunit=%d  omode=%d"
+          "  nc=%d  rratio=%d\n\n",
+          g_randseed, path, tnum, rnum, lmemb, nmemb, bnum, apow, fpow, opts, xmsiz, dfunit,
+          omode, nc, rratio);
+  bool err = false;
+  double stime = tctime();
+  TCBDB *bdb = tcbdbnew();
+  if(g_dbgfd >= 0) tcbdbsetdbgfd(bdb, g_dbgfd);
+  if(!tcbdbsetmutex(bdb)){
+    eprint(bdb, __LINE__, "tcbdbsetmutex");
+    err = true;
+  }
+  if(!tcbdbsetcodecfunc(bdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(bdb, __LINE__, "tcbdbsetcodecfunc");
+    err = true;
+  }
+  if(!tcbdbtune(bdb, lmemb, nmemb, bnum, apow, fpow, opts)){
+    eprint(bdb, __LINE__, "tcbdbtune");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tcbdbsetxmsiz(bdb, xmsiz)){
+    eprint(bdb, __LINE__, "tcbdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tcbdbsetdfunit(bdb, dfunit)){
+    eprint(bdb, __LINE__, "tcbdbsetdfunit");
+    err = true;
+  }
+  if(!tcbdbopen(bdb, path, BDBOWRITER | BDBOCREAT | BDBOTRUNC | omode)){
+    eprint(bdb, __LINE__, "tcbdbopen");
+    err = true;
+  }
+  TARGTYPICAL targs[tnum];
+  pthread_t threads[tnum];
+  if(tnum == 1){
+    targs[0].bdb = bdb;
+    targs[0].rnum = rnum;
+    targs[0].nc = nc;
+    targs[0].rratio = rratio;
+    targs[0].id = 0;
+    if(threadtypical(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].bdb = bdb;
+      targs[i].rnum = rnum;
+      targs[i].nc = nc;
+      targs[i].rratio = rratio;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadtypical, targs + i) != 0){
+        eprint(bdb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(bdb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcbdbrnum(bdb));
+  iprintf("size: %llu\n", (unsigned long long)tcbdbfsiz(bdb));
+  mprint(bdb);
+  sysprint();
+  if(!tcbdbclose(bdb)){
+    eprint(bdb, __LINE__, "tcbdbclose");
+    err = true;
+  }
+  tcbdbdel(bdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform race command */
+static int procrace(const char *path, int tnum, int rnum, int lmemb, int nmemb,
+                    int bnum, int apow, int fpow, int opts, int xmsiz, int dfunit, int omode){
+  iprintf("<Race Condition Test>\n  seed=%u  path=%s  tnum=%d  rnum=%d  lmemb=%d  nmemb=%d"
+          "  bnum=%d  apow=%d  fpow=%d  opts=%d  xmsiz=%d  dfunit=%d  omode=%d\n\n",
+          g_randseed, path, tnum, rnum, lmemb, nmemb, bnum, apow, fpow, opts,
+          xmsiz, dfunit, omode);
+  bool err = false;
+  double stime = tctime();
+  TCBDB *bdb = tcbdbnew();
+  if(g_dbgfd >= 0) tcbdbsetdbgfd(bdb, g_dbgfd);
+  if(!tcbdbsetmutex(bdb)){
+    eprint(bdb, __LINE__, "tcbdbsetmutex");
+    err = true;
+  }
+  if(!tcbdbsetcodecfunc(bdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(bdb, __LINE__, "tcbdbsetcodecfunc");
+    err = true;
+  }
+  if(!tcbdbtune(bdb, lmemb, nmemb, bnum, apow, fpow, opts)){
+    eprint(bdb, __LINE__, "tcbdbtune");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tcbdbsetxmsiz(bdb, xmsiz)){
+    eprint(bdb, __LINE__, "tcbdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tcbdbsetdfunit(bdb, dfunit)){
+    eprint(bdb, __LINE__, "tcbdbsetdfunit");
+    err = true;
+  }
+  if(!tcbdbopen(bdb, path, BDBOWRITER | BDBOCREAT | BDBOTRUNC | omode)){
+    eprint(bdb, __LINE__, "tcbdbopen");
+    err = true;
+  }
+  TARGRACE targs[tnum];
+  pthread_t threads[tnum];
+  if(tnum == 1){
+    targs[0].bdb = bdb;
+    targs[0].rnum = rnum;
+    targs[0].id = 0;
+    if(threadrace(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].bdb = bdb;
+      targs[i].rnum = rnum;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadrace, targs + i) != 0){
+        eprint(bdb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(bdb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcbdbrnum(bdb));
+  iprintf("size: %llu\n", (unsigned long long)tcbdbfsiz(bdb));
+  mprint(bdb);
+  sysprint();
+  if(!tcbdbclose(bdb)){
+    eprint(bdb, __LINE__, "tcbdbclose");
+    err = true;
+  }
+  tcbdbdel(bdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* thread the write function */
+static void *threadwrite(void *targ){
+  TCBDB *bdb = ((TARGWRITE *)targ)->bdb;
+  int rnum = ((TARGWRITE *)targ)->rnum;
+  bool rnd = ((TARGWRITE *)targ)->rnd;
+  int id = ((TARGWRITE *)targ)->id;
+  bool err = false;
+  int base = id * rnum;
+  for(int i = 1; i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", base + (rnd ? myrand(i) : i));
+    if(!tcbdbput(bdb, buf, len, buf, len)){
+      eprint(bdb, __LINE__, "tcbdbput");
+      err = true;
+      break;
+    }
+    if(id <= 0 && rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  return err ? "error" : NULL;
+}
+
+
+/* thread the read function */
+static void *threadread(void *targ){
+  TCBDB *bdb = ((TARGREAD *)targ)->bdb;
+  int rnum = ((TARGREAD *)targ)->rnum;
+  bool wb = ((TARGREAD *)targ)->wb;
+  bool rnd = ((TARGREAD *)targ)->rnd;
+  int id = ((TARGREAD *)targ)->id;
+  bool err = false;
+  int base = id * rnum;
+  for(int i = 1; i <= rnum && !err; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%08d", base + (rnd ? myrandnd(i) : i));
+    int vsiz;
+    if(wb){
+      int vsiz;
+      const char *vbuf = tcbdbget3(bdb, kbuf, ksiz, &vsiz);
+      if(!vbuf && (!rnd || tcbdbecode(bdb) != TCENOREC)){
+        eprint(bdb, __LINE__, "tcbdbget3");
+        err = true;
+      }
+    } else {
+      char *vbuf = tcbdbget(bdb, kbuf, ksiz, &vsiz);
+      if(!vbuf && (!rnd || tcbdbecode(bdb) != TCENOREC)){
+        eprint(bdb, __LINE__, "tcbdbget");
+        err = true;
+      }
+      tcfree(vbuf);
+    }
+    if(id == 0 && rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  return err ? "error" : NULL;
+}
+
+
+/* thread the remove function */
+static void *threadremove(void *targ){
+  TCBDB *bdb = ((TARGREMOVE *)targ)->bdb;
+  int rnum = ((TARGREMOVE *)targ)->rnum;
+  bool rnd = ((TARGREMOVE *)targ)->rnd;
+  int id = ((TARGREMOVE *)targ)->id;
+  bool err = false;
+  int base = id * rnum;
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%08d", base + (rnd ? myrand(i + 1) : i));
+    if(!tcbdbout(bdb, kbuf, ksiz) && (!rnd || tcbdbecode(bdb) != TCENOREC)){
+      eprint(bdb, __LINE__, "tcbdbout");
+      err = true;
+      break;
+    }
+    if(id == 0 && rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  return err ? "error" : NULL;
+}
+
+
+/* thread the wicked function */
+static void *threadwicked(void *targ){
+  TCBDB *bdb = ((TARGWICKED *)targ)->bdb;
+  int rnum = ((TARGWICKED *)targ)->rnum;
+  bool nc = ((TARGWICKED *)targ)->nc;
+  int id = ((TARGWICKED *)targ)->id;
+  TCMAP *map = ((TARGWICKED *)targ)->map;
+  BDBCUR *cur = tcbdbcurnew(bdb);
+  bool err = false;
+  for(int i = 1; i <= rnum && !err; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(rnum * (id + 1)));
+    char vbuf[RECBUFSIZ];
+    int vsiz = myrand(RECBUFSIZ);
+    memset(vbuf, '*', vsiz);
+    vbuf[vsiz] = '\0';
+    char *rbuf;
+    if(!nc) tcglobalmutexlock();
+    switch(myrand(16)){
+      case 0:
+        if(id == 0) iputchar('0');
+        if(!tcbdbput(bdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(bdb, __LINE__, "tcbdbput");
+          err = true;
+        }
+        if(!nc) tcmapput(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 1:
+        if(id == 0) iputchar('1');
+        if(!tcbdbput2(bdb, kbuf, vbuf)){
+          eprint(bdb, __LINE__, "tcbdbput2");
+          err = true;
+        }
+        if(!nc) tcmapput2(map, kbuf, vbuf);
+        break;
+      case 2:
+        if(id == 0) iputchar('2');
+        if(!tcbdbputkeep(bdb, kbuf, ksiz, vbuf, vsiz) && tcbdbecode(bdb) != TCEKEEP){
+          eprint(bdb, __LINE__, "tcbdbputkeep");
+          err = true;
+        }
+        if(!nc) tcmapputkeep(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 3:
+        if(id == 0) iputchar('3');
+        if(!tcbdbputkeep2(bdb, kbuf, vbuf) && tcbdbecode(bdb) != TCEKEEP){
+          eprint(bdb, __LINE__, "tcbdbputkeep2");
+          err = true;
+        }
+        if(!nc) tcmapputkeep2(map, kbuf, vbuf);
+        break;
+      case 4:
+        if(id == 0) iputchar('4');
+        if(!tcbdbputcat(bdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(bdb, __LINE__, "tcbdbputcat");
+          err = true;
+        }
+        if(!nc) tcmapputcat(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 5:
+        if(id == 0) iputchar('5');
+        if(!tcbdbputcat2(bdb, kbuf, vbuf)){
+          eprint(bdb, __LINE__, "tcbdbputcat2");
+          err = true;
+        }
+        if(!nc) tcmapputcat2(map, kbuf, vbuf);
+        break;
+      case 6:
+        if(id == 0) iputchar('6');
+        if(nc){
+          if(!tcbdbputdup(bdb, kbuf, ksiz, vbuf, vsiz)){
+            eprint(bdb, __LINE__, "tcbdbputdup");
+            err = true;
+          }
+        }
+        break;
+      case 7:
+        if(id == 0) iputchar('7');
+        if(nc){
+          if(!tcbdbputdup2(bdb, kbuf, vbuf)){
+            eprint(bdb, __LINE__, "tcbdbputdup2");
+            err = true;
+          }
+        }
+        break;
+      case 8:
+        if(id == 0) iputchar('8');
+        if(myrand(2) == 0){
+          if(!tcbdbout(bdb, kbuf, ksiz) && tcbdbecode(bdb) != TCENOREC){
+            eprint(bdb, __LINE__, "tcbdbout");
+            err = true;
+          }
+          if(!nc) tcmapout(map, kbuf, ksiz);
+        }
+        break;
+      case 9:
+        if(id == 0) iputchar('9');
+        if(myrand(2) == 0){
+          if(!tcbdbout2(bdb, kbuf) && tcbdbecode(bdb) != TCENOREC){
+            eprint(bdb, __LINE__, "tcbdbout2");
+            err = true;
+          }
+          if(!nc) tcmapout2(map, kbuf);
+        }
+        break;
+      case 10:
+        if(id == 0) iputchar('A');
+        if(!(rbuf = tcbdbget(bdb, kbuf, ksiz, &vsiz))){
+          if(tcbdbecode(bdb) != TCENOREC){
+            eprint(bdb, __LINE__, "tcbdbget");
+            err = true;
+          }
+          rbuf = tcsprintf("[%d]", myrand(i + 1));
+          vsiz = strlen(rbuf);
+        }
+        vsiz += myrand(vsiz);
+        if(myrand(3) == 0) vsiz += PATH_MAX;
+        rbuf = tcrealloc(rbuf, vsiz + 1);
+        for(int j = 0; j < vsiz; j++){
+          rbuf[j] = myrand(0x100);
+        }
+        if(!tcbdbput(bdb, kbuf, ksiz, rbuf, vsiz)){
+          eprint(bdb, __LINE__, "tcbdbput");
+          err = true;
+        }
+        if(!nc) tcmapput(map, kbuf, ksiz, rbuf, vsiz);
+        tcfree(rbuf);
+        break;
+      case 11:
+        if(id == 0) iputchar('B');
+        if(!(rbuf = tcbdbget(bdb, kbuf, ksiz, &vsiz)) && tcbdbecode(bdb) != TCENOREC){
+          eprint(bdb, __LINE__, "tcbdbget");
+          err = true;
+        }
+        tcfree(rbuf);
+        break;
+      case 12:
+        if(id == 0) iputchar('C');
+        if(!(rbuf = tcbdbget2(bdb, kbuf)) && tcbdbecode(bdb) != TCENOREC){
+          eprint(bdb, __LINE__, "tcbdbget2");
+          err = true;
+        }
+        tcfree(rbuf);
+        break;
+      case 13:
+        if(id == 0) iputchar('D');
+        if(!tcbdbget3(bdb, kbuf, ksiz, &vsiz) && tcbdbecode(bdb) != TCENOREC){
+          eprint(bdb, __LINE__, "tcbdbget3");
+          err = true;
+        }
+        break;
+      case 14:
+        if(id == 0) iputchar('E');
+        if(myrand(rnum / 50) == 0){
+          switch(myrand(5)){
+            case 0:
+              if(!tcbdbcurfirst(cur) && tcbdbecode(bdb) != TCENOREC){
+                eprint(bdb, __LINE__, "tcbdbcurfirst");
+                err = true;
+              }
+              break;
+            case 1:
+              if(!tcbdbcurlast(cur) && tcbdbecode(bdb) != TCENOREC){
+                eprint(bdb, __LINE__, "tcbdbcurlast");
+                err = true;
+              }
+              break;
+            default:
+              if(!tcbdbcurjump(cur, kbuf, ksiz) && tcbdbecode(bdb) != TCENOREC){
+                eprint(bdb, __LINE__, "tcbdbcurjump");
+                err = true;
+              }
+              break;
+          }
+        }
+        TCXSTR *ikey = tcxstrnew();
+        TCXSTR *ival = tcxstrnew();
+        for(int j = myrand(rnum) / 1000 + 1; j >= 0; j--){
+          if(j % 3 == 0){
+            if(!tcbdbcurrec(cur, ikey, ival)){
+              int ecode = tcbdbecode(bdb);
+              if(ecode != TCEINVALID && ecode != TCENOREC){
+                eprint(bdb, __LINE__, "tcbdbcurrec");
+                err = true;
+              }
+            }
+          } else {
+            int iksiz;
+            if(!tcbdbcurkey3(cur, &iksiz)){
+              int ecode = tcbdbecode(bdb);
+              if(ecode != TCEINVALID && ecode != TCENOREC){
+                eprint(bdb, __LINE__, "tcbdbcurkey3");
+                err = true;
+              }
+            }
+          }
+          if(myrand(5) == 0){
+            if(!tcbdbcurprev(cur)){
+              int ecode = tcbdbecode(bdb);
+              if(ecode != TCEINVALID && ecode != TCENOREC){
+                eprint(bdb, __LINE__, "tcbdbcurprev");
+                err = true;
+              }
+            }
+          } else {
+            if(!tcbdbcurnext(cur)){
+              int ecode = tcbdbecode(bdb);
+              if(ecode != TCEINVALID && ecode != TCENOREC){
+                eprint(bdb, __LINE__, "tcbdbcurnext");
+                err = true;
+              }
+            }
+          }
+        }
+        tcxstrdel(ival);
+        tcxstrdel(ikey);
+        break;
+      default:
+        if(id == 0) iputchar('@');
+        if(tcbdbtranbegin(bdb)){
+          if(myrand(2) == 0){
+            if(!tcbdbput(bdb, kbuf, ksiz, vbuf, vsiz)){
+              eprint(bdb, __LINE__, "tcbdbput");
+              err = true;
+            }
+            if(!nc) tcmapput(map, kbuf, ksiz, vbuf, vsiz);
+          } else {
+            if(!tcbdbout(bdb, kbuf, ksiz) && tcbdbecode(bdb) != TCENOREC){
+              eprint(bdb, __LINE__, "tcbdbout");
+              err = true;
+            }
+            if(!nc) tcmapout(map, kbuf, ksiz);
+          }
+          if(nc && myrand(2) == 0){
+            if(!tcbdbtranabort(bdb)){
+              eprint(bdb, __LINE__, "tcbdbtranabort");
+              err = true;
+            }
+          } else {
+            if(!tcbdbtrancommit(bdb)){
+              eprint(bdb, __LINE__, "tcbdbtrancommit");
+              err = true;
+            }
+          }
+        } else {
+          eprint(bdb, __LINE__, "tcbdbtranbegin");
+          err = true;
+        }
+        if(myrand(1000) == 0){
+          if(!tcbdbforeach(bdb, iterfunc, NULL)){
+            eprint(bdb, __LINE__, "tcbdbforeach");
+            err = true;
+          }
+        }
+        if(myrand(10000) == 0) srand((unsigned int)(tctime() * 1000) % UINT_MAX);
+        break;
+    }
+    if(!nc) tcglobalmutexunlock();
+    if(id == 0){
+      if(i % 50 == 0) iprintf(" (%08d)\n", i);
+      if(id == 0 && i == rnum / 4){
+        if(!tcbdboptimize(bdb, -1, -1, -1, -1, -1, -1) && tcbdbecode(bdb) != TCEINVALID){
+          eprint(bdb, __LINE__, "tcbdboptimize");
+          err = true;
+        }
+        if(!tcbdbcurfirst(cur) && tcbdbecode(bdb) != TCENOREC){
+          eprint(bdb, __LINE__, "tcbdbcurfirst");
+          err = true;
+        }
+      }
+    }
+  }
+  tcbdbcurdel(cur);
+  return err ? "error" : NULL;
+}
+
+
+/* thread the typical function */
+static void *threadtypical(void *targ){
+  TCBDB *bdb = ((TARGTYPICAL *)targ)->bdb;
+  int rnum = ((TARGTYPICAL *)targ)->rnum;
+  bool nc = ((TARGTYPICAL *)targ)->nc;
+  int rratio = ((TARGTYPICAL *)targ)->rratio;
+  int id = ((TARGTYPICAL *)targ)->id;
+  bool err = false;
+  TCMAP *map = (!nc && id == 0) ? tcmapnew2(rnum + 1) : NULL;
+  int base = id * rnum;
+  int mrange = tclmax(50 + rratio, 100);
+  BDBCUR *cur = tcbdbcurnew(bdb);
+  for(int i = 1; !err && i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", base + myrandnd(i));
+    int rnd = myrand(mrange);
+    if(rnd < 10){
+      if(!tcbdbput(bdb, buf, len, buf, len)){
+        eprint(bdb, __LINE__, "tcbdbput");
+        err = true;
+      }
+      if(map) tcmapput(map, buf, len, buf, len);
+    } else if(rnd < 15){
+      if(!tcbdbputkeep(bdb, buf, len, buf, len) && tcbdbecode(bdb) != TCEKEEP){
+        eprint(bdb, __LINE__, "tcbdbputkeep");
+        err = true;
+      }
+      if(map) tcmapputkeep(map, buf, len, buf, len);
+    } else if(rnd < 20){
+      if(!tcbdbputcat(bdb, buf, len, buf, len)){
+        eprint(bdb, __LINE__, "tcbdbputcat");
+        err = true;
+      }
+      if(map) tcmapputcat(map, buf, len, buf, len);
+    } else if(rnd < 25){
+      if(!tcbdbout(bdb, buf, len) && tcbdbecode(bdb) && tcbdbecode(bdb) != TCENOREC){
+        eprint(bdb, __LINE__, "tcbdbout");
+        err = true;
+      }
+      if(map) tcmapout(map, buf, len);
+    } else if(rnd < 27){
+      switch(myrand(3)){
+        case 0:
+          if(!tcbdbcurfirst(cur) && tcbdbecode(bdb) != TCENOREC){
+            eprint(bdb, __LINE__, "tcbdbcurfirst");
+            err = true;
+          }
+          for(int j = 0; !err && j < 10; j++){
+            int ksiz;
+            char *kbuf = tcbdbcurkey(cur, &ksiz);
+            if(kbuf){
+              int vsiz;
+              char *vbuf = tcbdbcurval(cur, &vsiz);
+              if(vbuf){
+                tcfree(vbuf);
+              } else if(tcbdbecode(bdb) != TCENOREC){
+                eprint(bdb, __LINE__, "tcbdbcurval");
+                err = true;
+              }
+              tcfree(kbuf);
+            } else if(tcbdbecode(bdb) != TCENOREC){
+              eprint(bdb, __LINE__, "tcbdbcurkey");
+              err = true;
+            }
+            tcbdbcurnext(cur);
+          }
+          break;
+        case 1:
+          if(!tcbdbcurlast(cur) && tcbdbecode(bdb) != TCENOREC){
+            eprint(bdb, __LINE__, "tcbdbcurlast");
+            err = true;
+          }
+          for(int j = 0; !err && j < 10; j++){
+            int ksiz;
+            char *kbuf = tcbdbcurkey(cur, &ksiz);
+            if(kbuf){
+              int vsiz;
+              char *vbuf = tcbdbcurval(cur, &vsiz);
+              if(vbuf){
+                tcfree(vbuf);
+              } else if(tcbdbecode(bdb) != TCENOREC){
+                eprint(bdb, __LINE__, "tcbdbcurval");
+                err = true;
+              }
+              tcfree(kbuf);
+            } else if(tcbdbecode(bdb) != TCENOREC){
+              eprint(bdb, __LINE__, "tcbdbcurkey");
+              err = true;
+            }
+            tcbdbcurprev(cur);
+          }
+          break;
+        case 2:
+          if(!tcbdbcurjump(cur, buf, len) && tcbdbecode(bdb) != TCENOREC){
+            eprint(bdb, __LINE__, "tcbdbcurjump");
+            err = true;
+          }
+          for(int j = 0; !err && j < 10; j++){
+            int ksiz;
+            char *kbuf = tcbdbcurkey(cur, &ksiz);
+            if(kbuf){
+              int vsiz;
+              char *vbuf = tcbdbcurval(cur, &vsiz);
+              if(vbuf){
+                tcfree(vbuf);
+              } else if(tcbdbecode(bdb) != TCENOREC){
+                eprint(bdb, __LINE__, "tcbdbcurval");
+                err = true;
+              }
+              tcfree(kbuf);
+            } else if(tcbdbecode(bdb) != TCENOREC){
+              eprint(bdb, __LINE__, "tcbdbcurkey");
+              err = true;
+            }
+            tcbdbcurnext(cur);
+          }
+          break;
+      }
+    } else {
+      int vsiz;
+      char *vbuf = tcbdbget(bdb, buf, len, &vsiz);
+      if(vbuf){
+        if(map){
+          int msiz;
+          const char *mbuf = tcmapget(map, buf, len, &msiz);
+          if(!mbuf || msiz != vsiz || memcmp(mbuf, vbuf, vsiz)){
+            eprint(bdb, __LINE__, "(validation)");
+            err = true;
+          }
+        }
+        tcfree(vbuf);
+      } else {
+        if(tcbdbecode(bdb) != TCENOREC){
+          eprint(bdb, __LINE__, "tcbdbget");
+          err = true;
+        }
+        if(map && tcmapget(map, buf, len, &vsiz)){
+          eprint(bdb, __LINE__, "(validation)");
+          err = true;
+        }
+      }
+    }
+    if(id == 0 && rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  tcbdbcurdel(cur);
+  if(map){
+    tcmapiterinit(map);
+    int ksiz;
+    const char *kbuf;
+    while(!err && (kbuf = tcmapiternext(map, &ksiz)) != NULL){
+      int vsiz;
+      char *vbuf = tcbdbget(bdb, kbuf, ksiz, &vsiz);
+      if(vbuf){
+        int msiz;
+        const char *mbuf = tcmapget(map, kbuf, ksiz, &msiz);
+        if(!mbuf || msiz != vsiz || memcmp(mbuf, vbuf, vsiz)){
+          eprint(bdb, __LINE__, "(validation)");
+          err = true;
+        }
+        tcfree(vbuf);
+      } else {
+        eprint(bdb, __LINE__, "(validation)");
+        err = true;
+      }
+    }
+    tcmapdel(map);
+  }
+  return err ? "error" : NULL;
+}
+
+
+/* thread the race function */
+static void *threadrace(void *targ){
+  TCBDB *bdb = ((TARGRACE *)targ)->bdb;
+  int rnum = ((TARGRACE *)targ)->rnum;
+  int id = ((TARGRACE *)targ)->id;
+  bool err = false;
+  int mid = rnum * 2;
+  for(int i = 1; !err && i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%d", myrandnd(i));
+    int rnd = myrand(100);
+    if(rnd < 10){
+      if(!tcbdbputkeep(bdb, buf, len, buf, len) && tcbdbecode(bdb) != TCEKEEP){
+        eprint(bdb, __LINE__, "tcbdbputkeep");
+        err = true;
+      }
+    } else if(rnd < 20){
+      if(!tcbdbputcat(bdb, buf, len, buf, len)){
+        eprint(bdb, __LINE__, "tcbdbputcat");
+        err = true;
+      }
+    } else if(rnd < 30){
+      if(!tcbdbputdup(bdb, buf, len, buf, len)){
+        eprint(bdb, __LINE__, "tcbdbputdup");
+        err = true;
+      }
+    } else if(rnd < 40){
+      if(!tcbdbputdupback(bdb, buf, len, buf, len)){
+        eprint(bdb, __LINE__, "tcbdbputdupback");
+        err = true;
+      }
+    } else if(rnd < 50){
+      if(!tcbdbout(bdb, buf, len) && tcbdbecode(bdb) != TCENOREC){
+        eprint(bdb, __LINE__, "tcbdbout");
+        err = true;
+      }
+    } else {
+      if(myrand(10) == 0){
+        int rsiz = myrand(1024);
+        char *rbuf = tcmalloc(rsiz + 1);
+        for(int j = 0; j < rsiz; j++){
+          rbuf[j] = myrand('z' - 'a') + 'a';
+        }
+        if(myrand(2) == 0){
+          if(!tcbdbput(bdb, buf, len, rbuf, rsiz)){
+            eprint(bdb, __LINE__, "tcbdbputcat");
+            err = true;
+          }
+        } else {
+          if(!tcbdbputcat(bdb, buf, len, rbuf, rsiz)){
+            eprint(bdb, __LINE__, "tcbdbputcat");
+            err = true;
+          }
+        }
+        tcfree(rbuf);
+      } else {
+        if(!tcbdbput(bdb, buf, len, buf, len)){
+          eprint(bdb, __LINE__, "tcbdbput");
+          err = true;
+        }
+      }
+    }
+    if(id == 0){
+      if(myrand(mid) == 0){
+        iprintf("[v]");
+        if(!tcbdbvanish(bdb)){
+          eprint(bdb, __LINE__, "tcbdbvanish");
+          err = true;
+        }
+      }
+      if(myrand(mid) == 0){
+        iprintf("[o]");
+        if(!tcbdboptimize(bdb, -1, -1, myrand(rnum) + 1, myrand(10), myrand(10), 0)){
+          eprint(bdb, __LINE__, "tcbdbvanish");
+          err = true;
+        }
+      }
+      if(myrand(mid) == 0){
+        iprintf("[d]");
+        if(!tcbdbdefrag(bdb, -1)){
+          eprint(bdb, __LINE__, "tcbdbdefrag");
+          err = true;
+        }
+      }
+      if(myrand(mid) == 0){
+        iprintf("[i]");
+        BDBCUR *cur = tcbdbcurnew(bdb);
+        if(!tcbdbcurfirst(cur) && tcbdbecode(bdb) != TCENOREC){
+          eprint(bdb, __LINE__, "tcbdbcurfirst");
+          err = true;
+        }
+        char *kbuf;
+        int ksiz;
+        while((kbuf = tcbdbcurkey(cur, &ksiz)) != NULL){
+          int vsiz;
+          char *vbuf = tcbdbcurval(cur, &vsiz);
+          if(vbuf){
+            tcfree(vbuf);
+          } else if(tcbdbecode(bdb) != TCENOREC){
+            eprint(bdb, __LINE__, "tcbdbget");
+            err = true;
+          }
+          tcfree(kbuf);
+          tcbdbcurnext(cur);
+        }
+        if(tcbdbecode(bdb) != TCENOREC){
+          eprint(bdb, __LINE__, "(validation)");
+          err = true;
+        }
+        tcbdbcurdel(cur);
+      }
+      if(rnum > 250 && i % (rnum / 250) == 0){
+        iputchar('.');
+        if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+      }
+    }
+  }
+  return err ? "error" : NULL;
+}
+
+
+
+// END OF FILE
diff --git a/tcejdb/tcbtest.c b/tcejdb/tcbtest.c
new file mode 100644 (file)
index 0000000..23e007d
--- /dev/null
@@ -0,0 +1,2586 @@
+/*************************************************************************************************
+ * The test cases of the B+ tree database API
+ *                                                               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 <tcbdb.h>
+#include "myconf.h"
+
+#define RECBUFSIZ      48                // buffer for records
+
+
+/* global variables */
+const char *g_progname;                  // program name
+unsigned int g_randseed;                 // random seed
+int g_dbgfd;                             // debugging output
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+static void usage(void);
+static void iprintf(const char *format, ...);
+static void iputchar(int c);
+static void eprint(TCBDB *bdb, int line, const char *func);
+static void mprint(TCBDB *bdb);
+static void sysprint(void);
+static int myrand(int range);
+static void *pdprocfunc(const void *vbuf, int vsiz, int *sp, void *op);
+static bool iterfunc(const void *kbuf, int ksiz, const void *vbuf, int vsiz, void *op);
+static int runwrite(int argc, char **argv);
+static int runread(int argc, char **argv);
+static int runremove(int argc, char **argv);
+static int runrcat(int argc, char **argv);
+static int runqueue(int argc, char **argv);
+static int runmisc(int argc, char **argv);
+static int runwicked(int argc, char **argv);
+static int procwrite(const char *path, int rnum, int lmemb, int nmemb, int bnum,
+                     int apow, int fpow, bool mt, TCCMP cmp, int opts, int lcnum, int ncnum,
+                     int xmsiz, int dfunit, int lsmax, int capnum, int omode, bool rnd);
+static int procread(const char *path, bool mt, TCCMP cmp, int lcnum, int ncnum,
+                    int xmsiz, int dfunit, int omode, bool wb, bool rnd);
+static int procremove(const char *path, bool mt, TCCMP cmp, int lcnum, int ncnum,
+                      int xmsiz, int dfunit, int omode, bool rnd);
+static int procrcat(const char *path, int rnum,
+                    int lmemb, int nmemb, int bnum, int apow, int fpow,
+                    bool mt, TCCMP cmp, int opts, int lcnum, int ncnum, int xmsiz, int dfunit,
+                    int lsmax, int capnum, int omode, int pnum, bool dai, bool dad,
+                    bool rl, bool ru);
+static int procqueue(const char *path, int rnum, int lmemb, int nmemb, int bnum,
+                     int apow, int fpow, bool mt, TCCMP cmp, int opts,
+                     int lcnum, int ncnum, int xmsiz, int dfunit, int lsmax, int capnum,
+                     int omode);
+static int procmisc(const char *path, int rnum, bool mt, int opts, int omode);
+static int procwicked(const char *path, int rnum, bool mt, int opts, int omode);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  g_progname = argv[0];
+  const char *ebuf = getenv("TCRNDSEED");
+  g_randseed = ebuf ? tcatoix(ebuf) : tctime() * 1000;
+  srand(g_randseed);
+  ebuf = getenv("TCDBGFD");
+  g_dbgfd = ebuf ? tcatoix(ebuf) : UINT16_MAX;
+  if(argc < 2) usage();
+  int rv = 0;
+  if(!strcmp(argv[1], "write")){
+    rv = runwrite(argc, argv);
+  } else if(!strcmp(argv[1], "read")){
+    rv = runread(argc, argv);
+  } else if(!strcmp(argv[1], "remove")){
+    rv = runremove(argc, argv);
+  } else if(!strcmp(argv[1], "rcat")){
+    rv = runrcat(argc, argv);
+  } else if(!strcmp(argv[1], "queue")){
+    rv = runqueue(argc, argv);
+  } else if(!strcmp(argv[1], "misc")){
+    rv = runmisc(argc, argv);
+  } else if(!strcmp(argv[1], "wicked")){
+    rv = runwicked(argc, argv);
+  } else {
+    usage();
+  }
+  if(rv != 0){
+    printf("FAILED: TCRNDSEED=%u PID=%d", g_randseed, (int)getpid());
+    for(int i = 0; i < argc; i++){
+      printf(" %s", argv[i]);
+    }
+    printf("\n\n");
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+static void usage(void){
+  fprintf(stderr, "%s: test cases of the B+ tree database API of Tokyo Cabinet\n", g_progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s write [-mt] [-cd|-ci|-cj] [-tl] [-td|-tb|-tt|-tx] [-lc num] [-nc num]"
+          " [-xm num] [-df num] [-ls num] [-ca num] [-nl|-nb] [-rnd] path rnum"
+          " [lmemb [nmemb [bnum [apow [fpow]]]]]\n", g_progname);
+  fprintf(stderr, "  %s read [-mt] [-cd|-ci|-cj] [-lc num] [-nc num] [-xm num] [-df num]"
+          " [-nl|-nb] [-wb] [-rnd] path\n", g_progname);
+  fprintf(stderr, "  %s remove [-mt] [-cd|-ci|-cj] [-lc num] [-nc num] [-xm num] [-df num]"
+          " [-nl|-nb] [-rnd] path\n", g_progname);
+  fprintf(stderr, "  %s rcat [-mt] [-cd|-ci|-cj] [-tl] [-td|-tb|-tt|-tx] [-lc num] [-nc num]"
+          " [-xm num] [-df num] [-ls num] [-ca num] [-nl|-nb] [-pn num] [-dai|-dad|-rl|-ru]"
+          " path rnum [lmemb [nmemb [bnum [apow [fpow]]]]]\n", g_progname);
+  fprintf(stderr, "  %s queue [-mt] [-cd|-ci|-cj] [-tl] [-td|-tb|-tt|-tx] [-lc num] [-nc num]"
+          " [-xm num] [-df num] [-ls num] [-ca num] [-nl|-nb] path rnum"
+          " [lmemb [nmemb [bnum [apow [fpow]]]]]\n", g_progname);
+  fprintf(stderr, "  %s misc [-mt] [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] path rnum\n", g_progname);
+  fprintf(stderr, "  %s wicked [-mt] [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] path rnum\n", g_progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* print formatted information string and flush the buffer */
+static void iprintf(const char *format, ...){
+  va_list ap;
+  va_start(ap, format);
+  vprintf(format, ap);
+  fflush(stdout);
+  va_end(ap);
+}
+
+
+/* print a character and flush the buffer */
+static void iputchar(int c){
+  putchar(c);
+  fflush(stdout);
+}
+
+
+/* print error message of hash database */
+static void eprint(TCBDB *bdb, int line, const char *func){
+  const char *path = tcbdbpath(bdb);
+  int ecode = tcbdbecode(bdb);
+  fprintf(stderr, "%s: %s: %d: %s: error: %d: %s\n",
+          g_progname, path ? path : "-", line, func, ecode, tcbdberrmsg(ecode));
+}
+
+
+/* print members of B+ tree database */
+static void mprint(TCBDB *bdb){
+  if(bdb->hdb->cnt_writerec < 0) return;
+  iprintf("max leaf member: %d\n", tcbdblmemb(bdb));
+  iprintf("max node member: %d\n", tcbdbnmemb(bdb));
+  iprintf("leaf number: %lld\n", (long long)tcbdblnum(bdb));
+  iprintf("node number: %lld\n", (long long)tcbdbnnum(bdb));
+  iprintf("bucket number: %lld\n", (long long)tcbdbbnum(bdb));
+  iprintf("used bucket number: %lld\n", (long long)tcbdbbnumused(bdb));
+  iprintf("cnt_saveleaf: %lld\n", (long long)bdb->cnt_saveleaf);
+  iprintf("cnt_loadleaf: %lld\n", (long long)bdb->cnt_loadleaf);
+  iprintf("cnt_killleaf: %lld\n", (long long)bdb->cnt_killleaf);
+  iprintf("cnt_adjleafc: %lld\n", (long long)bdb->cnt_adjleafc);
+  iprintf("cnt_savenode: %lld\n", (long long)bdb->cnt_savenode);
+  iprintf("cnt_loadnode: %lld\n", (long long)bdb->cnt_loadnode);
+  iprintf("cnt_adjnodec: %lld\n", (long long)bdb->cnt_adjnodec);
+  iprintf("cnt_writerec: %lld\n", (long long)bdb->hdb->cnt_writerec);
+  iprintf("cnt_reuserec: %lld\n", (long long)bdb->hdb->cnt_reuserec);
+  iprintf("cnt_moverec: %lld\n", (long long)bdb->hdb->cnt_moverec);
+  iprintf("cnt_readrec: %lld\n", (long long)bdb->hdb->cnt_readrec);
+  iprintf("cnt_searchfbp: %lld\n", (long long)bdb->hdb->cnt_searchfbp);
+  iprintf("cnt_insertfbp: %lld\n", (long long)bdb->hdb->cnt_insertfbp);
+  iprintf("cnt_splicefbp: %lld\n", (long long)bdb->hdb->cnt_splicefbp);
+  iprintf("cnt_dividefbp: %lld\n", (long long)bdb->hdb->cnt_dividefbp);
+  iprintf("cnt_mergefbp: %lld\n", (long long)bdb->hdb->cnt_mergefbp);
+  iprintf("cnt_reducefbp: %lld\n", (long long)bdb->hdb->cnt_reducefbp);
+  iprintf("cnt_appenddrp: %lld\n", (long long)bdb->hdb->cnt_appenddrp);
+  iprintf("cnt_deferdrp: %lld\n", (long long)bdb->hdb->cnt_deferdrp);
+  iprintf("cnt_flushdrp: %lld\n", (long long)bdb->hdb->cnt_flushdrp);
+  iprintf("cnt_adjrecc: %lld\n", (long long)bdb->hdb->cnt_adjrecc);
+  iprintf("cnt_defrag: %lld\n", (long long)bdb->hdb->cnt_defrag);
+  iprintf("cnt_shiftrec: %lld\n", (long long)bdb->hdb->cnt_shiftrec);
+  iprintf("cnt_trunc: %lld\n", (long long)bdb->hdb->cnt_trunc);
+}
+
+
+/* print system information */
+static void sysprint(void){
+  TCMAP *info = tcsysinfo();
+  if(info){
+    tcmapiterinit(info);
+    const char *kbuf;
+    while((kbuf = tcmapiternext2(info)) != NULL){
+      iprintf("sys_%s: %s\n", kbuf, tcmapiterval2(kbuf));
+    }
+    tcmapdel(info);
+  }
+}
+
+
+/* get a random number */
+static int myrand(int range){
+  if(range < 2) return 0;
+  int high = (unsigned int)rand() >> 4;
+  int low = range * (rand() / (RAND_MAX + 1.0));
+  low &= (unsigned int)INT_MAX >> 4;
+  return (high + low) % range;
+}
+
+
+/* duplication callback function */
+static void *pdprocfunc(const void *vbuf, int vsiz, int *sp, void *op){
+  if(op){
+    char *buf = NULL;
+    int len = 0;
+    switch((int)(intptr_t)op){
+      case 1:
+        len = vsiz + 1;
+        buf = tcmalloc(len + 1);
+        memset(buf, '*', len);
+        break;
+      case 2:
+        buf = (void *)-1;
+        break;
+    }
+    *sp = len;
+    return buf;
+  }
+  if(myrand(4) == 0) return (void *)-1;
+  if(myrand(2) == 0) return NULL;
+  int len = myrand(RECBUFSIZ);
+  char buf[RECBUFSIZ];
+  memset(buf, '*', len);
+  *sp = len;
+  return tcmemdup(buf, len);
+}
+
+
+/* iterator function */
+static bool iterfunc(const void *kbuf, int ksiz, const void *vbuf, int vsiz, void *op){
+  unsigned int sum = 0;
+  while(--ksiz >= 0){
+    sum += ((char *)kbuf)[ksiz];
+  }
+  while(--vsiz >= 0){
+    sum += ((char *)vbuf)[vsiz];
+  }
+  return myrand(100 + (sum & 0xff)) > 0;
+}
+
+
+/* parse arguments of write command */
+static int runwrite(int argc, char **argv){
+  char *path = NULL;
+  char *rstr = NULL;
+  char *lmstr = NULL;
+  char *nmstr = NULL;
+  char *bstr = NULL;
+  char *astr = NULL;
+  char *fstr = NULL;
+  bool mt = false;
+  TCCMP cmp = NULL;
+  int opts = 0;
+  int lcnum = 0;
+  int ncnum = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int lsmax = 0;
+  int capnum = 0;
+  int omode = 0;
+  bool rnd = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-cd")){
+        cmp = tccmpdecimal;
+      } else if(!strcmp(argv[i], "-ci")){
+        cmp = tccmpint32;
+      } else if(!strcmp(argv[i], "-cj")){
+        cmp = tccmpint64;
+      } else if(!strcmp(argv[i], "-tl")){
+        opts |= BDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= BDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= BDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= BDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= BDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-lc")){
+        if(++i >= argc) usage();
+        lcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nc")){
+        if(++i >= argc) usage();
+        ncnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-ls")){
+        if(++i >= argc) usage();
+        lsmax = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-ca")){
+        if(++i >= argc) usage();
+        capnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= BDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= BDBOLCKNB;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else if(!lmstr){
+      lmstr = argv[i];
+    } else if(!nmstr){
+      nmstr = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else if(!astr){
+      astr = argv[i];
+    } else if(!fstr){
+      fstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int lmemb = lmstr ? tcatoix(lmstr) : -1;
+  int nmemb = nmstr ? tcatoix(nmstr) : -1;
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int apow = astr ? tcatoix(astr) : -1;
+  int fpow = fstr ? tcatoix(fstr) : -1;
+  int rv = procwrite(path, rnum, lmemb, nmemb, bnum, apow, fpow,
+                     mt, cmp, opts, lcnum, ncnum, xmsiz, dfunit, lsmax, capnum, omode, rnd);
+  return rv;
+}
+
+
+/* parse arguments of read command */
+static int runread(int argc, char **argv){
+  char *path = NULL;
+  bool mt = false;
+  TCCMP cmp = NULL;
+  int lcnum = 0;
+  int ncnum = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int omode = 0;
+  bool wb = false;
+  bool rnd = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-cd")){
+        cmp = tccmpdecimal;
+      } else if(!strcmp(argv[i], "-ci")){
+        cmp = tccmpint32;
+      } else if(!strcmp(argv[i], "-cj")){
+        cmp = tccmpint64;
+      } else if(!strcmp(argv[i], "-lc")){
+        if(++i >= argc) usage();
+        lcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nc")){
+        if(++i >= argc) usage();
+        ncnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= BDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= BDBOLCKNB;
+      } else if(!strcmp(argv[i], "-wb")){
+        wb = true;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int rv = procread(path, mt, cmp, lcnum, ncnum, xmsiz, dfunit, omode, wb, rnd);
+  return rv;
+}
+
+
+/* parse arguments of remove command */
+static int runremove(int argc, char **argv){
+  char *path = NULL;
+  bool mt = false;
+  TCCMP cmp = NULL;
+  int lcnum = 0;
+  int ncnum = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int omode = 0;
+  bool rnd = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-cd")){
+        cmp = tccmpdecimal;
+      } else if(!strcmp(argv[i], "-ci")){
+        cmp = tccmpint32;
+      } else if(!strcmp(argv[i], "-cj")){
+        cmp = tccmpint64;
+      } else if(!strcmp(argv[i], "-lc")){
+        if(++i >= argc) usage();
+        lcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nc")){
+        if(++i >= argc) usage();
+        ncnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= BDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= BDBOLCKNB;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int rv = procremove(path, mt, cmp, lcnum, ncnum, xmsiz, dfunit, omode, rnd);
+  return rv;
+}
+
+
+/* parse arguments of rcat command */
+static int runrcat(int argc, char **argv){
+  char *path = NULL;
+  char *rstr = NULL;
+  char *lmstr = NULL;
+  char *nmstr = NULL;
+  char *bstr = NULL;
+  char *astr = NULL;
+  char *fstr = NULL;
+  bool mt = false;
+  TCCMP cmp = NULL;
+  int opts = 0;
+  int lcnum = 0;
+  int ncnum = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int lsmax = 0;
+  int capnum = 0;
+  int omode = 0;
+  int pnum = 0;
+  bool dai = false;
+  bool dad = false;
+  bool rl = false;
+  bool ru = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-cd")){
+        cmp = tccmpdecimal;
+      } else if(!strcmp(argv[i], "-ci")){
+        cmp = tccmpint32;
+      } else if(!strcmp(argv[i], "-cj")){
+        cmp = tccmpint64;
+      } else if(!strcmp(argv[i], "-tl")){
+        opts |= BDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= BDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= BDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= BDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= BDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-lc")){
+        if(++i >= argc) usage();
+        lcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nc")){
+        if(++i >= argc) usage();
+        ncnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-ls")){
+        if(++i >= argc) usage();
+        lsmax = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-ca")){
+        if(++i >= argc) usage();
+        capnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= BDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= BDBOLCKNB;
+      } else if(!strcmp(argv[i], "-pn")){
+        if(++i >= argc) usage();
+        pnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-dai")){
+        dai = true;
+      } else if(!strcmp(argv[i], "-dad")){
+        dad = true;
+      } else if(!strcmp(argv[i], "-rl")){
+        rl = true;
+      } else if(!strcmp(argv[i], "-ru")){
+        ru = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else if(!lmstr){
+      lmstr = argv[i];
+    } else if(!nmstr){
+      nmstr = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else if(!astr){
+      astr = argv[i];
+    } else if(!fstr){
+      fstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int lmemb = lmstr ? tcatoix(lmstr) : -1;
+  int nmemb = nmstr ? tcatoix(nmstr) : -1;
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int apow = astr ? tcatoix(astr) : -1;
+  int fpow = fstr ? tcatoix(fstr) : -1;
+  int rv = procrcat(path, rnum, lmemb, nmemb, bnum, apow, fpow, mt, cmp, opts,
+                    lcnum, ncnum, xmsiz, dfunit, lsmax, capnum, omode, pnum, dai, dad, rl, ru);
+  return rv;
+}
+
+
+/* parse arguments of queue command */
+static int runqueue(int argc, char **argv){
+  char *path = NULL;
+  char *rstr = NULL;
+  char *lmstr = NULL;
+  char *nmstr = NULL;
+  char *bstr = NULL;
+  char *astr = NULL;
+  char *fstr = NULL;
+  bool mt = false;
+  TCCMP cmp = NULL;
+  int opts = 0;
+  int lcnum = 0;
+  int ncnum = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int lsmax = 0;
+  int capnum = 0;
+  int omode = 0;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-cd")){
+        cmp = tccmpdecimal;
+      } else if(!strcmp(argv[i], "-ci")){
+        cmp = tccmpint32;
+      } else if(!strcmp(argv[i], "-cj")){
+        cmp = tccmpint64;
+      } else if(!strcmp(argv[i], "-tl")){
+        opts |= BDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= BDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= BDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= BDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= BDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-lc")){
+        if(++i >= argc) usage();
+        lcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nc")){
+        if(++i >= argc) usage();
+        ncnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-ls")){
+        if(++i >= argc) usage();
+        lsmax = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-ca")){
+        if(++i >= argc) usage();
+        capnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= BDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= BDBOLCKNB;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else if(!lmstr){
+      lmstr = argv[i];
+    } else if(!nmstr){
+      nmstr = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else if(!astr){
+      astr = argv[i];
+    } else if(!fstr){
+      fstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int lmemb = lmstr ? tcatoix(lmstr) : -1;
+  int nmemb = nmstr ? tcatoix(nmstr) : -1;
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int apow = astr ? tcatoix(astr) : -1;
+  int fpow = fstr ? tcatoix(fstr) : -1;
+  int rv = procqueue(path, rnum, lmemb, nmemb, bnum, apow, fpow,
+                     mt, cmp, opts, lcnum, ncnum, xmsiz, dfunit, lsmax, capnum, omode);
+  return rv;
+}
+
+
+/* parse arguments of misc command */
+static int runmisc(int argc, char **argv){
+  char *path = NULL;
+  char *rstr = NULL;
+  bool mt = false;
+  int opts = 0;
+  int omode = 0;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-tl")){
+        opts |= BDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= BDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= BDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= BDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= BDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= BDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= BDBOLCKNB;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int rv = procmisc(path, rnum, mt, opts, omode);
+  return rv;
+}
+
+
+/* parse arguments of wicked command */
+static int runwicked(int argc, char **argv){
+  char *path = NULL;
+  char *rstr = NULL;
+  bool mt = false;
+  int opts = 0;
+  int omode = 0;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-tl")){
+        opts |= BDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= BDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= BDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= BDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= BDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= BDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= BDBOLCKNB;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int rv = procwicked(path, rnum, mt, opts, omode);
+  return rv;
+}
+
+
+/* perform write command */
+static int procwrite(const char *path, int rnum, int lmemb, int nmemb, int bnum,
+                     int apow, int fpow, bool mt, TCCMP cmp, int opts, int lcnum, int ncnum,
+                     int xmsiz, int dfunit, int lsmax, int capnum, int omode, bool rnd){
+  iprintf("<Writing Test>\n  seed=%u  path=%s  rnum=%d  lmemb=%d  nmemb=%d  bnum=%d  apow=%d"
+          "  fpow=%d  mt=%d  cmp=%p  opts=%d  lcnum=%d  ncnum=%d  xmsiz=%d  dfunit=%d  lsmax=%d"
+          "  capnum=%d  omode=%d  rnd=%d\n\n",
+          g_randseed, path, rnum, lmemb, nmemb, bnum, apow, fpow, mt, (void *)(intptr_t)cmp,
+          opts, lcnum, ncnum, xmsiz, dfunit, lsmax, capnum, omode, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCBDB *bdb = tcbdbnew();
+  if(g_dbgfd >= 0) tcbdbsetdbgfd(bdb, g_dbgfd);
+  if(mt && !tcbdbsetmutex(bdb)){
+    eprint(bdb, __LINE__, "tcbdbsetmutex");
+    err = true;
+  }
+  if(cmp && !tcbdbsetcmpfunc(bdb, cmp, NULL)){
+    eprint(bdb, __LINE__, "tcbdbsetcmpfunc");
+    err = true;
+  }
+  if(!tcbdbsetcodecfunc(bdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(bdb, __LINE__, "tcbdbsetcodecfunc");
+    err = true;
+  }
+  if(!tcbdbtune(bdb, lmemb, nmemb, bnum, apow, fpow, opts)){
+    eprint(bdb, __LINE__, "tcbdbtune");
+    err = true;
+  }
+  if(!tcbdbsetcache(bdb, lcnum, ncnum)){
+    eprint(bdb, __LINE__, "tcbdbsetcache");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tcbdbsetxmsiz(bdb, xmsiz)){
+    eprint(bdb, __LINE__, "tcbdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tcbdbsetdfunit(bdb, dfunit)){
+    eprint(bdb, __LINE__, "tcbdbsetdfunit");
+    err = true;
+  }
+  if(!tcbdbsetlsmax(bdb, lsmax)){
+    eprint(bdb, __LINE__, "tcbdbsetlsmax");
+    err = true;
+  }
+  if(!tcbdbsetcapnum(bdb, capnum)){
+    eprint(bdb, __LINE__, "tcbdbsetcapnum");
+    err = true;
+  }
+  if(!rnd) omode |= BDBOTRUNC;
+  if(!tcbdbopen(bdb, path, BDBOWRITER | BDBOCREAT | omode)){
+    eprint(bdb, __LINE__, "tcbdbopen");
+    err = true;
+  }
+  for(int i = 1; i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len;
+    if(cmp == tccmpdecimal){
+      len = sprintf(buf, "%d", rnd ? myrand(rnum) + 1 : i);
+    } else if(cmp == tccmpint32){
+      int32_t lnum = rnd ? myrand(rnum) + 1 : i;
+      memcpy(buf, &lnum, sizeof(lnum));
+      len = sizeof(lnum);
+    } else if(cmp == tccmpint64){
+      int64_t llnum = rnd ? myrand(rnum) + 1 : i;
+      memcpy(buf, &llnum, sizeof(llnum));
+      len = sizeof(llnum);
+    } else {
+      len = sprintf(buf, "%08d", rnd ? myrand(rnum) + 1 : i);
+    }
+    if(!tcbdbput(bdb, buf, len, buf, len)){
+      eprint(bdb, __LINE__, "tcbdbput");
+      err = true;
+      break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcbdbrnum(bdb));
+  iprintf("size: %llu\n", (unsigned long long)tcbdbfsiz(bdb));
+  mprint(bdb);
+  sysprint();
+  if(!tcbdbclose(bdb)){
+    eprint(bdb, __LINE__, "tcbdbclose");
+    err = true;
+  }
+  tcbdbdel(bdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform read command */
+static int procread(const char *path, bool mt, TCCMP cmp, int lcnum, int ncnum,
+                    int xmsiz, int dfunit, int omode, bool wb, bool rnd){
+  iprintf("<Reading Test>\n  seed=%u  path=%s  mt=%d  cmp=%p  lcnum=%d  ncnum=%d"
+          "  xmsiz=%d  dfunit=%d  omode=%d  wb=%d  rnd=%d\n\n",
+          g_randseed, path, mt, (void *)(intptr_t)cmp, lcnum, ncnum, xmsiz, dfunit,
+          omode, wb, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCBDB *bdb = tcbdbnew();
+  if(g_dbgfd >= 0) tcbdbsetdbgfd(bdb, g_dbgfd);
+  if(mt && !tcbdbsetmutex(bdb)){
+    eprint(bdb, __LINE__, "tcbdbsetmutex");
+    err = true;
+  }
+  if(cmp && !tcbdbsetcmpfunc(bdb, cmp, NULL)){
+    eprint(bdb, __LINE__, "tcbdbsetcmpfunc");
+    err = true;
+  }
+  if(!tcbdbsetcodecfunc(bdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(bdb, __LINE__, "tcbdbsetcodecfunc");
+    err = true;
+  }
+  if(!tcbdbsetcache(bdb, lcnum, ncnum)){
+    eprint(bdb, __LINE__, "tcbdbsetcache");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tcbdbsetxmsiz(bdb, xmsiz)){
+    eprint(bdb, __LINE__, "tcbdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tcbdbsetdfunit(bdb, dfunit)){
+    eprint(bdb, __LINE__, "tcbdbsetdfunit");
+    err = true;
+  }
+  if(!tcbdbopen(bdb, path, BDBOREADER | omode)){
+    eprint(bdb, __LINE__, "tcbdbopen");
+    err = true;
+  }
+  int rnum = tcbdbrnum(bdb);
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz;
+    if(cmp == tccmpdecimal){
+      ksiz = sprintf(kbuf, "%d", rnd ? myrand(rnum) + 1 : i);
+    } else if(cmp == tccmpint32){
+      int32_t lnum = rnd ? myrand(rnum) + 1 : i;
+      memcpy(kbuf, &lnum, sizeof(lnum));
+      ksiz = sizeof(lnum);
+    } else if(cmp == tccmpint64){
+      int64_t llnum = rnd ? myrand(rnum) + 1 : i;
+      memcpy(kbuf, &llnum, sizeof(llnum));
+      ksiz = sizeof(llnum);
+    } else {
+      ksiz = sprintf(kbuf, "%08d", rnd ? myrand(rnum) + 1 : i);
+    }
+    int vsiz;
+    if(wb){
+      int vsiz;
+      const char *vbuf = tcbdbget3(bdb, kbuf, ksiz, &vsiz);
+      if(!vbuf && !(rnd && tcbdbecode(bdb) == TCENOREC)){
+        eprint(bdb, __LINE__, "tcbdbget3");
+        err = true;
+        break;
+      }
+    } else {
+      char *vbuf = tcbdbget(bdb, kbuf, ksiz, &vsiz);
+      if(!vbuf && !(rnd && tcbdbecode(bdb) == TCENOREC)){
+        eprint(bdb, __LINE__, "tcbdbget");
+        err = true;
+        break;
+      }
+      tcfree(vbuf);
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcbdbrnum(bdb));
+  iprintf("size: %llu\n", (unsigned long long)tcbdbfsiz(bdb));
+  mprint(bdb);
+  sysprint();
+  if(!tcbdbclose(bdb)){
+    eprint(bdb, __LINE__, "tcbdbclose");
+    err = true;
+  }
+  tcbdbdel(bdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform remove command */
+static int procremove(const char *path, bool mt, TCCMP cmp, int lcnum, int ncnum,
+                      int xmsiz, int dfunit, int omode, bool rnd){
+  iprintf("<Removing Test>\n  seed=%u  path=%s  mt=%d  cmp=%p  lcnum=%d  ncnum=%d"
+          "  xmsiz=%d  dfunit=%d  omode=%d  rnd=%d\n\n",
+          g_randseed, path, mt, (void *)(intptr_t)cmp, lcnum, ncnum, xmsiz, dfunit, omode, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCBDB *bdb = tcbdbnew();
+  if(g_dbgfd >= 0) tcbdbsetdbgfd(bdb, g_dbgfd);
+  if(mt && !tcbdbsetmutex(bdb)){
+    eprint(bdb, __LINE__, "tcbdbsetmutex");
+    err = true;
+  }
+  if(cmp && !tcbdbsetcmpfunc(bdb, cmp, NULL)){
+    eprint(bdb, __LINE__, "tcbdbsetcmpfunc");
+    err = true;
+  }
+  if(!tcbdbsetcodecfunc(bdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(bdb, __LINE__, "tcbdbsetcodecfunc");
+    err = true;
+  }
+  if(!tcbdbsetcache(bdb, lcnum, ncnum)){
+    eprint(bdb, __LINE__, "tcbdbsetcache");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tcbdbsetxmsiz(bdb, xmsiz)){
+    eprint(bdb, __LINE__, "tcbdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tcbdbsetdfunit(bdb, dfunit)){
+    eprint(bdb, __LINE__, "tcbdbsetdfunit");
+    err = true;
+  }
+  if(!tcbdbopen(bdb, path, BDBOWRITER | omode)){
+    eprint(bdb, __LINE__, "tcbdbopen");
+    err = true;
+  }
+  int rnum = tcbdbrnum(bdb);
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz;
+    if(cmp == tccmpdecimal){
+      ksiz = sprintf(kbuf, "%d", rnd ? myrand(rnum) + 1 : i);
+    } else if(cmp == tccmpint32){
+      int32_t lnum = rnd ? myrand(rnum) + 1 : i;
+      memcpy(kbuf, &lnum, sizeof(lnum));
+      ksiz = sizeof(lnum);
+    } else if(cmp == tccmpint64){
+      int64_t llnum = rnd ? myrand(rnum) + 1 : i;
+      memcpy(kbuf, &llnum, sizeof(llnum));
+      ksiz = sizeof(llnum);
+    } else {
+      ksiz = sprintf(kbuf, "%08d", rnd ? myrand(rnum) + 1 : i);
+    }
+    if(!tcbdbout(bdb, kbuf, ksiz) && !(rnd && tcbdbecode(bdb) == TCENOREC)){
+      eprint(bdb, __LINE__, "tcbdbout");
+      err = true;
+      break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcbdbrnum(bdb));
+  iprintf("size: %llu\n", (unsigned long long)tcbdbfsiz(bdb));
+  mprint(bdb);
+  sysprint();
+  if(!tcbdbclose(bdb)){
+    eprint(bdb, __LINE__, "tcbdbclose");
+    err = true;
+  }
+  tcbdbdel(bdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform rcat command */
+static int procrcat(const char *path, int rnum,
+                    int lmemb, int nmemb, int bnum, int apow, int fpow,
+                    bool mt, TCCMP cmp, int opts, int lcnum, int ncnum, int xmsiz, int dfunit,
+                    int lsmax, int capnum, int omode, int pnum, bool dai, bool dad,
+                    bool rl, bool ru){
+  iprintf("<Random Concatenating Test>\n"
+          "  seed=%u  path=%s  rnum=%d  lmemb=%d  nmemb=%d  bnum=%d  apow=%d  fpow=%d"
+          "  mt=%d  cmp=%p  opts=%d  lcnum=%d  ncnum=%d  xmsiz=%d  dfunit=%d"
+          "  lsmax=%d  capnum=%d  omode=%d  pnum=%d  dai=%d  dad=%d  rl=%d  ru=%d\n\n",
+          g_randseed, path, rnum, lmemb, nmemb, bnum, apow, fpow, mt, (void *)(intptr_t)cmp,
+          opts, lcnum, ncnum, xmsiz, dfunit, lsmax, capnum, omode, pnum, dai, dad, rl, ru);
+  if(pnum < 1) pnum = rnum;
+  bool err = false;
+  double stime = tctime();
+  TCBDB *bdb = tcbdbnew();
+  if(g_dbgfd >= 0) tcbdbsetdbgfd(bdb, g_dbgfd);
+  if(mt && !tcbdbsetmutex(bdb)){
+    eprint(bdb, __LINE__, "tcbdbsetmutex");
+    err = true;
+  }
+  if(cmp && !tcbdbsetcmpfunc(bdb, cmp, NULL)){
+    eprint(bdb, __LINE__, "tcbdbsetcmpfunc");
+    err = true;
+  }
+  if(!tcbdbsetcodecfunc(bdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(bdb, __LINE__, "tcbdbsetcodecfunc");
+    err = true;
+  }
+  if(!tcbdbtune(bdb, lmemb, nmemb, bnum, apow, fpow, opts)){
+    eprint(bdb, __LINE__, "tcbdbtune");
+    err = true;
+  }
+  if(!tcbdbsetcache(bdb, lcnum, ncnum)){
+    eprint(bdb, __LINE__, "tcbdbsetcache");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tcbdbsetxmsiz(bdb, xmsiz)){
+    eprint(bdb, __LINE__, "tcbdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tcbdbsetdfunit(bdb, dfunit)){
+    eprint(bdb, __LINE__, "tcbdbsetdfunit");
+    err = true;
+  }
+  if(!tcbdbsetlsmax(bdb, lsmax)){
+    eprint(bdb, __LINE__, "tcbdbsetlsmax");
+    err = true;
+  }
+  if(!tcbdbsetcapnum(bdb, capnum)){
+    eprint(bdb, __LINE__, "tcbdbsetcapnum");
+    err = true;
+  }
+  if(!tcbdbopen(bdb, path, BDBOWRITER | BDBOCREAT | BDBOTRUNC | omode)){
+    eprint(bdb, __LINE__, "tcbdbopen");
+    err = true;
+  }
+  for(int i = 1; i <= rnum; i++){
+    if(ru){
+      char fmt[RECBUFSIZ];
+      sprintf(fmt, "%%0%dd", myrand(RECBUFSIZ));
+      char kbuf[RECBUFSIZ];
+      int ksiz = sprintf(kbuf, fmt, myrand(pnum));
+      switch(myrand(10)){
+        case 0:
+          if(!tcbdbput(bdb, kbuf, ksiz, kbuf, ksiz)){
+            eprint(bdb, __LINE__, "tcbdbput");
+            err = true;
+          }
+          break;
+        case 1:
+          if(!tcbdbputkeep(bdb, kbuf, ksiz, kbuf, ksiz) && tcbdbecode(bdb) != TCEKEEP){
+            eprint(bdb, __LINE__, "tcbdbputkeep");
+            err = true;
+          }
+          break;
+        case 2:
+          if(!tcbdbputdup(bdb, kbuf, ksiz, kbuf, ksiz) && tcbdbecode(bdb) != TCEKEEP){
+            eprint(bdb, __LINE__, "tcbdbputkeep");
+            err = true;
+          }
+          break;
+        case 3:
+          if(!tcbdbputdupback(bdb, kbuf, ksiz, kbuf, ksiz) && tcbdbecode(bdb) != TCEKEEP){
+            eprint(bdb, __LINE__, "tcbdbputkeep");
+            err = true;
+          }
+          break;
+        case 4:
+          if(!tcbdbout(bdb, kbuf, ksiz) && tcbdbecode(bdb) != TCENOREC){
+            eprint(bdb, __LINE__, "tcbdbout");
+            err = true;
+          }
+          break;
+        case 5:
+          if(tcbdbaddint(bdb, kbuf, ksiz, 1) == INT_MIN && tcbdbecode(bdb) != TCEKEEP){
+            eprint(bdb, __LINE__, "tcbdbaddint");
+            err = true;
+          }
+          break;
+        case 6:
+          if(isnan(tcbdbadddouble(bdb, kbuf, ksiz, 1.0)) && tcbdbecode(bdb) != TCEKEEP){
+            eprint(bdb, __LINE__, "tcbdbadddouble");
+            err = true;
+          }
+          break;
+        case 7:
+          if(myrand(2) == 0){
+            if(!tcbdbputproc(bdb, kbuf, ksiz, kbuf, ksiz, pdprocfunc, NULL) &&
+               tcbdbecode(bdb) != TCEKEEP){
+              eprint(bdb, __LINE__, "tcbdbputproc");
+              err = true;
+            }
+          } else {
+            if(!tcbdbputproc(bdb, kbuf, ksiz, NULL, 0, pdprocfunc, NULL) &&
+               tcbdbecode(bdb) != TCEKEEP && tcbdbecode(bdb) != TCENOREC){
+              eprint(bdb, __LINE__, "tcbdbputproc");
+              err = true;
+            }
+          }
+          break;
+        default:
+          if(!tcbdbputcat(bdb, kbuf, ksiz, kbuf, ksiz)){
+            eprint(bdb, __LINE__, "tcbdbputcat");
+            err = true;
+          }
+          break;
+      }
+      if(err) break;
+    } else {
+      char kbuf[RECBUFSIZ];
+      int ksiz;
+      if(cmp == tccmpdecimal){
+        ksiz = sprintf(kbuf, "%d", myrand(pnum));
+      } else if(cmp == tccmpint32){
+        int32_t lnum = myrand(pnum);
+        memcpy(kbuf, &lnum, sizeof(lnum));
+        ksiz = sizeof(lnum);
+      } else if(cmp == tccmpint64){
+        int64_t llnum = myrand(pnum);
+        memcpy(kbuf, &llnum, sizeof(llnum));
+        ksiz = sizeof(llnum);
+      } else {
+        ksiz = sprintf(kbuf, "%d", myrand(pnum));
+      }
+      if(dai){
+        if(tcbdbaddint(bdb, kbuf, ksiz, myrand(3)) == INT_MIN){
+          eprint(bdb, __LINE__, "tcbdbaddint");
+          err = true;
+          break;
+        }
+      } else if(dad){
+        if(isnan(tcbdbadddouble(bdb, kbuf, ksiz, myrand(30) / 10.0))){
+          eprint(bdb, __LINE__, "tcbdbadddouble");
+          err = true;
+          break;
+        }
+      } else if(rl){
+        char vbuf[PATH_MAX];
+        int vsiz = myrand(PATH_MAX);
+        for(int j = 0; j < vsiz; j++){
+          vbuf[j] = myrand(0x100);
+        }
+        if(!tcbdbputcat(bdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(bdb, __LINE__, "tcbdbputcat");
+          err = true;
+          break;
+        }
+      } else {
+        if(!tcbdbputcat(bdb, kbuf, ksiz, kbuf, ksiz)){
+          eprint(bdb, __LINE__, "tcbdbputcat");
+          err = true;
+          break;
+        }
+      }
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcbdbrnum(bdb));
+  iprintf("size: %llu\n", (unsigned long long)tcbdbfsiz(bdb));
+  mprint(bdb);
+  sysprint();
+  if(!tcbdbclose(bdb)){
+    eprint(bdb, __LINE__, "tcbdbclose");
+    err = true;
+  }
+  tcbdbdel(bdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform queue command */
+static int procqueue(const char *path, int rnum, int lmemb, int nmemb, int bnum,
+                     int apow, int fpow, bool mt, TCCMP cmp, int opts,
+                     int lcnum, int ncnum, int xmsiz, int dfunit, int lsmax, int capnum,
+                     int omode){
+  iprintf("<Queueing Test>\n  seed=%u  path=%s  rnum=%d  lmemb=%d  nmemb=%d  bnum=%d  apow=%d"
+          "  fpow=%d  mt=%d  cmp=%p  opts=%d  lcnum=%d  ncnum=%d  xmsiz=%d  dfunit=%d"
+          "  lsmax=%d  capnum=%d  omode=%d\n\n",
+          g_randseed, path, rnum, lmemb, nmemb, bnum, apow, fpow, mt, (void *)(intptr_t)cmp,
+          opts, lcnum, ncnum, xmsiz, dfunit, lsmax, capnum, omode);
+  bool err = false;
+  double stime = tctime();
+  TCBDB *bdb = tcbdbnew();
+  if(g_dbgfd >= 0) tcbdbsetdbgfd(bdb, g_dbgfd);
+  if(mt && !tcbdbsetmutex(bdb)){
+    eprint(bdb, __LINE__, "tcbdbsetmutex");
+    err = true;
+  }
+  if(cmp && !tcbdbsetcmpfunc(bdb, cmp, NULL)){
+    eprint(bdb, __LINE__, "tcbdbsetcmpfunc");
+    err = true;
+  }
+  if(!tcbdbsetcodecfunc(bdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(bdb, __LINE__, "tcbdbsetcodecfunc");
+    err = true;
+  }
+  if(!tcbdbtune(bdb, lmemb, nmemb, bnum, apow, fpow, opts)){
+    eprint(bdb, __LINE__, "tcbdbtune");
+    err = true;
+  }
+  if(!tcbdbsetcache(bdb, lcnum, ncnum)){
+    eprint(bdb, __LINE__, "tcbdbsetcache");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tcbdbsetxmsiz(bdb, xmsiz)){
+    eprint(bdb, __LINE__, "tcbdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tcbdbsetdfunit(bdb, dfunit)){
+    eprint(bdb, __LINE__, "tcbdbsetdfunit");
+    err = true;
+  }
+  if(!tcbdbsetlsmax(bdb, lsmax)){
+    eprint(bdb, __LINE__, "tcbdbsetlsmax");
+    err = true;
+  }
+  if(!tcbdbsetcapnum(bdb, capnum)){
+    eprint(bdb, __LINE__, "tcbdbsetcapnum");
+    err = true;
+  }
+  if(!tcbdbopen(bdb, path, BDBOWRITER | BDBOCREAT | BDBOTRUNC)){
+    eprint(bdb, __LINE__, "tcbdbopen");
+    err = true;
+  }
+  int deqfreq = (lmemb > 0) ? lmemb * 10 : 256;
+  BDBCUR *cur = tcbdbcurnew(bdb);
+  for(int i = 1; i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len;
+    if(cmp == tccmpdecimal){
+      len = sprintf(buf, "%d", i);
+    } else if(cmp == tccmpint32){
+      int32_t lnum = i;
+      memcpy(buf, &lnum, sizeof(lnum));
+      len = sizeof(lnum);
+    } else if(cmp == tccmpint64){
+      int64_t llnum = i;
+      memcpy(buf, &llnum, sizeof(llnum));
+      len = sizeof(llnum);
+    } else {
+      len = sprintf(buf, "%08d", i);
+    }
+    if(!tcbdbput(bdb, buf, len, buf, len)){
+      eprint(bdb, __LINE__, "tcbdbput");
+      err = true;
+      break;
+    }
+    if(myrand(deqfreq) == 0){
+      if(!tcbdbcurfirst(cur) && tcbdbecode(bdb) != TCENOREC){
+        eprint(bdb, __LINE__, "tcbdbcurfirst");
+        err = true;
+        break;
+      }
+      int num = myrand(deqfreq * 2 + 1);
+      while(num >= 0){
+        if(tcbdbcurout(cur)){
+          num--;
+        } else {
+          if(tcbdbecode(bdb) != TCENOREC){
+            eprint(bdb, __LINE__, "tcbdbcurout");
+            err = true;
+          }
+          break;
+        }
+      }
+      if(err) break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(!tcbdbcurfirst(cur) && tcbdbecode(bdb) != TCENOREC){
+    eprint(bdb, __LINE__, "tcbdbcurfirst");
+    err = true;
+  }
+  while(true){
+    if(tcbdbcurout(cur)) continue;
+    if(tcbdbecode(bdb) != TCENOREC){
+      eprint(bdb, __LINE__, "tcbdbcurout");
+      err = true;
+    }
+    break;
+  }
+  tcbdbcurdel(cur);
+  iprintf("record number: %llu\n", (unsigned long long)tcbdbrnum(bdb));
+  iprintf("size: %llu\n", (unsigned long long)tcbdbfsiz(bdb));
+  mprint(bdb);
+  sysprint();
+  if(!tcbdbclose(bdb)){
+    eprint(bdb, __LINE__, "tcbdbclose");
+    err = true;
+  }
+  tcbdbdel(bdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform misc command */
+static int procmisc(const char *path, int rnum, bool mt, int opts, int omode){
+  iprintf("<Miscellaneous Test>\n  seed=%u  path=%s  rnum=%d  mt=%d  opts=%d  omode=%d\n\n",
+          g_randseed, path, rnum, mt, opts, omode);
+  bool err = false;
+  double stime = tctime();
+  TCBDB *bdb = tcbdbnew();
+  if(g_dbgfd >= 0) tcbdbsetdbgfd(bdb, g_dbgfd);
+  if(mt && !tcbdbsetmutex(bdb)){
+    eprint(bdb, __LINE__, "tcbdbsetmutex");
+    err = true;
+  }
+  if(!tcbdbsetcodecfunc(bdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(bdb, __LINE__, "tcbdbsetcodecfunc");
+    err = true;
+  }
+  if(!tcbdbtune(bdb, 10, 10, rnum / 50, 100, -1, opts)){
+    eprint(bdb, __LINE__, "tcbdbtune");
+    err = true;
+  }
+  if(!tcbdbsetcache(bdb, 128, 256)){
+    eprint(bdb, __LINE__, "tcbdbsetcache");
+    err = true;
+  }
+  if(!tcbdbsetxmsiz(bdb, rnum)){
+    eprint(bdb, __LINE__, "tcbdbsetxmsiz");
+    err = true;
+  }
+  if(!tcbdbsetdfunit(bdb, 8)){
+    eprint(bdb, __LINE__, "tcbdbsetdfunit");
+    err = true;
+  }
+  if(!tcbdbopen(bdb, path, BDBOWRITER | BDBOCREAT | BDBOTRUNC | omode)){
+    eprint(bdb, __LINE__, "tcbdbopen");
+    err = true;
+  }
+  if(TCUSEPTHREAD){
+    TCBDB *bdbdup = tcbdbnew();
+    if(tcbdbopen(bdbdup, path, BDBOREADER)){
+      eprint(bdb, __LINE__, "(validation)");
+      err = true;
+    } else if(tcbdbecode(bdbdup) != TCETHREAD){
+      eprint(bdb, __LINE__, "(validation)");
+      err = true;
+    }
+    tcbdbdel(bdbdup);
+  }
+  iprintf("writing:\n");
+  for(int i = 1; i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", i);
+    if(!tcbdbputkeep(bdb, buf, len, buf, len)){
+      eprint(bdb, __LINE__, "tcbdbputkeep");
+      err = true;
+      break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("reading:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%08d", i);
+    int vsiz;
+    char *vbuf = tcbdbget(bdb, kbuf, ksiz, &vsiz);
+    if(!vbuf){
+      eprint(bdb, __LINE__, "tcbdbget");
+      err = true;
+      break;
+    } else if(vsiz != ksiz || memcmp(vbuf, kbuf, vsiz)){
+      eprint(bdb, __LINE__, "(validation)");
+      err = true;
+      tcfree(vbuf);
+      break;
+    }
+    tcfree(vbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(tcbdbrnum(bdb) != rnum){
+    eprint(bdb, __LINE__, "(validation)");
+    err = true;
+  }
+  iprintf("random writing:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(rnum));
+    char vbuf[RECBUFSIZ];
+    int vsiz = myrand(RECBUFSIZ);
+    memset(vbuf, '*', vsiz);
+    if(!tcbdbput(bdb, kbuf, ksiz, vbuf, vsiz)){
+      eprint(bdb, __LINE__, "tcbdbput");
+      err = true;
+      break;
+    }
+    int rsiz;
+    char *rbuf = tcbdbget(bdb, kbuf, ksiz, &rsiz);
+    if(!rbuf){
+      eprint(bdb, __LINE__, "tcbdbget");
+      err = true;
+      break;
+    }
+    if(rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+      eprint(bdb, __LINE__, "(validation)");
+      err = true;
+      tcfree(rbuf);
+      break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+    tcfree(rbuf);
+  }
+  iprintf("word writing:\n");
+  const char *words[] = {
+    "a", "A", "bb", "BB", "ccc", "CCC", "dddd", "DDDD", "eeeee", "EEEEEE",
+    "mikio", "hirabayashi", "tokyo", "cabinet", "hyper", "estraier", "19780211", "birth day",
+    "one", "first", "two", "second", "three", "third", "four", "fourth", "five", "fifth",
+    "_[1]_", "uno", "_[2]_", "dos", "_[3]_", "tres", "_[4]_", "cuatro", "_[5]_", "cinco",
+    "[\xe5\xb9\xb3\xe6\x9e\x97\xe5\xb9\xb9\xe9\x9b\x84]", "[\xe9\xa6\xac\xe9\xb9\xbf]", NULL
+  };
+  for(int i = 0; words[i] != NULL; i += 2){
+    const char *kbuf = words[i];
+    int ksiz = strlen(kbuf);
+    const char *vbuf = words[i+1];
+    int vsiz = strlen(vbuf);
+    if(!tcbdbputkeep(bdb, kbuf, ksiz, vbuf, vsiz)){
+      eprint(bdb, __LINE__, "tcbdbputkeep");
+      err = true;
+      break;
+    }
+    if(rnum > 250) iputchar('.');
+  }
+  if(rnum > 250) iprintf(" (%08d)\n", (int)(sizeof(words) / sizeof(*words)));
+  iprintf("random erasing:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(rnum));
+    if(!tcbdbout(bdb, kbuf, ksiz) && tcbdbecode(bdb) != TCENOREC){
+      eprint(bdb, __LINE__, "tcbdbout");
+      err = true;
+      break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("writing:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "[%d]", i);
+    char vbuf[RECBUFSIZ];
+    int vsiz = i % RECBUFSIZ;
+    memset(vbuf, '*', vsiz);
+    if(!tcbdbputkeep(bdb, kbuf, ksiz, vbuf, vsiz)){
+      eprint(bdb, __LINE__, "tcbdbputkeep");
+      err = true;
+      break;
+    }
+    if(vsiz < 1){
+      char tbuf[PATH_MAX];
+      for(int j = 0; j < PATH_MAX; j++){
+        tbuf[j] = myrand(0x100);
+      }
+      if(!tcbdbput(bdb, kbuf, ksiz, tbuf, PATH_MAX)){
+        eprint(bdb, __LINE__, "tcbdbput");
+        err = true;
+        break;
+      }
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("erasing:\n");
+  for(int i = 1; i <= rnum; i++){
+    if(i % 2 == 1){
+      char kbuf[RECBUFSIZ];
+      int ksiz = sprintf(kbuf, "[%d]", i);
+      if(!tcbdbout(bdb, kbuf, ksiz)){
+        eprint(bdb, __LINE__, "tcbdbout");
+        err = true;
+        break;
+      }
+      if(tcbdbout(bdb, kbuf, ksiz) || tcbdbecode(bdb) != TCENOREC){
+        eprint(bdb, __LINE__, "tcbdbout");
+        err = true;
+        break;
+      }
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("random writing and reopening:\n");
+  for(int i = 1; i <= rnum; i++){
+    if(myrand(10) == 0){
+      int ksiz, vsiz;
+      char *kbuf, *vbuf;
+      ksiz = (myrand(5) == 0) ? myrand(UINT16_MAX) : myrand(RECBUFSIZ);
+      kbuf = tcmalloc(ksiz + 1);
+      for(int j = 0; j < ksiz; j++){
+        kbuf[j] = 128 + myrand(128);
+      }
+      vsiz = (myrand(5) == 0) ? myrand(UINT16_MAX) : myrand(RECBUFSIZ);
+      vbuf = tcmalloc(vsiz + 1);
+      for(int j = 0; j < vsiz; j++){
+        vbuf[j] = myrand(256);
+      }
+      switch(myrand(5)){
+        case 0:
+          if(!tcbdbput(bdb, kbuf, ksiz, vbuf, vsiz)){
+            eprint(bdb, __LINE__, "tcbdbput");
+            err = true;
+          }
+          break;
+        case 1:
+          if(!tcbdbputcat(bdb, kbuf, ksiz, vbuf, vsiz)){
+            eprint(bdb, __LINE__, "tcbdbputcat");
+            err = true;
+          }
+          break;
+        case 2:
+          if(!tcbdbputdup(bdb, kbuf, ksiz, vbuf, vsiz)){
+            eprint(bdb, __LINE__, "tcbdbputdup");
+            err = true;
+          }
+          break;
+        case 3:
+          if(!tcbdbputdupback(bdb, kbuf, ksiz, vbuf, vsiz)){
+            eprint(bdb, __LINE__, "tcbdbputdupback");
+            err = true;
+          }
+          break;
+        default:
+          if(!tcbdbout(bdb, kbuf, ksiz) && tcbdbecode(bdb) != TCENOREC){
+            eprint(bdb, __LINE__, "tcbdbout");
+            err = true;
+          }
+          break;
+      }
+      tcfree(vbuf);
+      tcfree(kbuf);
+    } else {
+      char kbuf[RECBUFSIZ];
+      int ksiz = myrand(RECBUFSIZ);
+      memset(kbuf, '@', ksiz);
+      char vbuf[RECBUFSIZ];
+      int vsiz = myrand(RECBUFSIZ);
+      memset(vbuf, '@', vsiz);
+      if(!tcbdbput(bdb, kbuf, ksiz, vbuf, vsiz)){
+        eprint(bdb, __LINE__, "tcbdbputcat");
+        err = true;
+        break;
+      }
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(!tcbdbclose(bdb)){
+    eprint(bdb, __LINE__, "tcbdbclose");
+    err = true;
+  }
+  if(!tcbdbopen(bdb, path, BDBOWRITER | omode)){
+    eprint(bdb, __LINE__, "tcbdbopen");
+    err = true;
+  }
+  iprintf("checking:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "[%d]", i);
+    int vsiz;
+    char *vbuf = tcbdbget(bdb, kbuf, ksiz, &vsiz);
+    if(i % 2 == 0){
+      if(!vbuf){
+        eprint(bdb, __LINE__, "tcbdbget");
+        err = true;
+        break;
+      }
+      if(vsiz != i % RECBUFSIZ && vsiz != PATH_MAX){
+        eprint(bdb, __LINE__, "(validation)");
+        err = true;
+        tcfree(vbuf);
+        break;
+      }
+    } else {
+      if(vbuf || tcbdbecode(bdb) != TCENOREC){
+        eprint(bdb, __LINE__, "(validation)");
+        err = true;
+        tcfree(vbuf);
+        break;
+      }
+    }
+    tcfree(vbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("writing:\n");
+  for(int i = 1; i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", i);
+    if(!tcbdbput(bdb, buf, len, buf, len)){
+      eprint(bdb, __LINE__, "tcbdbput");
+      err = true;
+      break;
+    }
+    if(i % 10 == 0){
+      TCLIST *vals = tclistnew();
+      for(int j = myrand(5) + 1; j >= 0; j--){
+        tclistpush(vals, buf, len);
+      }
+      if(!tcbdbputdup3(bdb, buf, len, vals)){
+        eprint(bdb, __LINE__, "tcbdbput3");
+        err = true;
+        break;
+      }
+      tclistdel(vals);
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("reading:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%08d", i);
+    int vsiz;
+    char *vbuf = tcbdbget(bdb, kbuf, ksiz, &vsiz);
+    if(!vbuf){
+      eprint(bdb, __LINE__, "tcbdbget");
+      err = true;
+      break;
+    } else if(vsiz != ksiz || memcmp(vbuf, kbuf, vsiz)){
+      eprint(bdb, __LINE__, "(validation)");
+      err = true;
+      tcfree(vbuf);
+      break;
+    }
+    tcfree(vbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("checking words:\n");
+  for(int i = 0; words[i] != NULL; i += 2){
+    const char *kbuf = words[i];
+    int ksiz = strlen(kbuf);
+    const char *vbuf = words[i+1];
+    int vsiz = strlen(vbuf);
+    int rsiz;
+    char *rbuf = tcbdbget(bdb, kbuf, ksiz, &rsiz);
+    if(!rbuf){
+      eprint(bdb, __LINE__, "tcbdbget");
+      err = true;
+      break;
+    } else if(rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+      eprint(bdb, __LINE__, "(validation)");
+      err = true;
+      tcfree(rbuf);
+      break;
+    }
+    tcfree(rbuf);
+    if(rnum > 250) iputchar('.');
+  }
+  if(rnum > 250) iprintf(" (%08d)\n", (int)(sizeof(words) / sizeof(*words)));
+  iprintf("checking cursor:\n");
+  BDBCUR *cur = tcbdbcurnew(bdb);
+  int inum = 0;
+  if(!tcbdbcurfirst(cur)){
+    eprint(bdb, __LINE__, "tcbdbcurfirst");
+    err = true;
+  }
+  char *kbuf;
+  int ksiz;
+  for(int i = 1; (kbuf = tcbdbcurkey(cur, &ksiz)) != NULL; i++, inum++){
+    int vsiz;
+    char *vbuf = tcbdbget(bdb, kbuf, ksiz, &vsiz);
+    if(!vbuf){
+      eprint(bdb, __LINE__, "tcbdbget");
+      err = true;
+      tcfree(kbuf);
+      break;
+    }
+    tcfree(vbuf);
+    tcfree(kbuf);
+    tcbdbcurnext(cur);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(rnum > 250) iprintf(" (%08d)\n", inum);
+  if(tcbdbecode(bdb) != TCENOREC || inum != tcbdbrnum(bdb)){
+    eprint(bdb, __LINE__, "(validation)");
+    err = true;
+  }
+  iprintf("cursor updating:\n");
+  if(!tcbdbcurfirst(cur)){
+    eprint(bdb, __LINE__, "tcbdbcurfirst");
+    err = true;
+  }
+  inum = 0;
+  for(int i = 1; !err && (kbuf = tcbdbcurkey(cur, &ksiz)) != NULL; i++, inum++){
+    switch(myrand(6)){
+      case 0:
+        if(!tcbdbputdup(bdb, kbuf, ksiz, "0123456789", 10)){
+          eprint(bdb, __LINE__, "tcbdbputcat");
+          err = true;
+        }
+        break;
+      case 1:
+        if(!tcbdbout(bdb, kbuf, ksiz)){
+          eprint(bdb, __LINE__, "tcbdbout");
+          err = true;
+        }
+        break;
+      case 2:
+        if(!tcbdbcurput(cur, kbuf, ksiz, BDBCPCURRENT)){
+          eprint(bdb, __LINE__, "tcbdbcurput");
+          err = true;
+        }
+        break;
+      case 3:
+        if(!tcbdbcurput(cur, kbuf, ksiz, BDBCPBEFORE)){
+          eprint(bdb, __LINE__, "tcbdbcurput");
+          err = true;
+        }
+        break;
+      case 4:
+        if(!tcbdbcurput(cur, kbuf, ksiz, BDBCPAFTER)){
+          eprint(bdb, __LINE__, "tcbdbcurput");
+          err = true;
+        }
+        break;
+      default:
+        if(!tcbdbcurout(cur) && tcbdbecode(bdb) != TCENOREC){
+          eprint(bdb, __LINE__, "tcbdbcurout");
+          err = true;
+        }
+        break;
+    }
+    tcfree(kbuf);
+    tcbdbcurnext(cur);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(rnum > 250) iprintf(" (%08d)\n", inum);
+  if(tcbdbecode(bdb) != TCENOREC){
+    eprint(bdb, __LINE__, "(validation)");
+    err = true;
+  }
+  if(myrand(10) == 0 && !tcbdbsync(bdb)){
+    eprint(bdb, __LINE__, "tcbdbsync");
+    err = true;
+  }
+  iprintf("cursor updating from empty:\n");
+  tcbdbcurfirst(cur);
+  inum = 0;
+  for(int i = 1; (kbuf = tcbdbcurkey(cur, &ksiz)) != NULL; i++, inum++){
+    tcfree(kbuf);
+    if(!tcbdbcurout(cur) && tcbdbecode(bdb) != TCENOREC){
+      eprint(bdb, __LINE__, "tcbdbcurout");
+      err = true;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(rnum > 250) iprintf(" (%08d)\n", inum);
+  if(tcbdbrnum(bdb) != 0){
+    eprint(bdb, __LINE__, "(validation)");
+    err = true;
+  }
+  if(!tcbdbput2(bdb, "one", "first")){
+    eprint(bdb, __LINE__, "tcbdbput");
+    err = true;
+  }
+  if(!tcbdbcurlast(cur)){
+    eprint(bdb, __LINE__, "tcbdbcurlast");
+    err = true;
+  }
+  if(!tcbdbcurput2(cur, "second", BDBCPCURRENT) || !tcbdbcurput2(cur, "first", BDBCPBEFORE) ||
+     !tcbdbcurput2(cur, "zero", BDBCPBEFORE) || !tcbdbcurput2(cur, "top", BDBCPBEFORE)){
+    eprint(bdb, __LINE__, "tcbdbcurput2");
+    err = true;
+  }
+  if(!tcbdbcurlast(cur)){
+    eprint(bdb, __LINE__, "tcbdbcurlast");
+    err = true;
+  }
+  if(!tcbdbcurput2(cur, "third", BDBCPAFTER) || !tcbdbcurput2(cur, "fourth", BDBCPAFTER) ||
+     !tcbdbcurput2(cur, "end", BDBCPCURRENT) ||  !tcbdbcurput2(cur, "bottom", BDBCPAFTER)){
+    eprint(bdb, __LINE__, "tcbdbcurput2");
+    err = true;
+  }
+  if(!tcbdbvanish(bdb)){
+    eprint(bdb, __LINE__, "tcbdbvanish");
+    err = true;
+  }
+  TCMAP *map = tcmapnew();
+  iprintf("random writing:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(rnum));
+    char vbuf[RECBUFSIZ];
+    int vsiz = sprintf(vbuf, "%d", myrand(rnum));
+    switch(myrand(4)){
+      case 0:
+        if(!tcbdbput(bdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(bdb, __LINE__, "tcbdbput");
+          err = true;
+        }
+        tcmapput(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 1:
+        if(!tcbdbputkeep(bdb, kbuf, ksiz, vbuf, vsiz) && tcbdbecode(bdb) != TCEKEEP){
+          eprint(bdb, __LINE__, "tcbdbputkeep");
+          err = true;
+        }
+        tcmapputkeep(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 2:
+        if(!tcbdbputcat(bdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(bdb, __LINE__, "tcbdbputcat");
+          err = true;
+        }
+        tcmapputcat(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 3:
+        if(!tcbdbout(bdb, kbuf, ksiz) && tcbdbecode(bdb) != TCENOREC){
+          eprint(bdb, __LINE__, "tcbdbout");
+          err = true;
+        }
+        tcmapout(map, kbuf, ksiz);
+        break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(myrand(4) == 0 && !tcbdbdefrag(bdb, 0)){
+    eprint(bdb, __LINE__, "tcbdbdefrag");
+    err = true;
+  }
+  if(myrand(4) == 0 && !tcbdbcacheclear(bdb)){
+    eprint(bdb, __LINE__, "tcbdbcacheclear");
+    err = true;
+  }
+  iprintf("checking transaction commit:\n");
+  if(!tcbdbtranbegin(bdb)){
+    eprint(bdb, __LINE__, "tcbdbtranbegin");
+    err = true;
+  }
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(rnum));
+    char vbuf[RECBUFSIZ];
+    int vsiz = sprintf(vbuf, "[%d]", myrand(rnum));
+    switch(myrand(7)){
+      case 0:
+        if(!tcbdbput(bdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(bdb, __LINE__, "tcbdbput");
+          err = true;
+        }
+        tcmapput(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 1:
+        if(!tcbdbputkeep(bdb, kbuf, ksiz, vbuf, vsiz) && tcbdbecode(bdb) != TCEKEEP){
+          eprint(bdb, __LINE__, "tcbdbputkeep");
+          err = true;
+        }
+        tcmapputkeep(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 2:
+        if(!tcbdbputcat(bdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(bdb, __LINE__, "tcbdbputcat");
+          err = true;
+        }
+        tcmapputcat(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 3:
+        if(tcbdbaddint(bdb, kbuf, ksiz, 1) == INT_MIN && tcbdbecode(bdb) != TCEKEEP){
+          eprint(bdb, __LINE__, "tcbdbaddint");
+          err = true;
+        }
+        tcmapaddint(map, kbuf, ksiz, 1);
+        break;
+      case 4:
+        if(isnan(tcbdbadddouble(bdb, kbuf, ksiz, 1.0)) && tcbdbecode(bdb) != TCEKEEP){
+          eprint(bdb, __LINE__, "tcbdbadddouble");
+          err = true;
+        }
+        tcmapadddouble(map, kbuf, ksiz, 1.0);
+        break;
+      case 5:
+        if(myrand(2) == 0){
+          void *op = (void *)(intptr_t)(myrand(3) + 1);
+          if(!tcbdbputproc(bdb, kbuf, ksiz, vbuf, vsiz, pdprocfunc, op) &&
+             tcbdbecode(bdb) != TCEKEEP){
+            eprint(bdb, __LINE__, "tcbdbputproc");
+            err = true;
+          }
+          tcmapputproc(map, kbuf, ksiz, vbuf, vsiz, pdprocfunc, op);
+        } else {
+          vsiz = myrand(10);
+          void *op = (void *)(intptr_t)(myrand(3) + 1);
+          if(!tcbdbputproc(bdb, kbuf, ksiz, NULL, vsiz, pdprocfunc, op) &&
+             tcbdbecode(bdb) != TCEKEEP && tcbdbecode(bdb) != TCENOREC){
+            eprint(bdb, __LINE__, "tcbdbputproc");
+            err = true;
+          }
+          tcmapputproc(map, kbuf, ksiz, NULL, vsiz, pdprocfunc, op);
+        }
+        break;
+      case 6:
+        if(!tcbdbout(bdb, kbuf, ksiz) && tcbdbecode(bdb) != TCENOREC){
+          eprint(bdb, __LINE__, "tcbdbout");
+          err = true;
+        }
+        tcmapout(map, kbuf, ksiz);
+        break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(!tcbdbtrancommit(bdb)){
+    eprint(bdb, __LINE__, "tcbdbtrancommit");
+    err = true;
+  }
+  iprintf("checking transaction abort:\n");
+  uint64_t ornum = tcbdbrnum(bdb);
+  uint64_t ofsiz = tcbdbfsiz(bdb);
+  if(!tcbdbtranbegin(bdb)){
+    eprint(bdb, __LINE__, "tcbdbtranbegin");
+    err = true;
+  }
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(rnum));
+    char vbuf[RECBUFSIZ];
+    int vsiz = sprintf(vbuf, "((%d))", myrand(rnum));
+    switch(myrand(8)){
+      case 0:
+        if(!tcbdbput(bdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(bdb, __LINE__, "tcbdbput");
+          err = true;
+        }
+        break;
+      case 1:
+        if(!tcbdbputkeep(bdb, kbuf, ksiz, vbuf, vsiz) && tcbdbecode(bdb) != TCEKEEP){
+          eprint(bdb, __LINE__, "tcbdbputkeep");
+          err = true;
+        }
+        break;
+      case 2:
+        if(!tcbdbputcat(bdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(bdb, __LINE__, "tcbdbputcat");
+          err = true;
+        }
+        break;
+      case 3:
+        if(!tcbdbputdup(bdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(bdb, __LINE__, "tcbdbputdup");
+          err = true;
+        }
+        break;
+      case 4:
+        if(tcbdbaddint(bdb, kbuf, ksiz, 1) == INT_MIN && tcbdbecode(bdb) != TCEKEEP){
+          eprint(bdb, __LINE__, "tcbdbaddint");
+          err = true;
+        }
+        break;
+      case 5:
+        if(isnan(tcbdbadddouble(bdb, kbuf, ksiz, 1.0)) && tcbdbecode(bdb) != TCEKEEP){
+          eprint(bdb, __LINE__, "tcbdbadddouble");
+          err = true;
+        }
+        break;
+      case 6:
+        if(myrand(2) == 0){
+          void *op = (void *)(intptr_t)(myrand(3) + 1);
+          if(!tcbdbputproc(bdb, kbuf, ksiz, vbuf, vsiz, pdprocfunc, op) &&
+             tcbdbecode(bdb) != TCEKEEP){
+            eprint(bdb, __LINE__, "tcbdbputproc");
+            err = true;
+          }
+        } else {
+          vsiz = myrand(10);
+          void *op = (void *)(intptr_t)(myrand(3) + 1);
+          if(!tcbdbputproc(bdb, kbuf, ksiz, NULL, vsiz, pdprocfunc, op) &&
+             tcbdbecode(bdb) != TCEKEEP && tcbdbecode(bdb) != TCENOREC){
+            eprint(bdb, __LINE__, "tcbdbputproc");
+            err = true;
+          }
+        }
+        break;
+      case 7:
+        if(!tcbdbout(bdb, kbuf, ksiz) && tcbdbecode(bdb) != TCENOREC){
+          eprint(bdb, __LINE__, "tcbdbout");
+          err = true;
+        }
+        break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(!tcbdbtranabort(bdb)){
+    eprint(bdb, __LINE__, "tcbdbtranabort");
+    err = true;
+  }
+  iprintf("checking consistency:\n");
+  if(tcbdbrnum(bdb) != ornum || tcbdbfsiz(bdb) != ofsiz || tcbdbrnum(bdb) != tcmaprnum(map)){
+    eprint(bdb, __LINE__, "(validation)");
+    err = true;
+  }
+  inum = 0;
+  tcmapiterinit(map);
+  const char *tkbuf;
+  int tksiz;
+  for(int i = 1; (tkbuf = tcmapiternext(map, &tksiz)) != NULL; i++, inum++){
+    int tvsiz;
+    const char *tvbuf = tcmapiterval(tkbuf, &tvsiz);
+    int rsiz;
+    char *rbuf = tcbdbget(bdb, tkbuf, tksiz, &rsiz);
+    if(!rbuf || rsiz != tvsiz || memcmp(rbuf, tvbuf, rsiz)){
+      eprint(bdb, __LINE__, "(validation)");
+      err = true;
+      tcfree(rbuf);
+      break;
+    }
+    tcfree(rbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(rnum > 250) iprintf(" (%08d)\n", inum);
+  inum = 0;
+  if(!tcbdbcurfirst(cur) && tcbdbecode(bdb) != TCENOREC){
+    eprint(bdb, __LINE__, "tcbdbcurfirst");
+    err = true;
+  }
+  for(int i = 1; (kbuf = tcbdbcurkey(cur, &ksiz)) != NULL; i++, inum++){
+    int vsiz;
+    char *vbuf = tcbdbcurval(cur, &vsiz);
+    int rsiz;
+    const char *rbuf = tcmapget(map, kbuf, ksiz, &rsiz);
+    if(!rbuf || rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+      eprint(bdb, __LINE__, "(validation)");
+      err = true;
+      tcfree(vbuf);
+      tcfree(kbuf);
+      break;
+    }
+    tcfree(vbuf);
+    tcfree(kbuf);
+    tcbdbcurnext(cur);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(rnum > 250) iprintf(" (%08d)\n", inum);
+  tcmapdel(map);
+  if(!tcbdbvanish(bdb)){
+    eprint(bdb, __LINE__, "tcbdbvanish");
+    err = true;
+  }
+  if(!tcbdbtranbegin(bdb)){
+    eprint(bdb, __LINE__, "tcbdbtranbegin");
+    err = true;
+  }
+  if(!tcbdbput2(bdb, "mikio", "hirabayashi")){
+    eprint(bdb, __LINE__, "tcbdbput2");
+    err = true;
+  }
+  for(int i = 0; i < 10; i++){
+    char buf[RECBUFSIZ];
+    int size = sprintf(buf, "%d", myrand(rnum));
+    if(!tcbdbput(bdb, buf, size, buf, size)){
+      eprint(bdb, __LINE__, "tcbdbput");
+      err = true;
+    }
+  }
+  for(int i = myrand(3) + 1; i < PATH_MAX; i = i * 2 + myrand(3)){
+    char vbuf[i];
+    memset(vbuf, '@', i - 1);
+    vbuf[i-1] = '\0';
+    if(!tcbdbput2(bdb, "mikio", vbuf)){
+      eprint(bdb, __LINE__, "tcbdbput2");
+      err = true;
+    }
+  }
+  if(!tcbdbforeach(bdb, iterfunc, NULL)){
+    eprint(bdb, __LINE__, "tcbdbforeach");
+    err = true;
+  }
+  tcbdbcurdel(cur);
+  iprintf("record number: %llu\n", (unsigned long long)tcbdbrnum(bdb));
+  iprintf("size: %llu\n", (unsigned long long)tcbdbfsiz(bdb));
+  mprint(bdb);
+  sysprint();
+  if(!tcbdbclose(bdb)){
+    eprint(bdb, __LINE__, "tcbdbclose");
+    err = true;
+  }
+  tcbdbdel(bdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform wicked command */
+static int procwicked(const char *path, int rnum, bool mt, int opts, int omode){
+  iprintf("<Wicked Writing Test>\n  seed=%u  path=%s  rnum=%d  mt=%d  opts=%d  omode=%d\n\n",
+          g_randseed, path, rnum, mt, opts, omode);
+  bool err = false;
+  double stime = tctime();
+  TCBDB *bdb = tcbdbnew();
+  if(g_dbgfd >= 0) tcbdbsetdbgfd(bdb, g_dbgfd);
+  if(mt && !tcbdbsetmutex(bdb)){
+    eprint(bdb, __LINE__, "tcbdbsetmutex");
+    err = true;
+  }
+  if(!tcbdbsetcodecfunc(bdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(bdb, __LINE__, "tcbdbsetcodecfunc");
+    err = true;
+  }
+  if(!tcbdbtune(bdb, 10, 10, rnum / 50, 100, -1, opts)){
+    eprint(bdb, __LINE__, "tcbdbtune");
+    err = true;
+  }
+  if(!tcbdbsetcache(bdb, 128, 256)){
+    eprint(bdb, __LINE__, "tcbdbsetcache");
+    err = true;
+  }
+  if(!tcbdbsetxmsiz(bdb, rnum)){
+    eprint(bdb, __LINE__, "tcbdbsetxmsiz");
+    err = true;
+  }
+  if(!tcbdbsetdfunit(bdb, 8)){
+    eprint(bdb, __LINE__, "tcbdbsetdfunit");
+    err = true;
+  }
+  if(!tcbdbopen(bdb, path, BDBOWRITER | BDBOCREAT | BDBOTRUNC | omode)){
+    eprint(bdb, __LINE__, "tcbdbopen");
+    err = true;
+  }
+  BDBCUR *cur = tcbdbcurnew(bdb);
+  if(!tcbdbcurfirst(cur) && tcbdbecode(bdb) != TCENOREC){
+    eprint(bdb, __LINE__, "tcbdbcurfirst");
+    err = true;
+  }
+  TCMAP *map = tcmapnew2(rnum / 5);
+  for(int i = 1; i <= rnum && !err; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(rnum));
+    char vbuf[RECBUFSIZ];
+    int vsiz = myrand(RECBUFSIZ);
+    memset(vbuf, '*', vsiz);
+    vbuf[vsiz] = '\0';
+    char *rbuf;
+    switch(myrand(16)){
+      case 0:
+        iputchar('0');
+        if(!tcbdbput(bdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(bdb, __LINE__, "tcbdbput");
+          err = true;
+        }
+        tcmapput(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 1:
+        iputchar('1');
+        if(!tcbdbput2(bdb, kbuf, vbuf)){
+          eprint(bdb, __LINE__, "tcbdbput2");
+          err = true;
+        }
+        tcmapput2(map, kbuf, vbuf);
+        break;
+      case 2:
+        iputchar('2');
+        if(!tcbdbputkeep(bdb, kbuf, ksiz, vbuf, vsiz) && tcbdbecode(bdb) != TCEKEEP){
+          eprint(bdb, __LINE__, "tcbdbputkeep");
+          err = true;
+        }
+        tcmapputkeep(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 3:
+        iputchar('3');
+        if(!tcbdbputkeep2(bdb, kbuf, vbuf) && tcbdbecode(bdb) != TCEKEEP){
+          eprint(bdb, __LINE__, "tcbdbputkeep2");
+          err = true;
+        }
+        tcmapputkeep2(map, kbuf, vbuf);
+        break;
+      case 4:
+        iputchar('4');
+        if(!tcbdbputcat(bdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(bdb, __LINE__, "tcbdbputcat");
+          err = true;
+        }
+        tcmapputcat(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 5:
+        iputchar('5');
+        if(!tcbdbputcat2(bdb, kbuf, vbuf)){
+          eprint(bdb, __LINE__, "tcbdbputcat2");
+          err = true;
+        }
+        tcmapputcat2(map, kbuf, vbuf);
+        break;
+      case 6:
+        iputchar('6');
+        if(myrand(10) == 0){
+          if(!tcbdbout(bdb, kbuf, ksiz) && tcbdbecode(bdb) != TCENOREC){
+            eprint(bdb, __LINE__, "tcbdbout");
+            err = true;
+          }
+          tcmapout(map, kbuf, ksiz);
+        }
+        break;
+      case 7:
+        iputchar('7');
+        if(myrand(10) == 0){
+          if(!tcbdbout2(bdb, kbuf) && tcbdbecode(bdb) != TCENOREC){
+            eprint(bdb, __LINE__, "tcbdbout2");
+            err = true;
+          }
+          tcmapout2(map, kbuf);
+        }
+        break;
+      case 8:
+        iputchar('8');
+        if(!(rbuf = tcbdbget(bdb, kbuf, ksiz, &vsiz))){
+          if(tcbdbecode(bdb) != TCENOREC){
+            eprint(bdb, __LINE__, "tcbdbget");
+            err = true;
+          }
+          rbuf = tcsprintf("[%d]", myrand(i + 1));
+          vsiz = strlen(rbuf);
+        }
+        vsiz += myrand(vsiz);
+        if(myrand(3) == 0) vsiz += PATH_MAX;
+        rbuf = tcrealloc(rbuf, vsiz + 1);
+        for(int j = 0; j < vsiz; j++){
+          rbuf[j] = myrand(0x100);
+        }
+        if(!tcbdbput(bdb, kbuf, ksiz, rbuf, vsiz)){
+          eprint(bdb, __LINE__, "tcbdbput");
+          err = true;
+        }
+        tcmapput(map, kbuf, ksiz, rbuf, vsiz);
+        tcfree(rbuf);
+        break;
+      case 9:
+        iputchar('9');
+        if(!(rbuf = tcbdbget(bdb, kbuf, ksiz, &vsiz)) && tcbdbecode(bdb) != TCENOREC){
+          eprint(bdb, __LINE__, "tcbdbget");
+          err = true;
+        }
+        tcfree(rbuf);
+        break;
+      case 10:
+        iputchar('A');
+        if(!(rbuf = tcbdbget2(bdb, kbuf)) && tcbdbecode(bdb) != TCENOREC){
+          eprint(bdb, __LINE__, "tcbdbget2");
+          err = true;
+        }
+        tcfree(rbuf);
+        break;
+      case 11:
+        iputchar('B');
+        if(myrand(1) == 0) vsiz = 1;
+        if(!tcbdbget3(bdb, kbuf, ksiz, &vsiz) && tcbdbecode(bdb) != TCENOREC){
+          eprint(bdb, __LINE__, "tcbdbget3");
+          err = true;
+        }
+        break;
+      case 12:
+        iputchar('C');
+        if(myrand(rnum / 50) == 0){
+          if(!tcbdbcurfirst(cur) && tcbdbecode(bdb) != TCENOREC){
+            eprint(bdb, __LINE__, "tcbdbcurfirst");
+            err = true;
+          }
+        }
+        TCXSTR *ikey = tcxstrnew();
+        TCXSTR *ival = tcxstrnew();
+        for(int j = myrand(rnum) / 1000 + 1; j >= 0; j--){
+          if(j % 3 == 0){
+            if(tcbdbcurrec(cur, ikey, ival)){
+              if(tcbdbvnum(bdb, tcxstrptr(ikey), tcxstrsize(ikey)) != 1){
+                eprint(bdb, __LINE__, "(validation)");
+                err = true;
+              }
+              if(tcxstrsize(ival) != tcbdbvsiz(bdb, tcxstrptr(ikey), tcxstrsize(ikey))){
+                eprint(bdb, __LINE__, "(validation)");
+                err = true;
+              }
+            } else {
+              int ecode = tcbdbecode(bdb);
+              if(ecode != TCEINVALID && ecode != TCENOREC){
+                eprint(bdb, __LINE__, "tcbdbcurrec");
+                err = true;
+              }
+            }
+          } else {
+            int iksiz;
+            char *ikbuf = tcbdbcurkey(cur, &iksiz);
+            if(ikbuf){
+              tcfree(ikbuf);
+            } else {
+              int ecode = tcbdbecode(bdb);
+              if(ecode != TCEINVALID && ecode != TCENOREC){
+                eprint(bdb, __LINE__, "tcbdbcurkey");
+                err = true;
+              }
+            }
+          }
+          tcbdbcurnext(cur);
+        }
+        tcxstrdel(ival);
+        tcxstrdel(ikey);
+        break;
+      default:
+        iputchar('@');
+        if(myrand(10000) == 0) srand((unsigned int)(tctime() * 1000) % UINT_MAX);
+        if(myrand(rnum / 32 + 1) == 0){
+          if(!tcbdbcurfirst(cur) && tcbdbecode(bdb) != TCENOREC){
+            eprint(bdb, __LINE__, "tcbdbcurfirst");
+            err = true;
+          }
+          int cnt = myrand(30);
+          for(int j = 0; j < rnum && !err; j++){
+            ksiz = sprintf(kbuf, "%d", i + j);
+            if(myrand(4) == 0){
+              if(tcbdbout3(bdb, kbuf, ksiz)){
+                cnt--;
+              } else if(tcbdbecode(bdb) != TCENOREC){
+                eprint(bdb, __LINE__, "tcbdbout3");
+                err = true;
+              }
+              tcmapout(map, kbuf, ksiz);
+            } else if(myrand(30) == 0){
+              int tksiz;
+              char *tkbuf = tcbdbcurkey(cur, &tksiz);
+              if(tkbuf){
+                if(tcbdbcurout(cur)){
+                  cnt--;
+                } else {
+                  eprint(bdb, __LINE__, "tcbdbcurout");
+                  err = true;
+                }
+                tcmapout(map, tkbuf, tksiz);
+                tcfree(tkbuf);
+              } else if(tcbdbecode(bdb) != TCENOREC){
+                eprint(bdb, __LINE__, "tcbdbcurkey");
+                err = true;
+              }
+            } else {
+              if(tcbdbout(bdb, kbuf, ksiz)){
+                cnt--;
+              } else if(tcbdbecode(bdb) != TCENOREC){
+                eprint(bdb, __LINE__, "tcbdbout");
+                err = true;
+              }
+              tcmapout(map, kbuf, ksiz);
+            }
+            if(cnt < 0) break;
+          }
+        }
+        break;
+    }
+    if(i % 50 == 0) iprintf(" (%08d)\n", i);
+    if(i == rnum / 2){
+      if(!tcbdbclose(bdb)){
+        eprint(bdb, __LINE__, "tcbdbclose");
+        err = true;
+      }
+      if(!tcbdbopen(bdb, path, BDBOWRITER | omode)){
+        eprint(bdb, __LINE__, "tcbdbopen");
+        err = true;
+      }
+    } else if(i == rnum / 4){
+      char *npath = tcsprintf("%s-tmp", path);
+      if(!tcbdbcopy(bdb, npath)){
+        eprint(bdb, __LINE__, "tcbdbcopy");
+        err = true;
+      }
+      TCBDB *nbdb = tcbdbnew();
+      if(!tcbdbsetcodecfunc(nbdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+        eprint(nbdb, __LINE__, "tcbdbsetcodecfunc");
+        err = true;
+      }
+      if(!tcbdbopen(nbdb, npath, BDBOREADER | omode)){
+        eprint(nbdb, __LINE__, "tcbdbopen");
+        err = true;
+      }
+      tcbdbdel(nbdb);
+      unlink(npath);
+      tcfree(npath);
+      if(!tcbdboptimize(bdb, -1, -1, -1, -1, -1, -1)){
+        eprint(bdb, __LINE__, "tcbdboptimize");
+        err = true;
+      }
+      if(!tcbdbcurfirst(cur) && tcbdbecode(bdb) != TCENOREC){
+        eprint(bdb, __LINE__, "tcbdbcurfirst");
+        err = true;
+      }
+    } else if(i == rnum / 8){
+      if(!tcbdbtranbegin(bdb)){
+        eprint(bdb, __LINE__, "tcbdbtranbegin");
+        err = true;
+      }
+    } else if(i == rnum / 8 + rnum / 16){
+      if(!tcbdbtrancommit(bdb)){
+        eprint(bdb, __LINE__, "tcbdbtrancommit");
+        err = true;
+      }
+    }
+  }
+  if(rnum % 50 > 0) iprintf(" (%08d)\n", rnum);
+  if(!tcbdbsync(bdb)){
+    eprint(bdb, __LINE__, "tcbdbsync");
+    err = true;
+  }
+  if(tcbdbrnum(bdb) != tcmaprnum(map)){
+    eprint(bdb, __LINE__, "(validation)");
+    err = true;
+  }
+  for(int i = 1; i <= rnum && !err; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", i - 1);
+    int vsiz;
+    const char *vbuf = tcmapget(map, kbuf, ksiz, &vsiz);
+    int rsiz;
+    char *rbuf = tcbdbget(bdb, kbuf, ksiz, &rsiz);
+    if(vbuf){
+      iputchar('.');
+      if(!rbuf){
+        eprint(bdb, __LINE__, "tcbdbget");
+        err = true;
+      } else if(rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+        eprint(bdb, __LINE__, "(validation)");
+        err = true;
+      }
+    } else {
+      iputchar('*');
+      if(rbuf || tcbdbecode(bdb) != TCENOREC){
+        eprint(bdb, __LINE__, "(validation)");
+        err = true;
+      }
+    }
+    tcfree(rbuf);
+    if(i % 50 == 0) iprintf(" (%08d)\n", i);
+  }
+  if(rnum % 50 > 0) iprintf(" (%08d)\n", rnum);
+  tcmapiterinit(map);
+  int ksiz;
+  const char *kbuf;
+  for(int i = 1; (kbuf = tcmapiternext(map, &ksiz)) != NULL; i++){
+    iputchar('+');
+    int vsiz;
+    const char *vbuf = tcmapiterval(kbuf, &vsiz);
+    int rsiz;
+    char *rbuf = tcbdbget(bdb, kbuf, ksiz, &rsiz);
+    if(!rbuf){
+      eprint(bdb, __LINE__, "tcbdbget");
+      err = true;
+    } else if(rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+      eprint(bdb, __LINE__, "(validation)");
+      err = true;
+    }
+    tcfree(rbuf);
+    if(!tcbdbout(bdb, kbuf, ksiz)){
+      eprint(bdb, __LINE__, "tcbdbout");
+      err = true;
+    }
+    if(i % 50 == 0) iprintf(" (%08d)\n", i);
+  }
+  int mrnum = tcmaprnum(map);
+  if(mrnum % 50 > 0) iprintf(" (%08d)\n", mrnum);
+  if(tcbdbrnum(bdb) != 0){
+    eprint(bdb, __LINE__, "(validation)");
+    err = true;
+  }
+  tcbdbcurdel(cur);
+  iprintf("record number: %llu\n", (unsigned long long)tcbdbrnum(bdb));
+  iprintf("size: %llu\n", (unsigned long long)tcbdbfsiz(bdb));
+  mprint(bdb);
+  sysprint();
+  tcmapdel(map);
+  if(!tcbdbclose(bdb)){
+    eprint(bdb, __LINE__, "tcbdbclose");
+    err = true;
+  }
+  tcbdbdel(bdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+
+// END OF FILE
diff --git a/tcejdb/tcejdb.iml b/tcejdb/tcejdb.iml
new file mode 100644 (file)
index 0000000..b3e688b
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$/../docs" />
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/nbproject" />
+    </content>
+    <orderEntry type="jdk" jdkName="1.7" jdkType="JavaSDK" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>
+
diff --git a/tcejdb/tcejdb.pc.in b/tcejdb/tcejdb.pc.in
new file mode 100644 (file)
index 0000000..d3dbcfa
--- /dev/null
@@ -0,0 +1,14 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+datarootdir = @datarootdir@
+bindir=@bindir@
+libdir=@libdir@
+libexecdir=@libexecdir@
+includedir=@includedir@
+datadir=@datadir@
+
+Name: Tokyo Cabinet EJDB edition
+Description: a modern implementation of DBM
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -ltcejdb @LIBS@
+Cflags: -I${includedir}
diff --git a/tcejdb/tcfdb.c b/tcejdb/tcfdb.c
new file mode 100644 (file)
index 0000000..4f70894
--- /dev/null
@@ -0,0 +1,2746 @@
+/*************************************************************************************************
+ * 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
diff --git a/tcejdb/tcfdb.h b/tcejdb/tcfdb.h
new file mode 100644 (file)
index 0000000..3dd3fa4
--- /dev/null
@@ -0,0 +1,858 @@
+/*************************************************************************************************
+ * 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.
+ *************************************************************************************************/
+
+
+#ifndef _TCFDB_H                         /* duplication check */
+#define _TCFDB_H
+
+#if defined(__cplusplus)
+#define __TCFDB_CLINKAGEBEGIN extern "C" {
+#define __TCFDB_CLINKAGEEND }
+#else
+#define __TCFDB_CLINKAGEBEGIN
+#define __TCFDB_CLINKAGEEND
+#endif
+__TCFDB_CLINKAGEBEGIN
+
+
+#include <tcutil.h>
+
+
+
+/*************************************************************************************************
+ * API
+ *************************************************************************************************/
+
+
+typedef struct {                         /* type of structure for a fixed-length database */
+  void *mmtx;                            /* mutex for method */
+  void *amtx;                            /* mutex for attribute */
+  void *rmtxs;                           /* mutexes for records */
+  void *tmtx;                            /* mutex for transaction */
+  void *wmtx;                            /* mutex for write ahead logging */
+  void *eckey;                           /* key for thread specific error code */
+  char *rpath;                           /* real path for locking */
+  uint8_t type;                          /* database type */
+  uint8_t flags;                         /* additional flags */
+  uint32_t width;                        /* width of the value of each record */
+  uint64_t limsiz;                       /* limit size of the file */
+  int wsiz;                              /* size of the width region */
+  int rsiz;                              /* size of each record */
+  uint64_t limid;                        /* limit ID number */
+  char *path;                            /* path of the database file */
+  int fd;                                /* file descriptor of the database file */
+  uint32_t omode;                        /* open mode */
+  uint64_t rnum;                         /* number of the records */
+  uint64_t fsiz;                         /* size of the database file */
+  uint64_t min;                          /* minimum ID number */
+  uint64_t max;                          /* maximum ID number */
+  uint64_t iter;                         /* ID number of the iterator */
+  char *map;                             /* pointer to the mapped memory */
+  unsigned char *array;                  /* pointer to the array region */
+  int ecode;                             /* last happened error code */
+  bool fatal;                            /* whether a fatal error occured */
+  uint64_t inode;                        /* inode number */
+  time_t mtime;                          /* modification time */
+  bool tran;                             /* whether in the transaction */
+  int walfd;                             /* file descriptor of write ahead logging */
+  uint64_t walend;                       /* end offset of write ahead logging */
+  int dbgfd;                             /* file descriptor for debugging */
+  int64_t cnt_writerec;                  /* tesing counter for record write times */
+  int64_t cnt_readrec;                   /* tesing counter for record read times */
+  int64_t cnt_truncfile;                 /* tesing counter for file truncate times */
+} TCFDB;
+
+enum {                                   /* enumeration for additional flags */
+  FDBFOPEN = 1 << 0,                     /* whether opened */
+  FDBFFATAL = 1 << 1                     /* whether with fatal error */
+};
+
+enum {                                   /* enumeration for open modes */
+  FDBOREADER = 1 << 0,                   /* open as a reader */
+  FDBOWRITER = 1 << 1,                   /* open as a writer */
+  FDBOCREAT = 1 << 2,                    /* writer creating */
+  FDBOTRUNC = 1 << 3,                    /* writer truncating */
+  FDBONOLCK = 1 << 4,                    /* open without locking */
+  FDBOLCKNB = 1 << 5,                    /* lock without blocking */
+  FDBOTSYNC = 1 << 6                     /* synchronize every transaction */
+};
+
+enum {                                   /* enumeration for ID constants */
+  FDBIDMIN = -1,                         /* minimum number */
+  FDBIDPREV = -2,                        /* less by one than the minimum */
+  FDBIDMAX = -3,                         /* maximum number */
+  FDBIDNEXT = -4                         /* greater by one than the miximum */
+};
+
+
+/* Get the message string corresponding to an error code.
+   `ecode' specifies the error code.
+   The return value is the message string of the error code. */
+const char *tcfdberrmsg(int ecode);
+
+
+/* Create a fixed-length database object.
+   The return value is the new fixed-length database object. */
+TCFDB *tcfdbnew(void);
+
+
+/* Delete a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   If the database is not closed, it is closed implicitly.  Note that the deleted object and its
+   derivatives can not be used anymore. */
+void tcfdbdel(TCFDB *fdb);
+
+
+/* Get the last happened error code of a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   The return value is the last happened error code.
+   The following error codes are defined: `TCESUCCESS' for success, `TCETHREAD' for threading
+   error, `TCEINVALID' for invalid operation, `TCENOFILE' for file not found, `TCENOPERM' for no
+   permission, `TCEMETA' for invalid meta data, `TCERHEAD' for invalid record header, `TCEOPEN'
+   for open error, `TCECLOSE' for close error, `TCETRUNC' for trunc error, `TCESYNC' for sync
+   error, `TCESTAT' for stat error, `TCESEEK' for seek error, `TCEREAD' for read error,
+   `TCEWRITE' for write error, `TCEMMAP' for mmap error, `TCELOCK' for lock error, `TCEUNLINK'
+   for unlink error, `TCERENAME' for rename error, `TCEMKDIR' for mkdir error, `TCERMDIR' for
+   rmdir error, `TCEKEEP' for existing record, `TCENOREC' for no record found, and `TCEMISC' for
+   miscellaneous error. */
+int tcfdbecode(TCFDB *fdb);
+
+
+/* Set mutual exclusion control of a fixed-length database object for threading.
+   `fdb' specifies the fixed-length database object which is not opened.
+   If successful, the return value is true, else, it is false.
+   Note that the mutual exclusion control is needed if the object is shared by plural threads and
+   this function should be called before the database is opened. */
+bool tcfdbsetmutex(TCFDB *fdb);
+
+
+/* Set the tuning parameters of a fixed-length database object.
+   `fdb' specifies the fixed-length database object which is not opened.
+   `width' specifies the width of the value of each record.  If it is not more than 0, the
+   default value is specified.  The default value is 255.
+   `limsiz' specifies the limit size of the database file.  If it is not more than 0, the default
+   value is specified.  The default value is 268435456.
+   If successful, the return value is true, else, it is false.
+   Note that the tuning parameters should be set before the database is opened. */
+bool tcfdbtune(TCFDB *fdb, int32_t width, int64_t limsiz);
+
+
+/* Open a database file and connect a fixed-length database object.
+   `fdb' specifies the fixed-length database object which is not opened.
+   `path' specifies the path of the database file.
+   `omode' specifies the connection mode: `FDBOWRITER' as a writer, `FDBOREADER' as a reader.
+   If the mode is `FDBOWRITER', the following may be added by bitwise-or: `FDBOCREAT', which
+   means it creates a new database if not exist, `FDBOTRUNC', which means it creates a new
+   database regardless if one exists, `FDBOTSYNC', which means every transaction synchronizes
+   updated contents with the device.  Both of `FDBOREADER' and `FDBOWRITER' can be added to by
+   bitwise-or: `FDBONOLCK', which means it opens the database file without file locking, or
+   `FDBOLCKNB', which means locking is performed without blocking.
+   If successful, the return value is true, else, it is false. */
+bool tcfdbopen(TCFDB *fdb, const char *path, int omode);
+
+
+/* Close a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   If successful, the return value is true, else, it is false.
+   Update of a database is assured to be written when the database is closed.  If a writer opens
+   a database but does not close it appropriately, the database will be broken. */
+bool tcfdbclose(TCFDB *fdb);
+
+
+/* Store a record into a fixed-length database object.
+   `fdb' specifies the fixed-length database object connected as a writer.
+   `id' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID
+   number of existing records is specified.  If it is `FDBIDPREV', the number less by one than
+   the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID
+   number of existing records is specified.  If it is `FDBIDNEXT', the number greater by one than
+   the maximum ID number of existing records is specified.
+   `vbuf' specifies the pointer to the region of the value.
+   `vsiz' specifies the size of the region of the value.  If the size of the value is greater
+   than the width tuning parameter of the database, the size is cut down to the width.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the database, it is overwritten. */
+bool tcfdbput(TCFDB *fdb, int64_t id, const void *vbuf, int vsiz);
+
+
+/* Store a record with a decimal key into a fixed-length database object.
+   `fdb' specifies the fixed-length database object connected as a writer.
+   `kbuf' specifies the pointer to the region of the decimal key.  It should be more than 0.  If
+   it is "min", the minimum ID number of existing records is specified.  If it is "prev", the
+   number less by one than the minimum ID number of existing records is specified.  If it is
+   "max", the maximum ID number of existing records is specified.  If it is "next", the number
+   greater by one than the maximum ID number of existing records is specified.
+   `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 the size of the value is greater
+   than the width tuning parameter of the database, the size is cut down to the width.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the database, it is overwritten. */
+bool tcfdbput2(TCFDB *fdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Store a string record with a decimal key into a fixed-length database object.
+   `fdb' specifies the fixed-length database object connected as a writer.
+   `kstr' specifies the string of the decimal key.  It should be more than 0.  If it is "min",
+   the minimum ID number of existing records is specified.  If it is "prev", the number less by
+   one than the minimum ID number of existing records is specified.  If it is "max", the maximum
+   ID number of existing records is specified.  If it is "next", the number greater by one than
+   the maximum ID number of existing records is specified.
+   `vstr' specifies the string of the value.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the database, it is overwritten. */
+bool tcfdbput3(TCFDB *fdb, const char *kstr, const void *vstr);
+
+
+/* Store a new record into a fixed-length database object.
+   `fdb' specifies the fixed-length database object connected as a writer.
+   `id' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID
+   number of existing records is specified.  If it is `FDBIDPREV', the number less by one than
+   the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID
+   number of existing records is specified.  If it is `FDBIDNEXT', the number greater by one than
+   the maximum ID number of existing records is specified.
+   `vbuf' specifies the pointer to the region of the value.
+   `vsiz' specifies the size of the region of the value.  If the size of the value is greater
+   than the width tuning parameter of the database, the size is cut down to the width.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the database, this function has no effect. */
+bool tcfdbputkeep(TCFDB *fdb, int64_t id, const void *vbuf, int vsiz);
+
+
+/* Store a new record with a decimal key into a fixed-length database object.
+   `fdb' specifies the fixed-length database object connected as a writer.
+   `kbuf' specifies the pointer to the region of the decimal key.  It should be more than 0.  If
+   it is "min", the minimum ID number of existing records is specified.  If it is "prev", the
+   number less by one than the minimum ID number of existing records is specified.  If it is
+   "max", the maximum ID number of existing records is specified.  If it is "next", the number
+   greater by one than the maximum ID number of existing records is specified.
+   `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 the size of the value is greater
+   than the width tuning parameter of the database, the size is cut down to the width.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the database, this function has no effect. */
+bool tcfdbputkeep2(TCFDB *fdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Store a new string record with a decimal key into a fixed-length database object.
+   `fdb' specifies the fixed-length database object connected as a writer.
+   `kstr' specifies the string of the decimal key.  It should be more than 0.  If it is "min",
+   the minimum ID number of existing records is specified.  If it is "prev", the number less by
+   one than the minimum ID number of existing records is specified.  If it is "max", the maximum
+   ID number of existing records is specified.  If it is "next", the number greater by one than
+   the maximum ID number of existing records is specified.
+   `vstr' specifies the string of the value.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the database, this function has no effect. */
+bool tcfdbputkeep3(TCFDB *fdb, const char *kstr, const void *vstr);
+
+
+/* Concatenate a value at the end of the existing record in a fixed-length database object.
+   `fdb' specifies the fixed-length database object connected as a writer.
+   `id' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID
+   number of existing records is specified.  If it is `FDBIDPREV', the number less by one than
+   the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID
+   number of existing records is specified.  If it is `FDBIDNEXT', the number greater by one than
+   the maximum ID number of existing records is specified.
+   `vbuf' specifies the pointer to the region of the value.
+   `vsiz' specifies the size of the region of the value.  If the size of the value is greater
+   than the width tuning parameter of the database, the size is cut down to the width.
+   If successful, the return value is true, else, it is false.
+   If there is no corresponding record, a new record is created. */
+bool tcfdbputcat(TCFDB *fdb, int64_t id, const void *vbuf, int vsiz);
+
+
+/* Concatenate a value with a decimal key in a fixed-length database object.
+   `fdb' specifies the fixed-length database object connected as a writer.
+   `kbuf' specifies the pointer to the region of the decimal key.  It should be more than 0.  If
+   it is "min", the minimum ID number of existing records is specified.  If it is "prev", the
+   number less by one than the minimum ID number of existing records is specified.  If it is
+   "max", the maximum ID number of existing records is specified.  If it is "next", the number
+   greater by one than the maximum ID number of existing records is specified.
+   `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 the size of the value is greater
+   than the width tuning parameter of the database, the size is cut down to the width.
+   If successful, the return value is true, else, it is false.
+   If there is no corresponding record, a new record is created. */
+bool tcfdbputcat2(TCFDB *fdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Concatenate a string value with a decimal key in a fixed-length database object.
+   `fdb' specifies the fixed-length database object connected as a writer.
+   `kstr' specifies the string of the decimal key.  It should be more than 0.  If it is "min",
+   the minimum ID number of existing records is specified.  If it is "prev", the number less by
+   one than the minimum ID number of existing records is specified.  If it is "max", the maximum
+   ID number of existing records is specified.  If it is "next", the number greater by one than
+   the maximum ID number of existing records is specified.
+   `vstr' specifies the string of the value.
+   If successful, the return value is true, else, it is false.
+   If there is no corresponding record, a new record is created. */
+bool tcfdbputcat3(TCFDB *fdb, const char *kstr, const void *vstr);
+
+
+/* Remove a record of a fixed-length database object.
+   `fdb' specifies the fixed-length database object connected as a writer.
+   `id' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID
+   number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of
+   existing records is specified.
+   If successful, the return value is true, else, it is false. */
+bool tcfdbout(TCFDB *fdb, int64_t id);
+
+
+/* Remove a record with a decimal key of a fixed-length database object.
+   `fdb' specifies the fixed-length database object connected as a writer.
+   `kbuf' specifies the pointer to the region of the decimal key.  It should be more than 0.  If
+   it is "min", the minimum ID number of existing records is specified.  If it is "max", the
+   maximum ID number of existing records is specified.
+   `ksiz' specifies the size of the region of the key.
+   If successful, the return value is true, else, it is false. */
+bool tcfdbout2(TCFDB *fdb, const void *kbuf, int ksiz);
+
+
+/* Remove a string record with a decimal key of a fixed-length database object.
+   `fdb' specifies the fixed-length database object connected as a writer.
+   `kstr' specifies the string of the decimal key.  It should be more than 0.  If it is "min",
+   the minimum ID number of existing records is specified.  If it is "max", the maximum ID number
+   of existing records is specified.
+   If successful, the return value is true, else, it is false. */
+bool tcfdbout3(TCFDB *fdb, const char *kstr);
+
+
+/* Retrieve a record in a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   `id' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID
+   number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of
+   existing records is specified.
+   `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.  `NULL' is returned if no record corresponds.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when
+   it is no longer in use. */
+void *tcfdbget(TCFDB *fdb, int64_t id, int *sp);
+
+
+/* Retrieve a record with a decimal key in a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   `kbuf' specifies the pointer to the region of the decimal key.  It should be more than 0.  If
+   it is "min", the minimum ID number of existing records is specified.  If it is "max", the
+   maximum ID number of existing records is specified.
+   `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.  `NULL' is returned if no record corresponds.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when
+   it is no longer in use. */
+void *tcfdbget2(TCFDB *fdb, const void *kbuf, int ksiz, int *sp);
+
+
+/* Retrieve a string record with a decimal key in a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   `kstr' specifies the string of the decimal key.  It should be more than 0.  If it is "min",
+   the minimum ID number of existing records is specified.  If it is "max", the maximum ID number
+   of existing records is specified.
+   If successful, the return value is the string of the value of the corresponding record.
+   `NULL' is returned if no record corresponds.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when
+   it is no longer in use. */
+char *tcfdbget3(TCFDB *fdb, const char *kstr);
+
+
+/* Retrieve a record in a fixed-length database object and write the value into a buffer.
+   `fdb' specifies the fixed-length database object.
+   `id' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID
+   number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of
+   existing records is specified.
+   `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.  -1 is
+   returned if no record corresponds to the specified key.
+   Note that an additional zero code is not appended at the end of the region of the writing
+   buffer. */
+int tcfdbget4(TCFDB *fdb, int64_t id, void *vbuf, int max);
+
+
+/* Get the size of the value of a record in a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   `id' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID
+   number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of
+   existing records is specified.
+   If successful, the return value is the size of the value of the corresponding record, else,
+   it is -1. */
+int tcfdbvsiz(TCFDB *fdb, int64_t id);
+
+
+/* Get the size of the value with a decimal key in a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   `kbuf' specifies the pointer to the region of the decimal key.  It should be more than 0.  If
+   it is "min", the minimum ID number of existing records is specified.  If it is "max", the
+   maximum ID number of existing records is specified.
+   `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. */
+int tcfdbvsiz2(TCFDB *fdb, const void *kbuf, int ksiz);
+
+
+/* Get the size of the string value with a decimal key in a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   `kstr' specifies the string of the decimal key.  It should be more than 0.  If it is "min",
+   the minimum ID number of existing records is specified.  If it is "max", the maximum ID number
+   of existing records is specified.
+   If successful, the return value is the size of the value of the corresponding record, else,
+   it is -1. */
+int tcfdbvsiz3(TCFDB *fdb, const char *kstr);
+
+
+/* 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.
+   The iterator is used in order to access the key of every record stored in a database. */
+bool tcfdbiterinit(TCFDB *fdb);
+
+
+/* Get the next ID number 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.  0 is
+   returned when no record is to be get out of the iterator.
+   It is possible to access every record by iteration of calling this function.  It is allowed to
+   update or remove records whose keys are fetched while the iteration.  The order of this
+   traversal access method is ascending of the ID number. */
+uint64_t tcfdbiternext(TCFDB *fdb);
+
+
+/* Get the next decimay key of the iterator of a fixed-length database object.
+   `fdb' specifies the fixed-length 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 decimal key, else, it
+   is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.
+   Because an additional zero code is appended at the end of the region of the return value, the
+   return value can be treated as a character string.  Because the region of the return value is
+   allocated with the `malloc' call, it should be released with the `free' call when it is no
+   longer in use.  It is possible to access every record by iteration of calling this function.
+   It is allowed to update or remove records whose keys are fetched while the iteration.  The
+   order of this traversal access method is ascending of the ID number. */
+void *tcfdbiternext2(TCFDB *fdb, int *sp);
+
+
+/* Get the next decimay key string of the iterator of a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   If successful, the return value is the string of the next decimal key, else, it is `NULL'.
+   `NULL' is returned when no record is to be get out of the iterator.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use.  It is possible to access every
+   record by iteration of calling this function.  It is allowed to update or remove records whose
+   keys are fetched while the iteration.  The order of this traversal access method is ascending
+   of the ID number. */
+char *tcfdbiternext3(TCFDB *fdb);
+
+
+/* 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.  If it is `FDBIDMIN', the minimum ID is
+   specified.
+   `upper' specifies the upper limit of the range.  If it is `FDBIDMAX', the maximum ID is
+   specified.
+   `max' specifies the maximum number of keys to be fetched.  If it is negative, no limit is
+   specified.
+   `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.  `NULL' is returned on failure.  This function does never fail.  It returns an empty
+   array even if no key corresponds.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+uint64_t *tcfdbrange(TCFDB *fdb, int64_t lower, int64_t upper, int max, int *np);
+
+
+/* Get range matching decimal keys in a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   `lbuf' specifies the pointer to the region of the lower key.  If it is "min", the minimum ID
+   number of existing records is specified.
+   `lsiz' specifies the size of the region of the lower key.
+   `ubuf' specifies the pointer to the region of the upper key.  If it is "max", the maximum ID
+   number of existing records is specified.
+   `usiz' specifies the size of the region of the upper key.
+   `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 decimal keys.  This function does never
+   fail.  It returns an empty list even if no key corresponds.
+   Because the object of the return value is created with the function `tclistnew', it should be
+   deleted with the function `tclistdel' when it is no longer in use.  Note that this function
+   may be very slow because every key in the database is scanned. */
+TCLIST *tcfdbrange2(TCFDB *fdb, const void *lbuf, int lsiz, const void *ubuf, int usiz, int max);
+
+
+/* Get range matching decimal keys with strings in a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   `lstr' specifies the string of the lower key.  If it is "min", the minimum ID number of
+   existing records is specified.
+   `ustr' specifies the string of the upper key.  If it is "max", the maximum ID number of
+   existing records is specified.
+   `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 decimal keys.  This function does never
+   fail.  It returns an empty list even if no key corresponds.
+   Because the object of the return value is created with the function `tclistnew', it should be
+   deleted with the function `tclistdel' when it is no longer in use.  Note that this function
+   may be very slow because every key in the database is scanned. */
+TCLIST *tcfdbrange3(TCFDB *fdb, const char *lstr, const char *ustr, int max);
+
+
+/* Get keys with an interval notation in a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   `ibuf' specifies the pointer to the region of the interval notation.
+   `isiz' specifies the size of the region of the interval notation.
+   `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 decimal keys.  This function does never
+   fail.  It returns an empty list even if no key corresponds.
+   Because the object of the return value is created with the function `tclistnew', it should be
+   deleted with the function `tclistdel' when it is no longer in use.  Note that this function
+   may be very slow because every key in the database is scanned. */
+TCLIST *tcfdbrange4(TCFDB *fdb, const void *ibuf, int isiz, int max);
+
+
+/* Get keys with an interval notation string in a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   `istr' specifies the pointer to the region of the interval notation string.
+   `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 decimal keys.  This function does never
+   fail.  It returns an empty list even if no key corresponds.
+   Because the object of the return value is created with the function `tclistnew', it should be
+   deleted with the function `tclistdel' when it is no longer in use.  Note that this function
+   may be very slow because every key in the database is scanned. */
+TCLIST *tcfdbrange5(TCFDB *fdb, const void *istr, int max);
+
+
+/* Add an integer to a record in a fixed-length database object.
+   `fdb' specifies the fixed-length database object connected as a writer.
+   `id' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID
+   number of existing records is specified.  If it is `FDBIDPREV', the number less by one than
+   the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID
+   number of existing records is specified.  If it is `FDBIDNEXT', the number greater by one than
+   the maximum ID number of existing records is specified.
+   `num' specifies the additional value.
+   If successful, the return value is the summation value, else, it is `INT_MIN'.
+   If the corresponding record exists, the value is treated as an integer and is added to.  If no
+   record corresponds, a new record of the additional value is stored. */
+int tcfdbaddint(TCFDB *fdb, int64_t id, int num);
+
+
+/* Add a real number to a record in a fixed-length database object.
+   `fdb' specifies the fixed-length database object connected as a writer.
+   `id' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID
+   number of existing records is specified.  If it is `FDBIDPREV', the number less by one than
+   the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID
+   number of existing records is specified.  If it is `FDBIDNEXT', the number greater by one than
+   the maximum ID number of existing records is specified.
+   `num' specifies the additional value.
+   If successful, the return value is the summation value, else, it is Not-a-Number.
+   If the corresponding record exists, the value is treated as a real number and is added to.  If
+   no record corresponds, a new record of the additional value is stored. */
+double tcfdbadddouble(TCFDB *fdb, int64_t id, double num);
+
+
+/* Synchronize updated contents of a fixed-length database object with the file and the device.
+   `fdb' specifies the fixed-length database object connected as a writer.
+   If successful, the return value is true, else, it is false.
+   This function is useful when another process connects to the same database file. */
+bool tcfdbsync(TCFDB *fdb);
+
+
+/* Optimize the file of a fixed-length database object.
+   `fdb' specifies the fixed-length database object connected as a writer.
+   `width' specifies the width of the value of each record.  If it is not more than 0, the current
+   setting is not changed.
+   `limsiz' specifies the limit size of the database file.  If it is not more than 0, the current
+   setting is not changed.
+   If successful, the return value is true, else, it is false. */
+bool tcfdboptimize(TCFDB *fdb, int32_t width, int64_t limsiz);
+
+
+/* Remove all records of a fixed-length database object.
+   `fdb' specifies the fixed-length database object connected as a writer.
+   If successful, the return value is true, else, it is false. */
+bool tcfdbvanish(TCFDB *fdb);
+
+
+/* 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 it begins with `@', the trailing
+   substring is executed as a command line.
+   If successful, the return value is true, else, it is false.  False is returned if the executed
+   command returns non-zero code.
+   The database file is assured to be kept synchronized and not modified while the copying or
+   executing operation is in progress.  So, this function is useful to create a backup file of
+   the database file. */
+bool tcfdbcopy(TCFDB *fdb, const char *path);
+
+
+/* Begin the transaction of a fixed-length database object.
+   `fdb' specifies the fixed-length database object connected as a writer.
+   If successful, the return value is true, else, it is false.
+   The database is locked by the thread while the transaction so that only one transaction can be
+   activated with a database object at the same time.  Thus, the serializable isolation level is
+   assumed if every database operation is performed in the transaction.  All updated regions are
+   kept track of by write ahead logging while the transaction.  If the database is closed during
+   transaction, the transaction is aborted implicitly. */
+bool tcfdbtranbegin(TCFDB *fdb);
+
+
+/* Commit the transaction of a fixed-length database object.
+   `fdb' specifies the fixed-length database object connected as a writer.
+   If successful, the return value is true, else, it is false.
+   Update in the transaction is fixed when it is committed successfully. */
+bool tcfdbtrancommit(TCFDB *fdb);
+
+
+/* Abort the transaction of a fixed-length database object.
+   `fdb' specifies the fixed-length database object connected as a writer.
+   If successful, the return value is true, else, it is false.
+   Update in the transaction is discarded when it is aborted.  The state of the database is
+   rollbacked to before transaction. */
+bool tcfdbtranabort(TCFDB *fdb);
+
+
+/* Get the file path of a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   The return value is the path of the database file or `NULL' if the object does not connect to
+   any database file. */
+const char *tcfdbpath(TCFDB *fdb);
+
+
+/* Get the number of records of a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   The return value is the number of records or 0 if the object does not connect to any database
+   file. */
+uint64_t tcfdbrnum(TCFDB *fdb);
+
+
+/* Get the size of the database file of a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   The return value is the size of the database file or 0 if the object does not connect to any
+   database file. */
+uint64_t tcfdbfsiz(TCFDB *fdb);
+
+
+
+/*************************************************************************************************
+ * features for experts
+ *************************************************************************************************/
+
+
+/* Set the error code of a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   `ecode' specifies the error code.
+   `file' specifies the file name of the code.
+   `line' specifies the line number of the code.
+   `func' specifies the function name of the code. */
+void tcfdbsetecode(TCFDB *fdb, int ecode, const char *filename, int line, const char *func);
+
+
+/* Set the file descriptor for debugging output.
+   `fdb' specifies the fixed-length database object.
+   `fd' specifies the file descriptor for debugging output. */
+void tcfdbsetdbgfd(TCFDB *fdb, int fd);
+
+
+/* Get the file descriptor for debugging output.
+   `fdb' specifies the fixed-length database object.
+   The return value is the file descriptor for debugging output. */
+int tcfdbdbgfd(TCFDB *fdb);
+
+
+/* Check whether mutual exclusion control is set to a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   If mutual exclusion control is set, it is true, else it is false. */
+bool tcfdbhasmutex(TCFDB *fdb);
+
+
+/* Synchronize updating contents on memory of a fixed-length database object.
+   `fdb' specifies the fixed-length database object connected as a writer.
+   `phys' specifies whether to synchronize physically.
+   If successful, the return value is true, else, it is false. */
+bool tcfdbmemsync(TCFDB *fdb, bool phys);
+
+
+/* Get the minimum ID number of records of a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   The return value is the minimum ID number of records or 0 if the object does not connect to
+   any database file. */
+uint64_t tcfdbmin(TCFDB *fdb);
+
+
+/* Get the maximum ID number of records of a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   The return value is the maximum ID number of records or 0 if the object does not connect to
+   any database file. */
+uint64_t tcfdbmax(TCFDB *fdb);
+
+
+/* Get the width of the value of each record of a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   The return value is the width of the value of each record or 0 if the object does not connect
+   to any database file. */
+uint32_t tcfdbwidth(TCFDB *fdb);
+
+
+/* Get the limit file size of a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   The return value is the limit file size or 0 if the object does not connect to any database
+   file. */
+uint64_t tcfdblimsiz(TCFDB *fdb);
+
+
+/* Get the limit ID number of a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   The return value is the limit ID number or 0 if the object does not connect to any database
+   file. */
+uint64_t tcfdblimid(TCFDB *fdb);
+
+
+/* Get the inode number of the database file of a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   The return value is the inode number of the database file or 0 if the object does not connect
+   to any database file. */
+uint64_t tcfdbinode(TCFDB *fdb);
+
+
+/* Get the modification time of the database file of a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   The return value is the inode number of the database file or 0 if the object does not connect
+   to any database file. */
+time_t tcfdbmtime(TCFDB *fdb);
+
+
+/* Get the connection mode of a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   The return value is the connection mode. */
+int tcfdbomode(TCFDB *fdb);
+
+
+/* Get the database type of a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   The return value is the database type. */
+uint8_t tcfdbtype(TCFDB *fdb);
+
+
+/* Get the additional flags of a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   The return value is the additional flags. */
+uint8_t tcfdbflags(TCFDB *fdb);
+
+
+/* Get the pointer to the opaque field of a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   The return value is the pointer to the opaque field whose size is 128 bytes. */
+char *tcfdbopaque(TCFDB *fdb);
+
+
+/* Store a record into a fixed-length database object with a duplication handler.
+   `fdb' specifies the fixed-length database object connected as a writer.
+   `id' specifies the ID number.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID
+   number of existing records is specified.  If it is `FDBIDPREV', the number less by one than
+   the minimum ID number of existing records is specified.  If it is `FDBIDMAX', the maximum ID
+   number of existing records is specified.  If it is `FDBIDNEXT', the number greater by one than
+   the maximum ID number of existing records is specified.
+   `vbuf' specifies the pointer to the region of the value.  `NULL' means that record addition is
+   ommited if there is no corresponding record.
+   `vsiz' specifies the size of the region of the value.  If the size of the value is greater
+   than the width tuning parameter of the database, the size is cut down to the width.
+   `proc' specifies the pointer to the callback function to process duplication.  It receives
+   four parameters.  The first parameter is the pointer to the region of the value.  The second
+   parameter is the size of the region of the value.  The third parameter is the pointer to the
+   variable into which the size of the region of the return value is assigned.  The fourth
+   parameter is the pointer to the optional opaque object.  It returns the pointer to the result
+   object allocated with `malloc'.  It is released by the caller.  If it is `NULL', the record is
+   not modified.  If it is `(void *)-1', the record is removed.
+   `op' specifies an arbitrary pointer to be given as a parameter of the callback function.  If
+   it is not needed, `NULL' can be specified.
+   If successful, the return value is true, else, it is false.
+   Note that the callback function can not perform any database operation because the function
+   is called in the critical section guarded by the same locks of database operations. */
+bool tcfdbputproc(TCFDB *fdb, int64_t id, const void *vbuf, int vsiz, TCPDPROC proc, void *op);
+
+
+/* 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.  It should be more than 0.  If it is `FDBIDMIN', the minimum ID
+   number of existing records is specified.  If it is `FDBIDMAX', the maximum ID number of
+   existing records is specified.
+   If successful, the return value is true, else, it is false.  False is returned if there is
+   no record corresponding the condition. */
+bool tcfdbiterinit2(TCFDB *fdb, int64_t id);
+
+
+/* Move the iterator to the decimal record of a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   `kbuf' specifies the pointer to the region of the decimal key.  It should be more than 0.  If
+   it is "min", the minimum ID number of existing records is specified.  If it is "max", the
+   maximum ID number of existing records is specified.
+   `ksiz' specifies the size of the region of the key.
+   If successful, the return value is true, else, it is false.  False is returned if there is
+   no record corresponding the condition. */
+bool tcfdbiterinit3(TCFDB *fdb, const void *kbuf, int ksiz);
+
+
+/* Move the iterator to the decimal string record of a fixed-length database object.
+   `fdb' specifies the fixed-length database object.
+   `kstr' specifies the string of the decimal key.  It should be more than 0.  If it is "min",
+   the minimum ID number of existing records is specified.  If it is "max", the maximum ID number
+   of existing records is specified.
+   If successful, the return value is true, else, it is false.  False is returned if there is
+   no record corresponding the condition. */
+bool tcfdbiterinit4(TCFDB *fdb, const char *kstr);
+
+
+/* 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.  It receives
+   five parameters.  The first parameter is the pointer to the region of the key.  The second
+   parameter is the size of the region of the key.  The third parameter is the pointer to the
+   region of the value.  The fourth parameter is the size of the region of the value.  The fifth
+   parameter is the pointer to the optional opaque object.  It returns true to continue iteration
+   or false to stop iteration.
+   `op' specifies an arbitrary pointer to be given as a parameter of the iterator function.  If
+   it is not needed, `NULL' can be specified.
+   If successful, the return value is true, else, it is false.
+   Note that the callback function can not perform any database operation because the function
+   is called in the critical section guarded by the same locks of database operations. */
+bool tcfdbforeach(TCFDB *fdb, TCITER iter, void *op);
+
+
+/* Generate the ID number from arbitrary binary data.
+   `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. */
+int64_t tcfdbkeytoid(const char *kbuf, int ksiz);
+
+
+
+__TCFDB_CLINKAGEEND
+#endif                                   /* duplication check */
+
+
+/* END OF FILE */
diff --git a/tcejdb/tcfmgr.c b/tcejdb/tcfmgr.c
new file mode 100644 (file)
index 0000000..85c2eda
--- /dev/null
@@ -0,0 +1,794 @@
+/*************************************************************************************************
+ * The command line utility of the fixed-length database API
+ *                                                               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"
+
+
+/* global variables */
+const char *g_progname;                  // program name
+int g_dbgfd;                             // debugging output
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+static void usage(void);
+static void printerr(TCFDB *fdb);
+static int printdata(const char *ptr, int size, bool px);
+static char *mygetline(FILE *ifp);
+static int runcreate(int argc, char **argv);
+static int runinform(int argc, char **argv);
+static int runput(int argc, char **argv);
+static int runout(int argc, char **argv);
+static int runget(int argc, char **argv);
+static int runlist(int argc, char **argv);
+static int runoptimize(int argc, char **argv);
+static int runimporttsv(int argc, char **argv);
+static int runversion(int argc, char **argv);
+static int proccreate(const char *path, int width, int64_t limsiz);
+static int procinform(const char *path, int omode);
+static int procput(const char *path, const char *kbuf, int ksiz, const char *vbuf, int vsiz,
+                   int omode, int dmode);
+static int procout(const char *path, const char *kbuf, int ksiz, int omode);
+static int procget(const char *path, const char *kbuf, int ksiz, int omode, bool px, bool pz);
+static int proclist(const char *path, int omode, int max, bool pv, bool px,
+                    const char *rlstr, const char *rustr, const char *ristr);
+static int procoptimize(const char *path, int width, int64_t limsiz, int omode);
+static int procimporttsv(const char *path, const char *file, int omode, bool sc);
+static int procversion(void);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  g_progname = argv[0];
+  g_dbgfd = -1;
+  const char *ebuf = getenv("TCDBGFD");
+  if(ebuf) g_dbgfd = tcatoix(ebuf);
+  if(argc < 2) usage();
+  int rv = 0;
+  if(!strcmp(argv[1], "create")){
+    rv = runcreate(argc, argv);
+  } else if(!strcmp(argv[1], "inform")){
+    rv = runinform(argc, argv);
+  } else if(!strcmp(argv[1], "put")){
+    rv = runput(argc, argv);
+  } else if(!strcmp(argv[1], "out")){
+    rv = runout(argc, argv);
+  } else if(!strcmp(argv[1], "get")){
+    rv = runget(argc, argv);
+  } else if(!strcmp(argv[1], "list")){
+    rv = runlist(argc, argv);
+  } else if(!strcmp(argv[1], "optimize")){
+    rv = runoptimize(argc, argv);
+  } else if(!strcmp(argv[1], "importtsv")){
+    rv = runimporttsv(argc, argv);
+  } else if(!strcmp(argv[1], "version") || !strcmp(argv[1], "--version")){
+    rv = runversion(argc, argv);
+  } else {
+    usage();
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+static void usage(void){
+  fprintf(stderr, "%s: the command line utility of the fixed-length database API\n", g_progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s create path [width [limsiz]]\n", g_progname);
+  fprintf(stderr, "  %s inform [-nl|-nb] path\n", g_progname);
+  fprintf(stderr, "  %s put [-nl|-nb] [-sx] [-dk|-dc|-dai|-dad] path key value\n", g_progname);
+  fprintf(stderr, "  %s out [-nl|-nb] [-sx] path key\n", g_progname);
+  fprintf(stderr, "  %s get [-nl|-nb] [-sx] [-px] [-pz] path key\n", g_progname);
+  fprintf(stderr, "  %s list [-nl|-nb] [-m num] [-pv] [-px] [-rb lkey ukey] [-ri str] path\n",
+          g_progname);
+  fprintf(stderr, "  %s optimize [-nl|-nb] path [width [limsiz]]\n", g_progname);
+  fprintf(stderr, "  %s importtsv [-nl|-nb] [-sc] path [file]\n", g_progname);
+  fprintf(stderr, "  %s version\n", g_progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* print error information */
+static void printerr(TCFDB *fdb){
+  const char *path = tcfdbpath(fdb);
+  int ecode = tcfdbecode(fdb);
+  fprintf(stderr, "%s: %s: %d: %s\n", g_progname, path ? path : "-", ecode, tcfdberrmsg(ecode));
+}
+
+
+/* print record data */
+static int printdata(const char *ptr, int size, bool px){
+  int len = 0;
+  while(size-- > 0){
+    if(px){
+      if(len > 0) putchar(' ');
+      len += printf("%02X", *(unsigned char *)ptr);
+    } else {
+      putchar(*ptr);
+      len++;
+    }
+    ptr++;
+  }
+  return len;
+}
+
+
+/* read a line from a file descriptor */
+static char *mygetline(FILE *ifp){
+  int len = 0;
+  int blen = 1024;
+  char *buf = tcmalloc(blen + 1);
+  bool end = true;
+  int c;
+  while((c = fgetc(ifp)) != EOF){
+    end = false;
+    if(c == '\0') continue;
+    if(blen <= len){
+      blen *= 2;
+      buf = tcrealloc(buf, blen + 1);
+    }
+    if(c == '\n' || c == '\r') c = '\0';
+    buf[len++] = c;
+    if(c == '\0') break;
+  }
+  if(end){
+    tcfree(buf);
+    return NULL;
+  }
+  buf[len] = '\0';
+  return buf;
+}
+
+
+/* parse arguments of create command */
+static int runcreate(int argc, char **argv){
+  char *path = NULL;
+  char *wstr = NULL;
+  char *lstr = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      usage();
+    } else if(!path){
+      path = argv[i];
+    } else if(!wstr){
+      wstr = argv[i];
+    } else if(!lstr){
+      lstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int width = wstr ? tcatoix(wstr) : -1;
+  int64_t limsiz = lstr ? tcatoix(lstr) : -1;
+  int rv = proccreate(path, width, limsiz);
+  return rv;
+}
+
+
+/* parse arguments of inform command */
+static int runinform(int argc, char **argv){
+  char *path = NULL;
+  int omode = 0;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= FDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= FDBOLCKNB;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int rv = procinform(path, omode);
+  return rv;
+}
+
+
+/* parse arguments of put command */
+static int runput(int argc, char **argv){
+  char *path = NULL;
+  char *key = NULL;
+  char *value = NULL;
+  int omode = 0;
+  int dmode = 0;
+  bool sx = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= FDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= FDBOLCKNB;
+      } else if(!strcmp(argv[i], "-dk")){
+        dmode = -1;
+      } else if(!strcmp(argv[i], "-dc")){
+        dmode = 1;
+      } else if(!strcmp(argv[i], "-dai")){
+        dmode = 10;
+      } else if(!strcmp(argv[i], "-dad")){
+        dmode = 11;
+      } else if(!strcmp(argv[i], "-sx")){
+        sx = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!key){
+      key = argv[i];
+    } else if(!value){
+      value = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !key || !value) usage();
+  char *kbuf, *vbuf;
+  int ksiz, vsiz;
+  if(sx){
+    kbuf = tchexdecode(key, &ksiz);
+    vbuf = tchexdecode(value, &vsiz);
+  } else {
+    ksiz = strlen(key);
+    kbuf = tcmemdup(key, ksiz);
+    vsiz = strlen(value);
+    vbuf = tcmemdup(value, vsiz);
+  }
+  int rv = procput(path, kbuf, ksiz, vbuf, vsiz, omode, dmode);
+  tcfree(vbuf);
+  tcfree(kbuf);
+  return rv;
+}
+
+
+/* parse arguments of out command */
+static int runout(int argc, char **argv){
+  char *path = NULL;
+  char *key = NULL;
+  int omode = 0;
+  bool sx = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= FDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= FDBOLCKNB;
+      } else if(!strcmp(argv[i], "-sx")){
+        sx = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!key){
+      key = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !key) usage();
+  int ksiz;
+  char *kbuf;
+  if(sx){
+    kbuf = tchexdecode(key, &ksiz);
+  } else {
+    ksiz = strlen(key);
+    kbuf = tcmemdup(key, ksiz);
+  }
+  int rv = procout(path, kbuf, ksiz, omode);
+  tcfree(kbuf);
+  return rv;
+}
+
+
+/* parse arguments of get command */
+static int runget(int argc, char **argv){
+  char *path = NULL;
+  char *key = NULL;
+  int omode = 0;
+  bool sx = false;
+  bool px = false;
+  bool pz = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= FDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= FDBOLCKNB;
+      } else if(!strcmp(argv[i], "-sx")){
+        sx = true;
+      } else if(!strcmp(argv[i], "-px")){
+        px = true;
+      } else if(!strcmp(argv[i], "-pz")){
+        pz = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!key){
+      key = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !key) usage();
+  int ksiz;
+  char *kbuf;
+  if(sx){
+    kbuf = tchexdecode(key, &ksiz);
+  } else {
+    ksiz = strlen(key);
+    kbuf = tcmemdup(key, ksiz);
+  }
+  int rv = procget(path, kbuf, ksiz, omode, px, pz);
+  tcfree(kbuf);
+  return rv;
+}
+
+
+/* parse arguments of list command */
+static int runlist(int argc, char **argv){
+  char *path = NULL;
+  int omode = 0;
+  int max = -1;
+  bool pv = false;
+  bool px = false;
+  char *rlstr = NULL;
+  char *rustr = NULL;
+  char *ristr = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= FDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= FDBOLCKNB;
+      } else if(!strcmp(argv[i], "-m")){
+        if(++i >= argc) usage();
+        max = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-pv")){
+        pv = true;
+      } else if(!strcmp(argv[i], "-px")){
+        px = true;
+      } else if(!strcmp(argv[i], "-rb")){
+        if(++i >= argc) usage();
+        rlstr = argv[i];
+        if(++i >= argc) usage();
+        rustr = argv[i];
+      } else if(!strcmp(argv[i], "-ri")){
+        if(++i >= argc) usage();
+        ristr = argv[i];
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int rv = proclist(path, omode, max, pv, px, rlstr, rustr, ristr);
+  return rv;
+}
+
+
+/* parse arguments of optimize command */
+static int runoptimize(int argc, char **argv){
+  char *path = NULL;
+  char *wstr = NULL;
+  char *lstr = NULL;
+  int omode = 0;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= FDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= FDBOLCKNB;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!wstr){
+      wstr = argv[i];
+    } else if(!lstr){
+      lstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int width = wstr ? tcatoix(wstr) : -1;
+  int64_t limsiz = lstr ? tcatoix(lstr) : -1;
+  int rv = procoptimize(path, width, limsiz, omode);
+  return rv;
+}
+
+
+/* parse arguments of importtsv command */
+static int runimporttsv(int argc, char **argv){
+  char *path = NULL;
+  char *file = NULL;
+  int omode = 0;
+  bool sc = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= FDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= FDBOLCKNB;
+      } else if(!strcmp(argv[i], "-sc")){
+        sc = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!file){
+      file = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int rv = procimporttsv(path, file, omode, sc);
+  return rv;
+}
+
+
+/* parse arguments of version command */
+static int runversion(int argc, char **argv){
+  int rv = procversion();
+  return rv;
+}
+
+
+/* perform create command */
+static int proccreate(const char *path, int width, int64_t limsiz){
+  TCFDB *fdb = tcfdbnew();
+  if(g_dbgfd >= 0) tcfdbsetdbgfd(fdb, g_dbgfd);
+  if(!tcfdbtune(fdb, width, limsiz)){
+    printerr(fdb);
+    tcfdbdel(fdb);
+    return 1;
+  }
+  if(!tcfdbopen(fdb, path, FDBOWRITER | FDBOCREAT | FDBOTRUNC)){
+    printerr(fdb);
+    tcfdbdel(fdb);
+    return 1;
+  }
+  bool err = false;
+  if(!tcfdbclose(fdb)){
+    printerr(fdb);
+    err = true;
+  }
+  tcfdbdel(fdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform inform command */
+static int procinform(const char *path, int omode){
+  TCFDB *fdb = tcfdbnew();
+  if(g_dbgfd >= 0) tcfdbsetdbgfd(fdb, g_dbgfd);
+  if(!tcfdbopen(fdb, path, FDBOREADER | omode)){
+    printerr(fdb);
+    tcfdbdel(fdb);
+    return 1;
+  }
+  bool err = false;
+  const char *npath = tcfdbpath(fdb);
+  if(!npath) npath = "(unknown)";
+  printf("path: %s\n", npath);
+  const char *type = "(unknown)";
+  switch(tcfdbtype(fdb)){
+    case TCDBTHASH: type = "hash"; break;
+    case TCDBTBTREE: type = "btree"; break;
+    case TCDBTFIXED: type = "fixed"; break;
+    case TCDBTTABLE: type = "table"; break;
+  }
+  printf("database type: %s\n", type);
+  uint8_t flags = tcfdbflags(fdb);
+  printf("additional flags:");
+  if(flags & FDBFOPEN) printf(" open");
+  if(flags & FDBFFATAL) printf(" fatal");
+  printf("\n");
+  printf("minimum ID number: %llu\n", (unsigned long long)tcfdbmin(fdb));
+  printf("maximum ID number: %llu\n", (unsigned long long)tcfdbmax(fdb));
+  printf("width of the value: %u\n", (unsigned int)tcfdbwidth(fdb));
+  printf("limit file size: %llu\n", (unsigned long long)tcfdblimsiz(fdb));
+  printf("limit ID number: %llu\n", (unsigned long long)tcfdblimid(fdb));
+  printf("inode number: %lld\n", (long long)tcfdbinode(fdb));
+  char date[48];
+  tcdatestrwww(tcfdbmtime(fdb), INT_MAX, date);
+  printf("modified time: %s\n", date);
+  printf("record number: %llu\n", (unsigned long long)tcfdbrnum(fdb));
+  printf("file size: %llu\n", (unsigned long long)tcfdbfsiz(fdb));
+  if(!tcfdbclose(fdb)){
+    if(!err) printerr(fdb);
+    err = true;
+  }
+  tcfdbdel(fdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform put command */
+static int procput(const char *path, const char *kbuf, int ksiz, const char *vbuf, int vsiz,
+                   int omode, int dmode){
+  TCFDB *fdb = tcfdbnew();
+  if(g_dbgfd >= 0) tcfdbsetdbgfd(fdb, g_dbgfd);
+  if(!tcfdbopen(fdb, path, FDBOWRITER | omode)){
+    printerr(fdb);
+    tcfdbdel(fdb);
+    return 1;
+  }
+  bool err = false;
+  int inum;
+  double dnum;
+  switch(dmode){
+    case -1:
+      if(!tcfdbputkeep2(fdb, kbuf, ksiz, vbuf, vsiz)){
+        printerr(fdb);
+        err = true;
+      }
+      break;
+    case 1:
+      if(!tcfdbputcat2(fdb, kbuf, ksiz, vbuf, vsiz)){
+        printerr(fdb);
+        err = true;
+      }
+      break;
+    case 10:
+      inum = tcfdbaddint(fdb, tcfdbkeytoid(kbuf, ksiz), tcatoi(vbuf));
+      if(inum == INT_MIN){
+        printerr(fdb);
+        err = true;
+      } else {
+        printf("%d\n", inum);
+      }
+      break;
+    case 11:
+      dnum = tcfdbadddouble(fdb, tcfdbkeytoid(kbuf, ksiz), tcatof(vbuf));
+      if(isnan(dnum)){
+        printerr(fdb);
+        err = true;
+      } else {
+        printf("%.6f\n", dnum);
+      }
+      break;
+    default:
+      if(!tcfdbput2(fdb, kbuf, ksiz, vbuf, vsiz)){
+        printerr(fdb);
+        err = true;
+      }
+      break;
+  }
+  if(!tcfdbclose(fdb)){
+    if(!err) printerr(fdb);
+    err = true;
+  }
+  tcfdbdel(fdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform out command */
+static int procout(const char *path, const char *kbuf, int ksiz, int omode){
+  TCFDB *fdb = tcfdbnew();
+  if(g_dbgfd >= 0) tcfdbsetdbgfd(fdb, g_dbgfd);
+  if(!tcfdbopen(fdb, path, FDBOWRITER | omode)){
+    printerr(fdb);
+    tcfdbdel(fdb);
+    return 1;
+  }
+  bool err = false;
+  if(!tcfdbout2(fdb, kbuf, ksiz)){
+    printerr(fdb);
+    err = true;
+  }
+  if(!tcfdbclose(fdb)){
+    if(!err) printerr(fdb);
+    err = true;
+  }
+  tcfdbdel(fdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform get command */
+static int procget(const char *path, const char *kbuf, int ksiz, int omode, bool px, bool pz){
+  TCFDB *fdb = tcfdbnew();
+  if(g_dbgfd >= 0) tcfdbsetdbgfd(fdb, g_dbgfd);
+  if(!tcfdbopen(fdb, path, FDBOREADER | omode)){
+    printerr(fdb);
+    tcfdbdel(fdb);
+    return 1;
+  }
+  bool err = false;
+  int vsiz;
+  char *vbuf = tcfdbget2(fdb, kbuf, ksiz, &vsiz);
+  if(vbuf){
+    printdata(vbuf, vsiz, px);
+    if(!pz) putchar('\n');
+    tcfree(vbuf);
+  } else {
+    printerr(fdb);
+    err = true;
+  }
+  if(!tcfdbclose(fdb)){
+    if(!err) printerr(fdb);
+    err = true;
+  }
+  tcfdbdel(fdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform list command */
+static int proclist(const char *path, int omode, int max, bool pv, bool px,
+                    const char *rlstr, const char *rustr, const char *ristr){
+  TCFDB *fdb = tcfdbnew();
+  if(g_dbgfd >= 0) tcfdbsetdbgfd(fdb, g_dbgfd);
+  if(!tcfdbopen(fdb, path, FDBOREADER | omode)){
+    printerr(fdb);
+    tcfdbdel(fdb);
+    return 1;
+  }
+  bool err = false;
+  if(rlstr || ristr){
+    TCLIST *keys = ristr ? tcfdbrange5(fdb, ristr, max) : tcfdbrange3(fdb, rlstr, rustr, max);
+    for(int i = 0; i < tclistnum(keys); i++){
+      int ksiz;
+      const char *kbuf = tclistval(keys, i, &ksiz);
+      printf("%s", kbuf);
+      if(pv){
+        int vsiz;
+        char *vbuf = tcfdbget2(fdb, kbuf, ksiz, &vsiz);
+        if(vbuf){
+          putchar('\t');
+          printdata(vbuf, vsiz, px);
+          tcfree(vbuf);
+        }
+      }
+      putchar('\n');
+    }
+    tclistdel(keys);
+  } else {
+    if(!tcfdbiterinit(fdb)){
+      printerr(fdb);
+      err = true;
+    }
+    int cnt = 0;
+    uint64_t id;
+    while((id = tcfdbiternext(fdb)) > 0){
+      printf("%llu", (unsigned long long)id);
+      if(pv){
+        int vsiz;
+        char *vbuf = tcfdbget(fdb, id, &vsiz);
+        if(vbuf){
+          putchar('\t');
+          printdata(vbuf, vsiz, px);
+          tcfree(vbuf);
+        }
+      }
+      putchar('\n');
+      if(max >= 0 && ++cnt >= max) break;
+    }
+  }
+  if(!tcfdbclose(fdb)){
+    if(!err) printerr(fdb);
+    err = true;
+  }
+  tcfdbdel(fdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform optimize command */
+static int procoptimize(const char *path, int width, int64_t limsiz, int omode){
+  TCFDB *fdb = tcfdbnew();
+  if(g_dbgfd >= 0) tcfdbsetdbgfd(fdb, g_dbgfd);
+  if(!tcfdbopen(fdb, path, FDBOWRITER | omode)){
+    printerr(fdb);
+    tcfdbdel(fdb);
+    return 1;
+  }
+  bool err = false;
+  if(!tcfdboptimize(fdb, width, limsiz)){
+    printerr(fdb);
+    err = true;
+  }
+  if(!tcfdbclose(fdb)){
+    if(!err) printerr(fdb);
+    err = true;
+  }
+  tcfdbdel(fdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform importtsv command */
+static int procimporttsv(const char *path, const char *file, int omode, bool sc){
+  FILE *ifp = file ? fopen(file, "rb") : stdin;
+  if(!ifp){
+    fprintf(stderr, "%s: could not open\n", file ? file : "(stdin)");
+    return 1;
+  }
+  TCFDB *fdb = tcfdbnew();
+  if(g_dbgfd >= 0) tcfdbsetdbgfd(fdb, g_dbgfd);
+  if(!tcfdbopen(fdb, path, FDBOWRITER | FDBOCREAT | omode)){
+    printerr(fdb);
+    tcfdbdel(fdb);
+    if(ifp != stdin) fclose(ifp);
+    return 1;
+  }
+  bool err = false;
+  char *line;
+  int cnt = 0;
+  while(!err && (line = mygetline(ifp)) != NULL){
+    char *pv = strchr(line, '\t');
+    if(!pv){
+      tcfree(line);
+      continue;
+    }
+    *pv = '\0';
+    if(sc) tcstrutfnorm(line, TCUNSPACE | TCUNLOWER | TCUNNOACC | TCUNWIDTH);
+    if(!tcfdbput3(fdb, line, pv + 1)){
+      printerr(fdb);
+      err = true;
+    }
+    tcfree(line);
+    if(cnt > 0 && cnt % 100 == 0){
+      putchar('.');
+      fflush(stdout);
+      if(cnt % 5000 == 0) printf(" (%08d)\n", cnt);
+    }
+    cnt++;
+  }
+  printf(" (%08d)\n", cnt);
+  if(!tcfdbclose(fdb)){
+    if(!err) printerr(fdb);
+    err = true;
+  }
+  tcfdbdel(fdb);
+  if(ifp != stdin) fclose(ifp);
+  return err ? 1 : 0;
+}
+
+
+/* perform version command */
+static int procversion(void){
+  printf("Tokyo Cabinet version %s (%d:%s) for %s\n",
+         tcversion, _TC_LIBVER, _TC_FORMATVER, TCSYSNAME);
+  printf("Copyright (C) 2006-2012 FAL Labs\n");
+  return 0;
+}
+
+
+
+// END OF FILE
diff --git a/tcejdb/tcfmttest.c b/tcejdb/tcfmttest.c
new file mode 100644 (file)
index 0000000..8b9124d
--- /dev/null
@@ -0,0 +1,1220 @@
+/*************************************************************************************************
+ * The test cases of the fixed-length database API
+ *                                                               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 RECBUFSIZ      48                // buffer for records
+#define EXHEADSIZ      256               // expected header size
+
+typedef struct {                         // type of structure for write thread
+  TCFDB *fdb;
+  int rnum;
+  bool rnd;
+  int id;
+} TARGWRITE;
+
+typedef struct {                         // type of structure for read thread
+  TCFDB *fdb;
+  int rnum;
+  bool wb;
+  bool rnd;
+  int id;
+} TARGREAD;
+
+typedef struct {                         // type of structure for remove thread
+  TCFDB *fdb;
+  int rnum;
+  bool rnd;
+  int id;
+} TARGREMOVE;
+
+typedef struct {                         // type of structure for wicked thread
+  TCFDB *fdb;
+  int rnum;
+  bool nc;
+  int id;
+  TCMAP *map;
+} TARGWICKED;
+
+typedef struct {                         // type of structure for typical thread
+  TCFDB *fdb;
+  int rnum;
+  bool nc;
+  int rratio;
+  int id;
+} TARGTYPICAL;
+
+
+/* global variables */
+const char *g_progname;                  // program name
+unsigned int g_randseed;                 // random seed
+int g_dbgfd;                             // debugging output
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+static void usage(void);
+static void iprintf(const char *format, ...);
+static void iputchar(int c);
+static void eprint(TCFDB *fdb, int line, const char *func);
+static void mprint(TCFDB *fdb);
+static void sysprint(void);
+static int myrand(int range);
+static int myrandnd(int range);
+static bool iterfunc(const void *kbuf, int ksiz, const void *vbuf, int vsiz, void *op);
+static int runwrite(int argc, char **argv);
+static int runread(int argc, char **argv);
+static int runremove(int argc, char **argv);
+static int runwicked(int argc, char **argv);
+static int runtypical(int argc, char **argv);
+static int procwrite(const char *path, int tnum, int rnum, int width, int64_t limsiz,
+                     int omode, bool rnd);
+static int procread(const char *path, int tnum, int omode, bool wb, bool rnd);
+static int procremove(const char *path, int tnum, int omode, bool rnd);
+static int procwicked(const char *path, int tnum, int rnum, int omode, bool nc);
+static int proctypical(const char *path, int tnum, int rnum, int width, int64_t limsiz,
+                       int omode, bool nc, int rratio);
+static void *threadwrite(void *targ);
+static void *threadread(void *targ);
+static void *threadremove(void *targ);
+static void *threadwicked(void *targ);
+static void *threadtypical(void *targ);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  g_progname = argv[0];
+  const char *ebuf = getenv("TCRNDSEED");
+  g_randseed = ebuf ? tcatoix(ebuf) : tctime() * 1000;
+  srand(g_randseed);
+  ebuf = getenv("TCDBGFD");
+  g_dbgfd = ebuf ? tcatoix(ebuf) : UINT16_MAX;
+  if(argc < 2) usage();
+  int rv = 0;
+  if(!strcmp(argv[1], "write")){
+    rv = runwrite(argc, argv);
+  } else if(!strcmp(argv[1], "read")){
+    rv = runread(argc, argv);
+  } else if(!strcmp(argv[1], "remove")){
+    rv = runremove(argc, argv);
+  } else if(!strcmp(argv[1], "wicked")){
+    rv = runwicked(argc, argv);
+  } else if(!strcmp(argv[1], "typical")){
+    rv = runtypical(argc, argv);
+  } else {
+    usage();
+  }
+  if(rv != 0){
+    printf("FAILED: TCRNDSEED=%u PID=%d", g_randseed, (int)getpid());
+    for(int i = 0; i < argc; i++){
+      printf(" %s", argv[i]);
+    }
+    printf("\n\n");
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+static void usage(void){
+  fprintf(stderr, "%s: test cases of the fixed-length database API of Tokyo Cabinet\n",
+          g_progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s write [-nl|-nb] [-rnd] path tnum rnum [width [limsiz]]\n", g_progname);
+  fprintf(stderr, "  %s read [-nl|-nb] [-wb] [-rnd] path tnum\n", g_progname);
+  fprintf(stderr, "  %s remove [-nl|-nb] [-rnd] path tnum\n", g_progname);
+  fprintf(stderr, "  %s wicked [-nl|-nb] [-nc] path tnum rnum\n", g_progname);
+  fprintf(stderr, "  %s typical [-nl|-nb] [-nc] [-rr num] path tnum rnum [width [limsiz]]\n",
+          g_progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* print formatted information string and flush the buffer */
+static void iprintf(const char *format, ...){
+  va_list ap;
+  va_start(ap, format);
+  vprintf(format, ap);
+  fflush(stdout);
+  va_end(ap);
+}
+
+
+/* print a character and flush the buffer */
+static void iputchar(int c){
+  putchar(c);
+  fflush(stdout);
+}
+
+
+/* print error message of fixed-length database */
+static void eprint(TCFDB *fdb, int line, const char *func){
+  const char *path = tcfdbpath(fdb);
+  int ecode = tcfdbecode(fdb);
+  fprintf(stderr, "%s: %s: %d: %s: error: %d: %s\n",
+          g_progname, path ? path : "-", line, func, ecode, tcfdberrmsg(ecode));
+}
+
+
+/* print members of fixed-length database */
+static void mprint(TCFDB *fdb){
+  if(fdb->cnt_writerec < 0) return;
+  iprintf("minimum ID number: %llu\n", (unsigned long long)tcfdbmin(fdb));
+  iprintf("maximum ID number: %llu\n", (unsigned long long)tcfdbmax(fdb));
+  iprintf("width of the value: %u\n", (unsigned int)tcfdbwidth(fdb));
+  iprintf("limit file size: %llu\n", (unsigned long long)tcfdblimsiz(fdb));
+  iprintf("limit ID number: %llu\n", (unsigned long long)tcfdblimid(fdb));
+  iprintf("cnt_writerec: %lld\n", (long long)fdb->cnt_writerec);
+  iprintf("cnt_readrec: %lld\n", (long long)fdb->cnt_readrec);
+  iprintf("cnt_truncfile: %lld\n", (long long)fdb->cnt_truncfile);
+}
+
+
+/* print system information */
+static void sysprint(void){
+  TCMAP *info = tcsysinfo();
+  if(info){
+    tcmapiterinit(info);
+    const char *kbuf;
+    while((kbuf = tcmapiternext2(info)) != NULL){
+      iprintf("sys_%s: %s\n", kbuf, tcmapiterval2(kbuf));
+    }
+    tcmapdel(info);
+  }
+}
+
+
+/* get a random number */
+static int myrand(int range){
+  if(range < 2) return 0;
+  int high = (unsigned int)rand() >> 4;
+  int low = range * (rand() / (RAND_MAX + 1.0));
+  low &= (unsigned int)INT_MAX >> 4;
+  return (high + low) % range;
+}
+
+
+/* get a random number based on normal distribution */
+static int myrandnd(int range){
+  int num = (int)tcdrandnd(range >> 1, range / 10);
+  return (num < 0 || num >= range) ? 0 : num;
+}
+
+
+/* iterator function */
+static bool iterfunc(const void *kbuf, int ksiz, const void *vbuf, int vsiz, void *op){
+  unsigned int sum = 0;
+  while(--ksiz >= 0){
+    sum += ((char *)kbuf)[ksiz];
+  }
+  while(--vsiz >= 0){
+    sum += ((char *)vbuf)[vsiz];
+  }
+  return myrand(100 + (sum & 0xff)) > 0;
+}
+
+
+/* parse arguments of write command */
+static int runwrite(int argc, char **argv){
+  char *path = NULL;
+  char *tstr = NULL;
+  char *rstr = NULL;
+  char *wstr = NULL;
+  char *lstr = NULL;
+  int omode = 0;
+  bool rnd = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= FDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= FDBOLCKNB;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else if(!wstr){
+      wstr = argv[i];
+    } else if(!lstr){
+      lstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !tstr || !rstr) usage();
+  int tnum = tcatoix(tstr);
+  int rnum = tcatoix(rstr);
+  if(tnum < 1 || rnum < 1) usage();
+  int width = wstr ? tcatoix(wstr) : -1;
+  int64_t limsiz = lstr ? tcatoix(lstr) : -1;
+  int rv = procwrite(path, tnum, rnum, width, limsiz, omode, rnd);
+  return rv;
+}
+
+
+/* parse arguments of read command */
+static int runread(int argc, char **argv){
+  char *path = NULL;
+  char *tstr = NULL;
+  int omode = 0;
+  bool wb = false;
+  bool rnd = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= FDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= FDBOLCKNB;
+      } else if(!strcmp(argv[i], "-wb")){
+        wb = true;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !tstr) usage();
+  int tnum = tcatoix(tstr);
+  if(tnum < 1) usage();
+  int rv = procread(path, tnum, omode, wb, rnd);
+  return rv;
+}
+
+
+/* parse arguments of remove command */
+static int runremove(int argc, char **argv){
+  char *path = NULL;
+  char *tstr = NULL;
+  int omode = 0;
+  bool rnd = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= FDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= FDBOLCKNB;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !tstr) usage();
+  int tnum = tcatoix(tstr);
+  if(tnum < 1) usage();
+  int rv = procremove(path, tnum, omode, rnd);
+  return rv;
+}
+
+
+/* parse arguments of wicked command */
+static int runwicked(int argc, char **argv){
+  char *path = NULL;
+  char *tstr = NULL;
+  char *rstr = NULL;
+  int omode = 0;
+  bool nc = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= FDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= FDBOLCKNB;
+      } else if(!strcmp(argv[i], "-nc")){
+        nc = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !tstr || !rstr) usage();
+  int tnum = tcatoix(tstr);
+  int rnum = tcatoix(rstr);
+  if(tnum < 1 || rnum < 1) usage();
+  int rv = procwicked(path, tnum, rnum, omode, nc);
+  return rv;
+}
+
+
+/* parse arguments of typical command */
+static int runtypical(int argc, char **argv){
+  char *path = NULL;
+  char *tstr = NULL;
+  char *rstr = NULL;
+  char *wstr = NULL;
+  char *lstr = NULL;
+  int omode = 0;
+  int rratio = -1;
+  bool nc = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= FDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= FDBOLCKNB;
+      } else if(!strcmp(argv[i], "-nc")){
+        nc = true;
+      } else if(!strcmp(argv[i], "-rr")){
+        if(++i >= argc) usage();
+        rratio = tcatoix(argv[i]);
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else if(!wstr){
+      wstr = argv[i];
+    } else if(!lstr){
+      lstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !tstr || !rstr) usage();
+  int tnum = tcatoix(tstr);
+  int rnum = tcatoix(rstr);
+  if(tnum < 1 || rnum < 1) usage();
+  int width = wstr ? tcatoix(wstr) : -1;
+  int64_t limsiz = lstr ? tcatoix(lstr) : -1;
+  int rv = proctypical(path, tnum, rnum, width, limsiz, omode, nc, rratio);
+  return rv;
+}
+
+
+/* perform write command */
+static int procwrite(const char *path, int tnum, int rnum, int width, int64_t limsiz,
+                     int omode, bool rnd){
+  iprintf("<Writing Test>\n  seed=%u  path=%s  tnum=%d  rnum=%d  width=%d  limsiz=%lld"
+          "  omode=%d  rnd=%d\n\n",
+          g_randseed, path, tnum, rnum, width, (long long)limsiz, omode, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCFDB *fdb = tcfdbnew();
+  if(g_dbgfd >= 0) tcfdbsetdbgfd(fdb, g_dbgfd);
+  if(!tcfdbsetmutex(fdb)){
+    eprint(fdb, __LINE__, "tcfdbsetmutex");
+    err = true;
+  }
+  if(!tcfdbtune(fdb, width, limsiz)){
+    eprint(fdb, __LINE__, "tcfdbtune");
+    err = true;
+  }
+  if(!tcfdbopen(fdb, path, FDBOWRITER | FDBOCREAT | FDBOTRUNC | omode)){
+    eprint(fdb, __LINE__, "tcfdbopen");
+    err = true;
+  }
+  TARGWRITE targs[tnum];
+  pthread_t threads[tnum];
+  if(tnum == 1){
+    targs[0].fdb = fdb;
+    targs[0].rnum = rnum;
+    targs[0].rnd = rnd;
+    targs[0].id = 0;
+    if(threadwrite(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].fdb = fdb;
+      targs[i].rnum = rnum;
+      targs[i].rnd = rnd;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadwrite, targs + i) != 0){
+        eprint(fdb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(fdb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcfdbrnum(fdb));
+  iprintf("size: %llu\n", (unsigned long long)tcfdbfsiz(fdb));
+  mprint(fdb);
+  sysprint();
+  if(!tcfdbclose(fdb)){
+    eprint(fdb, __LINE__, "tcfdbclose");
+    err = true;
+  }
+  tcfdbdel(fdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform read command */
+static int procread(const char *path, int tnum, int omode, bool wb, bool rnd){
+  iprintf("<Reading Test>\n  seed=%u  path=%s  tnum=%d  omode=%d  wb=%d  rnd=%d\n\n",
+          g_randseed, path, tnum, omode, wb, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCFDB *fdb = tcfdbnew();
+  if(g_dbgfd >= 0) tcfdbsetdbgfd(fdb, g_dbgfd);
+  if(!tcfdbsetmutex(fdb)){
+    eprint(fdb, __LINE__, "tcfdbsetmutex");
+    err = true;
+  }
+  if(!tcfdbopen(fdb, path, FDBOREADER | omode)){
+    eprint(fdb, __LINE__, "tcfdbopen");
+    err = true;
+  }
+  int rnum = tcfdbrnum(fdb) / tnum;
+  TARGREAD targs[tnum];
+  pthread_t threads[tnum];
+  if(tnum == 1){
+    targs[0].fdb = fdb;
+    targs[0].rnum = rnum;
+    targs[0].wb = wb;
+    targs[0].rnd = rnd;
+    targs[0].id = 0;
+    if(threadread(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].fdb = fdb;
+      targs[i].rnum = rnum;
+      targs[i].wb = wb;
+      targs[i].rnd = rnd;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadread, targs + i) != 0){
+        eprint(fdb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(fdb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcfdbrnum(fdb));
+  iprintf("size: %llu\n", (unsigned long long)tcfdbfsiz(fdb));
+  mprint(fdb);
+  sysprint();
+  if(!tcfdbclose(fdb)){
+    eprint(fdb, __LINE__, "tcfdbclose");
+    err = true;
+  }
+  tcfdbdel(fdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform remove command */
+static int procremove(const char *path, int tnum, int omode, bool rnd){
+  iprintf("<Removing Test>\n  seed=%u  path=%s  tnum=%d  omode=%d  rnd=%d\n\n",
+          g_randseed, path, tnum, omode, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCFDB *fdb = tcfdbnew();
+  if(g_dbgfd >= 0) tcfdbsetdbgfd(fdb, g_dbgfd);
+  if(!tcfdbsetmutex(fdb)){
+    eprint(fdb, __LINE__, "tcfdbsetmutex");
+    err = true;
+  }
+  if(!tcfdbopen(fdb, path, FDBOWRITER | omode)){
+    eprint(fdb, __LINE__, "tcfdbopen");
+    err = true;
+  }
+  int rnum = tcfdbrnum(fdb) / tnum;
+  TARGREMOVE targs[tnum];
+  pthread_t threads[tnum];
+  if(tnum == 1){
+    targs[0].fdb = fdb;
+    targs[0].rnum = rnum;
+    targs[0].rnd = rnd;
+    targs[0].id = 0;
+    if(threadremove(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].fdb = fdb;
+      targs[i].rnum = rnum;
+      targs[i].rnd = rnd;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadremove, targs + i) != 0){
+        eprint(fdb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(fdb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcfdbrnum(fdb));
+  iprintf("size: %llu\n", (unsigned long long)tcfdbfsiz(fdb));
+  mprint(fdb);
+  sysprint();
+  if(!tcfdbclose(fdb)){
+    eprint(fdb, __LINE__, "tcfdbclose");
+    err = true;
+  }
+  tcfdbdel(fdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform wicked command */
+static int procwicked(const char *path, int tnum, int rnum, int omode, bool nc){
+  iprintf("<Writing Test>\n  seed=%u  path=%s  tnum=%d  rnum=%d  omode=%d  nc=%d\n\n",
+          g_randseed, path, tnum, rnum, omode, nc);
+  bool err = false;
+  double stime = tctime();
+  TCFDB *fdb = tcfdbnew();
+  if(g_dbgfd >= 0) tcfdbsetdbgfd(fdb, g_dbgfd);
+  if(!tcfdbsetmutex(fdb)){
+    eprint(fdb, __LINE__, "tcfdbsetmutex");
+    err = true;
+  }
+  if(!tcfdbtune(fdb, RECBUFSIZ * 2, EXHEADSIZ + (RECBUFSIZ * 2 + sizeof(int)) * rnum * tnum)){
+    eprint(fdb, __LINE__, "tcfdbtune");
+    err = true;
+  }
+  if(!tcfdbopen(fdb, path, FDBOWRITER | FDBOCREAT | FDBOTRUNC | omode)){
+    eprint(fdb, __LINE__, "tcfdbopen");
+    err = true;
+  }
+  if(!tcfdbiterinit(fdb)){
+    eprint(fdb, __LINE__, "tcfdbiterinit");
+    err = true;
+  }
+  TARGWICKED targs[tnum];
+  pthread_t threads[tnum];
+  TCMAP *map = tcmapnew();
+  if(tnum == 1){
+    targs[0].fdb = fdb;
+    targs[0].rnum = rnum;
+    targs[0].nc = nc;
+    targs[0].id = 0;
+    targs[0].map = map;
+    if(threadwicked(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].fdb = fdb;
+      targs[i].rnum = rnum;
+      targs[i].nc = nc;
+      targs[i].id = i;
+      targs[i].map = map;
+      if(pthread_create(threads + i, NULL, threadwicked, targs + i) != 0){
+        eprint(fdb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(fdb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  if(!nc){
+    if(!tcfdbsync(fdb)){
+      eprint(fdb, __LINE__, "tcfdbsync");
+      err = true;
+    }
+    if(tcfdbrnum(fdb) != tcmaprnum(map)){
+      eprint(fdb, __LINE__, "(validation)");
+      err = true;
+    }
+    int end = rnum * tnum;
+    for(int i = 1; i <= end && !err; i++){
+      char kbuf[RECBUFSIZ];
+      int ksiz = sprintf(kbuf, "%d", i);
+      int vsiz;
+      const char *vbuf = tcmapget(map, kbuf, ksiz, &vsiz);
+      int rsiz;
+      char *rbuf = tcfdbget2(fdb, kbuf, ksiz, &rsiz);
+      if(vbuf){
+        iputchar('.');
+        if(vsiz > tcfdbwidth(fdb)) vsiz = tcfdbwidth(fdb);
+        if(!rbuf){
+          eprint(fdb, __LINE__, "tcfdbget");
+          err = true;
+        } else if(rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+          eprint(fdb, __LINE__, "(validation)");
+          err = true;
+        }
+      } else {
+        iputchar('*');
+        if(rbuf || tcfdbecode(fdb) != TCENOREC){
+          eprint(fdb, __LINE__, "(validation)");
+          err = true;
+        }
+      }
+      tcfree(rbuf);
+      if(i % 50 == 0) iprintf(" (%08d)\n", i);
+    }
+    if(rnum % 50 > 0) iprintf(" (%08d)\n", rnum);
+  }
+  tcmapdel(map);
+  iprintf("record number: %llu\n", (unsigned long long)tcfdbrnum(fdb));
+  iprintf("size: %llu\n", (unsigned long long)tcfdbfsiz(fdb));
+  mprint(fdb);
+  sysprint();
+  if(!tcfdbclose(fdb)){
+    eprint(fdb, __LINE__, "tcfdbclose");
+    err = true;
+  }
+  tcfdbdel(fdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform typical command */
+static int proctypical(const char *path, int tnum, int rnum, int width, int64_t limsiz,
+                       int omode, bool nc, int rratio){
+  iprintf("<Typical Access Test>\n  seed=%u  path=%s  tnum=%d  rnum=%d  width=%d  limsiz=%lld"
+          "  omode=%d  nc=%d  rratio=%d\n\n",
+          g_randseed, path, tnum, rnum, width, (long long)limsiz, omode, nc, rratio);
+  bool err = false;
+  double stime = tctime();
+  TCFDB *fdb = tcfdbnew();
+  if(g_dbgfd >= 0) tcfdbsetdbgfd(fdb, g_dbgfd);
+  if(!tcfdbsetmutex(fdb)){
+    eprint(fdb, __LINE__, "tcfdbsetmutex");
+    err = true;
+  }
+  if(!tcfdbtune(fdb, width, limsiz)){
+    eprint(fdb, __LINE__, "tcfdbtune");
+    err = true;
+  }
+  if(!tcfdbopen(fdb, path, FDBOWRITER | FDBOCREAT | FDBOTRUNC | omode)){
+    eprint(fdb, __LINE__, "tcfdbopen");
+    err = true;
+  }
+  TARGTYPICAL targs[tnum];
+  pthread_t threads[tnum];
+  if(tnum == 1){
+    targs[0].fdb = fdb;
+    targs[0].rnum = rnum;
+    targs[0].nc = nc;
+    targs[0].rratio = rratio;
+    targs[0].id = 0;
+    if(threadtypical(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].fdb = fdb;
+      targs[i].rnum = rnum;
+      targs[i].nc = nc;
+      targs[i].rratio= rratio;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadtypical, targs + i) != 0){
+        eprint(fdb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(fdb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcfdbrnum(fdb));
+  iprintf("size: %llu\n", (unsigned long long)tcfdbfsiz(fdb));
+  mprint(fdb);
+  sysprint();
+  if(!tcfdbclose(fdb)){
+    eprint(fdb, __LINE__, "tcfdbclose");
+    err = true;
+  }
+  tcfdbdel(fdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* thread the write function */
+static void *threadwrite(void *targ){
+  TCFDB *fdb = ((TARGWRITE *)targ)->fdb;
+  int rnum = ((TARGWRITE *)targ)->rnum;
+  bool rnd = ((TARGWRITE *)targ)->rnd;
+  int id = ((TARGWRITE *)targ)->id;
+  bool err = false;
+  int base = id * rnum;
+  for(int i = 1; i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", base + (rnd ? myrand(i) + 1 : i));
+    if(!tcfdbput2(fdb, buf, len, buf, len)){
+      eprint(fdb, __LINE__, "tcfdbput2");
+      err = true;
+      break;
+    }
+    if(id == 0 && rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  return err ? "error" : NULL;
+}
+
+
+/* thread the read function */
+static void *threadread(void *targ){
+  TCFDB *fdb = ((TARGREAD *)targ)->fdb;
+  int rnum = ((TARGREAD *)targ)->rnum;
+  bool wb = ((TARGREAD *)targ)->wb;
+  bool rnd = ((TARGREAD *)targ)->rnd;
+  int id = ((TARGREAD *)targ)->id;
+  bool err = false;
+  int base = id * rnum;
+  for(int i = 1; i <= rnum && !err; i++){
+    uint64_t kid = base + (rnd ? myrandnd(i) + 1 : i);
+    int vsiz;
+    if(wb){
+      char vbuf[RECBUFSIZ];
+      int vsiz = tcfdbget4(fdb, kid, vbuf, RECBUFSIZ);
+      if(vsiz < 0 && (!rnd || tcfdbecode(fdb) != TCENOREC)){
+        eprint(fdb, __LINE__, "tcfdbget4");
+        err = true;
+      }
+    } else {
+      char *vbuf = tcfdbget(fdb, kid, &vsiz);
+      if(!vbuf && (!rnd || tcfdbecode(fdb) != TCENOREC)){
+        eprint(fdb, __LINE__, "tcfdbget");
+        err = true;
+      }
+      tcfree(vbuf);
+    }
+    if(id == 0 && rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  return err ? "error" : NULL;
+}
+
+
+/* thread the remove function */
+static void *threadremove(void *targ){
+  TCFDB *fdb = ((TARGREMOVE *)targ)->fdb;
+  int rnum = ((TARGREMOVE *)targ)->rnum;
+  bool rnd = ((TARGREMOVE *)targ)->rnd;
+  int id = ((TARGREMOVE *)targ)->id;
+  bool err = false;
+  int base = id * rnum;
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%08d", base + (rnd ? myrand(i + 1) + 1 : i));
+    if(!tcfdbout2(fdb, kbuf, ksiz) && (!rnd || tcfdbecode(fdb) != TCENOREC)){
+      eprint(fdb, __LINE__, "tcfdbout2");
+      err = true;
+      break;
+    }
+    if(id == 0 && rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  return err ? "error" : NULL;
+}
+
+
+/* thread the wicked function */
+static void *threadwicked(void *targ){
+  TCFDB *fdb = ((TARGWICKED *)targ)->fdb;
+  int rnum = ((TARGWICKED *)targ)->rnum;
+  bool nc = ((TARGWICKED *)targ)->nc;
+  int id = ((TARGWICKED *)targ)->id;
+  TCMAP *map = ((TARGWICKED *)targ)->map;
+  bool err = false;
+  for(int i = 1; i <= rnum && !err; i++){
+    uint64_t kid = myrand(rnum * (id + 1)) + 1;
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%llu", (unsigned long long)kid);
+    char vbuf[RECBUFSIZ];
+    int vsiz = myrand(RECBUFSIZ);
+    memset(vbuf, '*', vsiz);
+    vbuf[vsiz] = '\0';
+    char *rbuf;
+    if(!nc) tcglobalmutexlock();
+    switch(myrand(16)){
+      case 0:
+        if(id == 0) iputchar('0');
+        if(!tcfdbput2(fdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(fdb, __LINE__, "tcfdbput2");
+          err = true;
+        }
+        if(!nc) tcmapput(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 1:
+        if(id == 0) iputchar('1');
+        if(!tcfdbput3(fdb, kbuf, vbuf)){
+          eprint(fdb, __LINE__, "tcfdbput3");
+          err = true;
+        }
+        if(!nc) tcmapput2(map, kbuf, vbuf);
+        break;
+      case 2:
+        if(id == 0) iputchar('2');
+        if(!tcfdbputkeep2(fdb, kbuf, ksiz, vbuf, vsiz) && tcfdbecode(fdb) != TCEKEEP){
+          eprint(fdb, __LINE__, "tcfdbputkeep2");
+          err = true;
+        }
+        if(!nc) tcmapputkeep(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 3:
+        if(id == 0) iputchar('3');
+        if(!tcfdbputkeep3(fdb, kbuf, vbuf) && tcfdbecode(fdb) != TCEKEEP){
+          eprint(fdb, __LINE__, "tcfdbputkeep3");
+          err = true;
+        }
+        if(!nc) tcmapputkeep2(map, kbuf, vbuf);
+        break;
+      case 4:
+        if(id == 0) iputchar('4');
+        if(!tcfdbputcat2(fdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(fdb, __LINE__, "tcfdbputcat2");
+          err = true;
+        }
+        if(!nc) tcmapputcat(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 5:
+        if(id == 0) iputchar('5');
+        if(!tcfdbputcat3(fdb, kbuf, vbuf)){
+          eprint(fdb, __LINE__, "tcfdbputcat3");
+          err = true;
+        }
+        if(!nc) tcmapputcat2(map, kbuf, vbuf);
+        break;
+      case 6:
+        if(id == 0) iputchar('6');
+        if(myrand(2) == 0){
+          if(!tcfdbout2(fdb, kbuf, ksiz) && tcfdbecode(fdb) != TCENOREC){
+            eprint(fdb, __LINE__, "tcfdbout2");
+            err = true;
+          }
+          if(!nc) tcmapout(map, kbuf, ksiz);
+        }
+        break;
+      case 7:
+        if(id == 0) iputchar('7');
+        if(myrand(2) == 0){
+          if(!tcfdbout3(fdb, kbuf) && tcfdbecode(fdb) != TCENOREC){
+            eprint(fdb, __LINE__, "tcfdbout3");
+            err = true;
+          }
+          if(!nc) tcmapout2(map, kbuf);
+        }
+        break;
+      case 8:
+        if(id == 0) iputchar('8');
+        if(!(rbuf = tcfdbget2(fdb, kbuf, ksiz, &vsiz))){
+          if(tcfdbecode(fdb) != TCENOREC){
+            eprint(fdb, __LINE__, "tcfdbget2");
+            err = true;
+          }
+          rbuf = tcsprintf("[%d]", myrand(i + 1));
+          vsiz = strlen(rbuf);
+        }
+        vsiz += myrand(vsiz);
+        if(myrand(3) == 0) vsiz += PATH_MAX;
+        rbuf = tcrealloc(rbuf, vsiz + 1);
+        for(int j = 0; j < vsiz; j++){
+          rbuf[j] = myrand(0x100);
+        }
+        if(!tcfdbput2(fdb, kbuf, ksiz, rbuf, vsiz)){
+          eprint(fdb, __LINE__, "tcfdbput2");
+          err = true;
+        }
+        if(!nc) tcmapput(map, kbuf, ksiz, rbuf, vsiz);
+        tcfree(rbuf);
+        break;
+      case 9:
+        if(id == 0) iputchar('9');
+        if(!(rbuf = tcfdbget2(fdb, kbuf, ksiz, &vsiz)) && tcfdbecode(fdb) != TCENOREC){
+          eprint(fdb, __LINE__, "tcfdbget2");
+          err = true;
+        }
+        tcfree(rbuf);
+        break;
+      case 10:
+        if(id == 0) iputchar('A');
+        if(!(rbuf = tcfdbget3(fdb, kbuf)) && tcfdbecode(fdb) != TCENOREC){
+          eprint(fdb, __LINE__, "tcfdbge3");
+          err = true;
+        }
+        tcfree(rbuf);
+        break;
+      case 11:
+        if(id == 0) iputchar('B');
+        if(myrand(1) == 0) vsiz = 1;
+        if((vsiz = tcfdbget4(fdb, kid, vbuf, vsiz)) < 0 && tcfdbecode(fdb) != TCENOREC){
+          eprint(fdb, __LINE__, "tcfdbget4");
+          err = true;
+        }
+        break;
+      case 14:
+        if(id == 0) iputchar('E');
+        if(myrand(rnum / 50) == 0){
+          if(!tcfdbiterinit(fdb)){
+            eprint(fdb, __LINE__, "tcfdbiterinit");
+            err = true;
+          }
+        }
+        for(int j = myrand(rnum) / 1000 + 1; j >= 0; j--){
+          if(tcfdbiternext(fdb) < 1){
+            int ecode = tcfdbecode(fdb);
+            if(ecode != TCEINVALID && ecode != TCENOREC){
+              eprint(fdb, __LINE__, "tcfdbiternext");
+              err = true;
+            }
+          }
+        }
+        break;
+      default:
+        if(id == 0) iputchar('@');
+        if(tcfdbtranbegin(fdb)){
+          if(myrand(2) == 0){
+            if(!tcfdbput2(fdb, kbuf, ksiz, vbuf, vsiz)){
+              eprint(fdb, __LINE__, "tcfdbput");
+              err = true;
+            }
+            if(!nc) tcmapput(map, kbuf, ksiz, vbuf, vsiz);
+          } else {
+            if(!tcfdbout2(fdb, kbuf, ksiz) && tcfdbecode(fdb) != TCENOREC){
+              eprint(fdb, __LINE__, "tcfdbout");
+              err = true;
+            }
+            if(!nc) tcmapout(map, kbuf, ksiz);
+          }
+          if(nc && myrand(2) == 0){
+            if(!tcfdbtranabort(fdb)){
+              eprint(fdb, __LINE__, "tcfdbtranabort");
+              err = true;
+            }
+          } else {
+            if(!tcfdbtrancommit(fdb)){
+              eprint(fdb, __LINE__, "tcfdbtrancommit");
+              err = true;
+            }
+          }
+        } else {
+          eprint(fdb, __LINE__, "tcfdbtranbegin");
+          err = true;
+        }
+        if(myrand(1000) == 0){
+          if(!tcfdbforeach(fdb, iterfunc, NULL)){
+            eprint(fdb, __LINE__, "tcfdbforeach");
+            err = true;
+          }
+        }
+        if(myrand(10000) == 0) srand((unsigned int)(tctime() * 1000) % UINT_MAX);
+        break;
+    }
+    if(!nc) tcglobalmutexunlock();
+    if(id == 0){
+      if(i % 50 == 0) iprintf(" (%08d)\n", i);
+      if(id == 0 && i == rnum / 4){
+        if(!tcfdboptimize(fdb, RECBUFSIZ, -1) && tcfdbecode(fdb) != TCEINVALID){
+          eprint(fdb, __LINE__, "tcfdboptimize");
+          err = true;
+        }
+        if(!tcfdbiterinit(fdb)){
+          eprint(fdb, __LINE__, "tcfdbiterinit");
+          err = true;
+        }
+      }
+    }
+  }
+  return err ? "error" : NULL;
+}
+
+
+/* thread the typical function */
+static void *threadtypical(void *targ){
+  TCFDB *fdb = ((TARGTYPICAL *)targ)->fdb;
+  int rnum = ((TARGTYPICAL *)targ)->rnum;
+  bool nc = ((TARGTYPICAL *)targ)->nc;
+  int rratio = ((TARGTYPICAL *)targ)->rratio;
+  int id = ((TARGTYPICAL *)targ)->id;
+  bool err = false;
+  TCMAP *map = (!nc && id == 0) ? tcmapnew2(rnum + 1) : NULL;
+  int base = id * rnum;
+  int mrange = tclmax(50 + rratio, 100);
+  int width = tcfdbwidth(fdb);
+  for(int i = 1; !err && i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", base + myrandnd(i) + 1);
+    int rnd = myrand(mrange);
+    if(rnd < 10){
+      if(!tcfdbput2(fdb, buf, len, buf, len)){
+        eprint(fdb, __LINE__, "tcfdbput2");
+        err = true;
+      }
+      if(map) tcmapput(map, buf, len, buf, len);
+    } else if(rnd < 15){
+      if(!tcfdbputkeep2(fdb, buf, len, buf, len) && tcfdbecode(fdb) != TCEKEEP){
+        eprint(fdb, __LINE__, "tcfdbputkeep2");
+        err = true;
+      }
+      if(map) tcmapputkeep(map, buf, len, buf, len);
+    } else if(rnd < 20){
+      if(!tcfdbputcat2(fdb, buf, len, buf, len)){
+        eprint(fdb, __LINE__, "tcfdbputcat2");
+        err = true;
+      }
+      if(map) tcmapputcat(map, buf, len, buf, len);
+    } else if(rnd < 25){
+      if(!tcfdbout2(fdb, buf, len) && tcfdbecode(fdb) && tcfdbecode(fdb) != TCENOREC){
+        eprint(fdb, __LINE__, "tcfdbout");
+        err = true;
+      }
+      if(map) tcmapout(map, buf, len);
+    } else if(rnd < 26){
+      if(myrand(10) == 0 && !tcfdbiterinit(fdb) && tcfdbecode(fdb) != TCENOREC){
+        eprint(fdb, __LINE__, "tcfdbiterinit");
+        err = true;
+      }
+      for(int j = 0; !err && j < 10; j++){
+        if(tcfdbiternext(fdb) < 1 &&
+           tcfdbecode(fdb) != TCEINVALID && tcfdbecode(fdb) != TCENOREC){
+          eprint(fdb, __LINE__, "tcfdbiternext");
+          err = true;
+        }
+      }
+    } else {
+      int vsiz;
+      char *vbuf = tcfdbget2(fdb, buf, len, &vsiz);
+      if(vbuf){
+        if(map){
+          int msiz = 0;
+          const char *mbuf = tcmapget(map, buf, len, &msiz);
+          if(msiz > width) msiz = width;
+          if(!mbuf || msiz != vsiz || memcmp(mbuf, vbuf, vsiz)){
+            eprint(fdb, __LINE__, "(validation)");
+            err = true;
+          }
+        }
+        tcfree(vbuf);
+      } else {
+        if(tcfdbecode(fdb) != TCENOREC){
+          eprint(fdb, __LINE__, "tcfdbget");
+          err = true;
+        }
+        if(map && tcmapget(map, buf, len, &vsiz)){
+          eprint(fdb, __LINE__, "(validation)");
+          err = true;
+        }
+      }
+    }
+    if(id == 0 && rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(map){
+    tcmapiterinit(map);
+    int ksiz;
+    const char *kbuf;
+    while(!err && (kbuf = tcmapiternext(map, &ksiz)) != NULL){
+      int vsiz;
+      char *vbuf = tcfdbget2(fdb, kbuf, ksiz, &vsiz);
+      if(vbuf){
+        int msiz = 0;
+        const char *mbuf = tcmapget(map, kbuf, ksiz, &msiz);
+        if(msiz > width) msiz = width;
+        if(!mbuf || msiz != vsiz || memcmp(mbuf, vbuf, vsiz)){
+          eprint(fdb, __LINE__, "(validation)");
+          err = true;
+        }
+        tcfree(vbuf);
+      } else {
+        eprint(fdb, __LINE__, "(validation)");
+        err = true;
+      }
+    }
+    tcmapdel(map);
+  }
+  return err ? "error" : NULL;
+}
+
+
+
+// END OF FILE
diff --git a/tcejdb/tcftest.c b/tcejdb/tcftest.c
new file mode 100644 (file)
index 0000000..a11c273
--- /dev/null
@@ -0,0 +1,1695 @@
+/*************************************************************************************************
+ * The test cases of the fixed-length database API
+ *                                                               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 RECBUFSIZ      48                // buffer for records
+#define EXHEADSIZ      256               // expected header size
+
+
+/* global variables */
+const char *g_progname;                  // program name
+unsigned int g_randseed;                 // random seed
+int g_dbgfd;                             // debugging output
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+static void usage(void);
+static void iprintf(const char *format, ...);
+static void iputchar(int c);
+static void eprint(TCFDB *fdb, int line, const char *func);
+static void mprint(TCFDB *fdb);
+static void sysprint(void);
+static int myrand(int range);
+static void *pdprocfunc(const void *vbuf, int vsiz, int *sp, void *op);
+static bool iterfunc(const void *kbuf, int ksiz, const void *vbuf, int vsiz, void *op);
+static int runwrite(int argc, char **argv);
+static int runread(int argc, char **argv);
+static int runremove(int argc, char **argv);
+static int runrcat(int argc, char **argv);
+static int runmisc(int argc, char **argv);
+static int runwicked(int argc, char **argv);
+static int procwrite(const char *path, int rnum, int width, int64_t limsiz,
+                     bool mt, int omode, bool rnd);
+static int procread(const char *path, bool mt, int omode, bool wb, bool rnd);
+static int procremove(const char *path, bool mt, int omode, bool rnd);
+static int procrcat(const char *path, int rnum, int width, int64_t limsiz,
+                    bool mt, int omode, int pnum, bool dai, bool dad, bool rl, bool ru);
+static int procmisc(const char *path, int rnum, bool mt, int omode);
+static int procwicked(const char *path, int rnum, bool mt, int omode);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  g_progname = argv[0];
+  const char *ebuf = getenv("TCRNDSEED");
+  g_randseed = ebuf ? tcatoix(ebuf) : tctime() * 1000;
+  srand(g_randseed);
+  ebuf = getenv("TCDBGFD");
+  g_dbgfd = ebuf ? tcatoix(ebuf) : UINT16_MAX;
+  if(argc < 2) usage();
+  int rv = 0;
+  if(!strcmp(argv[1], "write")){
+    rv = runwrite(argc, argv);
+  } else if(!strcmp(argv[1], "read")){
+    rv = runread(argc, argv);
+  } else if(!strcmp(argv[1], "remove")){
+    rv = runremove(argc, argv);
+  } else if(!strcmp(argv[1], "rcat")){
+    rv = runrcat(argc, argv);
+  } else if(!strcmp(argv[1], "misc")){
+    rv = runmisc(argc, argv);
+  } else if(!strcmp(argv[1], "wicked")){
+    rv = runwicked(argc, argv);
+  } else {
+    usage();
+  }
+  if(rv != 0){
+    printf("FAILED: TCRNDSEED=%u PID=%d", g_randseed, (int)getpid());
+    for(int i = 0; i < argc; i++){
+      printf(" %s", argv[i]);
+    }
+    printf("\n\n");
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+static void usage(void){
+  fprintf(stderr, "%s: test cases of the fixed-length database API of Tokyo Cabinet\n",
+          g_progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s write [-mt] [-nl|-nb] [-rnd] path rnum [width [limsiz]]\n", g_progname);
+  fprintf(stderr, "  %s read [-mt] [-nl|-nb] [-wb] [-rnd] path\n", g_progname);
+  fprintf(stderr, "  %s remove [-mt] [-nl|-nb] [-rnd] path\n", g_progname);
+  fprintf(stderr, "  %s rcat [-mt] [-nl|-nb] [-pn num] [-dai|-dad|-rl|-ru]"
+          " path rnum [width [limsiz]]\n", g_progname);
+  fprintf(stderr, "  %s misc [-mt] [-nl|-nb] path rnum\n", g_progname);
+  fprintf(stderr, "  %s wicked [-mt] [-nl|-nb] path rnum\n", g_progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* print formatted information string and flush the buffer */
+static void iprintf(const char *format, ...){
+  va_list ap;
+  va_start(ap, format);
+  vprintf(format, ap);
+  fflush(stdout);
+  va_end(ap);
+}
+
+
+/* print a character and flush the buffer */
+static void iputchar(int c){
+  putchar(c);
+  fflush(stdout);
+}
+
+
+/* print error message of fixed-length database */
+static void eprint(TCFDB *fdb, int line, const char *func){
+  const char *path = tcfdbpath(fdb);
+  int ecode = tcfdbecode(fdb);
+  fprintf(stderr, "%s: %s: %d: %s: error: %d: %s\n",
+          g_progname, path ? path : "-", line, func, ecode, tcfdberrmsg(ecode));
+}
+
+
+/* print members of fixed-length database */
+static void mprint(TCFDB *fdb){
+  if(fdb->cnt_writerec < 0) return;
+  iprintf("minimum ID number: %llu\n", (unsigned long long)tcfdbmin(fdb));
+  iprintf("maximum ID number: %llu\n", (unsigned long long)tcfdbmax(fdb));
+  iprintf("width of the value: %u\n", (unsigned int)tcfdbwidth(fdb));
+  iprintf("limit file size: %llu\n", (unsigned long long)tcfdblimsiz(fdb));
+  iprintf("limit ID number: %llu\n", (unsigned long long)tcfdblimid(fdb));
+  iprintf("cnt_writerec: %lld\n", (long long)fdb->cnt_writerec);
+  iprintf("cnt_readrec: %lld\n", (long long)fdb->cnt_readrec);
+  iprintf("cnt_truncfile: %lld\n", (long long)fdb->cnt_truncfile);
+}
+
+
+/* print system information */
+static void sysprint(void){
+  TCMAP *info = tcsysinfo();
+  if(info){
+    tcmapiterinit(info);
+    const char *kbuf;
+    while((kbuf = tcmapiternext2(info)) != NULL){
+      iprintf("sys_%s: %s\n", kbuf, tcmapiterval2(kbuf));
+    }
+    tcmapdel(info);
+  }
+}
+
+
+/* get a random number */
+static int myrand(int range){
+  if(range < 2) return 0;
+  int high = (unsigned int)rand() >> 4;
+  int low = range * (rand() / (RAND_MAX + 1.0));
+  low &= (unsigned int)INT_MAX >> 4;
+  return (high + low) % range;
+}
+
+
+/* duplication callback function */
+static void *pdprocfunc(const void *vbuf, int vsiz, int *sp, void *op){
+  if(op){
+    char *buf = NULL;
+    int len = 0;
+    switch((int)(intptr_t)op){
+      case 1:
+        len = vsiz + 1;
+        buf = tcmalloc(len + 1);
+        memset(buf, '*', len);
+        break;
+      case 2:
+        buf = (void *)-1;
+        break;
+    }
+    *sp = len;
+    return buf;
+  }
+  if(myrand(4) == 0) return (void *)-1;
+  if(myrand(2) == 0) return NULL;
+  int len = myrand(RECBUFSIZ);
+  char buf[RECBUFSIZ];
+  memset(buf, '*', len);
+  *sp = len;
+  return tcmemdup(buf, len);
+}
+
+
+/* iterator function */
+static bool iterfunc(const void *kbuf, int ksiz, const void *vbuf, int vsiz, void *op){
+  unsigned int sum = 0;
+  while(--ksiz >= 0){
+    sum += ((char *)kbuf)[ksiz];
+  }
+  while(--vsiz >= 0){
+    sum += ((char *)vbuf)[vsiz];
+  }
+  return myrand(100 + (sum & 0xff)) > 0;
+}
+
+
+/* parse arguments of write command */
+static int runwrite(int argc, char **argv){
+  char *path = NULL;
+  char *rstr = NULL;
+  char *wstr = NULL;
+  char *lstr = NULL;
+  bool mt = false;
+  int omode = 0;
+  bool rnd = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= FDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= FDBOLCKNB;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else if(!wstr){
+      wstr = argv[i];
+    } else if(!lstr){
+      lstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int width = wstr ? tcatoix(wstr) : -1;
+  int64_t limsiz = lstr ? tcatoix(lstr) : -1;
+  int rv = procwrite(path, rnum, width, limsiz, mt, omode, rnd);
+  return rv;
+}
+
+
+/* parse arguments of read command */
+static int runread(int argc, char **argv){
+  char *path = NULL;
+  bool mt = false;
+  int omode = 0;
+  bool wb = false;
+  bool rnd = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= FDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= FDBOLCKNB;
+      } else if(!strcmp(argv[i], "-wb")){
+        wb = true;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int rv = procread(path, mt, omode, wb, rnd);
+  return rv;
+}
+
+
+/* parse arguments of remove command */
+static int runremove(int argc, char **argv){
+  char *path = NULL;
+  bool mt = false;
+  int omode = 0;
+  bool rnd = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= FDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= FDBOLCKNB;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int rv = procremove(path, mt, omode, rnd);
+  return rv;
+}
+
+
+/* parse arguments of rcat command */
+static int runrcat(int argc, char **argv){
+  char *path = NULL;
+  char *rstr = NULL;
+  char *wstr = NULL;
+  char *lstr = NULL;
+  bool mt = false;
+  int omode = 0;
+  int pnum = 0;
+  bool dai = false;
+  bool dad = false;
+  bool rl = false;
+  bool ru = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= FDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= FDBOLCKNB;
+      } else if(!strcmp(argv[i], "-pn")){
+        if(++i >= argc) usage();
+        pnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-dai")){
+        dai = true;
+      } else if(!strcmp(argv[i], "-dad")){
+        dad = true;
+      } else if(!strcmp(argv[i], "-rl")){
+        rl = true;
+      } else if(!strcmp(argv[i], "-ru")){
+        ru = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else if(!wstr){
+      wstr = argv[i];
+    } else if(!lstr){
+      lstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int width = wstr ? tcatoix(wstr) : -1;
+  int64_t limsiz = lstr ? tcatoix(lstr) : -1;
+  int rv = procrcat(path, rnum, width, limsiz, mt, omode, pnum, dai, dad, rl, ru);
+  return rv;
+}
+
+
+/* parse arguments of misc command */
+static int runmisc(int argc, char **argv){
+  char *path = NULL;
+  char *rstr = NULL;
+  bool mt = false;
+  int omode = 0;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= FDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= FDBOLCKNB;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int rv = procmisc(path, rnum, mt, omode);
+  return rv;
+}
+
+
+/* parse arguments of wicked command */
+static int runwicked(int argc, char **argv){
+  char *path = NULL;
+  char *rstr = NULL;
+  bool mt = false;
+  int omode = 0;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= FDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= FDBOLCKNB;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int rv = procwicked(path, rnum, mt, omode);
+  return rv;
+}
+
+
+/* perform write command */
+static int procwrite(const char *path, int rnum, int width, int64_t limsiz,
+                     bool mt, int omode, bool rnd){
+  iprintf("<Writing Test>\n  seed=%u  path=%s  rnum=%d  width=%d  limsiz=%lld  mt=%d  omode=%d"
+          "  rnd=%d\n\n", g_randseed, path, rnum, width, (long long)limsiz, mt, omode, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCFDB *fdb = tcfdbnew();
+  if(g_dbgfd >= 0) tcfdbsetdbgfd(fdb, g_dbgfd);
+  if(mt && !tcfdbsetmutex(fdb)){
+    eprint(fdb, __LINE__, "tcfdbsetmutex");
+    err = true;
+  }
+  if(!tcfdbtune(fdb, width, limsiz)){
+    eprint(fdb, __LINE__, "tcfdbtune");
+    err = true;
+  }
+  if(!rnd) omode |= FDBOTRUNC;
+  if(!tcfdbopen(fdb, path, FDBOWRITER | FDBOCREAT | omode)){
+    eprint(fdb, __LINE__, "tcfdbopen");
+    err = true;
+  }
+  for(int i = 1; i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", rnd ? myrand(rnum) + 1 : i);
+    if(!tcfdbput2(fdb, buf, len, buf, len)){
+      eprint(fdb, __LINE__, "tcfdbput");
+      err = true;
+      break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcfdbrnum(fdb));
+  iprintf("size: %llu\n", (unsigned long long)tcfdbfsiz(fdb));
+  mprint(fdb);
+  sysprint();
+  if(!tcfdbclose(fdb)){
+    eprint(fdb, __LINE__, "tcfdbclose");
+    err = true;
+  }
+  tcfdbdel(fdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform read command */
+static int procread(const char *path, bool mt, int omode, bool wb, bool rnd){
+  iprintf("<Reading Test>\n  seed=%u  path=%s  mt=%d  omode=%d  wb=%d  rnd=%d\n\n",
+          g_randseed, path, mt, omode, wb, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCFDB *fdb = tcfdbnew();
+  if(g_dbgfd >= 0) tcfdbsetdbgfd(fdb, g_dbgfd);
+  if(mt && !tcfdbsetmutex(fdb)){
+    eprint(fdb, __LINE__, "tcfdbsetmutex");
+    err = true;
+  }
+  if(!tcfdbopen(fdb, path, FDBOREADER | omode)){
+    eprint(fdb, __LINE__, "tcfdbopen");
+    err = true;
+  }
+  int rnum = tcfdbrnum(fdb);
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%08d", rnd ? myrand(rnum) + 1 : i);
+    int vsiz;
+    if(wb){
+      char vbuf[RECBUFSIZ];
+      int vsiz = tcfdbget4(fdb, i, vbuf, RECBUFSIZ);
+      if(vsiz < 0 && !(rnd && tcfdbecode(fdb) == TCENOREC)){
+        eprint(fdb, __LINE__, "tcfdbget4");
+        err = true;
+        break;
+      }
+    } else {
+      char *vbuf = tcfdbget2(fdb, kbuf, ksiz, &vsiz);
+      if(!vbuf && !(rnd && tcfdbecode(fdb) == TCENOREC)){
+        eprint(fdb, __LINE__, "tcfdbget");
+        err = true;
+        break;
+      }
+      tcfree(vbuf);
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcfdbrnum(fdb));
+  iprintf("size: %llu\n", (unsigned long long)tcfdbfsiz(fdb));
+  mprint(fdb);
+  sysprint();
+  if(!tcfdbclose(fdb)){
+    eprint(fdb, __LINE__, "tcfdbclose");
+    err = true;
+  }
+  tcfdbdel(fdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform remove command */
+static int procremove(const char *path, bool mt, int omode, bool rnd){
+  iprintf("<Removing Test>\n  seed=%u  path=%s  mt=%d  omode=%d  rnd=%d\n\n",
+          g_randseed, path, mt, omode, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCFDB *fdb = tcfdbnew();
+  if(g_dbgfd >= 0) tcfdbsetdbgfd(fdb, g_dbgfd);
+  if(mt && !tcfdbsetmutex(fdb)){
+    eprint(fdb, __LINE__, "tcfdbsetmutex");
+    err = true;
+  }
+  if(!tcfdbopen(fdb, path, FDBOWRITER | omode)){
+    eprint(fdb, __LINE__, "tcfdbopen");
+    err = true;
+  }
+  int rnum = tcfdbrnum(fdb);
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%08d", rnd ? myrand(rnum) + 1 : i);
+    if(!tcfdbout2(fdb, kbuf, ksiz) && !(rnd && tcfdbecode(fdb) == TCENOREC)){
+      eprint(fdb, __LINE__, "tcfdbout");
+      err = true;
+      break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcfdbrnum(fdb));
+  iprintf("size: %llu\n", (unsigned long long)tcfdbfsiz(fdb));
+  mprint(fdb);
+  sysprint();
+  if(!tcfdbclose(fdb)){
+    eprint(fdb, __LINE__, "tcfdbclose");
+    err = true;
+  }
+  tcfdbdel(fdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform rcat command */
+static int procrcat(const char *path, int rnum, int width, int64_t limsiz,
+                    bool mt, int omode, int pnum, bool dai, bool dad, bool rl, bool ru){
+  iprintf("<Random Concatenating Test>\n"
+          "  seed=%u  path=%s  rnum=%d  width=%d  limsiz=%lld  mt=%d  omode=%d  pnum=%d"
+          "  dai=%d  dad=%d  rl=%d  ru=%d\n\n",
+          g_randseed, path, rnum, width, (long long)limsiz, mt, omode, pnum, dai, dad, rl, ru);
+  if(pnum < 1) pnum = rnum;
+  bool err = false;
+  double stime = tctime();
+  TCFDB *fdb = tcfdbnew();
+  if(g_dbgfd >= 0) tcfdbsetdbgfd(fdb, g_dbgfd);
+  if(mt && !tcfdbsetmutex(fdb)){
+    eprint(fdb, __LINE__, "tcfdbsetmutex");
+    err = true;
+  }
+  if(!tcfdbtune(fdb, width, limsiz)){
+    eprint(fdb, __LINE__, "tcfdbtune");
+    err = true;
+  }
+  if(!tcfdbopen(fdb, path, FDBOWRITER | FDBOCREAT | FDBOTRUNC | omode)){
+    eprint(fdb, __LINE__, "tcfdbopen");
+    err = true;
+  }
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(pnum) + 1);
+    if(dai){
+      if(tcfdbaddint(fdb, tcfdbkeytoid(kbuf, ksiz), myrand(3)) == INT_MIN){
+        eprint(fdb, __LINE__, "tcfdbaddint");
+        err = true;
+        break;
+      }
+    } else if(dad){
+      if(isnan(tcfdbadddouble(fdb, tcfdbkeytoid(kbuf, ksiz), myrand(30) / 10.0))){
+        eprint(fdb, __LINE__, "tcfdbadddouble");
+        err = true;
+        break;
+      }
+    } else if(rl){
+      char vbuf[PATH_MAX];
+      int vsiz = myrand(PATH_MAX);
+      for(int j = 0; j < vsiz; j++){
+        vbuf[j] = myrand(0x100);
+      }
+      if(!tcfdbputcat2(fdb, kbuf, ksiz, vbuf, vsiz)){
+        eprint(fdb, __LINE__, "tcfdbputcat");
+        err = true;
+        break;
+      }
+    } else if(ru){
+      int id = myrand(pnum) + 1;
+      switch(myrand(8)){
+        case 0:
+          if(!tcfdbput(fdb, id, kbuf, ksiz)){
+            eprint(fdb, __LINE__, "tcfdbput");
+            err = true;
+          }
+          break;
+        case 1:
+          if(!tcfdbputkeep(fdb, id, kbuf, ksiz) && tcfdbecode(fdb) != TCEKEEP){
+            eprint(fdb, __LINE__, "tcfdbputkeep");
+            err = true;
+          }
+          break;
+        case 2:
+          if(!tcfdbout(fdb, id) && tcfdbecode(fdb) != TCENOREC){
+            eprint(fdb, __LINE__, "tcfdbout");
+            err = true;
+          }
+          break;
+        case 3:
+          if(tcfdbaddint(fdb, id, 1) == INT_MIN && tcfdbecode(fdb) != TCEKEEP){
+            eprint(fdb, __LINE__, "tcfdbaddint");
+            err = true;
+          }
+          break;
+        case 4:
+          if(isnan(tcfdbadddouble(fdb, id, 1.0)) && tcfdbecode(fdb) != TCEKEEP){
+            eprint(fdb, __LINE__, "tcfdbadddouble");
+            err = true;
+          }
+          break;
+        case 5:
+          if(myrand(2) == 0){
+            if(!tcfdbputproc(fdb, id, kbuf, ksiz, pdprocfunc, NULL) &&
+               tcfdbecode(fdb) != TCEKEEP){
+              eprint(fdb, __LINE__, "tcfdbputproc");
+              err = true;
+            }
+          } else {
+            if(!tcfdbputproc(fdb, id, NULL, 0, pdprocfunc, NULL) &&
+               tcfdbecode(fdb) != TCEKEEP && tcfdbecode(fdb) != TCENOREC){
+              eprint(fdb, __LINE__, "tcfdbputproc");
+              err = true;
+            }
+          }
+          break;
+        default:
+          if(!tcfdbputcat(fdb, id, kbuf, ksiz)){
+            eprint(fdb, __LINE__, "tcfdbputcat");
+            err = true;
+          }
+          break;
+      }
+      if(err) break;
+    } else {
+      if(!tcfdbputcat2(fdb, kbuf, ksiz, kbuf, ksiz)){
+        eprint(fdb, __LINE__, "tcfdbputcat");
+        err = true;
+        break;
+      }
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcfdbrnum(fdb));
+  iprintf("size: %llu\n", (unsigned long long)tcfdbfsiz(fdb));
+  mprint(fdb);
+  sysprint();
+  if(!tcfdbclose(fdb)){
+    eprint(fdb, __LINE__, "tcfdbclose");
+    err = true;
+  }
+  tcfdbdel(fdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform misc command */
+static int procmisc(const char *path, int rnum, bool mt, int omode){
+  iprintf("<Miscellaneous Test>\n  seed=%u  path=%s  rnum=%d  mt=%d  omode=%d\n\n",
+          g_randseed, path, rnum, mt, omode);
+  bool err = false;
+  double stime = tctime();
+  TCFDB *fdb = tcfdbnew();
+  if(g_dbgfd >= 0) tcfdbsetdbgfd(fdb, g_dbgfd);
+  if(mt && !tcfdbsetmutex(fdb)){
+    eprint(fdb, __LINE__, "tcfdbsetmutex");
+    err = true;
+  }
+  if(!tcfdbtune(fdb, RECBUFSIZ, EXHEADSIZ + (RECBUFSIZ + sizeof(int)) * rnum)){
+    eprint(fdb, __LINE__, "tcfdbtune");
+    err = true;
+  }
+  if(!tcfdbopen(fdb, path, FDBOWRITER | FDBOCREAT | FDBOTRUNC | omode)){
+    eprint(fdb, __LINE__, "tcfdbopen");
+    err = true;
+  }
+  if(TCUSEPTHREAD){
+    TCFDB *fdbdup = tcfdbnew();
+    if(tcfdbopen(fdbdup, path, FDBOREADER)){
+      eprint(fdb, __LINE__, "(validation)");
+      err = true;
+    } else if(tcfdbecode(fdbdup) != TCETHREAD){
+      eprint(fdb, __LINE__, "(validation)");
+      err = true;
+    }
+    tcfdbdel(fdbdup);
+  }
+  iprintf("writing:\n");
+  for(int i = 1; i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", i);
+    if(!tcfdbputkeep2(fdb, buf, len, buf, len)){
+      eprint(fdb, __LINE__, "tcfdbputkeep");
+      err = true;
+      break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("reading:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%08d", i);
+    int vsiz;
+    char *vbuf = tcfdbget2(fdb, kbuf, ksiz, &vsiz);
+    if(!vbuf){
+      eprint(fdb, __LINE__, "tcfdbget");
+      err = true;
+      break;
+    } else if(vsiz != ksiz || memcmp(vbuf, kbuf, vsiz)){
+      eprint(fdb, __LINE__, "(validation)");
+      err = true;
+      tcfree(vbuf);
+      break;
+    }
+    tcfree(vbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(tcfdbrnum(fdb) != rnum){
+    eprint(fdb, __LINE__, "(validation)");
+    err = true;
+  }
+  iprintf("random writing:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(rnum) + 1);
+    char vbuf[RECBUFSIZ];
+    int vsiz = myrand(RECBUFSIZ);
+    memset(vbuf, '*', vsiz);
+    if(!tcfdbput2(fdb, kbuf, ksiz, vbuf, vsiz)){
+      eprint(fdb, __LINE__, "tcfdbput");
+      err = true;
+      break;
+    }
+    int rsiz;
+    char *rbuf = tcfdbget2(fdb, kbuf, ksiz, &rsiz);
+    if(!rbuf){
+      eprint(fdb, __LINE__, "tcfdbget");
+      err = true;
+      break;
+    }
+    if(rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+      eprint(fdb, __LINE__, "(validation)");
+      err = true;
+      tcfree(rbuf);
+      break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+    tcfree(rbuf);
+  }
+  iprintf("random erasing:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(rnum) + 1);
+    if(!tcfdbout2(fdb, kbuf, ksiz) && tcfdbecode(fdb) != TCENOREC){
+      eprint(fdb, __LINE__, "tcfdbout");
+      err = true;
+      break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("writing:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "[%d]", i);
+    char vbuf[RECBUFSIZ];
+    int vsiz = i % RECBUFSIZ;
+    memset(vbuf, '*', vsiz);
+    if(!tcfdbputkeep2(fdb, kbuf, ksiz, vbuf, vsiz) && tcfdbecode(fdb) != TCEKEEP){
+      eprint(fdb, __LINE__, "tcfdbputkeep");
+      err = true;
+      break;
+    }
+    if(vsiz < 1){
+      char tbuf[PATH_MAX];
+      for(int j = 0; j < PATH_MAX; j++){
+        tbuf[j] = myrand(0x100);
+      }
+      if(!tcfdbput2(fdb, kbuf, ksiz, tbuf, PATH_MAX)){
+        eprint(fdb, __LINE__, "tcfdbput");
+        err = true;
+        break;
+      }
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("erasing:\n");
+  for(int i = 1; i <= rnum; i++){
+    if(i % 2 == 1){
+      char kbuf[RECBUFSIZ];
+      int ksiz = sprintf(kbuf, "[%d]", i);
+      if(!tcfdbout2(fdb, kbuf, ksiz)){
+        eprint(fdb, __LINE__, "tcfdbout");
+        err = true;
+        break;
+      }
+      if(tcfdbout2(fdb, kbuf, ksiz) || tcfdbecode(fdb) != TCENOREC){
+        eprint(fdb, __LINE__, "tcfdbout");
+        err = true;
+        break;
+      }
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("random writing and reopening:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(rnum) + 1);
+    int vsiz = myrand(RECBUFSIZ);
+    char *vbuf = tcmalloc(vsiz + 1);
+    memset(vbuf, '*', vsiz);
+    switch(myrand(3)){
+      case 0:
+        if(!tcfdbput2(fdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(fdb, __LINE__, "tcfdbput");
+          err = true;
+        }
+        break;
+      case 1:
+        if(!tcfdbputcat2(fdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(fdb, __LINE__, "tcfdbputcat");
+          err = true;
+        }
+        break;
+      case 2:
+        if(!tcfdbout2(fdb, kbuf, ksiz) && tcfdbecode(fdb) != TCENOREC){
+          eprint(fdb, __LINE__, "tcfdbout");
+          err = true;
+        }
+        break;
+    }
+    tcfree(vbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(!tcfdbclose(fdb)){
+    eprint(fdb, __LINE__, "tcfdbclose");
+    err = true;
+  }
+  if(!tcfdbopen(fdb, path, FDBOWRITER | omode)){
+    eprint(fdb, __LINE__, "tcfdbopen");
+    err = true;
+  }
+  iprintf("checking:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "[%d]", i);
+    int vsiz;
+    char *vbuf = tcfdbget2(fdb, kbuf, ksiz, &vsiz);
+    if(vbuf){
+      tcfree(vbuf);
+    } else if(tcfdbecode(fdb) != TCENOREC){
+      eprint(fdb, __LINE__, "tcfdbget");
+      err = true;
+      break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("writing:\n");
+  for(int i = 1; i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", i);
+    if(!tcfdbput2(fdb, buf, len, buf, len)){
+      eprint(fdb, __LINE__, "tcfdbput");
+      err = true;
+      break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("reading:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%08d", i);
+    int vsiz;
+    char *vbuf = tcfdbget2(fdb, kbuf, ksiz, &vsiz);
+    if(!vbuf){
+      eprint(fdb, __LINE__, "tcfdbget");
+      err = true;
+      break;
+    } else if(vsiz != ksiz || memcmp(vbuf, kbuf, vsiz)){
+      eprint(fdb, __LINE__, "(validation)");
+      err = true;
+      tcfree(vbuf);
+      break;
+    }
+    tcfree(vbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("checking iterator:\n");
+  if(!tcfdbiterinit(fdb)){
+    eprint(fdb, __LINE__, "tcfdbiterinit");
+    err = true;
+  }
+  char *kbuf;
+  int ksiz;
+  int inum = 0;
+  for(int i = 1; (kbuf = tcfdbiternext2(fdb, &ksiz)) != NULL; i++, inum++){
+    int vsiz;
+    char *vbuf = tcfdbget2(fdb, kbuf, ksiz, &vsiz);
+    if(!vbuf){
+      eprint(fdb, __LINE__, "tcfdbget2");
+      err = true;
+      tcfree(kbuf);
+      break;
+    }
+    tcfree(vbuf);
+    tcfree(kbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(tcfdbecode(fdb) != TCENOREC || inum != tcfdbrnum(fdb)){
+    eprint(fdb, __LINE__, "(validation)");
+    err = true;
+  }
+  iprintf("iteration updating:\n");
+  if(!tcfdbiterinit(fdb)){
+    eprint(fdb, __LINE__, "tcfdbiterinit");
+    err = true;
+  }
+  inum = 0;
+  for(int i = 1; (kbuf = tcfdbiternext2(fdb, &ksiz)) != NULL; i++, inum++){
+    if(myrand(2) == 0){
+      if(!tcfdbputcat2(fdb, kbuf, ksiz, "0123456789", 10)){
+        eprint(fdb, __LINE__, "tcfdbputcat2");
+        err = true;
+        tcfree(kbuf);
+        break;
+      }
+    } else {
+      if(!tcfdbout2(fdb, kbuf, ksiz)){
+        eprint(fdb, __LINE__, "tcfdbout");
+        err = true;
+        tcfree(kbuf);
+        break;
+      }
+    }
+    tcfree(kbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(tcfdbecode(fdb) != TCENOREC || inum < tcfdbrnum(fdb)){
+    eprint(fdb, __LINE__, "(validation)");
+    err = true;
+  }
+  if(myrand(10) == 0 && !tcfdbsync(fdb)){
+    eprint(fdb, __LINE__, "tcfdbsync");
+    err = true;
+  }
+  if(!tcfdbvanish(fdb)){
+    eprint(fdb, __LINE__, "tcfdbvanish");
+    err = true;
+  }
+  TCMAP *map = tcmapnew();
+  iprintf("random writing:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(rnum) + 1);
+    char vbuf[RECBUFSIZ];
+    int vsiz = sprintf(vbuf, "%d", myrand(rnum) + 1);
+    switch(myrand(7)){
+      case 0:
+        if(!tcfdbput2(fdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(fdb, __LINE__, "tcfdbput2");
+          err = true;
+        }
+        tcmapput(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 1:
+        if(!tcfdbputkeep2(fdb, kbuf, ksiz, vbuf, vsiz) && tcfdbecode(fdb) != TCEKEEP){
+          eprint(fdb, __LINE__, "tcfdbputkeep2");
+          err = true;
+        }
+        tcmapputkeep(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 2:
+        if(!tcfdbputcat2(fdb, kbuf, ksiz, vbuf, vsiz) && tcfdbecode(fdb) != TCEKEEP){
+          eprint(fdb, __LINE__, "tcfdbputcat2");
+          err = true;
+        }
+        tcmapputcat(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 3:
+        if(!tcfdbout2(fdb, kbuf, ksiz) && tcfdbecode(fdb) != TCENOREC){
+          eprint(fdb, __LINE__, "tcfdbout2");
+          err = true;
+        }
+        tcmapout(map, kbuf, ksiz);
+        break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("checking transaction commit:\n");
+  if(!tcfdbtranbegin(fdb)){
+    eprint(fdb, __LINE__, "tcfdbtranbegin");
+    err = true;
+  }
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(rnum) + 1);
+    char vbuf[RECBUFSIZ];
+    int vsiz = sprintf(vbuf, "[%d]", myrand(rnum) + 1);
+    switch(myrand(7)){
+      case 0:
+        if(!tcfdbput2(fdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(fdb, __LINE__, "tcfdbput2");
+          err = true;
+        }
+        tcmapput(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 1:
+        if(!tcfdbputkeep2(fdb, kbuf, ksiz, vbuf, vsiz) && tcfdbecode(fdb) != TCEKEEP){
+          eprint(fdb, __LINE__, "tcfdbputkeep2");
+          err = true;
+        }
+        tcmapputkeep(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 2:
+        if(!tcfdbputcat2(fdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(fdb, __LINE__, "tcfdbputcat2");
+          err = true;
+        }
+        tcmapputcat(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 3:
+        if(tcfdbaddint(fdb, tcfdbkeytoid(kbuf, ksiz), 1) == INT_MIN &&
+           tcfdbecode(fdb) != TCEKEEP){
+          eprint(fdb, __LINE__, "tcfdbaddint");
+          err = true;
+        }
+        tcmapaddint(map, kbuf, ksiz, 1);
+        break;
+      case 4:
+        if(isnan(tcfdbadddouble(fdb, tcfdbkeytoid(kbuf, ksiz), 1.0)) &&
+           tcfdbecode(fdb) != TCEKEEP){
+          eprint(fdb, __LINE__, "tcfdbadddouble");
+          err = true;
+        }
+        tcmapadddouble(map, kbuf, ksiz, 1.0);
+        break;
+      case 5:
+        if(myrand(2) == 0){
+          void *op = (void *)(intptr_t)(myrand(3) + 1);
+          if(!tcfdbputproc(fdb, tcfdbkeytoid(kbuf, ksiz), vbuf, vsiz, pdprocfunc, op) &&
+             tcfdbecode(fdb) != TCEKEEP){
+            eprint(fdb, __LINE__, "tcfdbputproc");
+            err = true;
+          }
+          tcmapputproc(map, kbuf, ksiz, vbuf, vsiz, pdprocfunc, op);
+        } else {
+          vsiz = myrand(10);
+          void *op = (void *)(intptr_t)(myrand(3) + 1);
+          if(!tcfdbputproc(fdb, tcfdbkeytoid(kbuf, ksiz), NULL, vsiz, pdprocfunc, op) &&
+             tcfdbecode(fdb) != TCEKEEP && tcfdbecode(fdb) != TCENOREC){
+            eprint(fdb, __LINE__, "tcfdbputproc");
+            err = true;
+          }
+          tcmapputproc(map, kbuf, ksiz, NULL, vsiz, pdprocfunc, op);
+        }
+        break;
+      case 6:
+        if(!tcfdbout2(fdb, kbuf, ksiz) && tcfdbecode(fdb) != TCENOREC){
+          eprint(fdb, __LINE__, "tcfdbout2");
+          err = true;
+        }
+        tcmapout(map, kbuf, ksiz);
+        break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(!tcfdbtrancommit(fdb)){
+    eprint(fdb, __LINE__, "tcfdbtrancommit");
+    err = true;
+  }
+  iprintf("checking transaction abort:\n");
+  uint64_t ornum = tcfdbrnum(fdb);
+  uint64_t ofsiz = tcfdbfsiz(fdb);
+  if(!tcfdbtranbegin(fdb)){
+    eprint(fdb, __LINE__, "tcfdbtranbegin");
+    err = true;
+  }
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(rnum) + 1);
+    char vbuf[RECBUFSIZ];
+    int vsiz = sprintf(vbuf, "[%d]", myrand(rnum) + 1);
+    switch(myrand(7)){
+      case 0:
+        if(!tcfdbput2(fdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(fdb, __LINE__, "tcfdbput2");
+          err = true;
+        }
+        break;
+      case 1:
+        if(!tcfdbputkeep2(fdb, kbuf, ksiz, vbuf, vsiz) && tcfdbecode(fdb) != TCEKEEP){
+          eprint(fdb, __LINE__, "tcfdbputkeep2");
+          err = true;
+        }
+        break;
+      case 2:
+        if(!tcfdbputcat2(fdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(fdb, __LINE__, "tcfdbputcat2");
+          err = true;
+        }
+        break;
+      case 3:
+        if(tcfdbaddint(fdb, tcfdbkeytoid(kbuf, ksiz), 1) == INT_MIN &&
+           tcfdbecode(fdb) != TCEKEEP){
+          eprint(fdb, __LINE__, "tcfdbaddint");
+          err = true;
+        }
+        break;
+      case 4:
+        if(isnan(tcfdbadddouble(fdb, tcfdbkeytoid(kbuf, ksiz), 1.0)) &&
+           tcfdbecode(fdb) != TCEKEEP){
+          eprint(fdb, __LINE__, "tcfdbadddouble");
+          err = true;
+        }
+        break;
+      case 5:
+        if(myrand(2) == 0){
+          void *op = (void *)(intptr_t)(myrand(3) + 1);
+          if(!tcfdbputproc(fdb, tcfdbkeytoid(kbuf, ksiz), vbuf, vsiz, pdprocfunc, op) &&
+             tcfdbecode(fdb) != TCEKEEP){
+            eprint(fdb, __LINE__, "tcfdbputproc");
+            err = true;
+          }
+        } else {
+          vsiz = myrand(10);
+          void *op = (void *)(intptr_t)(myrand(3) + 1);
+          if(!tcfdbputproc(fdb, tcfdbkeytoid(kbuf, ksiz), NULL, vsiz, pdprocfunc, op) &&
+             tcfdbecode(fdb) != TCEKEEP && tcfdbecode(fdb) != TCENOREC){
+            eprint(fdb, __LINE__, "tcfdbputproc");
+            err = true;
+          }
+        }
+        break;
+      case 6:
+        if(!tcfdbout2(fdb, kbuf, ksiz) && tcfdbecode(fdb) != TCENOREC){
+          eprint(fdb, __LINE__, "tcfdbout2");
+          err = true;
+        }
+        break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(!tcfdbtranabort(fdb)){
+    eprint(fdb, __LINE__, "tcfdbtranabort");
+    err = true;
+  }
+  iprintf("checking consistency:\n");
+  if(tcfdbrnum(fdb) != ornum || tcfdbfsiz(fdb) != ofsiz || tcfdbrnum(fdb) != tcmaprnum(map)){
+    eprint(fdb, __LINE__, "(validation)");
+    err = true;
+  }
+  inum = 0;
+  tcmapiterinit(map);
+  const char *tkbuf;
+  int tksiz;
+  for(int i = 1; (tkbuf = tcmapiternext(map, &tksiz)) != NULL; i++, inum++){
+    int tvsiz;
+    const char *tvbuf = tcmapiterval(tkbuf, &tvsiz);
+    if(tvsiz > RECBUFSIZ) tvsiz = RECBUFSIZ;
+    int rsiz;
+    char *rbuf = tcfdbget2(fdb, tkbuf, tksiz, &rsiz);
+    if(!rbuf || rsiz != tvsiz || memcmp(rbuf, tvbuf, rsiz)){
+      eprint(fdb, __LINE__, "(validation)");
+      err = true;
+      tcfree(rbuf);
+      break;
+    }
+    tcfree(rbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(rnum > 250) iprintf(" (%08d)\n", inum);
+  inum = 0;
+  if(!tcfdbiterinit(fdb)){
+    eprint(fdb, __LINE__, "tcfdbiterinit");
+    err = true;
+  }
+  for(int i = 1; (kbuf = tcfdbiternext2(fdb, &ksiz)) != NULL; i++, inum++){
+    int vsiz;
+    char *vbuf = tcfdbget2(fdb, kbuf, ksiz, &vsiz);
+    int rsiz;
+    const char *rbuf = tcmapget(map, kbuf, ksiz, &rsiz);
+    if(rsiz > RECBUFSIZ) rsiz = RECBUFSIZ;
+    if(!rbuf || rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+      eprint(fdb, __LINE__, "(validation)");
+      err = true;
+      tcfree(vbuf);
+      tcfree(kbuf);
+      break;
+    }
+    tcfree(vbuf);
+    tcfree(kbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(rnum > 250) iprintf(" (%08d)\n", inum);
+  tcmapdel(map);
+  if(!tcfdbvanish(fdb)){
+    eprint(fdb, __LINE__, "tcfdbvanish");
+    err = true;
+  }
+  if(rnum >= 100){
+    if(!tcfdbtranbegin(fdb)){
+      eprint(fdb, __LINE__, "tcfdbtranbegin");
+      err = true;
+    }
+    if(!tcfdbput3(fdb, "99", "hirabayashi")){
+      eprint(fdb, __LINE__, "tcfdbput3");
+      err = true;
+    }
+    for(int i = 0; i < 10; i++){
+      char buf[RECBUFSIZ];
+      int size = sprintf(buf, "%d", myrand(rnum) + 1);
+      if(!tcfdbput2(fdb, buf, size, buf, size)){
+        eprint(fdb, __LINE__, "tcfdbput2");
+        err = true;
+      }
+    }
+    for(int i = myrand(3) + 1; i < PATH_MAX; i = i * 2 + myrand(3)){
+      char vbuf[i];
+      memset(vbuf, '@', i - 1);
+      vbuf[i-1] = '\0';
+      if(!tcfdbput3(fdb, "99", vbuf)){
+        eprint(fdb, __LINE__, "tcfdbput3");
+        err = true;
+      }
+    }
+    if(!tcfdbforeach(fdb, iterfunc, NULL)){
+      eprint(fdb, __LINE__, "tcfdbforeach");
+      err = true;
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcfdbrnum(fdb));
+  iprintf("size: %llu\n", (unsigned long long)tcfdbfsiz(fdb));
+  mprint(fdb);
+  sysprint();
+  if(!tcfdbclose(fdb)){
+    eprint(fdb, __LINE__, "tcfdbclose");
+    err = true;
+  }
+  tcfdbdel(fdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform wicked command */
+static int procwicked(const char *path, int rnum, bool mt, int omode){
+  iprintf("<Wicked Writing Test>\n  seed=%u  path=%s  rnum=%d  mt=%d  omode=%d\n\n",
+          g_randseed, path, rnum, mt, omode);
+  bool err = false;
+  double stime = tctime();
+  TCFDB *fdb = tcfdbnew();
+  if(g_dbgfd >= 0) tcfdbsetdbgfd(fdb, g_dbgfd);
+  if(mt && !tcfdbsetmutex(fdb)){
+    eprint(fdb, __LINE__, "tcfdbsetmutex");
+    err = true;
+  }
+  if(!tcfdbtune(fdb, RECBUFSIZ * 2, EXHEADSIZ + (RECBUFSIZ * 2 + sizeof(int)) * rnum)){
+    eprint(fdb, __LINE__, "tcfdbtune");
+    err = true;
+  }
+  if(!tcfdbopen(fdb, path, FDBOWRITER | FDBOCREAT | FDBOTRUNC | omode)){
+    eprint(fdb, __LINE__, "tcfdbopen");
+    err = true;
+  }
+  if(!tcfdbiterinit(fdb)){
+    eprint(fdb, __LINE__, "tcfdbiterinit");
+    err = true;
+  }
+  TCMAP *map = tcmapnew2(rnum / 5);
+  for(int i = 1; i <= rnum && !err; i++){
+    uint64_t id = myrand(rnum) + 1;
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%llu", (unsigned long long)id);
+    char vbuf[RECBUFSIZ];
+    int vsiz = myrand(RECBUFSIZ);
+    memset(vbuf, '*', vsiz);
+    vbuf[vsiz] = '\0';
+    char *rbuf;
+    switch(myrand(16)){
+      case 0:
+        iputchar('0');
+        if(!tcfdbput2(fdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(fdb, __LINE__, "tcfdbput2");
+          err = true;
+        }
+        tcmapput(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 1:
+        iputchar('1');
+        if(!tcfdbput3(fdb, kbuf, vbuf)){
+          eprint(fdb, __LINE__, "tcfdbput3");
+          err = true;
+        }
+        tcmapput2(map, kbuf, vbuf);
+        break;
+      case 2:
+        iputchar('2');
+        if(!tcfdbputkeep2(fdb, kbuf, ksiz, vbuf, vsiz) && tcfdbecode(fdb) != TCEKEEP){
+          eprint(fdb, __LINE__, "tcfdbputkeep2");
+          err = true;
+        }
+        tcmapputkeep(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 3:
+        iputchar('3');
+        if(!tcfdbputkeep3(fdb, kbuf, vbuf) && tcfdbecode(fdb) != TCEKEEP){
+          eprint(fdb, __LINE__, "tcfdbputkeep3");
+          err = true;
+        }
+        tcmapputkeep2(map, kbuf, vbuf);
+        break;
+      case 4:
+        iputchar('4');
+        if(!tcfdbputcat2(fdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(fdb, __LINE__, "tcfdbputcat2");
+          err = true;
+        }
+        tcmapputcat(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 5:
+        iputchar('5');
+        if(!tcfdbputcat3(fdb, kbuf, vbuf)){
+          eprint(fdb, __LINE__, "tcfdbputcat3");
+          err = true;
+        }
+        tcmapputcat2(map, kbuf, vbuf);
+        break;
+      case 6:
+        iputchar('6');
+        if(myrand(10) == 0){
+          if(!tcfdbout2(fdb, kbuf, ksiz) && tcfdbecode(fdb) != TCENOREC){
+            eprint(fdb, __LINE__, "tcfdbout2");
+            err = true;
+          }
+          tcmapout(map, kbuf, ksiz);
+        }
+        break;
+      case 7:
+        iputchar('7');
+        if(myrand(10) == 0){
+          if(!tcfdbout3(fdb, kbuf) && tcfdbecode(fdb) != TCENOREC){
+            eprint(fdb, __LINE__, "tcfdbout3");
+            err = true;
+          }
+          tcmapout2(map, kbuf);
+        }
+        break;
+      case 8:
+        iputchar('8');
+        if(!(rbuf = tcfdbget2(fdb, kbuf, ksiz, &vsiz))){
+          if(tcfdbecode(fdb) != TCENOREC){
+            eprint(fdb, __LINE__, "tcfdbget2");
+            err = true;
+          }
+          rbuf = tcsprintf("[%d]", myrand(i + 1));
+          vsiz = strlen(rbuf);
+        }
+        vsiz += myrand(vsiz);
+        rbuf = tcrealloc(rbuf, vsiz + 1);
+        for(int j = 0; j < vsiz; j++){
+          rbuf[j] = myrand(0x100);
+        }
+        if(!tcfdbput2(fdb, kbuf, ksiz, rbuf, vsiz)){
+          eprint(fdb, __LINE__, "tcfdbput2");
+          err = true;
+        }
+        tcmapput(map, kbuf, ksiz, rbuf, vsiz);
+        tcfree(rbuf);
+        break;
+      case 9:
+        iputchar('9');
+        if(!(rbuf = tcfdbget2(fdb, kbuf, ksiz, &vsiz)) && tcfdbecode(fdb) != TCENOREC){
+          eprint(fdb, __LINE__, "tcfdbget2");
+          err = true;
+        }
+        tcfree(rbuf);
+        break;
+      case 10:
+        iputchar('A');
+        if(!(rbuf = tcfdbget3(fdb, kbuf)) && tcfdbecode(fdb) != TCENOREC){
+          eprint(fdb, __LINE__, "tcfdbget3");
+          err = true;
+        }
+        tcfree(rbuf);
+        break;
+      case 11:
+        iputchar('B');
+        if(myrand(1) == 0) vsiz = 1;
+        if((vsiz = tcfdbget4(fdb, id, vbuf, vsiz)) < 0 && tcfdbecode(fdb) != TCENOREC){
+          eprint(fdb, __LINE__, "tcfdbget4");
+          err = true;
+        }
+        break;
+      case 12:
+        iputchar('C');
+        if(myrand(rnum / 128) == 0){
+          if(myrand(2) == 0){
+            if(!tcfdbiterinit(fdb)){
+              eprint(fdb, __LINE__, "tcfdbiterinit");
+              err = true;
+            }
+          } else {
+            if(!tcfdbiterinit2(fdb, myrand(rnum) + 1) && tcfdbecode(fdb) != TCENOREC){
+              eprint(fdb, __LINE__, "tcfdbiterinit2");
+              err = true;
+            }
+          }
+        }
+        for(int j = myrand(rnum) / 1000 + 1; j >= 0; j--){
+          if(tcfdbiternext(fdb) < 0){
+            int ecode = tcfdbecode(fdb);
+            if(ecode != TCEINVALID && ecode != TCENOREC){
+              eprint(fdb, __LINE__, "tcfdbiternext");
+              err = true;
+            }
+          }
+        }
+        break;
+      default:
+        iputchar('@');
+        if(myrand(10000) == 0) srand((unsigned int)(tctime() * 1000) % UINT_MAX);
+        if(myrand(rnum / 16 + 1) == 0){
+          int cnt = myrand(30);
+          for(int j = 0; j < rnum && !err; j++){
+            ksiz = sprintf(kbuf, "%d", i + j);
+            if(tcfdbout2(fdb, kbuf, ksiz)){
+              cnt--;
+            } else if(tcfdbecode(fdb) != TCENOREC){
+              eprint(fdb, __LINE__, "tcfdbout2");
+              err = true;
+            }
+            tcmapout(map, kbuf, ksiz);
+            if(cnt < 0) break;
+          }
+        }
+        break;
+    }
+    if(i % 50 == 0) iprintf(" (%08d)\n", i);
+    if(i == rnum / 2){
+      if(!tcfdbclose(fdb)){
+        eprint(fdb, __LINE__, "tcfdbclose");
+        err = true;
+      }
+      if(!tcfdbopen(fdb, path, FDBOWRITER | omode)){
+        eprint(fdb, __LINE__, "tcfdbopen");
+        err = true;
+      }
+    } else if(i == rnum / 4){
+      char *npath = tcsprintf("%s-tmp", path);
+      if(!tcfdbcopy(fdb, npath)){
+        eprint(fdb, __LINE__, "tcfdbcopy");
+        err = true;
+      }
+      TCFDB *nfdb = tcfdbnew();
+      if(!tcfdbopen(nfdb, npath, FDBOREADER | omode)){
+        eprint(nfdb, __LINE__, "tcfdbopen");
+        err = true;
+      }
+      tcfdbdel(nfdb);
+      unlink(npath);
+      tcfree(npath);
+      if(!tcfdboptimize(fdb, RECBUFSIZ, -1)){
+        eprint(fdb, __LINE__, "tcfdboptimize");
+        err = true;
+      }
+      if(!tcfdbiterinit(fdb)){
+        eprint(fdb, __LINE__, "tcfdbiterinit");
+        err = true;
+      }
+    } else if(i == rnum / 8){
+      if(!tcfdbtranbegin(fdb)){
+        eprint(fdb, __LINE__, "tcfdbtranbegin");
+        err = true;
+      }
+    } else if(i == rnum / 8 + rnum / 16){
+      if(!tcfdbtrancommit(fdb)){
+        eprint(fdb, __LINE__, "tcfdbtrancommit");
+        err = true;
+      }
+    }
+  }
+  if(rnum % 50 > 0) iprintf(" (%08d)\n", rnum);
+  if(!tcfdbsync(fdb)){
+    eprint(fdb, __LINE__, "tcfdbsync");
+    err = true;
+  }
+  if(tcfdbrnum(fdb) != tcmaprnum(map)){
+    eprint(fdb, __LINE__, "(validation)");
+    err = true;
+  }
+  for(int i = 1; i <= rnum && !err; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", i);
+    int vsiz;
+    const char *vbuf = tcmapget(map, kbuf, ksiz, &vsiz);
+    int rsiz;
+    char *rbuf = tcfdbget2(fdb, kbuf, ksiz, &rsiz);
+    if(vbuf){
+      iputchar('.');
+      if(vsiz > RECBUFSIZ) vsiz = RECBUFSIZ;
+      if(!rbuf){
+        eprint(fdb, __LINE__, "tcfdbget2");
+        err = true;
+      } else if(rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+        eprint(fdb, __LINE__, "(validation)");
+        err = true;
+      }
+    } else {
+      iputchar('*');
+      if(rbuf || tcfdbecode(fdb) != TCENOREC){
+        eprint(fdb, __LINE__, "(validation)");
+        err = true;
+      }
+    }
+    tcfree(rbuf);
+    if(i % 50 == 0) iprintf(" (%08d)\n", i);
+  }
+  if(rnum % 50 > 0) iprintf(" (%08d)\n", rnum);
+  tcmapiterinit(map);
+  int ksiz;
+  const char *kbuf;
+  for(int i = 1; (kbuf = tcmapiternext(map, &ksiz)) != NULL; i++){
+    iputchar('+');
+    int vsiz;
+    const char *vbuf = tcmapiterval(kbuf, &vsiz);
+    if(vsiz > tcfdbwidth(fdb)) vsiz = tcfdbwidth(fdb);
+    int rsiz;
+    char *rbuf = tcfdbget2(fdb, kbuf, ksiz, &rsiz);
+    if(!rbuf){
+      eprint(fdb, __LINE__, "tcfdbget2");
+      err = true;
+    } else if(rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+      eprint(fdb, __LINE__, "(validation)");
+      err = true;
+    }
+    tcfree(rbuf);
+    if(!tcfdbout2(fdb, kbuf, ksiz)){
+      eprint(fdb, __LINE__, "tcfdbout2");
+      err = true;
+    }
+    if(i % 50 == 0) iprintf(" (%08d)\n", i);
+  }
+  int mrnum = tcmaprnum(map);
+  if(mrnum % 50 > 0) iprintf(" (%08d)\n", mrnum);
+  if(tcfdbrnum(fdb) != 0){
+    eprint(fdb, __LINE__, "(validation)");
+    err = true;
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcfdbrnum(fdb));
+  iprintf("size: %llu\n", (unsigned long long)tcfdbfsiz(fdb));
+  mprint(fdb);
+  sysprint();
+  tcmapdel(map);
+  if(!tcfdbclose(fdb)){
+    eprint(fdb, __LINE__, "tcfdbclose");
+    err = true;
+  }
+  tcfdbdel(fdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+
+// END OF FILE
diff --git a/tcejdb/tchdb.c b/tcejdb/tchdb.c
new file mode 100644 (file)
index 0000000..0559c13
--- /dev/null
@@ -0,0 +1,5145 @@
+/*************************************************************************************************
+ * 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(ecode != TCENOREC && 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
diff --git a/tcejdb/tchdb.h b/tcejdb/tchdb.h
new file mode 100644 (file)
index 0000000..cb0ac23
--- /dev/null
@@ -0,0 +1,870 @@
+/*************************************************************************************************
+ * 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.
+ *************************************************************************************************/
+
+
+#ifndef _TCHDB_H                         /* duplication check */
+#define _TCHDB_H
+
+#if defined(__cplusplus)
+#define __TCHDB_CLINKAGEBEGIN extern "C" {
+#define __TCHDB_CLINKAGEEND }
+#else
+#define __TCHDB_CLINKAGEBEGIN
+#define __TCHDB_CLINKAGEEND
+#endif
+__TCHDB_CLINKAGEBEGIN
+
+
+#include <tcutil.h>
+
+
+
+/*************************************************************************************************
+ * API
+ *************************************************************************************************/
+
+
+typedef struct {                         /* type of structure for a hash database */
+  void *mmtx;                            /* mutex for method */
+  void *rmtxs;                           /* mutexes for records */
+  void *dmtx;                            /* mutex for the while database */
+  void *wmtx;                            /* mutex for write ahead logging */
+  void *eckey;                           /* key for thread specific error code */
+  char *rpath;                           /* real path for locking */
+  uint8_t type;                          /* database type */
+  uint8_t flags;                         /* additional flags */
+  uint64_t bnum;                         /* number of the bucket array */
+  uint8_t apow;                          /* power of record alignment */
+  uint8_t fpow;                          /* power of free block pool number */
+  uint8_t opts;                          /* options */
+  char *path;                            /* path of the database file */
+  int fd;                                /* file descriptor of the database file */
+  uint32_t omode;                        /* open mode */
+  uint64_t rnum;                         /* number of the records */
+  uint64_t fsiz;                         /* size of the database file */
+  uint64_t frec;                         /* offset of the first record */
+  uint64_t dfcur;                        /* offset of the cursor for defragmentation */
+  uint64_t iter;                         /* offset of the iterator */
+  char *map;                             /* pointer to the mapped memory */
+  uint64_t msiz;                         /* size of the mapped memory */
+  uint64_t xmsiz;                        /* size of the extra mapped memory */
+  uint64_t xfsiz;                        /* extra size of the file for mapped memory */
+  uint32_t *ba32;                        /* 32-bit bucket array */
+  uint64_t *ba64;                        /* 64-bit bucket array */
+  uint32_t align;                        /* record alignment */
+  uint32_t runit;                        /* record reading unit */
+  bool zmode;                            /* whether compression is used */
+  int32_t fbpmax;                        /* maximum number of the free block pool */
+  void *fbpool;                          /* free block pool */
+  int32_t fbpnum;                        /* number of the free block pool */
+  int32_t fbpmis;                        /* number of missing retrieval of the free block pool */
+  bool async;                            /* whether asynchronous storing is called */
+  TCXSTR *drpool;                        /* delayed record pool */
+  TCXSTR *drpdef;                        /* deferred records of the delayed record pool */
+  uint64_t drpoff;                       /* offset of the delayed record pool */
+  TCMDB *recc;                           /* cache for records */
+  uint32_t rcnum;                        /* maximum number of cached records */
+  TCCODEC enc;                           /* pointer to the encoding function */
+  void *encop;                           /* opaque object for the encoding functions */
+  TCCODEC dec;                           /* pointer to the decoding function */
+  void *decop;                           /* opaque object for the decoding functions */
+  int ecode;                             /* last happened error code */
+  bool fatal;                            /* whether a fatal error occured */
+  uint64_t inode;                        /* inode number */
+  time_t mtime;                          /* modification time */
+  uint32_t dfunit;                       /* unit step number of auto defragmentation */
+  uint32_t dfcnt;                        /* counter of auto defragmentation */
+  bool tran;                             /* whether in the transaction */
+  int walfd;                             /* file descriptor of write ahead logging */
+  uint64_t walend;                       /* end offset of write ahead logging */
+  int dbgfd;                             /* file descriptor for debugging */
+  volatile int64_t cnt_writerec;         /* tesing counter for record write times */
+  volatile int64_t cnt_reuserec;         /* tesing counter for record reuse times */
+  volatile int64_t cnt_moverec;          /* tesing counter for record move times */
+  volatile int64_t cnt_readrec;          /* tesing counter for record read times */
+  volatile int64_t cnt_searchfbp;        /* tesing counter for FBP search times */
+  volatile int64_t cnt_insertfbp;        /* tesing counter for FBP insert times */
+  volatile int64_t cnt_splicefbp;        /* tesing counter for FBP splice times */
+  volatile int64_t cnt_dividefbp;        /* tesing counter for FBP divide times */
+  volatile int64_t cnt_mergefbp;         /* tesing counter for FBP merge times */
+  volatile int64_t cnt_reducefbp;        /* tesing counter for FBP reduce times */
+  volatile int64_t cnt_appenddrp;        /* tesing counter for DRP append times */
+  volatile int64_t cnt_deferdrp;         /* tesing counter for DRP defer times */
+  volatile int64_t cnt_flushdrp;         /* tesing counter for DRP flush times */
+  volatile int64_t cnt_adjrecc;          /* tesing counter for record cache adjust times */
+  volatile int64_t cnt_defrag;           /* tesing counter for defragmentation times */
+  volatile int64_t cnt_shiftrec;         /* tesing counter for record shift times */
+  volatile int64_t cnt_trunc;            /* tesing counter for truncation times */
+} TCHDB;
+
+enum {                                   /* enumeration for additional flags */
+  HDBFOPEN = 1 << 0,                     /* whether opened */
+  HDBFFATAL = 1 << 1                     /* whether with fatal error */
+};
+
+enum {                                   /* enumeration for tuning options */
+  HDBTLARGE = 1 << 0,                    /* use 64-bit bucket array */
+  HDBTDEFLATE = 1 << 1,                  /* compress each record with Deflate */
+  HDBTBZIP = 1 << 2,                     /* compress each record with BZIP2 */
+  HDBTTCBS = 1 << 3,                     /* compress each record with TCBS */
+  HDBTEXCODEC = 1 << 4                   /* compress each record with custom functions */
+};
+
+enum {                                   /* enumeration for open modes */
+  HDBOREADER = 1 << 0,                   /* open as a reader */
+  HDBOWRITER = 1 << 1,                   /* open as a writer */
+  HDBOCREAT = 1 << 2,                    /* writer creating */
+  HDBOTRUNC = 1 << 3,                    /* writer truncating */
+  HDBONOLCK = 1 << 4,                    /* open without locking */
+  HDBOLCKNB = 1 << 5,                    /* lock without blocking */
+  HDBOTSYNC = 1 << 6                     /* synchronize every transaction */
+};
+
+
+/* Get the message string corresponding to an error code.
+   `ecode' specifies the error code.
+   The return value is the message string of the error code. */
+const char *tchdberrmsg(int ecode);
+
+
+/* Create a hash database object.
+   The return value is the new hash database object. */
+TCHDB *tchdbnew(void);
+
+
+/* Delete a hash database object.
+   `hdb' specifies the hash database object.
+   If the database is not closed, it is closed implicitly.  Note that the deleted object and its
+   derivatives can not be used anymore. */
+void tchdbdel(TCHDB *hdb);
+
+
+/* Get the last happened error code of a hash database object.
+   `hdb' specifies the hash database object.
+   The return value is the last happened error code.
+   The following error codes are defined: `TCESUCCESS' for success, `TCETHREAD' for threading
+   error, `TCEINVALID' for invalid operation, `TCENOFILE' for file not found, `TCENOPERM' for no
+   permission, `TCEMETA' for invalid meta data, `TCERHEAD' for invalid record header, `TCEOPEN'
+   for open error, `TCECLOSE' for close error, `TCETRUNC' for trunc error, `TCESYNC' for sync
+   error, `TCESTAT' for stat error, `TCESEEK' for seek error, `TCEREAD' for read error,
+   `TCEWRITE' for write error, `TCEMMAP' for mmap error, `TCELOCK' for lock error, `TCEUNLINK'
+   for unlink error, `TCERENAME' for rename error, `TCEMKDIR' for mkdir error, `TCERMDIR' for
+   rmdir error, `TCEKEEP' for existing record, `TCENOREC' for no record found, and `TCEMISC' for
+   miscellaneous error. */
+int tchdbecode(TCHDB *hdb);
+
+
+/* Set mutual exclusion control of a hash database object for threading.
+   `hdb' specifies the hash database object which is not opened.
+   If successful, the return value is true, else, it is false.
+   Note that the mutual exclusion control is needed if the object is shared by plural threads and
+   this function should be called before the database is opened. */
+bool tchdbsetmutex(TCHDB *hdb);
+
+
+/* Set the tuning parameters of a hash database object.
+   `hdb' specifies the hash database object which is not opened.
+   `bnum' specifies the number of elements of the bucket array.  If it is not more than 0, the
+   default value is specified.  The default value is 131071.  Suggested size of the bucket array
+   is about from 0.5 to 4 times of the number of all records to be stored.
+   `apow' specifies the size of record alignment by power of 2.  If it is negative, the default
+   value is specified.  The default value is 4 standing for 2^4=16.
+   `fpow' specifies the maximum number of elements of the free block pool by power of 2.  If it
+   is negative, the default value is specified.  The default value is 10 standing for 2^10=1024.
+   `opts' specifies options by bitwise-or: `HDBTLARGE' specifies that the size of the database
+   can be larger than 2GB by using 64-bit bucket array, `HDBTDEFLATE' specifies that each record
+   is compressed with Deflate encoding, `HDBTBZIP' specifies that each record is compressed with
+   BZIP2 encoding, `HDBTTCBS' specifies that each record is compressed with TCBS encoding.
+   If successful, the return value is true, else, it is false.
+   Note that the tuning parameters should be set before the database is opened. */
+bool tchdbtune(TCHDB *hdb, int64_t bnum, int8_t apow, int8_t fpow, uint8_t opts);
+
+
+/* Set the caching parameters of a hash database object.
+   `hdb' specifies the hash database object which is not opened.
+   `rcnum' specifies the maximum number of records to be cached.  If it is not more than 0, the
+   record cache is disabled.  It is disabled by default.
+   If successful, the return value is true, else, it is false.
+   Note that the caching parameters should be set before the database is opened. */
+bool tchdbsetcache(TCHDB *hdb, int32_t rcnum);
+
+
+/* Set the size of the extra mapped memory of a hash database object.
+   `hdb' specifies the hash database object which is not opened.
+   `xmsiz' specifies the size of the extra mapped memory.  If it is not more than 0, the extra
+   mapped memory is disabled.  The default size is 67108864.
+   If successful, the return value is true, else, it is false.
+   Note that the mapping parameters should be set before the database is opened. */
+bool tchdbsetxmsiz(TCHDB *hdb, int64_t xmsiz);
+
+
+/* Set the unit step number of auto defragmentation of a hash database object.
+   `hdb' specifies the hash database object which is not opened.
+   `dfunit' specifie the unit step number.  If it is not more than 0, the auto defragmentation
+   is disabled.  It is disabled by default.
+   If successful, the return value is true, else, it is false.
+   Note that the defragmentation parameters should be set before the database is opened. */
+bool tchdbsetdfunit(TCHDB *hdb, int32_t dfunit);
+
+
+/* Open a database file and connect a hash database object.
+   `hdb' specifies the hash database object which is not opened.
+   `path' specifies the path of the database file.
+   `omode' specifies the connection mode: `HDBOWRITER' as a writer, `HDBOREADER' as a reader.
+   If the mode is `HDBOWRITER', the following may be added by bitwise-or: `HDBOCREAT', which
+   means it creates a new database if not exist, `HDBOTRUNC', which means it creates a new
+   database regardless if one exists, `HDBOTSYNC', which means every transaction synchronizes
+   updated contents with the device.  Both of `HDBOREADER' and `HDBOWRITER' can be added to by
+   bitwise-or: `HDBONOLCK', which means it opens the database file without file locking, or
+   `HDBOLCKNB', which means locking is performed without blocking.
+   If successful, the return value is true, else, it is false. */
+bool tchdbopen(TCHDB *hdb, const char *path, int omode);
+
+
+/* Close a hash database object.
+   `hdb' specifies the hash database object.
+   If successful, the return value is true, else, it is false.
+   Update of a database is assured to be written when the database is closed.  If a writer opens
+   a database but does not close it appropriately, the database will be broken. */
+bool tchdbclose(TCHDB *hdb);
+
+
+/* Store a record into a hash database object.
+   `hdb' specifies the hash database object connected as a writer.
+   `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.
+   If a record with the same key exists in the database, it is overwritten. */
+bool tchdbput(TCHDB *hdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Store a string record into a hash database object.
+   `hdb' specifies the hash database object connected as a writer.
+   `kstr' specifies the string of the key.
+   `vstr' specifies the string of the value.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the database, it is overwritten. */
+bool tchdbput2(TCHDB *hdb, const char *kstr, const char *vstr);
+
+
+/* Store a new record into a hash database object.
+   `hdb' specifies the hash database object connected as a writer.
+   `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.
+   If a record with the same key exists in the database, this function has no effect. */
+bool tchdbputkeep(TCHDB *hdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Store a new string record into a hash database object.
+   `hdb' specifies the hash database object connected as a writer.
+   `kstr' specifies the string of the key.
+   `vstr' specifies the string of the value.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the database, this function has no effect. */
+bool tchdbputkeep2(TCHDB *hdb, const char *kstr, const char *vstr);
+
+
+/* Concatenate a value at the end of the existing record in a hash database object.
+   `hdb' specifies the hash database object connected as a writer.
+   `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.
+   If there is no corresponding record, a new record is created. */
+bool tchdbputcat(TCHDB *hdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Concatenate a string value at the end of the existing record in a hash database object.
+   `hdb' specifies the hash database object connected as a writer.
+   `kstr' specifies the string of the key.
+   `vstr' specifies the string of the value.
+   If successful, the return value is true, else, it is false.
+   If there is no corresponding record, a new record is created. */
+bool tchdbputcat2(TCHDB *hdb, const char *kstr, const char *vstr);
+
+
+/* Store a record into a hash database object in asynchronous fashion.
+   `hdb' specifies the hash database object connected as a writer.
+   `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.
+   If a record with the same key exists in the database, it is overwritten.  Records passed to
+   this function are accumulated into the inner buffer and wrote into the file at a blast. */
+bool tchdbputasync(TCHDB *hdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Store a string record into a hash database object in asynchronous fashion.
+   `hdb' specifies the hash database object connected as a writer.
+   `kstr' specifies the string of the key.
+   `vstr' specifies the string of the value.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the database, it is overwritten.  Records passed to
+   this function are accumulated into the inner buffer and wrote into the file at a blast. */
+bool tchdbputasync2(TCHDB *hdb, const char *kstr, const char *vstr);
+
+
+/* Remove a record of a hash database object.
+   `hdb' specifies the hash database object connected as a writer.
+   `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. */
+bool tchdbout(TCHDB *hdb, const void *kbuf, int ksiz);
+
+
+/* Remove a string record of a hash database object.
+   `hdb' specifies the hash database object connected as a writer.
+   `kstr' specifies the string of the key.
+   If successful, the return value is true, else, it is false. */
+bool tchdbout2(TCHDB *hdb, const char *kstr);
+
+
+/* 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.
+   `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.  `NULL' is returned if no record corresponds.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when
+   it is no longer in use. */
+void *tchdbget(TCHDB *hdb, const void *kbuf, int ksiz, int *sp);
+
+
+/* Retrieve a string record in a hash database object.
+   `hdb' specifies the hash database object.
+   `kstr' specifies the string of the key.
+   If successful, the return value is the string of the value of the corresponding record.
+   `NULL' is returned if no record corresponds.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+char *tchdbget2(TCHDB *hdb, const char *kstr);
+
+
+/* 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.
+   `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.  -1 is
+   returned if no record corresponds to the specified key.
+   Note that an additional zero code is not appended at the end of the region of the writing
+   buffer. */
+int tchdbget3(TCHDB *hdb, const void *kbuf, int ksiz, void *vbuf, int max);
+
+
+/* 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.
+   If successful, the return value is the size of the value of the corresponding record, else,
+   it is -1. */
+int tchdbvsiz(TCHDB *hdb, const void *kbuf, int ksiz);
+
+
+/* Get the size of the value of a string record in a hash database object.
+   `hdb' specifies the hash database object.
+   `kstr' specifies the string of the key.
+   If successful, the return value is the size of the value of the corresponding record, else,
+   it is -1. */
+int tchdbvsiz2(TCHDB *hdb, const char *kstr);
+
+
+/* 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.
+   The iterator is used in order to access the key of every record stored in a database. */
+bool tchdbiterinit(TCHDB *hdb);
+
+
+/* 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'.  `NULL' is returned when no record is to be get out of the iterator.
+   Because an additional zero code is appended at the end of the region of the return value, the
+   return value can be treated as a character string.  Because the region of the return value is
+   allocated with the `malloc' call, it should be released with the `free' call when it is no
+   longer in use.  It is possible to access every record by iteration of calling this function.
+   It is allowed to update or remove records whose keys are fetched while the iteration.
+   However, it is not assured if updating the database is occurred while the iteration.  Besides,
+   the order of this traversal access method is arbitrary, so it is not assured that the order of
+   storing matches the one of the traversal access. */
+void *tchdbiternext(TCHDB *hdb, int *sp);
+
+
+/* Get the next key string of the iterator of a hash database object.
+   `hdb' specifies the hash database object.
+   If successful, the return value is the string of the next key, else, it is `NULL'.  `NULL' is
+   returned when no record is to be get out of the iterator.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use.  It is possible to access every
+   record by iteration of calling this function.  However, it is not assured if updating the
+   database is occurred while the iteration.  Besides, the order of this traversal access method
+   is arbitrary, so it is not assured that the order of storing matches the one of the traversal
+   access. */
+char *tchdbiternext2(TCHDB *hdb);
+
+
+/* Get the next extensible objects of the iterator of a hash database object.
+   `hdb' specifies the hash database object.
+   `kxstr' specifies the object into which the next key is wrote down.
+   `vxstr' specifies the object into which the next value is wrote down.
+   If successful, the return value is true, else, it is false.  False is returned when no record
+   is to be get out of the iterator. */
+bool tchdbiternext3(TCHDB *hdb, TCXSTR *kxstr, TCXSTR *vxstr);
+
+
+/* Get forward matching keys in a hash database object.
+   `hdb' specifies the hash 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.  This function does never fail.
+   It returns an empty list even if no key corresponds.
+   Because the object of the return value is created with the function `tclistnew', it should be
+   deleted with the function `tclistdel' when it is no longer in use.  Note that this function
+   may be very slow because every key in the database is scanned. */
+TCLIST *tchdbfwmkeys(TCHDB *hdb, const void *pbuf, int psiz, int max);
+
+
+/* Get forward matching string keys in a hash database object.
+   `hdb' specifies the hash database object.
+   `pstr' specifies the string 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.  This function does never fail.
+   It returns an empty list even if no key corresponds.
+   Because the object of the return value is created with the function `tclistnew', it should be
+   deleted with the function `tclistdel' when it is no longer in use.  Note that this function
+   may be very slow because every key in the database is scanned. */
+TCLIST *tchdbfwmkeys2(TCHDB *hdb, const char *pstr, int max);
+
+
+/* Add an integer to a record in a hash database object.
+   `hdb' specifies the hash database object connected as a writer.
+   `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'.
+   If the corresponding record exists, the value is treated as an integer and is added to.  If no
+   record corresponds, a new record of the additional value is stored. */
+int tchdbaddint(TCHDB *hdb, const void *kbuf, int ksiz, int num);
+
+
+/* Add a real number to a record in a hash database object.
+   `hdb' specifies the hash database object connected as a writer.
+   `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.
+   If the corresponding record exists, the value is treated as a real number and is added to.  If
+   no record corresponds, a new record of the additional value is stored. */
+double tchdbadddouble(TCHDB *hdb, const void *kbuf, int ksiz, double num);
+
+
+/* Synchronize updated contents of a hash database object with the file and the device.
+   `hdb' specifies the hash database object connected as a writer.
+   If successful, the return value is true, else, it is false.
+   This function is useful when another process connects to the same database file. */
+bool tchdbsync(TCHDB *hdb);
+
+
+/* Optimize the file of a hash database object.
+   `hdb' specifies the hash database object connected as a writer.
+   `bnum' specifies the number of elements of the bucket array.  If it is not more than 0, the
+   default value is specified.  The default value is two times of the number of records.
+   `apow' specifies the size of record alignment by power of 2.  If it is negative, the current
+   setting is not changed.
+   `fpow' specifies the maximum number of elements of the free block pool by power of 2.  If it
+   is negative, the current setting is not changed.
+   `opts' specifies options by bitwise-or: `HDBTLARGE' specifies that the size of the database
+   can be larger than 2GB by using 64-bit bucket array, `HDBTDEFLATE' specifies that each record
+   is compressed with Deflate encoding, `HDBTBZIP' specifies that each record is compressed with
+   BZIP2 encoding, `HDBTTCBS' specifies that each record is compressed with TCBS encoding.  If it
+   is `UINT8_MAX', the current setting is not changed.
+   If successful, the return value is true, else, it is false.
+   This function is useful to reduce the size of the database file with data fragmentation by
+   successive updating. */
+bool tchdboptimize(TCHDB *hdb, int64_t bnum, int8_t apow, int8_t fpow, uint8_t opts);
+
+
+/* Remove all records of a hash database object.
+   `hdb' specifies the hash database object connected as a writer.
+   If successful, the return value is true, else, it is false. */
+bool tchdbvanish(TCHDB *hdb);
+
+
+/* Copy the database file of a hash database object.
+   `hdb' specifies the hash database object.
+   `path' specifies the path of the destination file.  If it begins with `@', the trailing
+   substring is executed as a command line.
+   If successful, the return value is true, else, it is false.  False is returned if the executed
+   command returns non-zero code.
+   The database file is assured to be kept synchronized and not modified while the copying or
+   executing operation is in progress.  So, this function is useful to create a backup file of
+   the database file. */
+bool tchdbcopy(TCHDB *hdb, const char *path);
+
+
+/* Begin the transaction of a hash database object.
+   `hdb' specifies the hash database object connected as a writer.
+   If successful, the return value is true, else, it is false.
+   The database is locked by the thread while the transaction so that only one transaction can be
+   activated with a database object at the same time.  Thus, the serializable isolation level is
+   assumed if every database operation is performed in the transaction.  All updated regions are
+   kept track of by write ahead logging while the transaction.  If the database is closed during
+   transaction, the transaction is aborted implicitly. */
+bool tchdbtranbegin(TCHDB *hdb);
+
+
+/* Commit the transaction of a hash database object.
+   `hdb' specifies the hash database object connected as a writer.
+   If successful, the return value is true, else, it is false.
+   Update in the transaction is fixed when it is committed successfully. */
+bool tchdbtrancommit(TCHDB *hdb);
+
+
+/* Abort the transaction of a hash database object.
+   `hdb' specifies the hash database object connected as a writer.
+   If successful, the return value is true, else, it is false.
+   Update in the transaction is discarded when it is aborted.  The state of the database is
+   rollbacked to before transaction. */
+bool tchdbtranabort(TCHDB *hdb);
+
+
+/* Get the file path of a hash database object.
+   `hdb' specifies the hash database object.
+   The return value is the path of the database file or `NULL' if the object does not connect to
+   any database file. */
+const char *tchdbpath(TCHDB *hdb);
+
+
+/* Get the number of records of a hash database object.
+   `hdb' specifies the hash database object.
+   The return value is the number of records or 0 if the object does not connect to any database
+   file. */
+uint64_t tchdbrnum(TCHDB *hdb);
+
+
+/* Get the size of the database file of a hash database object.
+   `hdb' specifies the hash database object.
+   The return value is the size of the database file or 0 if the object does not connect to any
+   database file. */
+uint64_t tchdbfsiz(TCHDB *hdb);
+
+
+
+/*************************************************************************************************
+ * features for experts
+ *************************************************************************************************/
+
+
+/* Set the error code of a hash database object.
+   `hdb' specifies the hash database object.
+   `ecode' specifies the error code.
+   `file' specifies the file name of the code.
+   `line' specifies the line number of the code.
+   `func' specifies the function name of the code. */
+void tchdbsetecode(TCHDB *hdb, int ecode, const char *filename, int line, const char *func);
+
+
+/* Set the type of a hash database object.
+   `hdb' specifies the hash database object.
+   `type' specifies the database type. */
+void tchdbsettype(TCHDB *hdb, uint8_t type);
+
+
+/* Set the file descriptor for debugging output.
+   `hdb' specifies the hash database object.
+   `fd' specifies the file descriptor for debugging output. */
+void tchdbsetdbgfd(TCHDB *hdb, int fd);
+
+
+/* Get the file descriptor for debugging output.
+   `hdb' specifies the hash database object.
+   The return value is the file descriptor for debugging output. */
+int tchdbdbgfd(TCHDB *hdb);
+
+
+/* Check whether mutual exclusion control is set to a hash database object.
+   `hdb' specifies the hash database object.
+   If mutual exclusion control is set, it is true, else it is false. */
+bool tchdbhasmutex(TCHDB *hdb);
+
+
+/* Synchronize updating contents on memory of a hash database object.
+   `hdb' specifies the hash database object connected as a writer.
+   `phys' specifies whether to synchronize physically.
+   If successful, the return value is true, else, it is false. */
+bool tchdbmemsync(TCHDB *hdb, bool phys);
+
+
+/* Get the number of elements of the bucket array of a hash database object.
+   `hdb' specifies the hash database object.
+   The return value is the number of elements of the bucket array or 0 if the object does not
+   connect to any database file. */
+uint64_t tchdbbnum(TCHDB *hdb);
+
+
+/* Get the record alignment of a hash database object.
+   `hdb' specifies the hash database object.
+   The return value is the record alignment or 0 if the object does not connect to any database
+   file. */
+uint32_t tchdbalign(TCHDB *hdb);
+
+
+/* Get the maximum number of the free block pool of a a hash database object.
+   `hdb' specifies the hash database object.
+   The return value is the maximum number of the free block pool or 0 if the object does not
+   connect to any database file. */
+uint32_t tchdbfbpmax(TCHDB *hdb);
+
+
+/* Get the size of the extra mapped memory of a hash database object.
+   `hdb' specifies the hash database object.
+   The return value is the size of the extra mapped memory or 0 if the object does not connect to
+   any database file. */
+uint64_t tchdbxmsiz(TCHDB *hdb);
+
+
+/* Get the inode number of the database file of a hash database object.
+   `hdb' specifies the hash database object.
+   The return value is the inode number of the database file or 0 if the object does not connect
+   to any database file. */
+uint64_t tchdbinode(TCHDB *hdb);
+
+
+/* Get the modification time of the database file of a hash database object.
+   `hdb' specifies the hash database object.
+   The return value is the inode number of the database file or 0 if the object does not connect
+   to any database file. */
+time_t tchdbmtime(TCHDB *hdb);
+
+
+/* Get the connection mode of a hash database object.
+   `hdb' specifies the hash database object.
+   The return value is the connection mode. */
+int tchdbomode(TCHDB *hdb);
+
+
+/* Get the database type of a hash database object.
+   `hdb' specifies the hash database object.
+   The return value is the database type. */
+uint8_t tchdbtype(TCHDB *hdb);
+
+
+/* Get the additional flags of a hash database object.
+   `hdb' specifies the hash database object.
+   The return value is the additional flags. */
+uint8_t tchdbflags(TCHDB *hdb);
+
+
+/* Get the options of a hash database object.
+   `hdb' specifies the hash database object.
+   The return value is the options. */
+uint8_t tchdbopts(TCHDB *hdb);
+
+
+/* Get the pointer to the opaque field of a hash database object.
+   `hdb' specifies the hash database object.
+   The return value is the pointer to the opaque field whose size is 128 bytes. */
+char *tchdbopaque(TCHDB *hdb);
+
+
+/* Get the number of used elements of the bucket array of a hash database object.
+   `hdb' specifies the hash database object.
+   The return value is the number of used elements of the bucket array or 0 if the object does
+   not connect to any database file. */
+uint64_t tchdbbnumused(TCHDB *hdb);
+
+
+/* Set the custom codec functions of a hash database object.
+   `hdb' specifies the hash database object.
+   `enc' specifies the pointer to the custom encoding function.  It receives four parameters.
+   The first parameter is the pointer to the region.  The second parameter is the size of the
+   region.  The third parameter is the pointer to the variable into which the size of the region
+   of the return value is assigned.  The fourth parameter is the pointer to the optional opaque
+   object.  It returns the pointer to the result object allocated with `malloc' call if
+   successful, else, it returns `NULL'.
+   `encop' specifies an arbitrary pointer to be given as a parameter of the encoding function.
+   If it is not needed, `NULL' can be specified.
+   `dec' specifies the pointer to the custom decoding function.
+   `decop' specifies an arbitrary pointer to be given as a parameter of the decoding function.
+   If it is not needed, `NULL' can be specified.
+   If successful, the return value is true, else, it is false.
+   Note that the custom codec functions should be set before the database is opened and should be
+   set every time the database is being opened. */
+bool tchdbsetcodecfunc(TCHDB *hdb, TCCODEC enc, void *encop, TCCODEC dec, void *decop);
+
+
+/* Get the custom codec functions of a hash database object.
+   `hdb' specifies the hash database object.
+   `ep' specifies the pointer to a variable into which the pointer to the custom encoding
+   function is assigned
+   `eop' specifies the pointer to a variable into which the arbitrary pointer to be given to the
+   encoding function is assigned.
+   `dp' specifies the pointer to a variable into which the pointer to the custom decoding
+   function is assigned
+   `dop' specifies the pointer to a variable into which the arbitrary pointer to be given to the
+   decoding function is assigned. */
+void tchdbcodecfunc(TCHDB *hdb, TCCODEC *ep, void **eop, TCCODEC *dp, void **dop);
+
+
+/* Get the unit step number of auto defragmentation of a hash database object.
+   `hdb' specifies the hash database object.
+   The return value is the unit step number of auto defragmentation. */
+uint32_t tchdbdfunit(TCHDB *hdb);
+
+
+/* Perform dynamic defragmentation of a hash database object.
+   `hdb' specifies the hash database object connected as a writer.
+   `step' specifie the number of steps.  If it is not more than 0, the whole file is defragmented
+   gradually without keeping a continuous lock.
+   If successful, the return value is true, else, it is false. */
+bool tchdbdefrag(TCHDB *hdb, int64_t step);
+
+
+/* Clear the cache of a hash tree database object.
+   `hdb' specifies the hash tree database object.
+   If successful, the return value is true, else, it is false. */
+bool tchdbcacheclear(TCHDB *hdb);
+
+
+/* Store a record into a hash database object with a duplication handler.
+   `hdb' specifies the hash database object connected as a writer.
+   `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.  `NULL' means that record addition is
+   ommited if there is no corresponding record.
+   `vsiz' specifies the size of the region of the value.
+   `proc' specifies the pointer to the callback function to process duplication.  It receives
+   four parameters.  The first parameter is the pointer to the region of the value.  The second
+   parameter is the size of the region of the value.  The third parameter is the pointer to the
+   variable into which the size of the region of the return value is assigned.  The fourth
+   parameter is the pointer to the optional opaque object.  It returns the pointer to the result
+   object allocated with `malloc'.  It is released by the caller.  If it is `NULL', the record is
+   not modified.  If it is `(void *)-1', the record is removed.
+   `op' specifies an arbitrary pointer to be given as a parameter of the callback function.  If
+   it is not needed, `NULL' can be specified.
+   If successful, the return value is true, else, it is false.
+   Note that the callback function can not perform any database operation because the function
+   is called in the critical section guarded by the same locks of database operations. */
+bool tchdbputproc(TCHDB *hdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz,
+                  TCPDPROC proc, void *op);
+
+
+/* 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.  If it is `NULL', the first record is
+   retrieved.
+   `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 key of the next record.
+   `NULL' is returned if no record corresponds.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when
+   it is no longer in use. */
+void *tchdbgetnext(TCHDB *hdb, const void *kbuf, int ksiz, int *sp);
+
+
+/* Retrieve the next string record in a hash database object.
+   `hdb' specifies the hash database object.
+   `kstr' specifies the string of the key.  If it is `NULL', the first record is retrieved.
+   If successful, the return value is the string of the key of the next record.  `NULL' is
+   returned if no record corresponds.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+char *tchdbgetnext2(TCHDB *hdb, const char *kstr);
+
+
+/* Retrieve the key and the value of 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 key of the next
+   record.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use.  The retion pointed to by `vbp'
+   should not be released. */
+char *tchdbgetnext3(TCHDB *hdb, const char *kbuf, int ksiz, int *sp, const char **vbp, int *vsp);
+
+
+/* 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.  False is returned if there is
+   no record corresponding the condition. */
+bool tchdbiterinit2(TCHDB *hdb, const void *kbuf, int ksiz);
+
+
+/* Move the iterator to the record corresponding a key string of a hash database object.
+   `hdb' specifies the hash database object.
+   `kstr' specifies the string of the key.
+   If successful, the return value is true, else, it is false.  False is returned if there is
+   no record corresponding the condition. */
+bool tchdbiterinit3(TCHDB *hdb, const char *kstr);
+
+
+/* Process each record atomically of a hash database object.
+   `hdb' specifies the hash database object.
+   `iter' specifies the pointer to the iterator function called for each record.  It receives
+   five parameters.  The first parameter is the pointer to the region of the key.  The second
+   parameter is the size of the region of the key.  The third parameter is the pointer to the
+   region of the value.  The fourth parameter is the size of the region of the value.  The fifth
+   parameter is the pointer to the optional opaque object.  It returns true to continue iteration
+   or false to stop iteration.
+   `op' specifies an arbitrary pointer to be given as a parameter of the iterator function.  If
+   it is not needed, `NULL' can be specified.
+   If successful, the return value is true, else, it is false.
+   Note that the callback function can not perform any database operation because the function
+   is called in the critical section guarded by the same locks of database operations. */
+bool tchdbforeach(TCHDB *hdb, TCITER iter, void *op);
+
+
+/* Void the transaction of a hash database object.
+   `hdb' specifies the hash database object connected as a writer.
+   If successful, the return value is true, else, it is false.
+   This function should be called only when no update in the transaction. */
+bool tchdbtranvoid(TCHDB *hdb);
+
+
+
+__TCHDB_CLINKAGEEND
+#endif                                   /* duplication check */
+
+
+/* END OF FILE */
diff --git a/tcejdb/tchmgr.c b/tcejdb/tchmgr.c
new file mode 100644 (file)
index 0000000..f68dabe
--- /dev/null
@@ -0,0 +1,850 @@
+/*************************************************************************************************
+ * The command line utility of the hash database API
+ *                                                               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 "myconf.h"
+
+
+/* global variables */
+const char *g_progname;                  // program name
+int g_dbgfd;                             // debugging output
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+static void usage(void);
+static void printerr(TCHDB *hdb);
+static int printdata(const char *ptr, int size, bool px);
+static char *mygetline(FILE *ifp);
+static int runcreate(int argc, char **argv);
+static int runinform(int argc, char **argv);
+static int runput(int argc, char **argv);
+static int runout(int argc, char **argv);
+static int runget(int argc, char **argv);
+static int runlist(int argc, char **argv);
+static int runoptimize(int argc, char **argv);
+static int runimporttsv(int argc, char **argv);
+static int runversion(int argc, char **argv);
+static int proccreate(const char *path, int bnum, int apow, int fpow, int opts);
+static int procinform(const char *path, int omode);
+static int procput(const char *path, const char *kbuf, int ksiz, const char *vbuf, int vsiz,
+                   int omode, int dmode);
+static int procout(const char *path, const char *kbuf, int ksiz, int omode);
+static int procget(const char *path, const char *kbuf, int ksiz, int omode, bool px, bool pz);
+static int proclist(const char *path, int omode, int max, bool pv, bool px, const char *fmstr);
+static int procoptimize(const char *path, int bnum, int apow, int fpow, int opts, int omode,
+                        bool df);
+static int procimporttsv(const char *path, const char *file, int omode, bool sc);
+static int procversion(void);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  g_progname = argv[0];
+  g_dbgfd = -1;
+  const char *ebuf = getenv("TCDBGFD");
+  if(ebuf) g_dbgfd = tcatoix(ebuf);
+  if(argc < 2) usage();
+  int rv = 0;
+  if(!strcmp(argv[1], "create")){
+    rv = runcreate(argc, argv);
+  } else if(!strcmp(argv[1], "inform")){
+    rv = runinform(argc, argv);
+  } else if(!strcmp(argv[1], "put")){
+    rv = runput(argc, argv);
+  } else if(!strcmp(argv[1], "out")){
+    rv = runout(argc, argv);
+  } else if(!strcmp(argv[1], "get")){
+    rv = runget(argc, argv);
+  } else if(!strcmp(argv[1], "list")){
+    rv = runlist(argc, argv);
+  } else if(!strcmp(argv[1], "optimize")){
+    rv = runoptimize(argc, argv);
+  } else if(!strcmp(argv[1], "importtsv")){
+    rv = runimporttsv(argc, argv);
+  } else if(!strcmp(argv[1], "version") || !strcmp(argv[1], "--version")){
+    rv = runversion(argc, argv);
+  } else {
+    usage();
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+static void usage(void){
+  fprintf(stderr, "%s: the command line utility of the hash database API\n", g_progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s create [-tl] [-td|-tb|-tt|-tx] path [bnum [apow [fpow]]]\n", g_progname);
+  fprintf(stderr, "  %s inform [-nl|-nb] path\n", g_progname);
+  fprintf(stderr, "  %s put [-nl|-nb] [-sx] [-dk|-dc|-dai|-dad] path key value\n", g_progname);
+  fprintf(stderr, "  %s out [-nl|-nb] [-sx] path key\n", g_progname);
+  fprintf(stderr, "  %s get [-nl|-nb] [-sx] [-px] [-pz] path key\n", g_progname);
+  fprintf(stderr, "  %s list [-nl|-nb] [-m num] [-pv] [-px] [-fm str] path\n", g_progname);
+  fprintf(stderr, "  %s optimize [-tl] [-td|-tb|-tt|-tx] [-tz] [-nl|-nb] [-df]"
+          " path [bnum [apow [fpow]]]\n", g_progname);
+  fprintf(stderr, "  %s importtsv [-nl|-nb] [-sc] path [file]\n", g_progname);
+  fprintf(stderr, "  %s version\n", g_progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* print error information */
+static void printerr(TCHDB *hdb){
+  const char *path = tchdbpath(hdb);
+  int ecode = tchdbecode(hdb);
+  fprintf(stderr, "%s: %s: %d: %s\n", g_progname, path ? path : "-", ecode, tchdberrmsg(ecode));
+}
+
+
+/* print record data */
+static int printdata(const char *ptr, int size, bool px){
+  int len = 0;
+  while(size-- > 0){
+    if(px){
+      if(len > 0) putchar(' ');
+      len += printf("%02X", *(unsigned char *)ptr);
+    } else {
+      putchar(*ptr);
+      len++;
+    }
+    ptr++;
+  }
+  return len;
+}
+
+
+/* read a line from a file descriptor */
+static char *mygetline(FILE *ifp){
+  int len = 0;
+  int blen = 1024;
+  char *buf = tcmalloc(blen + 1);
+  bool end = true;
+  int c;
+  while((c = fgetc(ifp)) != EOF){
+    end = false;
+    if(c == '\0') continue;
+    if(blen <= len){
+      blen *= 2;
+      buf = tcrealloc(buf, blen + 1);
+    }
+    if(c == '\n' || c == '\r') c = '\0';
+    buf[len++] = c;
+    if(c == '\0') break;
+  }
+  if(end){
+    tcfree(buf);
+    return NULL;
+  }
+  buf[len] = '\0';
+  return buf;
+}
+
+
+/* parse arguments of create command */
+static int runcreate(int argc, char **argv){
+  char *path = NULL;
+  char *bstr = NULL;
+  char *astr = NULL;
+  char *fstr = NULL;
+  int opts = 0;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-tl")){
+        opts |= HDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= HDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= HDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= HDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= HDBTEXCODEC;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else if(!astr){
+      astr = argv[i];
+    } else if(!fstr){
+      fstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int apow = astr ? tcatoix(astr) : -1;
+  int fpow = fstr ? tcatoix(fstr) : -1;
+  int rv = proccreate(path, bnum, apow, fpow, opts);
+  return rv;
+}
+
+
+/* parse arguments of inform command */
+static int runinform(int argc, char **argv){
+  char *path = NULL;
+  int omode = 0;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= HDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= HDBOLCKNB;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int rv = procinform(path, omode);
+  return rv;
+}
+
+
+/* parse arguments of put command */
+static int runput(int argc, char **argv){
+  char *path = NULL;
+  char *key = NULL;
+  char *value = NULL;
+  int omode = 0;
+  int dmode = 0;
+  bool sx = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= HDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= HDBOLCKNB;
+      } else if(!strcmp(argv[i], "-dk")){
+        dmode = -1;
+      } else if(!strcmp(argv[i], "-dc")){
+        dmode = 1;
+      } else if(!strcmp(argv[i], "-dai")){
+        dmode = 10;
+      } else if(!strcmp(argv[i], "-dad")){
+        dmode = 11;
+      } else if(!strcmp(argv[i], "-sx")){
+        sx = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!key){
+      key = argv[i];
+    } else if(!value){
+      value = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !key || !value) usage();
+  char *kbuf, *vbuf;
+  int ksiz, vsiz;
+  if(sx){
+    kbuf = tchexdecode(key, &ksiz);
+    vbuf = tchexdecode(value, &vsiz);
+  } else {
+    ksiz = strlen(key);
+    kbuf = tcmemdup(key, ksiz);
+    vsiz = strlen(value);
+    vbuf = tcmemdup(value, vsiz);
+  }
+  int rv = procput(path, kbuf, ksiz, vbuf, vsiz, omode, dmode);
+  tcfree(vbuf);
+  tcfree(kbuf);
+  return rv;
+}
+
+
+/* parse arguments of out command */
+static int runout(int argc, char **argv){
+  char *path = NULL;
+  char *key = NULL;
+  int omode = 0;
+  bool sx = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= HDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= HDBOLCKNB;
+      } else if(!strcmp(argv[i], "-sx")){
+        sx = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!key){
+      key = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !key) usage();
+  int ksiz;
+  char *kbuf;
+  if(sx){
+    kbuf = tchexdecode(key, &ksiz);
+  } else {
+    ksiz = strlen(key);
+    kbuf = tcmemdup(key, ksiz);
+  }
+  int rv = procout(path, kbuf, ksiz, omode);
+  tcfree(kbuf);
+  return rv;
+}
+
+
+/* parse arguments of get command */
+static int runget(int argc, char **argv){
+  char *path = NULL;
+  char *key = NULL;
+  int omode = 0;
+  bool sx = false;
+  bool px = false;
+  bool pz = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= HDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= HDBOLCKNB;
+      } else if(!strcmp(argv[i], "-sx")){
+        sx = true;
+      } else if(!strcmp(argv[i], "-px")){
+        px = true;
+      } else if(!strcmp(argv[i], "-pz")){
+        pz = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!key){
+      key = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !key) usage();
+  int ksiz;
+  char *kbuf;
+  if(sx){
+    kbuf = tchexdecode(key, &ksiz);
+  } else {
+    ksiz = strlen(key);
+    kbuf = tcmemdup(key, ksiz);
+  }
+  int rv = procget(path, kbuf, ksiz, omode, px, pz);
+  tcfree(kbuf);
+  return rv;
+}
+
+
+/* parse arguments of list command */
+static int runlist(int argc, char **argv){
+  char *path = NULL;
+  int omode = 0;
+  int max = -1;
+  bool pv = false;
+  bool px = false;
+  char *fmstr = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= HDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= HDBOLCKNB;
+      } else if(!strcmp(argv[i], "-m")){
+        if(++i >= argc) usage();
+        max = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-pv")){
+        pv = true;
+      } else if(!strcmp(argv[i], "-px")){
+        px = true;
+      } else if(!strcmp(argv[i], "-fm")){
+        if(++i >= argc) usage();
+        fmstr = argv[i];
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int rv = proclist(path, omode, max, pv, px, fmstr);
+  return rv;
+}
+
+
+/* parse arguments of optimize command */
+static int runoptimize(int argc, char **argv){
+  char *path = NULL;
+  char *bstr = NULL;
+  char *astr = NULL;
+  char *fstr = NULL;
+  int opts = UINT8_MAX;
+  int omode = 0;
+  bool df = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-tl")){
+        if(opts == UINT8_MAX) opts = 0;
+        opts |= HDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        if(opts == UINT8_MAX) opts = 0;
+        opts |= HDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        if(opts == UINT8_MAX) opts = 0;
+        opts |= HDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        if(opts == UINT8_MAX) opts = 0;
+        opts |= HDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        if(opts == UINT8_MAX) opts = 0;
+        opts |= HDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-tz")){
+        if(opts == UINT8_MAX) opts = 0;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= HDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= HDBOLCKNB;
+      } else if(!strcmp(argv[i], "-df")){
+        df = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else if(!astr){
+      astr = argv[i];
+    } else if(!fstr){
+      fstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int apow = astr ? tcatoix(astr) : -1;
+  int fpow = fstr ? tcatoix(fstr) : -1;
+  int rv = procoptimize(path, bnum, apow, fpow, opts, omode, df);
+  return rv;
+}
+
+
+/* parse arguments of importtsv command */
+static int runimporttsv(int argc, char **argv){
+  char *path = NULL;
+  char *file = NULL;
+  int omode = 0;
+  bool sc = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= HDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= HDBOLCKNB;
+      } else if(!strcmp(argv[i], "-sc")){
+        sc = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!file){
+      file = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int rv = procimporttsv(path, file, omode, sc);
+  return rv;
+}
+
+
+/* parse arguments of version command */
+static int runversion(int argc, char **argv){
+  int rv = procversion();
+  return rv;
+}
+
+
+/* perform create command */
+static int proccreate(const char *path, int bnum, int apow, int fpow, int opts){
+  TCHDB *hdb = tchdbnew();
+  if(g_dbgfd >= 0) tchdbsetdbgfd(hdb, g_dbgfd);
+  if(!tchdbsetcodecfunc(hdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(hdb);
+  if(!tchdbtune(hdb, bnum, apow, fpow, opts)){
+    printerr(hdb);
+    tchdbdel(hdb);
+    return 1;
+  }
+  if(!tchdbopen(hdb, path, HDBOWRITER | HDBOCREAT | HDBOTRUNC)){
+    printerr(hdb);
+    tchdbdel(hdb);
+    return 1;
+  }
+  bool err = false;
+  if(!tchdbclose(hdb)){
+    printerr(hdb);
+    err = true;
+  }
+  tchdbdel(hdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform inform command */
+static int procinform(const char *path, int omode){
+  TCHDB *hdb = tchdbnew();
+  if(g_dbgfd >= 0) tchdbsetdbgfd(hdb, g_dbgfd);
+  tchdbsetcodecfunc(hdb, _tc_recencode, NULL, _tc_recdecode, NULL);
+  if(!tchdbopen(hdb, path, HDBOREADER | omode)){
+    printerr(hdb);
+    tchdbdel(hdb);
+    return 1;
+  }
+  bool err = false;
+  const char *npath = tchdbpath(hdb);
+  if(!npath) npath = "(unknown)";
+  printf("path: %s\n", npath);
+  const char *type = "(unknown)";
+  switch(tchdbtype(hdb)){
+    case TCDBTHASH: type = "hash"; break;
+    case TCDBTBTREE: type = "btree"; break;
+    case TCDBTFIXED: type = "fixed"; break;
+    case TCDBTTABLE: type = "table"; break;
+  }
+  printf("database type: %s\n", type);
+  uint8_t flags = tchdbflags(hdb);
+  printf("additional flags:");
+  if(flags & HDBFOPEN) printf(" open");
+  if(flags & HDBFFATAL) printf(" fatal");
+  printf("\n");
+  printf("bucket number: %llu\n", (unsigned long long)tchdbbnum(hdb));
+  if(hdb->cnt_writerec >= 0)
+    printf("used bucket number: %lld\n", (long long)tchdbbnumused(hdb));
+  printf("alignment: %u\n", tchdbalign(hdb));
+  printf("free block pool: %u\n", tchdbfbpmax(hdb));
+  printf("inode number: %lld\n", (long long)tchdbinode(hdb));
+  char date[48];
+  tcdatestrwww(tchdbmtime(hdb), INT_MAX, date);
+  printf("modified time: %s\n", date);
+  uint8_t opts = tchdbopts(hdb);
+  printf("options:");
+  if(opts & HDBTLARGE) printf(" large");
+  if(opts & HDBTDEFLATE) printf(" deflate");
+  if(opts & HDBTBZIP) printf(" bzip");
+  if(opts & HDBTTCBS) printf(" tcbs");
+  if(opts & HDBTEXCODEC) printf(" excodec");
+  printf("\n");
+  printf("record number: %llu\n", (unsigned long long)tchdbrnum(hdb));
+  printf("file size: %llu\n", (unsigned long long)tchdbfsiz(hdb));
+  if(!tchdbclose(hdb)){
+    if(!err) printerr(hdb);
+    err = true;
+  }
+  tchdbdel(hdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform put command */
+static int procput(const char *path, const char *kbuf, int ksiz, const char *vbuf, int vsiz,
+                   int omode, int dmode){
+  TCHDB *hdb = tchdbnew();
+  if(g_dbgfd >= 0) tchdbsetdbgfd(hdb, g_dbgfd);
+  if(!tchdbsetcodecfunc(hdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(hdb);
+  if(!tchdbopen(hdb, path, HDBOWRITER | omode)){
+    printerr(hdb);
+    tchdbdel(hdb);
+    return 1;
+  }
+  bool err = false;
+  int inum;
+  double dnum;
+  switch(dmode){
+    case -1:
+      if(!tchdbputkeep(hdb, kbuf, ksiz, vbuf, vsiz)){
+        printerr(hdb);
+        err = true;
+      }
+      break;
+    case 1:
+      if(!tchdbputcat(hdb, kbuf, ksiz, vbuf, vsiz)){
+        printerr(hdb);
+        err = true;
+      }
+      break;
+    case 10:
+      inum = tchdbaddint(hdb, kbuf, ksiz, tcatoi(vbuf));
+      if(inum == INT_MIN){
+        printerr(hdb);
+        err = true;
+      } else {
+        printf("%d\n", inum);
+      }
+      break;
+    case 11:
+      dnum = tchdbadddouble(hdb, kbuf, ksiz, tcatof(vbuf));
+      if(isnan(dnum)){
+        printerr(hdb);
+        err = true;
+      } else {
+        printf("%.6f\n", dnum);
+      }
+      break;
+    default:
+      if(!tchdbput(hdb, kbuf, ksiz, vbuf, vsiz)){
+        printerr(hdb);
+        err = true;
+      }
+      break;
+  }
+  if(!tchdbclose(hdb)){
+    if(!err) printerr(hdb);
+    err = true;
+  }
+  tchdbdel(hdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform out command */
+static int procout(const char *path, const char *kbuf, int ksiz, int omode){
+  TCHDB *hdb = tchdbnew();
+  if(g_dbgfd >= 0) tchdbsetdbgfd(hdb, g_dbgfd);
+  if(!tchdbsetcodecfunc(hdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(hdb);
+  if(!tchdbopen(hdb, path, HDBOWRITER | omode)){
+    printerr(hdb);
+    tchdbdel(hdb);
+    return 1;
+  }
+  bool err = false;
+  if(!tchdbout(hdb, kbuf, ksiz)){
+    printerr(hdb);
+    err = true;
+  }
+  if(!tchdbclose(hdb)){
+    if(!err) printerr(hdb);
+    err = true;
+  }
+  tchdbdel(hdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform get command */
+static int procget(const char *path, const char *kbuf, int ksiz, int omode, bool px, bool pz){
+  TCHDB *hdb = tchdbnew();
+  if(g_dbgfd >= 0) tchdbsetdbgfd(hdb, g_dbgfd);
+  if(!tchdbsetcodecfunc(hdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(hdb);
+  if(!tchdbopen(hdb, path, HDBOREADER | omode)){
+    printerr(hdb);
+    tchdbdel(hdb);
+    return 1;
+  }
+  bool err = false;
+  int vsiz;
+  char *vbuf = tchdbget(hdb, kbuf, ksiz, &vsiz);
+  if(vbuf){
+    printdata(vbuf, vsiz, px);
+    if(!pz) putchar('\n');
+    tcfree(vbuf);
+  } else {
+    printerr(hdb);
+    err = true;
+  }
+  if(!tchdbclose(hdb)){
+    if(!err) printerr(hdb);
+    err = true;
+  }
+  tchdbdel(hdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform list command */
+static int proclist(const char *path, int omode, int max, bool pv, bool px, const char *fmstr){
+  TCHDB *hdb = tchdbnew();
+  if(g_dbgfd >= 0) tchdbsetdbgfd(hdb, g_dbgfd);
+  if(!tchdbsetcodecfunc(hdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(hdb);
+  if(!tchdbopen(hdb, path, HDBOREADER | omode)){
+    printerr(hdb);
+    tchdbdel(hdb);
+    return 1;
+  }
+  bool err = false;
+  if(fmstr){
+    TCLIST *keys = tchdbfwmkeys2(hdb, fmstr, max);
+    for(int i = 0; i < tclistnum(keys); i++){
+      int ksiz;
+      const char *kbuf = tclistval(keys, i, &ksiz);
+      printdata(kbuf, ksiz, px);
+      if(pv){
+        int vsiz;
+        char *vbuf = tchdbget(hdb, kbuf, ksiz, &vsiz);
+        if(vbuf){
+          putchar('\t');
+          printdata(vbuf, vsiz, px);
+          tcfree(vbuf);
+        }
+      }
+      putchar('\n');
+    }
+    tclistdel(keys);
+  } else {
+    if(!tchdbiterinit(hdb)){
+      printerr(hdb);
+      err = true;
+    }
+    TCXSTR *key = tcxstrnew();
+    TCXSTR *val = tcxstrnew();
+    int cnt = 0;
+    while(tchdbiternext3(hdb, key, val)){
+      printdata(tcxstrptr(key), tcxstrsize(key), px);
+      if(pv){
+        putchar('\t');
+        printdata(tcxstrptr(val), tcxstrsize(val), px);
+      }
+      putchar('\n');
+      if(max >= 0 && ++cnt >= max) break;
+    }
+    tcxstrdel(val);
+    tcxstrdel(key);
+  }
+  if(!tchdbclose(hdb)){
+    if(!err) printerr(hdb);
+    err = true;
+  }
+  tchdbdel(hdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform optimize command */
+static int procoptimize(const char *path, int bnum, int apow, int fpow, int opts, int omode,
+                        bool df){
+  TCHDB *hdb = tchdbnew();
+  if(g_dbgfd >= 0) tchdbsetdbgfd(hdb, g_dbgfd);
+  if(!tchdbsetcodecfunc(hdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(hdb);
+  if(!tchdbopen(hdb, path, HDBOWRITER | omode)){
+    printerr(hdb);
+    tchdbdel(hdb);
+    return 1;
+  }
+  bool err = false;
+  if(df){
+    if(!tchdbdefrag(hdb, INT64_MAX)){
+      printerr(hdb);
+      err = true;
+    }
+  } else {
+    if(!tchdboptimize(hdb, bnum, apow, fpow, opts)){
+      printerr(hdb);
+      err = true;
+    }
+  }
+  if(!tchdbclose(hdb)){
+    if(!err) printerr(hdb);
+    err = true;
+  }
+  tchdbdel(hdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform importtsv command */
+static int procimporttsv(const char *path, const char *file, int omode, bool sc){
+  FILE *ifp = file ? fopen(file, "rb") : stdin;
+  if(!ifp){
+    fprintf(stderr, "%s: could not open\n", file ? file : "(stdin)");
+    return 1;
+  }
+  TCHDB *hdb = tchdbnew();
+  if(g_dbgfd >= 0) tchdbsetdbgfd(hdb, g_dbgfd);
+  if(!tchdbsetcodecfunc(hdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(hdb);
+  if(!tchdbopen(hdb, path, HDBOWRITER | HDBOCREAT | omode)){
+    printerr(hdb);
+    tchdbdel(hdb);
+    if(ifp != stdin) fclose(ifp);
+    return 1;
+  }
+  bool err = false;
+  char *line;
+  int cnt = 0;
+  while(!err && (line = mygetline(ifp)) != NULL){
+    char *pv = strchr(line, '\t');
+    if(!pv){
+      tcfree(line);
+      continue;
+    }
+    *pv = '\0';
+    if(sc) tcstrutfnorm(line, TCUNSPACE | TCUNLOWER | TCUNNOACC | TCUNWIDTH);
+    if(!tchdbput2(hdb, line, pv + 1)){
+      printerr(hdb);
+      err = true;
+    }
+    tcfree(line);
+    if(cnt > 0 && cnt % 100 == 0){
+      putchar('.');
+      fflush(stdout);
+      if(cnt % 5000 == 0) printf(" (%08d)\n", cnt);
+    }
+    cnt++;
+  }
+  printf(" (%08d)\n", cnt);
+  if(!tchdbclose(hdb)){
+    if(!err) printerr(hdb);
+    err = true;
+  }
+  tchdbdel(hdb);
+  if(ifp != stdin) fclose(ifp);
+  return err ? 1 : 0;
+}
+
+
+/* perform version command */
+static int procversion(void){
+  printf("Tokyo Cabinet version %s (%d:%s) for %s\n",
+         tcversion, _TC_LIBVER, _TC_FORMATVER, TCSYSNAME);
+  printf("Copyright (C) 2006-2012 FAL Labs\n");
+  return 0;
+}
+
+
+
+// END OF FILE
diff --git a/tcejdb/tchmttest.c b/tcejdb/tchmttest.c
new file mode 100644 (file)
index 0000000..d46b5b5
--- /dev/null
@@ -0,0 +1,1757 @@
+/*************************************************************************************************
+ * The test cases of the hash database API
+ *                                                               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 "myconf.h"
+
+#define RECBUFSIZ      48                // buffer for records
+
+typedef struct {                         // type of structure for write thread
+  TCHDB *hdb;
+  int rnum;
+  bool as;
+  bool rnd;
+  int id;
+} TARGWRITE;
+
+typedef struct {                         // type of structure for read thread
+  TCHDB *hdb;
+  int rnum;
+  bool wb;
+  bool rnd;
+  int id;
+} TARGREAD;
+
+typedef struct {                         // type of structure for remove thread
+  TCHDB *hdb;
+  int rnum;
+  bool rnd;
+  int id;
+} TARGREMOVE;
+
+typedef struct {                         // type of structure for wicked thread
+  TCHDB *hdb;
+  int rnum;
+  bool nc;
+  int id;
+  TCMAP *map;
+} TARGWICKED;
+
+typedef struct {                         // type of structure for typical thread
+  TCHDB *hdb;
+  int rnum;
+  bool nc;
+  int rratio;
+  int id;
+} TARGTYPICAL;
+
+typedef struct {                         // type of structure for race thread
+  TCHDB *hdb;
+  int rnum;
+  int id;
+} TARGRACE;
+
+
+/* global variables */
+const char *g_progname;                  // program name
+unsigned int g_randseed;                 // random seed
+int g_dbgfd;                             // debugging output
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+static void usage(void);
+static void iprintf(const char *format, ...);
+static void iputchar(int c);
+static void eprint(TCHDB *hdb, int line, const char *func);
+static void mprint(TCHDB *hdb);
+static void sysprint(void);
+static int myrand(int range);
+static int myrandnd(int range);
+static bool iterfunc(const void *kbuf, int ksiz, const void *vbuf, int vsiz, void *op);
+static int runwrite(int argc, char **argv);
+static int runread(int argc, char **argv);
+static int runremove(int argc, char **argv);
+static int runwicked(int argc, char **argv);
+static int runtypical(int argc, char **argv);
+static int runrace(int argc, char **argv);
+static int procwrite(const char *path, int tnum, int rnum, int bnum, int apow, int fpow,
+                     int opts, int rcnum, int xmsiz, int dfunit, int omode, bool as, bool rnd);
+static int procread(const char *path, int tnum, int rcnum, int xmsiz, int dfunit, int omode,
+                    bool wb, bool rnd);
+static int procremove(const char *path, int tnum, int rcnum, int xmsiz, int dfunit, int omode,
+                      bool rnd);
+static int procwicked(const char *path, int tnum, int rnum, int opts, int omode, bool nc);
+static int proctypical(const char *path, int tnum, int rnum, int bnum, int apow, int fpow,
+                       int opts, int rcnum, int xmsiz, int dfunit, int omode,
+                       bool nc, int rratio);
+static int procrace(const char *path, int tnum, int rnum, int bnum, int apow, int fpow,
+                    int opts, int xmsiz, int dfunit, int omode);
+static void *threadwrite(void *targ);
+static void *threadread(void *targ);
+static void *threadremove(void *targ);
+static void *threadwicked(void *targ);
+static void *threadtypical(void *targ);
+static void *threadrace(void *targ);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  g_progname = argv[0];
+  const char *ebuf = getenv("TCRNDSEED");
+  g_randseed = ebuf ? tcatoix(ebuf) : tctime() * 1000;
+  srand(g_randseed);
+  ebuf = getenv("TCDBGFD");
+  g_dbgfd = ebuf ? tcatoix(ebuf) : UINT16_MAX;
+  if(argc < 2) usage();
+  int rv = 0;
+  if(!strcmp(argv[1], "write")){
+    rv = runwrite(argc, argv);
+  } else if(!strcmp(argv[1], "read")){
+    rv = runread(argc, argv);
+  } else if(!strcmp(argv[1], "remove")){
+    rv = runremove(argc, argv);
+  } else if(!strcmp(argv[1], "wicked")){
+    rv = runwicked(argc, argv);
+  } else if(!strcmp(argv[1], "typical")){
+    rv = runtypical(argc, argv);
+  } else if(!strcmp(argv[1], "race")){
+    rv = runrace(argc, argv);
+  } else {
+    usage();
+  }
+  if(rv != 0){
+    printf("FAILED: TCRNDSEED=%u PID=%d", g_randseed, (int)getpid());
+    for(int i = 0; i < argc; i++){
+      printf(" %s", argv[i]);
+    }
+    printf("\n\n");
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+static void usage(void){
+  fprintf(stderr, "%s: test cases of the hash database API of Tokyo Cabinet\n", g_progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s write [-tl] [-td|-tb|-tt|-tx] [-rc num] [-xm num] [-df num]"
+          " [-nl|-nb] [-as] [-rnd] path tnum rnum [bnum [apow [fpow]]]\n", g_progname);
+  fprintf(stderr, "  %s read [-rc num] [-xm num] [-df num] [-nl|-nb] [-wb] [-rnd] path tnum\n",
+          g_progname);
+  fprintf(stderr, "  %s remove [-rc num] [-xm num] [-df num] [-nl|-nb] [-rnd] path tnum\n",
+          g_progname);
+  fprintf(stderr, "  %s wicked [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] [-nc]"
+          " path tnum rnum\n", g_progname);
+  fprintf(stderr, "  %s typical [-tl] [-td|-tb|-tt|-tx] [-rc num] [-xm num] [-df num]"
+          " [-nl|-nb] [-nc] [-rr num] path tnum rnum [bnum [apow [fpow]]]\n", g_progname);
+  fprintf(stderr, "  %s race [-tl] [-td|-tb|-tt|-tx] [-xm num] [-df num] [-nl|-nb]"
+          " path tnum rnum [bnum [apow [fpow]]]\n", g_progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* print formatted information string and flush the buffer */
+static void iprintf(const char *format, ...){
+  va_list ap;
+  va_start(ap, format);
+  vprintf(format, ap);
+  fflush(stdout);
+  va_end(ap);
+}
+
+
+/* print a character and flush the buffer */
+static void iputchar(int c){
+  putchar(c);
+  fflush(stdout);
+}
+
+
+/* print error message of hash database */
+static void eprint(TCHDB *hdb, int line, const char *func){
+  const char *path = tchdbpath(hdb);
+  int ecode = tchdbecode(hdb);
+  fprintf(stderr, "%s: %s: %d: %s: error: %d: %s\n",
+          g_progname, path ? path : "-", line, func, ecode, tchdberrmsg(ecode));
+}
+
+
+/* print members of hash database */
+static void mprint(TCHDB *hdb){
+  if(hdb->cnt_writerec < 0) return;
+  iprintf("bucket number: %lld\n", (long long)tchdbbnum(hdb));
+  iprintf("used bucket number: %lld\n", (long long)tchdbbnumused(hdb));
+  iprintf("cnt_writerec: %lld\n", (long long)hdb->cnt_writerec);
+  iprintf("cnt_reuserec: %lld\n", (long long)hdb->cnt_reuserec);
+  iprintf("cnt_moverec: %lld\n", (long long)hdb->cnt_moverec);
+  iprintf("cnt_readrec: %lld\n", (long long)hdb->cnt_readrec);
+  iprintf("cnt_searchfbp: %lld\n", (long long)hdb->cnt_searchfbp);
+  iprintf("cnt_insertfbp: %lld\n", (long long)hdb->cnt_insertfbp);
+  iprintf("cnt_splicefbp: %lld\n", (long long)hdb->cnt_splicefbp);
+  iprintf("cnt_dividefbp: %lld\n", (long long)hdb->cnt_dividefbp);
+  iprintf("cnt_mergefbp: %lld\n", (long long)hdb->cnt_mergefbp);
+  iprintf("cnt_reducefbp: %lld\n", (long long)hdb->cnt_reducefbp);
+  iprintf("cnt_appenddrp: %lld\n", (long long)hdb->cnt_appenddrp);
+  iprintf("cnt_deferdrp: %lld\n", (long long)hdb->cnt_deferdrp);
+  iprintf("cnt_flushdrp: %lld\n", (long long)hdb->cnt_flushdrp);
+  iprintf("cnt_adjrecc: %lld\n", (long long)hdb->cnt_adjrecc);
+  iprintf("cnt_defrag: %lld\n", (long long)hdb->cnt_defrag);
+  iprintf("cnt_shiftrec: %lld\n", (long long)hdb->cnt_shiftrec);
+  iprintf("cnt_trunc: %lld\n", (long long)hdb->cnt_trunc);
+}
+
+
+/* print system information */
+static void sysprint(void){
+  TCMAP *info = tcsysinfo();
+  if(info){
+    tcmapiterinit(info);
+    const char *kbuf;
+    while((kbuf = tcmapiternext2(info)) != NULL){
+      iprintf("sys_%s: %s\n", kbuf, tcmapiterval2(kbuf));
+    }
+    tcmapdel(info);
+  }
+}
+
+
+/* get a random number */
+static int myrand(int range){
+  if(range < 2) return 0;
+  int high = (unsigned int)rand() >> 4;
+  int low = range * (rand() / (RAND_MAX + 1.0));
+  low &= (unsigned int)INT_MAX >> 4;
+  return (high + low) % range;
+}
+
+
+/* get a random number based on normal distribution */
+static int myrandnd(int range){
+  int num = (int)tcdrandnd(range >> 1, range / 10);
+  return (num < 0 || num >= range) ? 0 : num;
+}
+
+
+/* iterator function */
+static bool iterfunc(const void *kbuf, int ksiz, const void *vbuf, int vsiz, void *op){
+  unsigned int sum = 0;
+  while(--ksiz >= 0){
+    sum += ((char *)kbuf)[ksiz];
+  }
+  while(--vsiz >= 0){
+    sum += ((char *)vbuf)[vsiz];
+  }
+  return myrand(100 + (sum & 0xff)) > 0;
+}
+
+
+/* parse arguments of write command */
+static int runwrite(int argc, char **argv){
+  char *path = NULL;
+  char *tstr = NULL;
+  char *rstr = NULL;
+  char *bstr = NULL;
+  char *astr = NULL;
+  char *fstr = NULL;
+  int opts = 0;
+  int rcnum = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int omode = 0;
+  bool as = false;
+  bool rnd = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-tl")){
+        opts |= HDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= HDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= HDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= HDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= HDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-rc")){
+        if(++i >= argc) usage();
+        rcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= HDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= HDBOLCKNB;
+      } else if(!strcmp(argv[i], "-as")){
+        as = true;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else if(!astr){
+      astr = argv[i];
+    } else if(!fstr){
+      fstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !tstr || !rstr) usage();
+  int tnum = tcatoix(tstr);
+  int rnum = tcatoix(rstr);
+  if(tnum < 1 || rnum < 1) usage();
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int apow = astr ? tcatoix(astr) : -1;
+  int fpow = fstr ? tcatoix(fstr) : -1;
+  int rv = procwrite(path, tnum, rnum, bnum, apow, fpow, opts, rcnum, xmsiz, dfunit, omode,
+                     as, rnd);
+  return rv;
+}
+
+
+/* parse arguments of read command */
+static int runread(int argc, char **argv){
+  char *path = NULL;
+  char *tstr = NULL;
+  int rcnum = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int omode = 0;
+  bool wb = false;
+  bool rnd = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-rc")){
+        if(++i >= argc) usage();
+        rcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= HDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= HDBOLCKNB;
+      } else if(!strcmp(argv[i], "-wb")){
+        wb = true;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !tstr) usage();
+  int tnum = tcatoix(tstr);
+  if(tnum < 1) usage();
+  int rv = procread(path, tnum, rcnum, xmsiz, dfunit, omode, wb, rnd);
+  return rv;
+}
+
+
+/* parse arguments of remove command */
+static int runremove(int argc, char **argv){
+  char *path = NULL;
+  char *tstr = NULL;
+  int rcnum = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int omode = 0;
+  bool rnd = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-rc")){
+        if(++i >= argc) usage();
+        rcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= HDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= HDBOLCKNB;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !tstr) usage();
+  int tnum = tcatoix(tstr);
+  if(tnum < 1) usage();
+  int rv = procremove(path, tnum, rcnum, xmsiz, dfunit, omode, rnd);
+  return rv;
+}
+
+
+/* parse arguments of wicked command */
+static int runwicked(int argc, char **argv){
+  char *path = NULL;
+  char *tstr = NULL;
+  char *rstr = NULL;
+  int opts = 0;
+  int omode = 0;
+  bool nc = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-tl")){
+        opts |= HDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= HDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= HDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= HDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= HDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= HDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= HDBOLCKNB;
+      } else if(!strcmp(argv[i], "-nc")){
+        nc = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !tstr || !rstr) usage();
+  int tnum = tcatoix(tstr);
+  int rnum = tcatoix(rstr);
+  if(tnum < 1 || rnum < 1) usage();
+  int rv = procwicked(path, tnum, rnum, opts, omode, nc);
+  return rv;
+}
+
+
+/* parse arguments of typical command */
+static int runtypical(int argc, char **argv){
+  char *path = NULL;
+  char *tstr = NULL;
+  char *rstr = NULL;
+  char *bstr = NULL;
+  char *astr = NULL;
+  char *fstr = NULL;
+  int opts = 0;
+  int rcnum = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int omode = 0;
+  int rratio = -1;
+  bool nc = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-tl")){
+        opts |= HDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= HDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= HDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= HDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= HDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-rc")){
+        if(++i >= argc) usage();
+        rcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= HDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= HDBOLCKNB;
+      } else if(!strcmp(argv[i], "-nc")){
+        nc = true;
+      } else if(!strcmp(argv[i], "-rr")){
+        if(++i >= argc) usage();
+        rratio = tcatoix(argv[i]);
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else if(!astr){
+      astr = argv[i];
+    } else if(!fstr){
+      fstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !tstr || !rstr) usage();
+  int tnum = tcatoix(tstr);
+  int rnum = tcatoix(rstr);
+  if(tnum < 1 || rnum < 1) usage();
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int apow = astr ? tcatoix(astr) : -1;
+  int fpow = fstr ? tcatoix(fstr) : -1;
+  int rv = proctypical(path, tnum, rnum, bnum, apow, fpow, opts, rcnum, xmsiz, dfunit, omode,
+                       nc, rratio);
+  return rv;
+}
+
+
+/* parse arguments of race command */
+static int runrace(int argc, char **argv){
+  char *path = NULL;
+  char *tstr = NULL;
+  char *rstr = NULL;
+  char *bstr = NULL;
+  char *astr = NULL;
+  char *fstr = NULL;
+  int opts = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int omode = 0;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-tl")){
+        opts |= HDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= HDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= HDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= HDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= HDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= HDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= HDBOLCKNB;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else if(!astr){
+      astr = argv[i];
+    } else if(!fstr){
+      fstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !tstr || !rstr) usage();
+  int tnum = tcatoix(tstr);
+  int rnum = tcatoix(rstr);
+  if(tnum < 1 || rnum < 1) usage();
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int apow = astr ? tcatoix(astr) : -1;
+  int fpow = fstr ? tcatoix(fstr) : -1;
+  int rv = procrace(path, tnum, rnum, bnum, apow, fpow, opts, xmsiz, dfunit, omode);
+  return rv;
+}
+
+
+/* perform write command */
+static int procwrite(const char *path, int tnum, int rnum, int bnum, int apow, int fpow,
+                     int opts, int rcnum, int xmsiz, int dfunit, int omode, bool as, bool rnd){
+  iprintf("<Writing Test>\n  seed=%u  path=%s  tnum=%d  rnum=%d  bnum=%d  apow=%d  fpow=%d"
+          "  opts=%d  rcnum=%d  xmsiz=%d  dfunit=%d  omode=%d  as=%d  rnd=%d\n\n",
+          g_randseed, path, tnum, rnum, bnum, apow, fpow, opts, rcnum, xmsiz, dfunit, omode,
+          as, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCHDB *hdb = tchdbnew();
+  if(g_dbgfd >= 0) tchdbsetdbgfd(hdb, g_dbgfd);
+  if(!tchdbsetmutex(hdb)){
+    eprint(hdb, __LINE__, "tchdbsetmutex");
+    err = true;
+  }
+  if(!tchdbsetcodecfunc(hdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(hdb, __LINE__, "tchdbsetcodecfunc");
+    err = true;
+  }
+  if(!tchdbtune(hdb, bnum, apow, fpow, opts)){
+    eprint(hdb, __LINE__, "tchdbtune");
+    err = true;
+  }
+  if(!tchdbsetcache(hdb, rcnum)){
+    eprint(hdb, __LINE__, "tchdbsetcache");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tchdbsetxmsiz(hdb, xmsiz)){
+    eprint(hdb, __LINE__, "tchdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tchdbsetdfunit(hdb, dfunit)){
+    eprint(hdb, __LINE__, "tchdbsetdfunit");
+    err = true;
+  }
+  if(!tchdbopen(hdb, path, HDBOWRITER | HDBOCREAT | HDBOTRUNC | omode)){
+    eprint(hdb, __LINE__, "tchdbopen");
+    err = true;
+  }
+  TARGWRITE targs[tnum];
+  pthread_t threads[tnum];
+  if(tnum == 1){
+    targs[0].hdb = hdb;
+    targs[0].rnum = rnum;
+    targs[0].as = as;
+    targs[0].rnd = rnd;
+    targs[0].id = 0;
+    if(threadwrite(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].hdb = hdb;
+      targs[i].rnum = rnum;
+      targs[i].as = as;
+      targs[i].rnd = rnd;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadwrite, targs + i) != 0){
+        eprint(hdb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(hdb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tchdbrnum(hdb));
+  iprintf("size: %llu\n", (unsigned long long)tchdbfsiz(hdb));
+  mprint(hdb);
+  sysprint();
+  if(!tchdbclose(hdb)){
+    eprint(hdb, __LINE__, "tchdbclose");
+    err = true;
+  }
+  tchdbdel(hdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform read command */
+static int procread(const char *path, int tnum, int rcnum, int xmsiz, int dfunit, int omode,
+                    bool wb, bool rnd){
+  iprintf("<Reading Test>\n  seed=%u  path=%s  tnum=%d  rcnum=%d  xmsiz=%d  dfunit=%d  omode=%d"
+          "  wb=%d  rnd=%d\n\n", g_randseed, path, tnum, rcnum, xmsiz, dfunit, omode, wb, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCHDB *hdb = tchdbnew();
+  if(g_dbgfd >= 0) tchdbsetdbgfd(hdb, g_dbgfd);
+  if(!tchdbsetmutex(hdb)){
+    eprint(hdb, __LINE__, "tchdbsetmutex");
+    err = true;
+  }
+  if(!tchdbsetcodecfunc(hdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(hdb, __LINE__, "tchdbsetcodecfunc");
+    err = true;
+  }
+  if(!tchdbsetcache(hdb, rcnum)){
+    eprint(hdb, __LINE__, "tchdbsetcache");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tchdbsetxmsiz(hdb, xmsiz)){
+    eprint(hdb, __LINE__, "tchdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tchdbsetdfunit(hdb, dfunit)){
+    eprint(hdb, __LINE__, "tchdbsetdfunit");
+    err = true;
+  }
+  if(!tchdbopen(hdb, path, HDBOREADER | omode)){
+    eprint(hdb, __LINE__, "tchdbopen");
+    err = true;
+  }
+  int rnum = tchdbrnum(hdb) / tnum;
+  TARGREAD targs[tnum];
+  pthread_t threads[tnum];
+  if(tnum == 1){
+    targs[0].hdb = hdb;
+    targs[0].rnum = rnum;
+    targs[0].wb = wb;
+    targs[0].rnd = rnd;
+    targs[0].id = 0;
+    if(threadread(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].hdb = hdb;
+      targs[i].rnum = rnum;
+      targs[i].wb = wb;
+      targs[i].rnd = rnd;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadread, targs + i) != 0){
+        eprint(hdb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(hdb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tchdbrnum(hdb));
+  iprintf("size: %llu\n", (unsigned long long)tchdbfsiz(hdb));
+  mprint(hdb);
+  sysprint();
+  if(!tchdbclose(hdb)){
+    eprint(hdb, __LINE__, "tchdbclose");
+    err = true;
+  }
+  tchdbdel(hdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform remove command */
+static int procremove(const char *path, int tnum, int rcnum, int xmsiz, int dfunit, int omode,
+                      bool rnd){
+  iprintf("<Removing Test>\n  seed=%u  path=%s  tnum=%d  rcnum=%d  xmsiz=%d  dfunit=%d  omode=%d"
+          "  rnd=%d\n\n", g_randseed, path, tnum, rcnum, xmsiz, dfunit, omode, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCHDB *hdb = tchdbnew();
+  if(g_dbgfd >= 0) tchdbsetdbgfd(hdb, g_dbgfd);
+  if(!tchdbsetmutex(hdb)){
+    eprint(hdb, __LINE__, "tchdbsetmutex");
+    err = true;
+  }
+  if(!tchdbsetcodecfunc(hdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(hdb, __LINE__, "tchdbsetcodecfunc");
+    err = true;
+  }
+  if(!tchdbsetcache(hdb, rcnum)){
+    eprint(hdb, __LINE__, "tchdbsetcache");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tchdbsetxmsiz(hdb, xmsiz)){
+    eprint(hdb, __LINE__, "tchdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tchdbsetdfunit(hdb, dfunit)){
+    eprint(hdb, __LINE__, "tchdbsetdfunit");
+    err = true;
+  }
+  if(!tchdbopen(hdb, path, HDBOWRITER | omode)){
+    eprint(hdb, __LINE__, "tchdbopen");
+    err = true;
+  }
+  int rnum = tchdbrnum(hdb) / tnum;
+  TARGREMOVE targs[tnum];
+  pthread_t threads[tnum];
+  if(tnum == 1){
+    targs[0].hdb = hdb;
+    targs[0].rnum = rnum;
+    targs[0].rnd = rnd;
+    targs[0].id = 0;
+    if(threadremove(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].hdb = hdb;
+      targs[i].rnum = rnum;
+      targs[i].rnd = rnd;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadremove, targs + i) != 0){
+        eprint(hdb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(hdb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tchdbrnum(hdb));
+  iprintf("size: %llu\n", (unsigned long long)tchdbfsiz(hdb));
+  mprint(hdb);
+  sysprint();
+  if(!tchdbclose(hdb)){
+    eprint(hdb, __LINE__, "tchdbclose");
+    err = true;
+  }
+  tchdbdel(hdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform wicked command */
+static int procwicked(const char *path, int tnum, int rnum, int opts, int omode, bool nc){
+  iprintf("<Writing Test>\n  seed=%u  path=%s  tnum=%d  rnum=%d  opts=%d  omode=%d  nc=%d\n\n",
+          g_randseed, path, tnum, rnum, opts, omode, nc);
+  bool err = false;
+  double stime = tctime();
+  TCHDB *hdb = tchdbnew();
+  if(g_dbgfd >= 0) tchdbsetdbgfd(hdb, g_dbgfd);
+  if(!tchdbsetmutex(hdb)){
+    eprint(hdb, __LINE__, "tchdbsetmutex");
+    err = true;
+  }
+  if(!tchdbsetcodecfunc(hdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(hdb, __LINE__, "tchdbsetcodecfunc");
+    err = true;
+  }
+  if(!tchdbtune(hdb, rnum / 50, 2, -1, opts)){
+    eprint(hdb, __LINE__, "tchdbtune");
+    err = true;
+  }
+  if(!tchdbsetcache(hdb, rnum / 2)){
+    eprint(hdb, __LINE__, "tchdbsetcache");
+    err = true;
+  }
+  if(!tchdbsetxmsiz(hdb, rnum * sizeof(int))){
+    eprint(hdb, __LINE__, "tchdbsetxmsiz");
+    err = true;
+  }
+  if(!tchdbsetdfunit(hdb, 8)){
+    eprint(hdb, __LINE__, "tchdbsetdfunit");
+    err = true;
+  }
+  if(!tchdbopen(hdb, path, HDBOWRITER | HDBOCREAT | HDBOTRUNC | omode)){
+    eprint(hdb, __LINE__, "tchdbopen");
+    err = true;
+  }
+  if(!tchdbiterinit(hdb)){
+    eprint(hdb, __LINE__, "tchdbiterinit");
+    err = true;
+  }
+  TARGWICKED targs[tnum];
+  pthread_t threads[tnum];
+  TCMAP *map = tcmapnew();
+  if(tnum == 1){
+    targs[0].hdb = hdb;
+    targs[0].rnum = rnum;
+    targs[0].nc = nc;
+    targs[0].id = 0;
+    targs[0].map = map;
+    if(threadwicked(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].hdb = hdb;
+      targs[i].rnum = rnum;
+      targs[i].nc = nc;
+      targs[i].id = i;
+      targs[i].map = map;
+      if(pthread_create(threads + i, NULL, threadwicked, targs + i) != 0){
+        eprint(hdb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(hdb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  if(!nc){
+    if(!tchdbsync(hdb)){
+      eprint(hdb, __LINE__, "tchdbsync");
+      err = true;
+    }
+    if(tchdbrnum(hdb) != tcmaprnum(map)){
+      eprint(hdb, __LINE__, "(validation)");
+      err = true;
+    }
+    int end = rnum * tnum;
+    for(int i = 1; i <= end && !err; i++){
+      char kbuf[RECBUFSIZ];
+      int ksiz = sprintf(kbuf, "%d", i - 1);
+      int vsiz;
+      const char *vbuf = tcmapget(map, kbuf, ksiz, &vsiz);
+      int rsiz;
+      char *rbuf = tchdbget(hdb, kbuf, ksiz, &rsiz);
+      if(vbuf){
+        iputchar('.');
+        if(!rbuf){
+          eprint(hdb, __LINE__, "tchdbget");
+          err = true;
+        } else if(rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+          eprint(hdb, __LINE__, "(validation)");
+          err = true;
+        }
+      } else {
+        iputchar('*');
+        if(rbuf || tchdbecode(hdb) != TCENOREC){
+          eprint(hdb, __LINE__, "(validation)");
+          err = true;
+        }
+      }
+      tcfree(rbuf);
+      if(i % 50 == 0) iprintf(" (%08d)\n", i);
+    }
+    if(rnum % 50 > 0) iprintf(" (%08d)\n", rnum);
+  }
+  tcmapdel(map);
+  iprintf("record number: %llu\n", (unsigned long long)tchdbrnum(hdb));
+  iprintf("size: %llu\n", (unsigned long long)tchdbfsiz(hdb));
+  mprint(hdb);
+  sysprint();
+  if(!tchdbclose(hdb)){
+    eprint(hdb, __LINE__, "tchdbclose");
+    err = true;
+  }
+  tchdbdel(hdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform typical command */
+static int proctypical(const char *path, int tnum, int rnum, int bnum, int apow, int fpow,
+                       int opts, int rcnum, int xmsiz, int dfunit, int omode,
+                       bool nc, int rratio){
+  iprintf("<Typical Access Test>\n  seed=%u  path=%s  tnum=%d  rnum=%d  bnum=%d  apow=%d"
+          "  fpow=%d  opts=%d  rcnum=%d  xmsiz=%d  dfunit=%d  omode=%d  nc=%d  rratio=%d\n\n",
+          g_randseed, path, tnum, rnum, bnum, apow, fpow, opts, rcnum, xmsiz, dfunit, omode,
+          nc, rratio);
+  bool err = false;
+  double stime = tctime();
+  TCHDB *hdb = tchdbnew();
+  if(g_dbgfd >= 0) tchdbsetdbgfd(hdb, g_dbgfd);
+  if(!tchdbsetmutex(hdb)){
+    eprint(hdb, __LINE__, "tchdbsetmutex");
+    err = true;
+  }
+  if(!tchdbsetcodecfunc(hdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(hdb, __LINE__, "tchdbsetcodecfunc");
+    err = true;
+  }
+  if(!tchdbtune(hdb, bnum, apow, fpow, opts)){
+    eprint(hdb, __LINE__, "tchdbtune");
+    err = true;
+  }
+  if(!tchdbsetcache(hdb, rcnum)){
+    eprint(hdb, __LINE__, "tchdbsetcache");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tchdbsetxmsiz(hdb, xmsiz)){
+    eprint(hdb, __LINE__, "tchdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tchdbsetdfunit(hdb, dfunit)){
+    eprint(hdb, __LINE__, "tchdbsetdfunit");
+    err = true;
+  }
+  if(!tchdbopen(hdb, path, HDBOWRITER | HDBOCREAT | HDBOTRUNC | omode)){
+    eprint(hdb, __LINE__, "tchdbopen");
+    err = true;
+  }
+  TARGTYPICAL targs[tnum];
+  pthread_t threads[tnum];
+  if(tnum == 1){
+    targs[0].hdb = hdb;
+    targs[0].rnum = rnum;
+    targs[0].nc = nc;
+    targs[0].rratio = rratio;
+    targs[0].id = 0;
+    if(threadtypical(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].hdb = hdb;
+      targs[i].rnum = rnum;
+      targs[i].nc = nc;
+      targs[i].rratio= rratio;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadtypical, targs + i) != 0){
+        eprint(hdb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(hdb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tchdbrnum(hdb));
+  iprintf("size: %llu\n", (unsigned long long)tchdbfsiz(hdb));
+  mprint(hdb);
+  sysprint();
+  if(!tchdbclose(hdb)){
+    eprint(hdb, __LINE__, "tchdbclose");
+    err = true;
+  }
+  tchdbdel(hdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform race command */
+static int procrace(const char *path, int tnum, int rnum, int bnum, int apow, int fpow,
+                    int opts, int xmsiz, int dfunit, int omode){
+  iprintf("<Race Condition Test>\n  seed=%u  path=%s  tnum=%d  rnum=%d  bnum=%d  apow=%d"
+          "  fpow=%d  opts=%d  xmsiz=%d  dfunit=%d  omode=%d\n\n",
+          g_randseed, path, tnum, rnum, bnum, apow, fpow, opts, xmsiz, dfunit, omode);
+  bool err = false;
+  double stime = tctime();
+  TCHDB *hdb = tchdbnew();
+  if(g_dbgfd >= 0) tchdbsetdbgfd(hdb, g_dbgfd);
+  if(!tchdbsetmutex(hdb)){
+    eprint(hdb, __LINE__, "tchdbsetmutex");
+    err = true;
+  }
+  if(!tchdbsetcodecfunc(hdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(hdb, __LINE__, "tchdbsetcodecfunc");
+    err = true;
+  }
+  if(!tchdbtune(hdb, bnum, apow, fpow, opts)){
+    eprint(hdb, __LINE__, "tchdbtune");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tchdbsetxmsiz(hdb, xmsiz)){
+    eprint(hdb, __LINE__, "tchdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tchdbsetdfunit(hdb, dfunit)){
+    eprint(hdb, __LINE__, "tchdbsetdfunit");
+    err = true;
+  }
+  if(!tchdbopen(hdb, path, HDBOWRITER | HDBOCREAT | HDBOTRUNC | omode)){
+    eprint(hdb, __LINE__, "tchdbopen");
+    err = true;
+  }
+  TARGRACE targs[tnum];
+  pthread_t threads[tnum];
+  if(tnum == 1){
+    targs[0].hdb = hdb;
+    targs[0].rnum = rnum;
+    targs[0].id = 0;
+    if(threadrace(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].hdb = hdb;
+      targs[i].rnum = rnum;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadrace, targs + i) != 0){
+        eprint(hdb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(hdb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tchdbrnum(hdb));
+  iprintf("size: %llu\n", (unsigned long long)tchdbfsiz(hdb));
+  mprint(hdb);
+  sysprint();
+  if(!tchdbclose(hdb)){
+    eprint(hdb, __LINE__, "tchdbclose");
+    err = true;
+  }
+  tchdbdel(hdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* thread the write function */
+static void *threadwrite(void *targ){
+  TCHDB *hdb = ((TARGWRITE *)targ)->hdb;
+  int rnum = ((TARGWRITE *)targ)->rnum;
+  bool as = ((TARGWRITE *)targ)->as;
+  bool rnd = ((TARGWRITE *)targ)->rnd;
+  int id = ((TARGWRITE *)targ)->id;
+  bool err = false;
+  int base = id * rnum;
+  for(int i = 1; i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", base + (rnd ? myrand(i) : i));
+    if(as){
+      if(!tchdbputasync(hdb, buf, len, buf, len)){
+        eprint(hdb, __LINE__, "tchdbputasync");
+        err = true;
+        break;
+      }
+    } else {
+      if(!tchdbput(hdb, buf, len, buf, len)){
+        eprint(hdb, __LINE__, "tchdbput");
+        err = true;
+        break;
+      }
+    }
+    if(id == 0 && rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  return err ? "error" : NULL;
+}
+
+
+/* thread the read function */
+static void *threadread(void *targ){
+  TCHDB *hdb = ((TARGREAD *)targ)->hdb;
+  int rnum = ((TARGREAD *)targ)->rnum;
+  bool wb = ((TARGREAD *)targ)->wb;
+  bool rnd = ((TARGREAD *)targ)->rnd;
+  int id = ((TARGREAD *)targ)->id;
+  bool err = false;
+  int base = id * rnum;
+  for(int i = 1; i <= rnum && !err; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%08d", base + (rnd ? myrandnd(i) : i));
+    int vsiz;
+    if(wb){
+      char vbuf[RECBUFSIZ];
+      int vsiz = tchdbget3(hdb, kbuf, ksiz, vbuf, RECBUFSIZ);
+      if(vsiz < 0 && (!rnd || tchdbecode(hdb) != TCENOREC)){
+        eprint(hdb, __LINE__, "tchdbget3");
+        err = true;
+      }
+    } else {
+      char *vbuf = tchdbget(hdb, kbuf, ksiz, &vsiz);
+      if(!vbuf && (!rnd || tchdbecode(hdb) != TCENOREC)){
+        eprint(hdb, __LINE__, "tchdbget");
+        err = true;
+      }
+      tcfree(vbuf);
+    }
+    if(id == 0 && rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  return err ? "error" : NULL;
+}
+
+
+/* thread the remove function */
+static void *threadremove(void *targ){
+  TCHDB *hdb = ((TARGREMOVE *)targ)->hdb;
+  int rnum = ((TARGREMOVE *)targ)->rnum;
+  bool rnd = ((TARGREMOVE *)targ)->rnd;
+  int id = ((TARGREMOVE *)targ)->id;
+  bool err = false;
+  int base = id * rnum;
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%08d", base + (rnd ? myrand(i + 1) : i));
+    if(!tchdbout(hdb, kbuf, ksiz) && (!rnd || tchdbecode(hdb) != TCENOREC)){
+      eprint(hdb, __LINE__, "tchdbout");
+      err = true;
+      break;
+    }
+    if(id == 0 && rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  return err ? "error" : NULL;
+}
+
+
+/* thread the wicked function */
+static void *threadwicked(void *targ){
+  TCHDB *hdb = ((TARGWICKED *)targ)->hdb;
+  int rnum = ((TARGWICKED *)targ)->rnum;
+  bool nc = ((TARGWICKED *)targ)->nc;
+  int id = ((TARGWICKED *)targ)->id;
+  TCMAP *map = ((TARGWICKED *)targ)->map;
+  bool err = false;
+  for(int i = 1; i <= rnum && !err; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(rnum * (id + 1)));
+    char vbuf[RECBUFSIZ];
+    int vsiz = myrand(RECBUFSIZ);
+    memset(vbuf, '*', vsiz);
+    vbuf[vsiz] = '\0';
+    char *rbuf;
+    if(!nc) tcglobalmutexlock();
+    switch(myrand(16)){
+      case 0:
+        if(id == 0) iputchar('0');
+        if(!tchdbput(hdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(hdb, __LINE__, "tchdbput");
+          err = true;
+        }
+        if(!nc) tcmapput(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 1:
+        if(id == 0) iputchar('1');
+        if(!tchdbput2(hdb, kbuf, vbuf)){
+          eprint(hdb, __LINE__, "tchdbput2");
+          err = true;
+        }
+        if(!nc) tcmapput2(map, kbuf, vbuf);
+        break;
+      case 2:
+        if(id == 0) iputchar('2');
+        if(!tchdbputkeep(hdb, kbuf, ksiz, vbuf, vsiz) && tchdbecode(hdb) != TCEKEEP){
+          eprint(hdb, __LINE__, "tchdbputkeep");
+          err = true;
+        }
+        if(!nc) tcmapputkeep(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 3:
+        if(id == 0) iputchar('3');
+        if(!tchdbputkeep2(hdb, kbuf, vbuf) && tchdbecode(hdb) != TCEKEEP){
+          eprint(hdb, __LINE__, "tchdbputkeep2");
+          err = true;
+        }
+        if(!nc) tcmapputkeep2(map, kbuf, vbuf);
+        break;
+      case 4:
+        if(id == 0) iputchar('4');
+        if(!tchdbputcat(hdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(hdb, __LINE__, "tchdbputcat");
+          err = true;
+        }
+        if(!nc) tcmapputcat(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 5:
+        if(id == 0) iputchar('5');
+        if(!tchdbputcat2(hdb, kbuf, vbuf)){
+          eprint(hdb, __LINE__, "tchdbputcat2");
+          err = true;
+        }
+        if(!nc) tcmapputcat2(map, kbuf, vbuf);
+        break;
+      case 6:
+        if(id == 0) iputchar('6');
+        if(i > rnum / 4 * 3){
+          if(!tchdbputasync(hdb, kbuf, ksiz, vbuf, vsiz)){
+            eprint(hdb, __LINE__, "tchdbputasync");
+            err = true;
+          }
+        } else {
+          if(!tchdbput(hdb, kbuf, ksiz, vbuf, vsiz)){
+            eprint(hdb, __LINE__, "tchdbput");
+            err = true;
+          }
+        }
+        if(!nc) tcmapput(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 7:
+        if(id == 0) iputchar('7');
+        if(i > rnum / 4 * 3){
+          if(!tchdbputasync2(hdb, kbuf, vbuf)){
+            eprint(hdb, __LINE__, "tchdbputasync2");
+            err = true;
+          }
+        } else {
+          if(!tchdbput2(hdb, kbuf, vbuf)){
+            eprint(hdb, __LINE__, "tchdbput2");
+            err = true;
+          }
+        }
+        if(!nc) tcmapput2(map, kbuf, vbuf);
+        break;
+      case 8:
+        if(id == 0) iputchar('8');
+        if(myrand(2) == 0){
+          if(!tchdbout(hdb, kbuf, ksiz) && tchdbecode(hdb) != TCENOREC){
+            eprint(hdb, __LINE__, "tchdbout");
+            err = true;
+          }
+          if(!nc) tcmapout(map, kbuf, ksiz);
+        }
+        break;
+      case 9:
+        if(id == 0) iputchar('9');
+        if(myrand(2) == 0){
+          if(!tchdbout2(hdb, kbuf) && tchdbecode(hdb) != TCENOREC){
+            eprint(hdb, __LINE__, "tchdbout2");
+            err = true;
+          }
+          if(!nc) tcmapout2(map, kbuf);
+        }
+        break;
+      case 10:
+        if(id == 0) iputchar('A');
+        if(!(rbuf = tchdbget(hdb, kbuf, ksiz, &vsiz))){
+          if(tchdbecode(hdb) != TCENOREC){
+            eprint(hdb, __LINE__, "tchdbget");
+            err = true;
+          }
+          rbuf = tcsprintf("[%d]", myrand(i + 1));
+          vsiz = strlen(rbuf);
+        }
+        vsiz += myrand(vsiz);
+        if(myrand(3) == 0) vsiz += PATH_MAX;
+        rbuf = tcrealloc(rbuf, vsiz + 1);
+        for(int j = 0; j < vsiz; j++){
+          rbuf[j] = myrand(0x100);
+        }
+        if(!tchdbput(hdb, kbuf, ksiz, rbuf, vsiz)){
+          eprint(hdb, __LINE__, "tchdbput");
+          err = true;
+        }
+        if(!nc) tcmapput(map, kbuf, ksiz, rbuf, vsiz);
+        tcfree(rbuf);
+        break;
+      case 11:
+        if(id == 0) iputchar('B');
+        if(!(rbuf = tchdbget(hdb, kbuf, ksiz, &vsiz)) && tchdbecode(hdb) != TCENOREC){
+          eprint(hdb, __LINE__, "tchdbget");
+          err = true;
+        }
+        tcfree(rbuf);
+        break;
+      case 12:
+        if(id == 0) iputchar('C');
+        if(!(rbuf = tchdbget2(hdb, kbuf)) && tchdbecode(hdb) != TCENOREC){
+          eprint(hdb, __LINE__, "tchdbget2");
+          err = true;
+        }
+        tcfree(rbuf);
+        break;
+      case 13:
+        if(id == 0) iputchar('D');
+        if(myrand(1) == 0) vsiz = 1;
+        if((vsiz = tchdbget3(hdb, kbuf, ksiz, vbuf, vsiz)) < 0 && tchdbecode(hdb) != TCENOREC){
+          eprint(hdb, __LINE__, "tchdbget3");
+          err = true;
+        }
+        break;
+      case 14:
+        if(id == 0) iputchar('E');
+        if(myrand(rnum / 50) == 0){
+          if(!tchdbiterinit(hdb)){
+            eprint(hdb, __LINE__, "tchdbiterinit");
+            err = true;
+          }
+        }
+        TCXSTR *ikey = tcxstrnew();
+        TCXSTR *ival = tcxstrnew();
+        for(int j = myrand(rnum) / 1000 + 1; j >= 0; j--){
+          if(j % 3 == 0){
+            if(!tchdbiternext3(hdb, ikey, ival)){
+              int ecode = tchdbecode(hdb);
+              if(ecode != TCEINVALID && ecode != TCENOREC){
+                eprint(hdb, __LINE__, "tchdbiternext3");
+                err = true;
+              }
+            }
+          } else {
+            int iksiz;
+            char *ikbuf = tchdbiternext(hdb, &iksiz);
+            if(ikbuf){
+              tcfree(ikbuf);
+            } else {
+              int ecode = tchdbecode(hdb);
+              if(ecode != TCEINVALID && ecode != TCENOREC){
+                eprint(hdb, __LINE__, "tchdbiternext");
+                err = true;
+              }
+            }
+          }
+        }
+        tcxstrdel(ival);
+        tcxstrdel(ikey);
+        break;
+      default:
+        if(id == 0) iputchar('@');
+        if(tchdbtranbegin(hdb)){
+          if(myrand(2) == 0){
+            if(!tchdbput(hdb, kbuf, ksiz, vbuf, vsiz)){
+              eprint(hdb, __LINE__, "tchdbput");
+              err = true;
+            }
+            if(!nc) tcmapput(map, kbuf, ksiz, vbuf, vsiz);
+          } else {
+            if(!tchdbout(hdb, kbuf, ksiz) && tchdbecode(hdb) != TCENOREC){
+              eprint(hdb, __LINE__, "tchdbout");
+              err = true;
+            }
+            if(!nc) tcmapout(map, kbuf, ksiz);
+          }
+          if(nc && myrand(2) == 0){
+            if(!tchdbtranabort(hdb)){
+              eprint(hdb, __LINE__, "tchdbtranabort");
+              err = true;
+            }
+          } else {
+            if(!tchdbtrancommit(hdb)){
+              eprint(hdb, __LINE__, "tchdbtrancommit");
+              err = true;
+            }
+          }
+        } else {
+          eprint(hdb, __LINE__, "tchdbtranbegin");
+          err = true;
+        }
+        if(myrand(1000) == 0){
+          if(!tchdbforeach(hdb, iterfunc, NULL)){
+            eprint(hdb, __LINE__, "tchdbforeach");
+            err = true;
+          }
+        }
+        if(myrand(10000) == 0) srand((unsigned int)(tctime() * 1000) % UINT_MAX);
+        break;
+    }
+    if(!nc) tcglobalmutexunlock();
+    if(id == 0){
+      if(i % 50 == 0) iprintf(" (%08d)\n", i);
+      if(id == 0 && i == rnum / 4){
+        if(!tchdboptimize(hdb, rnum / 50, -1, -1, -1) && tchdbecode(hdb) != TCEINVALID){
+          eprint(hdb, __LINE__, "tchdboptimize");
+          err = true;
+        }
+        if(!tchdbiterinit(hdb)){
+          eprint(hdb, __LINE__, "tchdbiterinit");
+          err = true;
+        }
+      }
+    }
+  }
+  return err ? "error" : NULL;
+}
+
+
+/* thread the typical function */
+static void *threadtypical(void *targ){
+  TCHDB *hdb = ((TARGTYPICAL *)targ)->hdb;
+  int rnum = ((TARGTYPICAL *)targ)->rnum;
+  bool nc = ((TARGTYPICAL *)targ)->nc;
+  int rratio = ((TARGTYPICAL *)targ)->rratio;
+  int id = ((TARGTYPICAL *)targ)->id;
+  bool err = false;
+  TCMAP *map = (!nc && id == 0) ? tcmapnew2(rnum + 1) : NULL;
+  int base = id * rnum;
+  int mrange = tclmax(50 + rratio, 100);
+  for(int i = 1; !err && i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", base + myrandnd(i));
+    int rnd = myrand(mrange);
+    if(rnd < 10){
+      if(!tchdbput(hdb, buf, len, buf, len)){
+        eprint(hdb, __LINE__, "tchdbput");
+        err = true;
+      }
+      if(map) tcmapput(map, buf, len, buf, len);
+    } else if(rnd < 15){
+      if(!tchdbputkeep(hdb, buf, len, buf, len) && tchdbecode(hdb) != TCEKEEP){
+        eprint(hdb, __LINE__, "tchdbputkeep");
+        err = true;
+      }
+      if(map) tcmapputkeep(map, buf, len, buf, len);
+    } else if(rnd < 20){
+      if(!tchdbputcat(hdb, buf, len, buf, len)){
+        eprint(hdb, __LINE__, "tchdbputcat");
+        err = true;
+      }
+      if(map) tcmapputcat(map, buf, len, buf, len);
+    } else if(rnd < 25){
+      if(i > rnum / 10 * 9){
+        if(!tchdbputasync(hdb, buf, len, buf, len)){
+          eprint(hdb, __LINE__, "tchdbputasync");
+          err = true;
+        }
+      } else {
+        if(!tchdbput(hdb, buf, len, buf, len)){
+          eprint(hdb, __LINE__, "tchdbput");
+          err = true;
+        }
+      }
+      if(map) tcmapput(map, buf, len, buf, len);
+    } else if(rnd < 30){
+      if(!tchdbout(hdb, buf, len) && tchdbecode(hdb) && tchdbecode(hdb) != TCENOREC){
+        eprint(hdb, __LINE__, "tchdbout");
+        err = true;
+      }
+      if(map) tcmapout(map, buf, len);
+    } else if(rnd < 31){
+      if(myrand(10) == 0 && !tchdbiterinit(hdb) && tchdbecode(hdb) != TCENOREC){
+        eprint(hdb, __LINE__, "tchdbiterinit");
+        err = true;
+      }
+      for(int j = 0; !err && j < 10; j++){
+        int ksiz;
+        char *kbuf = tchdbiternext(hdb, &ksiz);
+        if(kbuf){
+          tcfree(kbuf);
+        } else if(tchdbecode(hdb) != TCEINVALID && tchdbecode(hdb) != TCENOREC){
+          eprint(hdb, __LINE__, "tchdbiternext");
+          err = true;
+        }
+      }
+    } else {
+      int vsiz;
+      char *vbuf = tchdbget(hdb, buf, len, &vsiz);
+      if(vbuf){
+        if(map){
+          int msiz;
+          const char *mbuf = tcmapget(map, buf, len, &msiz);
+          if(!mbuf || msiz != vsiz || memcmp(mbuf, vbuf, vsiz)){
+            eprint(hdb, __LINE__, "(validation)");
+            err = true;
+          }
+        }
+        tcfree(vbuf);
+      } else {
+        if(tchdbecode(hdb) != TCENOREC){
+          eprint(hdb, __LINE__, "tchdbget");
+          err = true;
+        }
+        if(map && tcmapget(map, buf, len, &vsiz)){
+          eprint(hdb, __LINE__, "(validation)");
+          err = true;
+        }
+      }
+    }
+    if(id == 0 && rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(map){
+    tcmapiterinit(map);
+    int ksiz;
+    const char *kbuf;
+    while(!err && (kbuf = tcmapiternext(map, &ksiz)) != NULL){
+      int vsiz;
+      char *vbuf = tchdbget(hdb, kbuf, ksiz, &vsiz);
+      if(vbuf){
+        int msiz;
+        const char *mbuf = tcmapget(map, kbuf, ksiz, &msiz);
+        if(!mbuf || msiz != vsiz || memcmp(mbuf, vbuf, vsiz)){
+          eprint(hdb, __LINE__, "(validation)");
+          err = true;
+        }
+        tcfree(vbuf);
+      } else {
+        eprint(hdb, __LINE__, "(validation)");
+        err = true;
+      }
+    }
+    tcmapdel(map);
+  }
+  return err ? "error" : NULL;
+}
+
+
+/* thread the race function */
+static void *threadrace(void *targ){
+  TCHDB *hdb = ((TARGRACE *)targ)->hdb;
+  int rnum = ((TARGRACE *)targ)->rnum;
+  int id = ((TARGRACE *)targ)->id;
+  bool err = false;
+  int mid = rnum * 2;
+  for(int i = 1; !err && i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%d", myrandnd(i));
+    int rnd = myrand(100);
+    if(rnd < 10){
+      if(!tchdbputkeep(hdb, buf, len, buf, len) && tchdbecode(hdb) != TCEKEEP){
+        eprint(hdb, __LINE__, "tchdbputkeep");
+        err = true;
+      }
+    } else if(rnd < 20){
+      if(!tchdbputcat(hdb, buf, len, buf, len)){
+        eprint(hdb, __LINE__, "tchdbputcat");
+        err = true;
+      }
+    } else if(rnd < 30){
+      if(!tchdbout(hdb, buf, len) && tchdbecode(hdb) != TCENOREC){
+        eprint(hdb, __LINE__, "tchdbout");
+        err = true;
+      }
+    } else if(rnd < 31){
+      if(!tchdbputasync(hdb, buf, len, buf, len)){
+        eprint(hdb, __LINE__, "tchdbputasync");
+        err = true;
+      }
+    } else {
+      if(myrand(10) == 0){
+        int rsiz = myrand(256);
+        char *rbuf = tcmalloc(rsiz + 1);
+        for(int j = 0; j < rsiz; j++){
+          rbuf[j] = myrand('z' - 'a') + 'a';
+        }
+        if(myrand(2) == 0){
+          if(!tchdbput(hdb, buf, len, rbuf, rsiz)){
+            eprint(hdb, __LINE__, "tchdbputcat");
+            err = true;
+          }
+        } else {
+          if(!tchdbputcat(hdb, buf, len, rbuf, rsiz)){
+            eprint(hdb, __LINE__, "tchdbputcat");
+            err = true;
+          }
+        }
+        tcfree(rbuf);
+      } else {
+        if(!tchdbput(hdb, buf, len, buf, len)){
+          eprint(hdb, __LINE__, "tchdbput");
+          err = true;
+        }
+      }
+    }
+    if(id == 0){
+      if(myrand(mid) == 0){
+        iprintf("[v]");
+        if(!tchdbvanish(hdb)){
+          eprint(hdb, __LINE__, "tchdbvanish");
+          err = true;
+        }
+      }
+      if(myrand(mid) == 0){
+        iprintf("[o]");
+        if(!tchdboptimize(hdb, myrand(rnum) + 1, myrand(10), myrand(10), 0)){
+          eprint(hdb, __LINE__, "tchdboptimize");
+          err = true;
+        }
+      }
+      if(myrand(mid) == 0){
+        iprintf("[d]");
+        if(!tchdbdefrag(hdb, -1)){
+          eprint(hdb, __LINE__, "tchdbdefrag");
+          err = true;
+        }
+      }
+      if(myrand(mid) == 0){
+        iprintf("[i]");
+        if(!tchdbiterinit(hdb)){
+          eprint(hdb, __LINE__, "tchdbput");
+          err = true;
+        }
+        char *kbuf;
+        int ksiz;
+        while((kbuf = tchdbiternext(hdb, &ksiz)) != NULL){
+          int vsiz;
+          char *vbuf = tchdbget(hdb, kbuf, ksiz, &vsiz);
+          if(vbuf){
+            tcfree(vbuf);
+          } else if(tchdbecode(hdb) != TCENOREC){
+            eprint(hdb, __LINE__, "tchdbget");
+            err = true;
+          }
+          tcfree(kbuf);
+        }
+        if(tchdbecode(hdb) != TCENOREC){
+          eprint(hdb, __LINE__, "(validation)");
+          err = true;
+        }
+      }
+      if(rnum > 250 && i % (rnum / 250) == 0){
+        iputchar('.');
+        if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+      }
+    }
+  }
+  return err ? "error" : NULL;
+}
+
+
+
+// END OF FILE
diff --git a/tcejdb/tchtest.c b/tcejdb/tchtest.c
new file mode 100644 (file)
index 0000000..7d63b9e
--- /dev/null
@@ -0,0 +1,2129 @@
+/*************************************************************************************************
+ * The test cases of the hash database API
+ *                                                               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 "myconf.h"
+
+#define RECBUFSIZ      48                // buffer for records
+
+
+/* global variables */
+const char *g_progname;                  // program name
+unsigned int g_randseed;                 // random seed
+int g_dbgfd;                             // debugging output
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+static void usage(void);
+static void iprintf(const char *format, ...);
+static void iputchar(int c);
+static void eprint(TCHDB *hdb, int line, const char *func);
+static void mprint(TCHDB *hdb);
+static void sysprint(void);
+static int myrand(int range);
+static void *pdprocfunc(const void *vbuf, int vsiz, int *sp, void *op);
+static bool iterfunc(const void *kbuf, int ksiz, const void *vbuf, int vsiz, void *op);
+static int runwrite(int argc, char **argv);
+static int runread(int argc, char **argv);
+static int runremove(int argc, char **argv);
+static int runrcat(int argc, char **argv);
+static int runmisc(int argc, char **argv);
+static int runwicked(int argc, char **argv);
+static int procwrite(const char *path, int rnum, int bnum, int apow, int fpow,
+                     bool mt, int opts, int rcnum, int xmsiz, int dfunit, int omode,
+                     bool as, bool rnd);
+static int procread(const char *path, bool mt, int rcnum, int xmsiz, int dfunit, int omode,
+                    bool wb, bool rnd);
+static int procremove(const char *path, bool mt, int rcnum, int xmsiz, int dfunit, int omode,
+                      bool rnd);
+static int procrcat(const char *path, int rnum, int bnum, int apow, int fpow,
+                    bool mt, int opts, int rcnum, int xmsiz, int dfunit, int omode, int pnum,
+                    bool dai, bool dad, bool rl, bool ru);
+static int procmisc(const char *path, int rnum, bool mt, int opts, int omode);
+static int procwicked(const char *path, int rnum, bool mt, int opts, int omode);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  g_progname = argv[0];
+  const char *ebuf = getenv("TCRNDSEED");
+  g_randseed = ebuf ? tcatoix(ebuf) : tctime() * 1000;
+  srand(g_randseed);
+  ebuf = getenv("TCDBGFD");
+  g_dbgfd = ebuf ? tcatoix(ebuf) : UINT16_MAX;
+  if(argc < 2) usage();
+  int rv = 0;
+  if(!strcmp(argv[1], "write")){
+    rv = runwrite(argc, argv);
+  } else if(!strcmp(argv[1], "read")){
+    rv = runread(argc, argv);
+  } else if(!strcmp(argv[1], "remove")){
+    rv = runremove(argc, argv);
+  } else if(!strcmp(argv[1], "rcat")){
+    rv = runrcat(argc, argv);
+  } else if(!strcmp(argv[1], "misc")){
+    rv = runmisc(argc, argv);
+  } else if(!strcmp(argv[1], "wicked")){
+    rv = runwicked(argc, argv);
+  } else {
+    usage();
+  }
+  if(rv != 0){
+    printf("FAILED: TCRNDSEED=%u PID=%d", g_randseed, (int)getpid());
+    for(int i = 0; i < argc; i++){
+      printf(" %s", argv[i]);
+    }
+    printf("\n\n");
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+static void usage(void){
+  fprintf(stderr, "%s: test cases of the hash database API of Tokyo Cabinet\n", g_progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s write [-mt] [-tl] [-td|-tb|-tt|-tx] [-rc num] [-xm num] [-df num]"
+          " [-nl|-nb] [-as] [-rnd] path rnum [bnum [apow [fpow]]]\n", g_progname);
+  fprintf(stderr, "  %s read [-mt] [-rc num] [-xm num] [-df num] [-nl|-nb] [-wb] [-rnd] path\n",
+          g_progname);
+  fprintf(stderr, "  %s remove [-mt] [-rc num] [-xm num] [-df num] [-nl|-nb] [-rnd] path\n",
+          g_progname);
+  fprintf(stderr, "  %s rcat [-mt] [-tl] [-td|-tb|-tt|-tx] [-rc num] [-xm num] [-df num]"
+          " [-nl|-nb] [-pn num] [-dai|-dad|-rl|-ru] path rnum [bnum [apow [fpow]]]\n",
+          g_progname);
+  fprintf(stderr, "  %s misc [-mt] [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] path rnum\n", g_progname);
+  fprintf(stderr, "  %s wicked [-mt] [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] path rnum\n", g_progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* print formatted information string and flush the buffer */
+static void iprintf(const char *format, ...){
+  va_list ap;
+  va_start(ap, format);
+  vprintf(format, ap);
+  fflush(stdout);
+  va_end(ap);
+}
+
+
+/* print a character and flush the buffer */
+static void iputchar(int c){
+  putchar(c);
+  fflush(stdout);
+}
+
+
+/* print error message of hash database */
+static void eprint(TCHDB *hdb, int line, const char *func){
+  const char *path = tchdbpath(hdb);
+  int ecode = tchdbecode(hdb);
+  fprintf(stderr, "%s: %s: %d: %s: error: %d: %s\n",
+          g_progname, path ? path : "-", line, func, ecode, tchdberrmsg(ecode));
+}
+
+
+/* print members of hash database */
+static void mprint(TCHDB *hdb){
+  if(hdb->cnt_writerec < 0) return;
+  iprintf("bucket number: %lld\n", (long long)tchdbbnum(hdb));
+  iprintf("used bucket number: %lld\n", (long long)tchdbbnumused(hdb));
+  iprintf("cnt_writerec: %lld\n", (long long)hdb->cnt_writerec);
+  iprintf("cnt_reuserec: %lld\n", (long long)hdb->cnt_reuserec);
+  iprintf("cnt_moverec: %lld\n", (long long)hdb->cnt_moverec);
+  iprintf("cnt_readrec: %lld\n", (long long)hdb->cnt_readrec);
+  iprintf("cnt_searchfbp: %lld\n", (long long)hdb->cnt_searchfbp);
+  iprintf("cnt_insertfbp: %lld\n", (long long)hdb->cnt_insertfbp);
+  iprintf("cnt_splicefbp: %lld\n", (long long)hdb->cnt_splicefbp);
+  iprintf("cnt_dividefbp: %lld\n", (long long)hdb->cnt_dividefbp);
+  iprintf("cnt_mergefbp: %lld\n", (long long)hdb->cnt_mergefbp);
+  iprintf("cnt_reducefbp: %lld\n", (long long)hdb->cnt_reducefbp);
+  iprintf("cnt_appenddrp: %lld\n", (long long)hdb->cnt_appenddrp);
+  iprintf("cnt_deferdrp: %lld\n", (long long)hdb->cnt_deferdrp);
+  iprintf("cnt_flushdrp: %lld\n", (long long)hdb->cnt_flushdrp);
+  iprintf("cnt_adjrecc: %lld\n", (long long)hdb->cnt_adjrecc);
+  iprintf("cnt_defrag: %lld\n", (long long)hdb->cnt_defrag);
+  iprintf("cnt_shiftrec: %lld\n", (long long)hdb->cnt_shiftrec);
+  iprintf("cnt_trunc: %lld\n", (long long)hdb->cnt_trunc);
+}
+
+
+/* print system information */
+static void sysprint(void){
+  TCMAP *info = tcsysinfo();
+  if(info){
+    tcmapiterinit(info);
+    const char *kbuf;
+    while((kbuf = tcmapiternext2(info)) != NULL){
+      iprintf("sys_%s: %s\n", kbuf, tcmapiterval2(kbuf));
+    }
+    tcmapdel(info);
+  }
+}
+
+
+/* get a random number */
+static int myrand(int range){
+  if(range < 2) return 0;
+  int high = (unsigned int)rand() >> 4;
+  int low = range * (rand() / (RAND_MAX + 1.0));
+  low &= (unsigned int)INT_MAX >> 4;
+  return (high + low) % range;
+}
+
+
+/* duplication callback function */
+static void *pdprocfunc(const void *vbuf, int vsiz, int *sp, void *op){
+  if(op){
+    char *buf = NULL;
+    int len = 0;
+    switch((int)(intptr_t)op){
+      case 1:
+        len = vsiz + 1;
+        buf = tcmalloc(len + 1);
+        memset(buf, '*', len);
+        break;
+      case 2:
+        buf = (void *)-1;
+        break;
+    }
+    *sp = len;
+    return buf;
+  }
+  if(myrand(4) == 0) return (void *)-1;
+  if(myrand(2) == 0) return NULL;
+  int len = myrand(RECBUFSIZ);
+  char buf[RECBUFSIZ];
+  memset(buf, '*', len);
+  *sp = len;
+  return tcmemdup(buf, len);
+}
+
+
+/* iterator function */
+static bool iterfunc(const void *kbuf, int ksiz, const void *vbuf, int vsiz, void *op){
+  unsigned int sum = 0;
+  while(--ksiz >= 0){
+    sum += ((char *)kbuf)[ksiz];
+  }
+  while(--vsiz >= 0){
+    sum += ((char *)vbuf)[vsiz];
+  }
+  return myrand(100 + (sum & 0xff)) > 0;
+}
+
+
+/* parse arguments of write command */
+static int runwrite(int argc, char **argv){
+  char *path = NULL;
+  char *rstr = NULL;
+  char *bstr = NULL;
+  char *astr = NULL;
+  char *fstr = NULL;
+  bool mt = false;
+  int opts = 0;
+  int rcnum = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int omode = 0;
+  bool as = false;
+  bool rnd = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-tl")){
+        opts |= HDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= HDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= HDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= HDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= HDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-rc")){
+        if(++i >= argc) usage();
+        rcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= HDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= HDBOLCKNB;
+      } else if(!strcmp(argv[i], "-as")){
+        as = true;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else if(!astr){
+      astr = argv[i];
+    } else if(!fstr){
+      fstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int apow = astr ? tcatoix(astr) : -1;
+  int fpow = fstr ? tcatoix(fstr) : -1;
+  int rv = procwrite(path, rnum, bnum, apow, fpow,
+                     mt, opts, rcnum, xmsiz, dfunit, omode, as, rnd);
+  return rv;
+}
+
+
+/* parse arguments of read command */
+static int runread(int argc, char **argv){
+  char *path = NULL;
+  bool mt = false;
+  int rcnum = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int omode = 0;
+  bool wb = false;
+  bool rnd = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-rc")){
+        if(++i >= argc) usage();
+        rcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= HDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= HDBOLCKNB;
+      } else if(!strcmp(argv[i], "-wb")){
+        wb = true;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int rv = procread(path, mt, rcnum, xmsiz, dfunit, omode, wb, rnd);
+  return rv;
+}
+
+
+/* parse arguments of remove command */
+static int runremove(int argc, char **argv){
+  char *path = NULL;
+  bool mt = false;
+  int rcnum = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int omode = 0;
+  bool rnd = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-rc")){
+        if(++i >= argc) usage();
+        rcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= HDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= HDBOLCKNB;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int rv = procremove(path, mt, rcnum, xmsiz, dfunit, omode, rnd);
+  return rv;
+}
+
+
+/* parse arguments of rcat command */
+static int runrcat(int argc, char **argv){
+  char *path = NULL;
+  char *rstr = NULL;
+  char *bstr = NULL;
+  char *astr = NULL;
+  char *fstr = NULL;
+  bool mt = false;
+  int opts = 0;
+  int rcnum = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int omode = 0;
+  int pnum = 0;
+  bool dai = false;
+  bool dad = false;
+  bool rl = false;
+  bool ru = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-tl")){
+        opts |= HDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= HDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= HDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= HDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= HDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-rc")){
+        if(++i >= argc) usage();
+        rcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= HDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= HDBOLCKNB;
+      } else if(!strcmp(argv[i], "-pn")){
+        if(++i >= argc) usage();
+        pnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-dai")){
+        dai = true;
+      } else if(!strcmp(argv[i], "-dad")){
+        dad = true;
+      } else if(!strcmp(argv[i], "-rl")){
+        rl = true;
+      } else if(!strcmp(argv[i], "-ru")){
+        ru = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else if(!astr){
+      astr = argv[i];
+    } else if(!fstr){
+      fstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int apow = astr ? tcatoix(astr) : -1;
+  int fpow = fstr ? tcatoix(fstr) : -1;
+  int rv = procrcat(path, rnum, bnum, apow, fpow, mt, opts, rcnum, xmsiz, dfunit, omode, pnum,
+                    dai, dad, rl, ru);
+  return rv;
+}
+
+
+/* parse arguments of misc command */
+static int runmisc(int argc, char **argv){
+  char *path = NULL;
+  char *rstr = NULL;
+  bool mt = false;
+  int opts = 0;
+  int omode = 0;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-tl")){
+        opts |= HDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= HDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= HDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= HDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= HDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= HDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= HDBOLCKNB;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int rv = procmisc(path, rnum, mt, opts, omode);
+  return rv;
+}
+
+
+/* parse arguments of wicked command */
+static int runwicked(int argc, char **argv){
+  char *path = NULL;
+  char *rstr = NULL;
+  bool mt = false;
+  int opts = 0;
+  int omode = 0;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-tl")){
+        opts |= HDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= HDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= HDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= HDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= HDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= HDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= HDBOLCKNB;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int rv = procwicked(path, rnum, mt, opts, omode);
+  return rv;
+}
+
+
+/* perform write command */
+static int procwrite(const char *path, int rnum, int bnum, int apow, int fpow,
+                     bool mt, int opts, int rcnum, int xmsiz, int dfunit, int omode,
+                     bool as, bool rnd){
+  iprintf("<Writing Test>\n  seed=%u  path=%s  rnum=%d  bnum=%d  apow=%d  fpow=%d  mt=%d"
+          "  opts=%d  rcnum=%d  xmsiz=%d  dfunit=%d  omode=%d  as=%d  rnd=%d\n\n",
+          g_randseed, path, rnum, bnum, apow, fpow, mt, opts, rcnum, xmsiz, dfunit,
+          omode, as, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCHDB *hdb = tchdbnew();
+  if(g_dbgfd >= 0) tchdbsetdbgfd(hdb, g_dbgfd);
+  if(mt && !tchdbsetmutex(hdb)){
+    eprint(hdb, __LINE__, "tchdbsetmutex");
+    err = true;
+  }
+  if(!tchdbsetcodecfunc(hdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(hdb, __LINE__, "tchdbsetcodecfunc");
+    err = true;
+  }
+  if(!tchdbtune(hdb, bnum, apow, fpow, opts)){
+    eprint(hdb, __LINE__, "tchdbtune");
+    err = true;
+  }
+  if(!tchdbsetcache(hdb, rcnum)){
+    eprint(hdb, __LINE__, "tchdbsetcache");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tchdbsetxmsiz(hdb, xmsiz)){
+    eprint(hdb, __LINE__, "tchdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tchdbsetdfunit(hdb, dfunit)){
+    eprint(hdb, __LINE__, "tchdbsetdfunit");
+    err = true;
+  }
+  if(!rnd) omode |= HDBOTRUNC;
+  if(!tchdbopen(hdb, path, HDBOWRITER | HDBOCREAT | omode)){
+    eprint(hdb, __LINE__, "tchdbopen");
+    err = true;
+  }
+  for(int i = 1; i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", rnd ? myrand(rnum) + 1 : i);
+    if(as){
+      if(!tchdbputasync(hdb, buf, len, buf, len)){
+        eprint(hdb, __LINE__, "tchdbput");
+        err = true;
+        break;
+      }
+    } else {
+      if(!tchdbput(hdb, buf, len, buf, len)){
+        eprint(hdb, __LINE__, "tchdbput");
+        err = true;
+        break;
+      }
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tchdbrnum(hdb));
+  iprintf("size: %llu\n", (unsigned long long)tchdbfsiz(hdb));
+  mprint(hdb);
+  sysprint();
+  if(!tchdbclose(hdb)){
+    eprint(hdb, __LINE__, "tchdbclose");
+    err = true;
+  }
+  tchdbdel(hdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform read command */
+static int procread(const char *path, bool mt, int rcnum, int xmsiz, int dfunit, int omode,
+                    bool wb, bool rnd){
+  iprintf("<Reading Test>\n  seed=%u  path=%s  mt=%d  rcnum=%d  xmsiz=%d  dfunit=%d  omode=%d"
+          "  wb=%d  rnd=%d\n\n", g_randseed, path, mt, rcnum, xmsiz, dfunit, omode, wb, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCHDB *hdb = tchdbnew();
+  if(g_dbgfd >= 0) tchdbsetdbgfd(hdb, g_dbgfd);
+  if(mt && !tchdbsetmutex(hdb)){
+    eprint(hdb, __LINE__, "tchdbsetmutex");
+    err = true;
+  }
+  if(!tchdbsetcodecfunc(hdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(hdb, __LINE__, "tchdbsetcodecfunc");
+    err = true;
+  }
+  if(!tchdbsetcache(hdb, rcnum)){
+    eprint(hdb, __LINE__, "tchdbsetcache");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tchdbsetxmsiz(hdb, xmsiz)){
+    eprint(hdb, __LINE__, "tchdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tchdbsetdfunit(hdb, dfunit)){
+    eprint(hdb, __LINE__, "tchdbsetdfunit");
+    err = true;
+  }
+  if(!tchdbopen(hdb, path, HDBOREADER | omode)){
+    eprint(hdb, __LINE__, "tchdbopen");
+    err = true;
+  }
+  int rnum = tchdbrnum(hdb);
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%08d", rnd ? myrand(rnum) + 1 : i);
+    int vsiz;
+    if(wb){
+      char vbuf[RECBUFSIZ];
+      int vsiz = tchdbget3(hdb, kbuf, ksiz, vbuf, RECBUFSIZ);
+      if(vsiz < 0 && !(rnd && tchdbecode(hdb) == TCENOREC)){
+        eprint(hdb, __LINE__, "tchdbget3");
+        err = true;
+        break;
+      }
+    } else {
+      char *vbuf = tchdbget(hdb, kbuf, ksiz, &vsiz);
+      if(!vbuf && !(rnd && tchdbecode(hdb) == TCENOREC)){
+        eprint(hdb, __LINE__, "tchdbget");
+        err = true;
+        break;
+      }
+      tcfree(vbuf);
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tchdbrnum(hdb));
+  iprintf("size: %llu\n", (unsigned long long)tchdbfsiz(hdb));
+  mprint(hdb);
+  sysprint();
+  if(!tchdbclose(hdb)){
+    eprint(hdb, __LINE__, "tchdbclose");
+    err = true;
+  }
+  tchdbdel(hdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform remove command */
+static int procremove(const char *path, bool mt, int rcnum, int xmsiz, int dfunit, int omode,
+                      bool rnd){
+  iprintf("<Removing Test>\n  seed=%u  path=%s  mt=%d  rcnum=%d  xmsiz=%d  dfunit=%d"
+          "  omode=%d  rnd=%d\n\n", g_randseed, path, mt, rcnum, xmsiz, dfunit, omode, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCHDB *hdb = tchdbnew();
+  if(g_dbgfd >= 0) tchdbsetdbgfd(hdb, g_dbgfd);
+  if(mt && !tchdbsetmutex(hdb)){
+    eprint(hdb, __LINE__, "tchdbsetmutex");
+    err = true;
+  }
+  if(!tchdbsetcodecfunc(hdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(hdb, __LINE__, "tchdbsetcodecfunc");
+    err = true;
+  }
+  if(!tchdbsetcache(hdb, rcnum)){
+    eprint(hdb, __LINE__, "tchdbsetcache");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tchdbsetxmsiz(hdb, xmsiz)){
+    eprint(hdb, __LINE__, "tchdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tchdbsetdfunit(hdb, dfunit)){
+    eprint(hdb, __LINE__, "tchdbsetdfunit");
+    err = true;
+  }
+  if(!tchdbopen(hdb, path, HDBOWRITER | omode)){
+    eprint(hdb, __LINE__, "tchdbopen");
+    err = true;
+  }
+  int rnum = tchdbrnum(hdb);
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%08d", rnd ? myrand(rnum) + 1 : i);
+    if(!tchdbout(hdb, kbuf, ksiz) && !(rnd && tchdbecode(hdb) == TCENOREC)){
+      eprint(hdb, __LINE__, "tchdbout");
+      err = true;
+      break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tchdbrnum(hdb));
+  iprintf("size: %llu\n", (unsigned long long)tchdbfsiz(hdb));
+  mprint(hdb);
+  sysprint();
+  if(!tchdbclose(hdb)){
+    eprint(hdb, __LINE__, "tchdbclose");
+    err = true;
+  }
+  tchdbdel(hdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform rcat command */
+static int procrcat(const char *path, int rnum, int bnum, int apow, int fpow,
+                    bool mt, int opts, int rcnum, int xmsiz, int dfunit, int omode, int pnum,
+                    bool dai, bool dad, bool rl, bool ru){
+  iprintf("<Random Concatenating Test>\n"
+          "  seed=%u  path=%s  rnum=%d  bnum=%d  apow=%d  fpow=%d  mt=%d  opts=%d"
+          "  rcnum=%d  xmsiz=%d  dfunit=%d  omode=%d  pnum=%d  dai=%d  dad=%d  rl=%d  ru=%d\n\n",
+          g_randseed, path, rnum, bnum, apow, fpow, mt, opts, rcnum, xmsiz, dfunit, omode, pnum,
+          dai, dad, rl, ru);
+  if(pnum < 1) pnum = rnum;
+  bool err = false;
+  double stime = tctime();
+  TCHDB *hdb = tchdbnew();
+  if(g_dbgfd >= 0) tchdbsetdbgfd(hdb, g_dbgfd);
+  if(mt && !tchdbsetmutex(hdb)){
+    eprint(hdb, __LINE__, "tchdbsetmutex");
+    err = true;
+  }
+  if(!tchdbsetcodecfunc(hdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(hdb, __LINE__, "tchdbsetcodecfunc");
+    err = true;
+  }
+  if(!tchdbtune(hdb, bnum, apow, fpow, opts)){
+    eprint(hdb, __LINE__, "tchdbtune");
+    err = true;
+  }
+  if(!tchdbsetcache(hdb, rcnum)){
+    eprint(hdb, __LINE__, "tchdbsetcache");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tchdbsetxmsiz(hdb, xmsiz)){
+    eprint(hdb, __LINE__, "tchdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tchdbsetdfunit(hdb, dfunit)){
+    eprint(hdb, __LINE__, "tchdbsetdfunit");
+    err = true;
+  }
+  if(!tchdbopen(hdb, path, HDBOWRITER | HDBOCREAT | HDBOTRUNC | omode)){
+    eprint(hdb, __LINE__, "tchdbopen");
+    err = true;
+  }
+  for(int i = 1; i <= rnum; i++){
+    if(ru){
+      char fmt[RECBUFSIZ];
+      sprintf(fmt, "%%0%dd", myrand(RECBUFSIZ));
+      char kbuf[RECBUFSIZ];
+      int ksiz = sprintf(kbuf, fmt, myrand(pnum));
+      switch(myrand(8)){
+        case 0:
+          if(!tchdbput(hdb, kbuf, ksiz, kbuf, ksiz)){
+            eprint(hdb, __LINE__, "tchdbput");
+            err = true;
+          }
+          break;
+        case 1:
+          if(!tchdbputkeep(hdb, kbuf, ksiz, kbuf, ksiz) && tchdbecode(hdb) != TCEKEEP){
+            eprint(hdb, __LINE__, "tchdbputkeep");
+            err = true;
+          }
+          break;
+        case 2:
+          if(!tchdbout(hdb, kbuf, ksiz) && tchdbecode(hdb) != TCENOREC){
+            eprint(hdb, __LINE__, "tchdbout");
+            err = true;
+          }
+          break;
+        case 3:
+          if(tchdbaddint(hdb, kbuf, ksiz, 1) == INT_MIN && tchdbecode(hdb) != TCEKEEP){
+            eprint(hdb, __LINE__, "tchdbaddint");
+            err = true;
+          }
+          break;
+        case 4:
+          if(isnan(tchdbadddouble(hdb, kbuf, ksiz, 1.0)) && tchdbecode(hdb) != TCEKEEP){
+            eprint(hdb, __LINE__, "tchdbadddouble");
+            err = true;
+          }
+          break;
+        case 5:
+          if(myrand(2) == 0){
+            if(!tchdbputproc(hdb, kbuf, ksiz, kbuf, ksiz, pdprocfunc, NULL) &&
+               tchdbecode(hdb) != TCEKEEP){
+              eprint(hdb, __LINE__, "tchdbputproc");
+              err = true;
+            }
+          } else {
+            if(!tchdbputproc(hdb, kbuf, ksiz, NULL, 0, pdprocfunc, NULL) &&
+               tchdbecode(hdb) != TCEKEEP && tchdbecode(hdb) != TCENOREC){
+              eprint(hdb, __LINE__, "tchdbputproc");
+              err = true;
+            }
+          }
+          break;
+        default:
+          if(!tchdbputcat(hdb, kbuf, ksiz, kbuf, ksiz)){
+            eprint(hdb, __LINE__, "tchdbputcat");
+            err = true;
+          }
+          break;
+      }
+      if(err) break;
+    } else {
+      char kbuf[RECBUFSIZ];
+      int ksiz = sprintf(kbuf, "%d", myrand(pnum));
+      if(dai){
+        if(tchdbaddint(hdb, kbuf, ksiz, myrand(3)) == INT_MIN){
+          eprint(hdb, __LINE__, "tchdbaddint");
+          err = true;
+          break;
+        }
+      } else if(dad){
+        if(isnan(tchdbadddouble(hdb, kbuf, ksiz, myrand(30) / 10.0))){
+          eprint(hdb, __LINE__, "tchdbadddouble");
+          err = true;
+          break;
+        }
+      } else if(rl){
+        char vbuf[PATH_MAX];
+        int vsiz = myrand(PATH_MAX);
+        for(int j = 0; j < vsiz; j++){
+          vbuf[j] = myrand(0x100);
+        }
+        if(!tchdbputcat(hdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(hdb, __LINE__, "tchdbputcat");
+          err = true;
+          break;
+        }
+      } else {
+        if(!tchdbputcat(hdb, kbuf, ksiz, kbuf, ksiz)){
+          eprint(hdb, __LINE__, "tchdbputcat");
+          err = true;
+          break;
+        }
+      }
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tchdbrnum(hdb));
+  iprintf("size: %llu\n", (unsigned long long)tchdbfsiz(hdb));
+  mprint(hdb);
+  sysprint();
+  if(!tchdbclose(hdb)){
+    eprint(hdb, __LINE__, "tchdbclose");
+    err = true;
+  }
+  tchdbdel(hdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform misc command */
+static int procmisc(const char *path, int rnum, bool mt, int opts, int omode){
+  iprintf("<Miscellaneous Test>\n  seed=%u  path=%s  rnum=%d  mt=%d  opts=%d  omode=%d\n\n",
+          g_randseed, path, rnum, mt, opts, omode);
+  bool err = false;
+  double stime = tctime();
+  TCHDB *hdb = tchdbnew();
+  if(g_dbgfd >= 0) tchdbsetdbgfd(hdb, g_dbgfd);
+  if(mt && !tchdbsetmutex(hdb)){
+    eprint(hdb, __LINE__, "tchdbsetmutex");
+    err = true;
+  }
+  if(!tchdbsetcodecfunc(hdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(hdb, __LINE__, "tchdbsetcodecfunc");
+    err = true;
+  }
+  if(!tchdbtune(hdb, rnum / 50, 2, -1, opts)){
+    eprint(hdb, __LINE__, "tchdbtune");
+    err = true;
+  }
+  if(!tchdbsetcache(hdb, rnum / 10)){
+    eprint(hdb, __LINE__, "tchdbsetcache");
+    err = true;
+  }
+  if(!tchdbsetxmsiz(hdb, rnum * sizeof(int))){
+    eprint(hdb, __LINE__, "tchdbsetxmsiz");
+    err = true;
+  }
+  if(!tchdbsetdfunit(hdb, 8)){
+    eprint(hdb, __LINE__, "tchdbsetdfunit");
+    err = true;
+  }
+  if(!tchdbopen(hdb, path, HDBOWRITER | HDBOCREAT | HDBOTRUNC | omode)){
+    eprint(hdb, __LINE__, "tchdbopen");
+    err = true;
+  }
+  if(TCUSEPTHREAD){
+    TCHDB *hdbdup = tchdbnew();
+    if(tchdbopen(hdbdup, path, HDBOREADER)){
+      eprint(hdb, __LINE__, "(validation)");
+      err = true;
+    } else if(tchdbecode(hdbdup) != TCETHREAD){
+      eprint(hdb, __LINE__, "(validation)");
+      err = true;
+    }
+    tchdbdel(hdbdup);
+  }
+  iprintf("writing:\n");
+  for(int i = 1; i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", i);
+    if(i % 3 == 0){
+      if(!tchdbputkeep(hdb, buf, len, buf, len)){
+        eprint(hdb, __LINE__, "tchdbputkeep");
+        err = true;
+        break;
+      }
+    } else {
+      if(!tchdbputasync(hdb, buf, len, buf, len)){
+        eprint(hdb, __LINE__, "tchdbputasync");
+        err = true;
+        break;
+      }
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("reading:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%08d", i);
+    int vsiz;
+    char *vbuf = tchdbget(hdb, kbuf, ksiz, &vsiz);
+    if(!vbuf){
+      eprint(hdb, __LINE__, "tchdbget");
+      err = true;
+      break;
+    } else if(vsiz != ksiz || memcmp(vbuf, kbuf, vsiz)){
+      eprint(hdb, __LINE__, "(validation)");
+      err = true;
+      tcfree(vbuf);
+      break;
+    }
+    tcfree(vbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(tchdbrnum(hdb) != rnum){
+    eprint(hdb, __LINE__, "(validation)");
+    err = true;
+  }
+  iprintf("random writing:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(rnum));
+    char vbuf[RECBUFSIZ];
+    int vsiz = myrand(RECBUFSIZ);
+    memset(vbuf, '*', vsiz);
+    if(!tchdbput(hdb, kbuf, ksiz, vbuf, vsiz)){
+      eprint(hdb, __LINE__, "tchdbput");
+      err = true;
+      break;
+    }
+    int rsiz;
+    char *rbuf = tchdbget(hdb, kbuf, ksiz, &rsiz);
+    if(!rbuf){
+      eprint(hdb, __LINE__, "tchdbget");
+      err = true;
+      break;
+    }
+    if(rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+      eprint(hdb, __LINE__, "(validation)");
+      err = true;
+      tcfree(rbuf);
+      break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+    tcfree(rbuf);
+  }
+  iprintf("word writing:\n");
+  const char *words[] = {
+    "a", "A", "bb", "BB", "ccc", "CCC", "dddd", "DDDD", "eeeee", "EEEEEE",
+    "mikio", "hirabayashi", "tokyo", "cabinet", "hyper", "estraier", "19780211", "birth day",
+    "one", "first", "two", "second", "three", "third", "four", "fourth", "five", "fifth",
+    "_[1]_", "uno", "_[2]_", "dos", "_[3]_", "tres", "_[4]_", "cuatro", "_[5]_", "cinco",
+    "[\xe5\xb9\xb3\xe6\x9e\x97\xe5\xb9\xb9\xe9\x9b\x84]", "[\xe9\xa6\xac\xe9\xb9\xbf]", NULL
+  };
+  for(int i = 0; words[i] != NULL; i += 2){
+    const char *kbuf = words[i];
+    int ksiz = strlen(kbuf);
+    const char *vbuf = words[i+1];
+    int vsiz = strlen(vbuf);
+    if(!tchdbputkeep(hdb, kbuf, ksiz, vbuf, vsiz)){
+      eprint(hdb, __LINE__, "tchdbputkeep");
+      err = true;
+      break;
+    }
+    if(rnum > 250) iputchar('.');
+  }
+  if(rnum > 250) iprintf(" (%08d)\n", (int)(sizeof(words) / sizeof(*words)));
+  iprintf("random erasing:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(rnum));
+    if(!tchdbout(hdb, kbuf, ksiz) && tchdbecode(hdb) != TCENOREC){
+      eprint(hdb, __LINE__, "tchdbout");
+      err = true;
+      break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("writing:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "[%d]", i);
+    char vbuf[RECBUFSIZ];
+    int vsiz = i % RECBUFSIZ;
+    memset(vbuf, '*', vsiz);
+    if(!tchdbputkeep(hdb, kbuf, ksiz, vbuf, vsiz)){
+      eprint(hdb, __LINE__, "tchdbputkeep");
+      err = true;
+      break;
+    }
+    if(vsiz < 1){
+      char tbuf[PATH_MAX];
+      for(int j = 0; j < PATH_MAX; j++){
+        tbuf[j] = myrand(0x100);
+      }
+      if(!tchdbput(hdb, kbuf, ksiz, tbuf, PATH_MAX)){
+        eprint(hdb, __LINE__, "tchdbput");
+        err = true;
+        break;
+      }
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("erasing:\n");
+  for(int i = 1; i <= rnum; i++){
+    if(i % 2 == 1){
+      char kbuf[RECBUFSIZ];
+      int ksiz = sprintf(kbuf, "[%d]", i);
+      if(!tchdbout(hdb, kbuf, ksiz)){
+        eprint(hdb, __LINE__, "tchdbout");
+        err = true;
+        break;
+      }
+      if(tchdbout(hdb, kbuf, ksiz) || tchdbecode(hdb) != TCENOREC){
+        eprint(hdb, __LINE__, "tchdbout");
+        err = true;
+        break;
+      }
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("random writing and reopening:\n");
+  for(int i = 1; i <= rnum; i++){
+    if(myrand(10) == 0){
+      int ksiz, vsiz;
+      char *kbuf, *vbuf;
+      ksiz = (myrand(5) == 0) ? myrand(UINT16_MAX) : myrand(RECBUFSIZ);
+      kbuf = tcmalloc(ksiz + 1);
+      memset(kbuf, '@', ksiz);
+      vsiz = (myrand(5) == 0) ? myrand(UINT16_MAX) : myrand(RECBUFSIZ);
+      vbuf = tcmalloc(vsiz + 1);
+      for(int j = 0; j < vsiz; j++){
+        vbuf[j] = myrand(256);
+      }
+      switch(myrand(4)){
+        case 0:
+          if(!tchdbput(hdb, kbuf, ksiz, vbuf, vsiz)){
+            eprint(hdb, __LINE__, "tchdbput");
+            err = true;
+          }
+          break;
+        case 1:
+          if(!tchdbputcat(hdb, kbuf, ksiz, vbuf, vsiz)){
+            eprint(hdb, __LINE__, "tchdbputcat");
+            err = true;
+          }
+          break;
+        case 2:
+          if(!tchdbputasync(hdb, kbuf, ksiz, vbuf, vsiz)){
+            eprint(hdb, __LINE__, "tchdbputasync");
+            err = true;
+          }
+          break;
+        case 3:
+          if(!tchdbout(hdb, kbuf, ksiz) && tchdbecode(hdb) != TCENOREC){
+            eprint(hdb, __LINE__, "tchdbout");
+            err = true;
+          }
+          break;
+      }
+      tcfree(vbuf);
+      tcfree(kbuf);
+    } else {
+      char kbuf[RECBUFSIZ];
+      int ksiz = myrand(RECBUFSIZ);
+      memset(kbuf, '@', ksiz);
+      char vbuf[RECBUFSIZ];
+      int vsiz = myrand(RECBUFSIZ);
+      memset(vbuf, '@', vsiz);
+      if(!tchdbput(hdb, kbuf, ksiz, vbuf, vsiz)){
+        eprint(hdb, __LINE__, "tchdbputcat");
+        err = true;
+        break;
+      }
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(!tchdbclose(hdb)){
+    eprint(hdb, __LINE__, "tchdbclose");
+    err = true;
+  }
+  if(!tchdbopen(hdb, path, HDBOWRITER | omode)){
+    eprint(hdb, __LINE__, "tchdbopen");
+    err = true;
+  }
+  iprintf("checking:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "[%d]", i);
+    int vsiz;
+    char *vbuf = tchdbget(hdb, kbuf, ksiz, &vsiz);
+    if(i % 2 == 0){
+      if(!vbuf){
+        eprint(hdb, __LINE__, "tchdbget");
+        err = true;
+        break;
+      }
+      if(vsiz != i % RECBUFSIZ && vsiz != PATH_MAX){
+        eprint(hdb, __LINE__, "(validation)");
+        err = true;
+        tcfree(vbuf);
+        break;
+      }
+    } else {
+      if(vbuf || tchdbecode(hdb) != TCENOREC){
+        eprint(hdb, __LINE__, "(validation)");
+        err = true;
+        tcfree(vbuf);
+        break;
+      }
+    }
+    tcfree(vbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("writing:\n");
+  for(int i = 1; i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", i);
+    if(!tchdbput(hdb, buf, len, buf, len)){
+      eprint(hdb, __LINE__, "tchdbput");
+      err = true;
+      break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("reading:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%08d", i);
+    int vsiz;
+    char *vbuf = tchdbget(hdb, kbuf, ksiz, &vsiz);
+    if(!vbuf){
+      eprint(hdb, __LINE__, "tchdbget");
+      err = true;
+      break;
+    } else if(vsiz != ksiz || memcmp(vbuf, kbuf, vsiz)){
+      eprint(hdb, __LINE__, "(validation)");
+      err = true;
+      tcfree(vbuf);
+      break;
+    }
+    tcfree(vbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("checking words:\n");
+  for(int i = 0; words[i] != NULL; i += 2){
+    const char *kbuf = words[i];
+    int ksiz = strlen(kbuf);
+    const char *vbuf = words[i+1];
+    int vsiz = strlen(vbuf);
+    int rsiz;
+    char *rbuf = tchdbget(hdb, kbuf, ksiz, &rsiz);
+    if(!rbuf){
+      eprint(hdb, __LINE__, "tchdbget");
+      err = true;
+      break;
+    } else if(rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+      eprint(hdb, __LINE__, "(validation)");
+      err = true;
+      tcfree(rbuf);
+      break;
+    }
+    tcfree(rbuf);
+    if(rnum > 250) iputchar('.');
+  }
+  if(rnum > 250) iprintf(" (%08d)\n", (int)(sizeof(words) / sizeof(*words)));
+  iprintf("checking iterator:\n");
+  int inum = 0;
+  if(!tchdbiterinit(hdb)){
+    eprint(hdb, __LINE__, "tchdbiterinit");
+    err = true;
+  }
+  char *kbuf;
+  int ksiz;
+  for(int i = 1; (kbuf = tchdbiternext(hdb, &ksiz)) != NULL; i++, inum++){
+    int vsiz;
+    char *vbuf = tchdbget(hdb, kbuf, ksiz, &vsiz);
+    if(!vbuf){
+      eprint(hdb, __LINE__, "tchdbget");
+      err = true;
+      tcfree(kbuf);
+      break;
+    }
+    tcfree(vbuf);
+    tcfree(kbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(rnum > 250) iprintf(" (%08d)\n", inum);
+  if(tchdbecode(hdb) != TCENOREC || inum != tchdbrnum(hdb)){
+    eprint(hdb, __LINE__, "(validation)");
+    err = true;
+  }
+  iprintf("iteration updating:\n");
+  if(!tchdbiterinit(hdb)){
+    eprint(hdb, __LINE__, "tchdbiterinit");
+    err = true;
+  }
+  inum = 0;
+  for(int i = 1; (kbuf = tchdbiternext(hdb, &ksiz)) != NULL; i++, inum++){
+    if(myrand(2) == 0){
+      if(!tchdbputcat(hdb, kbuf, ksiz, "0123456789", 10)){
+        eprint(hdb, __LINE__, "tchdbputcat");
+        err = true;
+        tcfree(kbuf);
+        break;
+      }
+    } else {
+      if(!tchdbout(hdb, kbuf, ksiz)){
+        eprint(hdb, __LINE__, "tchdbout");
+        err = true;
+        tcfree(kbuf);
+        break;
+      }
+    }
+    tcfree(kbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(rnum > 250) iprintf(" (%08d)\n", inum);
+  if(tchdbecode(hdb) != TCENOREC || inum < tchdbrnum(hdb)){
+    eprint(hdb, __LINE__, "(validation)");
+    err = true;
+  }
+  if(myrand(10) == 0 && !tchdbsync(hdb)){
+    eprint(hdb, __LINE__, "tchdbsync");
+    err = true;
+  }
+  if(!tchdbvanish(hdb)){
+    eprint(hdb, __LINE__, "tchdbvanish");
+    err = true;
+  }
+  TCMAP *map = tcmapnew();
+  iprintf("random writing:\n");
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(rnum));
+    char vbuf[RECBUFSIZ];
+    int vsiz = sprintf(vbuf, "%d", myrand(rnum));
+    switch(myrand(4)){
+      case 0:
+        if(!tchdbput(hdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(hdb, __LINE__, "tchdbput");
+          err = true;
+        }
+        tcmapput(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 1:
+        if(!tchdbputkeep(hdb, kbuf, ksiz, vbuf, vsiz) && tchdbecode(hdb) != TCEKEEP){
+          eprint(hdb, __LINE__, "tchdbputkeep");
+          err = true;
+        }
+        tcmapputkeep(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 2:
+        if(!tchdbputcat(hdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(hdb, __LINE__, "tchdbputcat");
+          err = true;
+        }
+        tcmapputcat(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 3:
+        if(!tchdbout(hdb, kbuf, ksiz) && tchdbecode(hdb) != TCENOREC){
+          eprint(hdb, __LINE__, "tchdbout");
+          err = true;
+        }
+        tcmapout(map, kbuf, ksiz);
+        break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(myrand(4) == 0 && !tchdbdefrag(hdb, 0)){
+    eprint(hdb, __LINE__, "tchdbdefrag");
+    err = true;
+  }
+  if(myrand(4) == 0 && !tchdbcacheclear(hdb)){
+    eprint(hdb, __LINE__, "tchdbcacheclear");
+    err = true;
+  }
+  iprintf("checking transaction commit:\n");
+  if(!tchdbtranbegin(hdb)){
+    eprint(hdb, __LINE__, "tchdbtranbegin");
+    err = true;
+  }
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(rnum));
+    char vbuf[RECBUFSIZ];
+    int vsiz = sprintf(vbuf, "[%d]", myrand(rnum));
+    switch(myrand(7)){
+      case 0:
+        if(!tchdbput(hdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(hdb, __LINE__, "tchdbput");
+          err = true;
+        }
+        tcmapput(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 1:
+        if(!tchdbputkeep(hdb, kbuf, ksiz, vbuf, vsiz) && tchdbecode(hdb) != TCEKEEP){
+          eprint(hdb, __LINE__, "tchdbputkeep");
+          err = true;
+        }
+        tcmapputkeep(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 2:
+        if(!tchdbputcat(hdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(hdb, __LINE__, "tchdbputcat");
+          err = true;
+        }
+        tcmapputcat(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 3:
+        if(tchdbaddint(hdb, kbuf, ksiz, 1) == INT_MIN && tchdbecode(hdb) != TCEKEEP){
+          eprint(hdb, __LINE__, "tchdbaddint");
+          err = true;
+        }
+        tcmapaddint(map, kbuf, ksiz, 1);
+        break;
+      case 4:
+        if(isnan(tchdbadddouble(hdb, kbuf, ksiz, 1.0)) && tchdbecode(hdb) != TCEKEEP){
+          eprint(hdb, __LINE__, "tchdbadddouble");
+          err = true;
+        }
+        tcmapadddouble(map, kbuf, ksiz, 1.0);
+        break;
+      case 5:
+        if(myrand(2) == 0){
+          void *op = (void *)(intptr_t)(myrand(3) + 1);
+          if(!tchdbputproc(hdb, kbuf, ksiz, vbuf, vsiz, pdprocfunc, op) &&
+             tchdbecode(hdb) != TCEKEEP){
+            eprint(hdb, __LINE__, "tchdbputproc");
+            err = true;
+          }
+          tcmapputproc(map, kbuf, ksiz, vbuf, vsiz, pdprocfunc, op);
+        } else {
+          vsiz = myrand(10);
+          void *op = (void *)(intptr_t)(myrand(3) + 1);
+          if(!tchdbputproc(hdb, kbuf, ksiz, NULL, vsiz, pdprocfunc, op) &&
+             tchdbecode(hdb) != TCEKEEP && tchdbecode(hdb) != TCENOREC){
+            eprint(hdb, __LINE__, "tchdbputproc");
+            err = true;
+          }
+          tcmapputproc(map, kbuf, ksiz, NULL, vsiz, pdprocfunc, op);
+        }
+        break;
+      case 6:
+        if(!tchdbout(hdb, kbuf, ksiz) && tchdbecode(hdb) != TCENOREC){
+          eprint(hdb, __LINE__, "tchdbout");
+          err = true;
+        }
+        tcmapout(map, kbuf, ksiz);
+        break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(!tchdbtrancommit(hdb)){
+    eprint(hdb, __LINE__, "tchdbtrancommit");
+    err = true;
+  }
+  iprintf("checking transaction abort:\n");
+  uint64_t ornum = tchdbrnum(hdb);
+  uint64_t ofsiz = tchdbfsiz(hdb);
+  if(!tchdbtranbegin(hdb)){
+    eprint(hdb, __LINE__, "tchdbtranbegin");
+    err = true;
+  }
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(rnum));
+    char vbuf[RECBUFSIZ];
+    int vsiz = sprintf(vbuf, "((%d))", myrand(rnum));
+    switch(myrand(7)){
+      case 0:
+        if(!tchdbput(hdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(hdb, __LINE__, "tchdbput");
+          err = true;
+        }
+        break;
+      case 1:
+        if(!tchdbputkeep(hdb, kbuf, ksiz, vbuf, vsiz) && tchdbecode(hdb) != TCEKEEP){
+          eprint(hdb, __LINE__, "tchdbputkeep");
+          err = true;
+        }
+        break;
+      case 2:
+        if(!tchdbputcat(hdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(hdb, __LINE__, "tchdbputcat");
+          err = true;
+        }
+        break;
+      case 3:
+        if(tchdbaddint(hdb, kbuf, ksiz, 1) == INT_MIN && tchdbecode(hdb) != TCEKEEP){
+          eprint(hdb, __LINE__, "tchdbaddint");
+          err = true;
+        }
+        break;
+      case 4:
+        if(isnan(tchdbadddouble(hdb, kbuf, ksiz, 1.0)) && tchdbecode(hdb) != TCEKEEP){
+          eprint(hdb, __LINE__, "tchdbadddouble");
+          err = true;
+        }
+        break;
+      case 5:
+        if(myrand(2) == 0){
+          void *op = (void *)(intptr_t)(myrand(3) + 1);
+          if(!tchdbputproc(hdb, kbuf, ksiz, vbuf, vsiz, pdprocfunc, op) &&
+             tchdbecode(hdb) != TCEKEEP){
+            eprint(hdb, __LINE__, "tchdbputproc");
+            err = true;
+          }
+        } else {
+          vsiz = myrand(10);
+          void *op = (void *)(intptr_t)(myrand(3) + 1);
+          if(!tchdbputproc(hdb, kbuf, ksiz, NULL, vsiz, pdprocfunc, op) &&
+             tchdbecode(hdb) != TCEKEEP && tchdbecode(hdb) != TCENOREC){
+            eprint(hdb, __LINE__, "tchdbputproc");
+            err = true;
+          }
+        }
+        break;
+      case 6:
+        if(!tchdbout(hdb, kbuf, ksiz) && tchdbecode(hdb) != TCENOREC){
+          eprint(hdb, __LINE__, "tchdbout");
+          err = true;
+        }
+        break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(!tchdbtranabort(hdb)){
+    eprint(hdb, __LINE__, "tchdbtranabort");
+    err = true;
+  }
+  iprintf("checking consistency:\n");
+  if(tchdbrnum(hdb) != ornum || tchdbfsiz(hdb) != ofsiz || tchdbrnum(hdb) != tcmaprnum(map)){
+    eprint(hdb, __LINE__, "(validation)");
+    err = true;
+  }
+  inum = 0;
+  tcmapiterinit(map);
+  const char *tkbuf;
+  int tksiz;
+  for(int i = 1; (tkbuf = tcmapiternext(map, &tksiz)) != NULL; i++, inum++){
+    int tvsiz;
+    const char *tvbuf = tcmapiterval(tkbuf, &tvsiz);
+    int rsiz;
+    char *rbuf = tchdbget(hdb, tkbuf, tksiz, &rsiz);
+    if(!rbuf || rsiz != tvsiz || memcmp(rbuf, tvbuf, rsiz)){
+      eprint(hdb, __LINE__, "(validation)");
+      err = true;
+      tcfree(rbuf);
+      break;
+    }
+    tcfree(rbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(rnum > 250) iprintf(" (%08d)\n", inum);
+  inum = 0;
+  if(!tchdbiterinit(hdb)){
+    eprint(hdb, __LINE__, "tchdbiterinit");
+    err = true;
+  }
+  for(int i = 1; (kbuf = tchdbiternext(hdb, &ksiz)) != NULL; i++, inum++){
+    int vsiz;
+    char *vbuf = tchdbget(hdb, kbuf, ksiz, &vsiz);
+    int rsiz;
+    const char *rbuf = tcmapget(map, kbuf, ksiz, &rsiz);
+    if(!rbuf || rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+      eprint(hdb, __LINE__, "(validation)");
+      err = true;
+      tcfree(vbuf);
+      tcfree(kbuf);
+      break;
+    }
+    tcfree(vbuf);
+    tcfree(kbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(rnum > 250) iprintf(" (%08d)\n", inum);
+  tcmapdel(map);
+  if(!tchdbvanish(hdb)){
+    eprint(hdb, __LINE__, "tchdbvanish");
+    err = true;
+  }
+  for(int i = myrand(3) + 1; i < PATH_MAX; i = i * 2 + myrand(3)){
+    char vbuf[i];
+    memset(vbuf, '@', i - 1);
+    vbuf[i-1] = '\0';
+    if(!tchdbput2(hdb, "mikio", vbuf)){
+      eprint(hdb, __LINE__, "tchdbput2");
+      err = true;
+    }
+  }
+  if(!tchdbput2(hdb, "mikio", "nanashi")){
+    eprint(hdb, __LINE__, "tchdbput2");
+    err = true;
+  }
+  if(!tchdbtranbegin(hdb)){
+    eprint(hdb, __LINE__, "tchdbtranbegin");
+    err = true;
+  }
+  if(!tchdbput2(hdb, "mikio", "hirabayashi")){
+    eprint(hdb, __LINE__, "tchdbput2");
+    err = true;
+  }
+  for(int i = 0; i < 10; i++){
+    char buf[RECBUFSIZ];
+    int size = sprintf(buf, "%d", myrand(rnum));
+    if(!tchdbput(hdb, buf, size, buf, size)){
+      eprint(hdb, __LINE__, "tchdbput");
+      err = true;
+    }
+  }
+  for(int i = myrand(3) + 1; i < PATH_MAX; i = i * 2 + myrand(3)){
+    char vbuf[i];
+    memset(vbuf, '@', i - 1);
+    vbuf[i-1] = '\0';
+    if(!tchdbput2(hdb, "mikio", vbuf)){
+      eprint(hdb, __LINE__, "tchdbput2");
+      err = true;
+    }
+  }
+  if(!tchdbforeach(hdb, iterfunc, NULL)){
+    eprint(hdb, __LINE__, "tchdbforeach");
+    err = true;
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tchdbrnum(hdb));
+  iprintf("size: %llu\n", (unsigned long long)tchdbfsiz(hdb));
+  mprint(hdb);
+  sysprint();
+  if(!tchdbclose(hdb)){
+    eprint(hdb, __LINE__, "tchdbclose");
+    err = true;
+  }
+  tchdbdel(hdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform wicked command */
+static int procwicked(const char *path, int rnum, bool mt, int opts, int omode){
+  iprintf("<Wicked Writing Test>\n  seed=%u  path=%s  rnum=%d  mt=%d  opts=%d  omode=%d\n\n",
+          g_randseed, path, rnum, mt, opts, omode);
+  bool err = false;
+  double stime = tctime();
+  TCHDB *hdb = tchdbnew();
+  if(g_dbgfd >= 0) tchdbsetdbgfd(hdb, g_dbgfd);
+  if(mt && !tchdbsetmutex(hdb)){
+    eprint(hdb, __LINE__, "tchdbsetmutex");
+    err = true;
+  }
+  if(!tchdbsetcodecfunc(hdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(hdb, __LINE__, "tchdbsetcodecfunc");
+    err = true;
+  }
+  if(!tchdbtune(hdb, rnum / 50, 2, -1, opts)){
+    eprint(hdb, __LINE__, "tchdbtune");
+    err = true;
+  }
+  if(!tchdbsetcache(hdb, rnum / 10)){
+    eprint(hdb, __LINE__, "tchdbsetcache");
+    err = true;
+  }
+  if(!tchdbsetxmsiz(hdb, rnum * sizeof(int))){
+    eprint(hdb, __LINE__, "tchdbsetxmsiz");
+    err = true;
+  }
+  if(!tchdbsetdfunit(hdb, 8)){
+    eprint(hdb, __LINE__, "tchdbsetdfunit");
+    err = true;
+  }
+  if(!tchdbopen(hdb, path, HDBOWRITER | HDBOCREAT | HDBOTRUNC | omode)){
+    eprint(hdb, __LINE__, "tchdbopen");
+    err = true;
+  }
+  if(!tchdbiterinit(hdb)){
+    eprint(hdb, __LINE__, "tchdbiterinit");
+    err = true;
+  }
+  TCMAP *map = tcmapnew2(rnum / 5);
+  for(int i = 1; i <= rnum && !err; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(rnum));
+    char vbuf[RECBUFSIZ];
+    int vsiz = myrand(RECBUFSIZ);
+    memset(vbuf, '*', vsiz);
+    vbuf[vsiz] = '\0';
+    char *rbuf;
+    switch(myrand(16)){
+      case 0:
+        iputchar('0');
+        if(!tchdbput(hdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(hdb, __LINE__, "tchdbput");
+          err = true;
+        }
+        tcmapput(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 1:
+        iputchar('1');
+        if(!tchdbput2(hdb, kbuf, vbuf)){
+          eprint(hdb, __LINE__, "tchdbput2");
+          err = true;
+        }
+        tcmapput2(map, kbuf, vbuf);
+        break;
+      case 2:
+        iputchar('2');
+        if(!tchdbputkeep(hdb, kbuf, ksiz, vbuf, vsiz) && tchdbecode(hdb) != TCEKEEP){
+          eprint(hdb, __LINE__, "tchdbputkeep");
+          err = true;
+        }
+        tcmapputkeep(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 3:
+        iputchar('3');
+        if(!tchdbputkeep2(hdb, kbuf, vbuf) && tchdbecode(hdb) != TCEKEEP){
+          eprint(hdb, __LINE__, "tchdbputkeep2");
+          err = true;
+        }
+        tcmapputkeep2(map, kbuf, vbuf);
+        break;
+      case 4:
+        iputchar('4');
+        if(!tchdbputcat(hdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(hdb, __LINE__, "tchdbputcat");
+          err = true;
+        }
+        tcmapputcat(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 5:
+        iputchar('5');
+        if(!tchdbputcat2(hdb, kbuf, vbuf)){
+          eprint(hdb, __LINE__, "tchdbputcat2");
+          err = true;
+        }
+        tcmapputcat2(map, kbuf, vbuf);
+        break;
+      case 6:
+        iputchar('6');
+        if(!tchdbputasync(hdb, kbuf, ksiz, vbuf, vsiz)){
+          eprint(hdb, __LINE__, "tchdbputasync");
+          err = true;
+        }
+        tcmapput(map, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 7:
+        iputchar('7');
+        if(!tchdbputasync2(hdb, kbuf, vbuf)){
+          eprint(hdb, __LINE__, "tchdbputasync2");
+          err = true;
+        }
+        tcmapput2(map, kbuf, vbuf);
+        break;
+      case 8:
+        iputchar('8');
+        if(myrand(10) == 0){
+          if(!tchdbout(hdb, kbuf, ksiz) && tchdbecode(hdb) != TCENOREC){
+            eprint(hdb, __LINE__, "tchdbout");
+            err = true;
+          }
+          tcmapout(map, kbuf, ksiz);
+        }
+        break;
+      case 9:
+        iputchar('9');
+        if(myrand(10) == 0){
+          if(!tchdbout2(hdb, kbuf) && tchdbecode(hdb) != TCENOREC){
+            eprint(hdb, __LINE__, "tchdbout2");
+            err = true;
+          }
+          tcmapout2(map, kbuf);
+        }
+        break;
+      case 10:
+        iputchar('A');
+        if(!(rbuf = tchdbget(hdb, kbuf, ksiz, &vsiz))){
+          if(tchdbecode(hdb) != TCENOREC){
+            eprint(hdb, __LINE__, "tchdbget");
+            err = true;
+          }
+          rbuf = tcsprintf("[%d]", myrand(i + 1));
+          vsiz = strlen(rbuf);
+        }
+        vsiz += myrand(vsiz);
+        if(myrand(3) == 0) vsiz += PATH_MAX;
+        rbuf = tcrealloc(rbuf, vsiz + 1);
+        for(int j = 0; j < vsiz; j++){
+          rbuf[j] = myrand(0x100);
+        }
+        if(!tchdbput(hdb, kbuf, ksiz, rbuf, vsiz)){
+          eprint(hdb, __LINE__, "tchdbput");
+          err = true;
+        }
+        tcmapput(map, kbuf, ksiz, rbuf, vsiz);
+        tcfree(rbuf);
+        break;
+      case 11:
+        iputchar('B');
+        if(!(rbuf = tchdbget(hdb, kbuf, ksiz, &vsiz)) && tchdbecode(hdb) != TCENOREC){
+          eprint(hdb, __LINE__, "tchdbget");
+          err = true;
+        }
+        tcfree(rbuf);
+        break;
+      case 12:
+        iputchar('C');
+        if(!(rbuf = tchdbget2(hdb, kbuf)) && tchdbecode(hdb) != TCENOREC){
+          eprint(hdb, __LINE__, "tchdbget2");
+          err = true;
+        }
+        tcfree(rbuf);
+        break;
+      case 13:
+        iputchar('D');
+        if(myrand(1) == 0) vsiz = 1;
+        if((vsiz = tchdbget3(hdb, kbuf, ksiz, vbuf, vsiz)) < 0 && tchdbecode(hdb) != TCENOREC){
+          eprint(hdb, __LINE__, "tchdbget3");
+          err = true;
+        }
+        break;
+      case 14:
+        iputchar('E');
+        if(myrand(rnum / 50) == 0){
+          if(!tchdbiterinit(hdb)){
+            eprint(hdb, __LINE__, "tchdbiterinit");
+            err = true;
+          }
+        }
+        TCXSTR *ikey = tcxstrnew();
+        TCXSTR *ival = tcxstrnew();
+        for(int j = myrand(rnum) / 1000 + 1; j >= 0; j--){
+          if(j % 3 == 0){
+            if(tchdbiternext3(hdb, ikey, ival)){
+              if(tcxstrsize(ival) != tchdbvsiz(hdb, tcxstrptr(ikey), tcxstrsize(ikey))){
+                eprint(hdb, __LINE__, "(validation)");
+                err = true;
+              }
+            } else {
+              int ecode = tchdbecode(hdb);
+              if(ecode != TCEINVALID && ecode != TCENOREC){
+                eprint(hdb, __LINE__, "tchdbiternext3");
+                err = true;
+              }
+            }
+          } else {
+            int iksiz;
+            char *ikbuf = tchdbiternext(hdb, &iksiz);
+            if(ikbuf){
+              tcfree(ikbuf);
+            } else {
+              int ecode = tchdbecode(hdb);
+              if(ecode != TCEINVALID && ecode != TCENOREC){
+                eprint(hdb, __LINE__, "tchdbiternext");
+                err = true;
+              }
+            }
+          }
+        }
+        tcxstrdel(ival);
+        tcxstrdel(ikey);
+        break;
+      default:
+        iputchar('@');
+        if(myrand(10000) == 0) srand((unsigned int)(tctime() * 1000) % UINT_MAX);
+        if(myrand(rnum / 16 + 1) == 0){
+          int cnt = myrand(30);
+          for(int j = 0; j < rnum && !err; j++){
+            ksiz = sprintf(kbuf, "%d", i + j);
+            if(tchdbout(hdb, kbuf, ksiz)){
+              cnt--;
+            } else if(tchdbecode(hdb) != TCENOREC){
+              eprint(hdb, __LINE__, "tchdbout");
+              err = true;
+            }
+            tcmapout(map, kbuf, ksiz);
+            if(cnt < 0) break;
+          }
+        }
+        break;
+    }
+    if(i % 50 == 0) iprintf(" (%08d)\n", i);
+    if(i == rnum / 2){
+      if(!tchdbclose(hdb)){
+        eprint(hdb, __LINE__, "tchdbclose");
+        err = true;
+      }
+      if(!tchdbopen(hdb, path, HDBOWRITER | omode)){
+        eprint(hdb, __LINE__, "tchdbopen");
+        err = true;
+      }
+    } else if(i == rnum / 4){
+      char *npath = tcsprintf("%s-tmp", path);
+      if(!tchdbcopy(hdb, npath)){
+        eprint(hdb, __LINE__, "tchdbcopy");
+        err = true;
+      }
+      TCHDB *nhdb = tchdbnew();
+      if(!tchdbsetcodecfunc(nhdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+        eprint(nhdb, __LINE__, "tchdbsetcodecfunc");
+        err = true;
+      }
+      if(!tchdbopen(nhdb, npath, HDBOREADER | omode)){
+        eprint(nhdb, __LINE__, "tchdbopen");
+        err = true;
+      }
+      tchdbdel(nhdb);
+      unlink(npath);
+      tcfree(npath);
+      if(!tchdboptimize(hdb, rnum / 50, -1, -1, -1)){
+        eprint(hdb, __LINE__, "tchdboptimize");
+        err = true;
+      }
+      if(!tchdbiterinit(hdb)){
+        eprint(hdb, __LINE__, "tchdbiterinit");
+        err = true;
+      }
+    } else if(i == rnum / 8){
+      if(!tchdbtranbegin(hdb)){
+        eprint(hdb, __LINE__, "tchdbtranbegin");
+        err = true;
+      }
+    } else if(i == rnum / 8 + rnum / 16){
+      if(!tchdbtrancommit(hdb)){
+        eprint(hdb, __LINE__, "tchdbtrancommit");
+        err = true;
+      }
+    }
+  }
+  if(rnum % 50 > 0) iprintf(" (%08d)\n", rnum);
+  if(!tchdbsync(hdb)){
+    eprint(hdb, __LINE__, "tchdbsync");
+    err = true;
+  }
+  if(tchdbrnum(hdb) != tcmaprnum(map)){
+    eprint(hdb, __LINE__, "(validation)");
+    err = true;
+  }
+  for(int i = 1; i <= rnum && !err; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", i - 1);
+    int vsiz;
+    const char *vbuf = tcmapget(map, kbuf, ksiz, &vsiz);
+    int rsiz;
+    char *rbuf = tchdbget(hdb, kbuf, ksiz, &rsiz);
+    if(vbuf){
+      iputchar('.');
+      if(!rbuf){
+        eprint(hdb, __LINE__, "tchdbget");
+        err = true;
+      } else if(rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+        eprint(hdb, __LINE__, "(validation)");
+        err = true;
+      }
+    } else {
+      iputchar('*');
+      if(rbuf || tchdbecode(hdb) != TCENOREC){
+        eprint(hdb, __LINE__, "(validation)");
+        err = true;
+      }
+    }
+    tcfree(rbuf);
+    if(i % 50 == 0) iprintf(" (%08d)\n", i);
+  }
+  if(rnum % 50 > 0) iprintf(" (%08d)\n", rnum);
+  int inum = 0;
+  char *cbuf;
+  int csiz;
+  if(myrand(2) == 0){
+    cbuf = tchdbgetnext(hdb, NULL, -1, &csiz);
+  } else {
+    const char *cvbuf;
+    int cvsiz;
+    cbuf = tchdbgetnext3(hdb, NULL, -1, &csiz, &cvbuf, &cvsiz);
+  }
+  while(cbuf){
+    inum++;
+    iputchar(':');
+    int nsiz;
+    char *nbuf;
+    if(myrand(2) == 0){
+      nbuf = tchdbgetnext(hdb, cbuf, csiz, &nsiz);
+    } else {
+      const char *cvbuf;
+      int cvsiz;
+      nbuf = tchdbgetnext3(hdb, cbuf, csiz, &nsiz, &cvbuf, &cvsiz);
+    }
+    if(myrand(10) == 0){
+      if(!tchdbiterinit2(hdb, cbuf, csiz)){
+        eprint(hdb, __LINE__, "tchdbiterinit2");
+        err = true;
+      }
+      int ksiz;
+      char *kbuf = tchdbiternext(hdb, &ksiz);
+      if(kbuf){
+        tcfree(kbuf);
+      } else {
+        eprint(hdb, __LINE__, "tchdbiternext");
+        err = true;
+      }
+      for(int i = 0; i < 5; i++){
+        kbuf = tchdbiternext(hdb, &ksiz);
+        if(kbuf){
+          tcfree(kbuf);
+        } else if(tchdbecode(hdb) != TCENOREC){
+          eprint(hdb, __LINE__, "tchdbiternext");
+          err = true;
+        }
+      }
+    }
+    tcfree(cbuf);
+    cbuf = nbuf;
+    csiz = nsiz;
+    if(inum % 50 == 0) iprintf(" (%08d)\n", inum);
+  }
+  tcfree(cbuf);
+  if(inum % 50 > 0) iprintf(" (%08d)\n", inum);
+  if(inum != tchdbrnum(hdb)){
+    eprint(hdb, __LINE__, "(validation)");
+    err = true;
+  }
+  tcmapiterinit(map);
+  int ksiz;
+  const char *kbuf;
+  for(int i = 1; (kbuf = tcmapiternext(map, &ksiz)) != NULL; i++){
+    iputchar('+');
+    int vsiz;
+    const char *vbuf = tcmapiterval(kbuf, &vsiz);
+    int rsiz;
+    char *rbuf = tchdbget(hdb, kbuf, ksiz, &rsiz);
+    if(!rbuf){
+      eprint(hdb, __LINE__, "tchdbget");
+      err = true;
+    } else if(rsiz != vsiz || memcmp(rbuf, vbuf, rsiz)){
+      eprint(hdb, __LINE__, "(validation)");
+      err = true;
+    }
+    tcfree(rbuf);
+    if(!tchdbout(hdb, kbuf, ksiz)){
+      eprint(hdb, __LINE__, "tchdbout");
+      err = true;
+    }
+    if(i % 50 == 0) iprintf(" (%08d)\n", i);
+  }
+  int mrnum = tcmaprnum(map);
+  if(mrnum % 50 > 0) iprintf(" (%08d)\n", mrnum);
+  if(tchdbrnum(hdb) != 0){
+    eprint(hdb, __LINE__, "(validation)");
+    err = true;
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tchdbrnum(hdb));
+  iprintf("size: %llu\n", (unsigned long long)tchdbfsiz(hdb));
+  mprint(hdb);
+  sysprint();
+  tcmapdel(map);
+  if(!tchdbclose(hdb)){
+    eprint(hdb, __LINE__, "tchdbclose");
+    err = true;
+  }
+  tchdbdel(hdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+
+// END OF FILE
diff --git a/tcejdb/tctdb.c b/tcejdb/tctdb.c
new file mode 100644 (file)
index 0000000..29efb95
--- /dev/null
@@ -0,0 +1,6566 @@
+/*************************************************************************************************
+ * 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 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 char* tctdbmaprowldr(TCLIST *tokens, const char *pkbuf, int pksz,
+                              const char *rowdata, int rowdatasz,
+                              const char* cname, int cnamesz,
+                              void *op, int *vsz);
+static bool tctdbsetindeximpl(TCTDB *tdb, const char *name, int type, TDBRVALOADER rvldr, void* rvldrop);
+static int64_t tctdbgenuidimpl(TCTDB *tdb, int64_t inc);
+static TCLIST *tctdbqrysearchimpl(TDBQRY *qry);
+static TCMAP *tctdbqryidxfetch(TDBQRY *qry, TDBCOND *cond, TDBIDX *idx);
+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 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 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 tctdbidxputtoken2(TCTDB *tdb, TDBIDX *idx, const char *pkbuf, int pksiz, TCLIST *tokens);
+static bool tctdbidxputqgram(TCTDB *tdb, TDBIDX *idx, const char *pkbuf, int pksiz,
+                             const char *vbuf, int vsiz);
+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 tctdbidxouttoken2(TCTDB *tdb, TDBIDX *idx, const char *pkbuf, int pksiz, TCLIST *tokens);
+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 *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){
+  return tctdbsetindexrldr(tdb, name, type, tctdbmaprowldr, NULL);
+}
+
+bool tctdbsetindexrldr(TCTDB *tdb, const char *name, int type, TDBRVALOADER rvldr, void* rvldrop){
+  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, rvldr, rvldrop)) 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 = tdb->hdb->rnum > 0 ? tchdbget(tdb->hdb, pkbuf, pksiz, &osiz) : NULL;
+  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. */
+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. */
+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. */
+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;
+}
+
+static char* tctdbmaprowldr(TCLIST *tokens,
+                             const char *pkbuf, int pksz,
+                             const char *rowdata, int rowdatasz,
+                             const char *cname, int cnamesz,
+                             void *op, int *vsz) {
+    char* res = NULL;
+    if (cname && *cname == '\0') {
+        TCMEMDUP(res, pkbuf, pksz);
+        *vsz = pksz;
+    } else {
+        res = tcmaploadone(rowdata, rowdatasz, cname, cnamesz, vsz);
+    }
+    if (tokens && res) {
+        const unsigned char *sp = (unsigned char *) res;
+        const unsigned char *start = sp;
+        while (sp - start <= *vsz && *sp != '\0') {
+            while (sp - start <= *vsz && ((*sp != '\0' && *sp <= ' ') || *sp == ',')) {
+                sp++;
+            }
+            const unsigned char *ep = sp;
+            while (ep - start <= *vsz && *ep > ' ' && *ep != ',') {
+                ep++;
+            }
+            if (ep > sp) TCLISTPUSH(tokens, sp, ep - sp);
+            sp = ep;
+        }
+        TCFREE(res);
+        *vsz = 0;
+        return NULL;
+    }
+    return res;
+}
+
+/* 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, TDBRVALOADER rvldr, void* rvldrop){
+  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)){
+      TCLIST *tokens = (type == TDBITTOKEN) ? tclistnew() : NULL;
+      int vsiz;
+      const char *pkbuf = TCXSTRPTR(kxstr);
+      int pksiz = TCXSTRSIZE(kxstr);
+      char *vbuf = rvldr(tokens, pkbuf, pksiz, TCXSTRPTR(vxstr), TCXSTRSIZE(vxstr), name, nsiz, rvldrop, &vsiz);
+      if(nsiz < 1){
+            switch(type){
+                case TDBITLEXICAL:
+                case TDBITDECIMAL:
+                  assert(vbuf);
+                  if(!tcbdbput(db, pkbuf, pksiz, vbuf, vsiz)){
+                    tctdbsetecode(tdb, tcbdbecode(db), __FILE__, __LINE__, __func__);
+                    err = true;
+                  }
+                  break;
+                case TDBITTOKEN:
+                  if(!tctdbidxputtoken2(tdb, idx, pkbuf, pksiz, tokens)) err = true;
+                  break;
+                case TDBITQGRAM:
+                  assert(vbuf);
+                  if(!tctdbidxputqgram(tdb, idx, pkbuf, pksiz, vbuf, vsiz)) err = true;
+                  break;
+            }
+      } else {
+            switch(type){
+                case TDBITLEXICAL:
+                case TDBITDECIMAL:
+                  if(vbuf && !tctdbidxputone(tdb, idx, pkbuf, pksiz, tctdbidxhash(pkbuf, pksiz), vbuf, vsiz)) err = true;
+                  break;
+                case TDBITTOKEN:
+                  if(tokens && !tctdbidxputtoken2(tdb, idx, pkbuf, pksiz, tokens)) err = true;
+                  break;
+                case TDBITQGRAM:
+                  if(vbuf && !tctdbidxputqgram(tdb, idx, pkbuf, pksiz, vbuf, vsiz)) err = true;
+                  break;
+            }
+      }
+      if (vbuf) TCFREE(vbuf);
+      if (tokens) tclistdel(tokens);
+    }
+    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. */
+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;
+}
+
+
+double tctdbatof2(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 nan("");
+  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++;
+    double fract = 0.0;
+    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;
+}
+
+int64_t tctdbatoi(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 (INT64_MAX * sign);
+  if(tcstrifwm(str, "nan")) return 0;
+  int64_t num = 0;
+  int col = 0;
+  while(*str != '\0'){
+    if(*str < '0' || *str > '9') break;
+    num = num * 10 + *str - '0';
+    str++;
+    if(num > 0) col++;
+  }
+  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. */
+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. */
+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. */
+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. */
+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;
+}
+
+bool tctdbidxput2(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:
+        {
+          TCLIST *tokens = tclistload(vbuf, vsiz);
+          if(!tctdbidxputtoken2(tdb, idx, pkbuf, pksiz, tokens)) err = true;
+          tclistdel(tokens);
+          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;
+}
+
+
+static bool tctdbidxputtoken2(TCTDB *tdb, TDBIDX *idx, const char *pkbuf, int pksiz,
+                                TCLIST *tokens){
+  assert(tdb && idx && pkbuf && pksiz >= 0 && tokens);
+  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;
+  }
+
+  for (int i = 0; i < TCLISTNUM(tokens); ++i) {
+    tcmapputcat3(cc, TCLISTVALPTR(tokens, i), TCLISTVALSIZ(tokens, i), rbuf, rsiz);
+  }
+
+  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. */
+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;
+}
+
+bool tctdbidxout2(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:
+        {
+          TCLIST *tokens = tclistload(vbuf, vsiz);
+          if(!tctdbidxouttoken2(tdb, idx, pkbuf, pksiz, tokens)) err = true;
+          tclistdel(tokens);
+          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;
+}
+
+static bool tctdbidxouttoken2(TCTDB *tdb, TDBIDX *idx, const char *pkbuf, int pksiz,
+                                TCLIST *tokens){
+  assert(tdb && idx && pkbuf && pksiz >= 0 && tokens);
+  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();
+  for (int i = 0; i < TCLISTNUM(tokens); ++i) {
+      int csiz;
+      const unsigned char *sp = TCLISTVALPTR(tokens, i);
+      int len = TCLISTVALSIZ(tokens, i);
+      tcxstrclear(xstr);
+      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));
+  }
+  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. */
+TCMAP *tctdbidxgetbytokens(TCTDB *tdb, const 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
diff --git a/tcejdb/tctdb.h b/tcejdb/tctdb.h
new file mode 100644 (file)
index 0000000..47b1c89
--- /dev/null
@@ -0,0 +1,1174 @@
+/*************************************************************************************************
+ * 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.
+ *************************************************************************************************/
+
+
+#ifndef _TCTDB_H                         /* duplication check */
+#define _TCTDB_H
+
+#if defined(__cplusplus)
+#define __TCTDB_CLINKAGEBEGIN extern "C" {
+#define __TCTDB_CLINKAGEEND }
+#else
+#define __TCTDB_CLINKAGEBEGIN
+#define __TCTDB_CLINKAGEEND
+#endif
+
+__TCTDB_CLINKAGEBEGIN
+
+
+#include <tcutil.h>
+#include <tchdb.h>
+#include <tcbdb.h>
+
+
+
+/*************************************************************************************************
+ * API
+ *************************************************************************************************/
+
+
+typedef struct { /* type of structure for a column index */
+    char *name; /* column name */
+    int type; /* data type */
+    void *db; /* internal database object */
+    void *cc; /* internal cache object */
+} TDBIDX;
+
+typedef struct { /* type of structure for a table database */
+    void *mmtx; /* mutex for method */
+    TCHDB *hdb; /* internal database object */
+    bool open; /* whether the internal database is opened */
+    bool wmode; /* whether to be writable */
+    uint8_t opts; /* options */
+    int32_t lcnum; /* max number of cached leaves */
+    int32_t ncnum; /* max number of cached nodes */
+    int64_t iccmax; /* maximum size of the inverted cache */
+    double iccsync; /* synchronization ratio of the inverted cache */
+    TDBIDX *idxs; /* column indices */
+    int inum; /* number of column indices */
+    bool tran; /* whether in the transaction */
+} TCTDB;
+
+enum { /* enumeration for additional flags */
+    TDBFOPEN = HDBFOPEN, /* whether opened */
+    TDBFFATAL = HDBFFATAL /* whether with fatal error */
+};
+
+enum { /* enumeration for tuning options */
+    TDBTLARGE = 1 << 0, /* use 64-bit bucket array */
+    TDBTDEFLATE = 1 << 1, /* compress each page with Deflate */
+    TDBTBZIP = 1 << 2, /* compress each record with BZIP2 */
+    TDBTTCBS = 1 << 3, /* compress each page with TCBS */
+    TDBTEXCODEC = 1 << 4 /* compress each record with outer functions */
+};
+
+enum { /* enumeration for open modes */
+    TDBOREADER = 1 << 0, /* open as a reader */
+    TDBOWRITER = 1 << 1, /* open as a writer */
+    TDBOCREAT = 1 << 2, /* writer creating */
+    TDBOTRUNC = 1 << 3, /* writer truncating */
+    TDBONOLCK = 1 << 4, /* open without locking */
+    TDBOLCKNB = 1 << 5, /* lock without blocking */
+    TDBOTSYNC = 1 << 6 /* synchronize every transaction */
+};
+
+enum { /* enumeration for index types */
+    TDBITLEXICAL, /* lexical string */
+    TDBITDECIMAL, /* decimal string */
+    TDBITTOKEN, /* token inverted index */
+    TDBITQGRAM, /* q-gram inverted index */
+    TDBITOPT = 9998, /* optimize */
+    TDBITVOID = 9999, /* void */
+    TDBITKEEP = 1 << 24 /* keep existing index */
+};
+
+typedef struct { /* type of structure for a condition */
+    char *name; /* column name */
+    int nsiz; /* size of the column name */
+    int op; /* operation type */
+    bool sign; /* positive sign */
+    bool noidx; /* no index flag */
+    char *expr; /* operand expression */
+    int esiz; /* size of the operand expression */
+    void *regex; /* regular expression object */
+    void *ftsunits; /* full-text search units */
+    int ftsnum; /* number of full-text search units */
+    bool alive; /* alive flag */
+} TDBCOND;
+
+typedef struct { /* type of structure for a query */
+    TCTDB *tdb; /* database object */
+    TDBCOND *conds; /* condition objects */
+    int cnum; /* number of conditions */
+    char *oname; /* column name for ordering */
+    int otype; /* type of order */
+    int max; /* max number of retrieval */
+    int skip; /* skipping number of retrieval */
+    TCXSTR *hint; /* hint string */
+    int count; /* count of corresponding records */
+} TDBQRY;
+
+enum { /* enumeration for query conditions */
+    TDBQCSTREQ, /* string is equal to */
+    TDBQCSTRINC, /* string is included in */
+    TDBQCSTRBW, /* string begins with */
+    TDBQCSTREW, /* string ends with */
+    TDBQCSTRAND, /* string includes all tokens in */
+    TDBQCSTROR, /* string includes at least one token in */
+    TDBQCSTROREQ, /* string is equal to at least one token in */
+    TDBQCSTRRX, /* string matches regular expressions of */
+    TDBQCNUMEQ, /* number is equal to */
+    TDBQCNUMGT, /* number is greater than */
+    TDBQCNUMGE, /* number is greater than or equal to */
+    TDBQCNUMLT, /* number is less than */
+    TDBQCNUMLE, /* number is less than or equal to */
+    TDBQCNUMBT, /* number is between two tokens of */
+    TDBQCNUMOREQ, /* number is equal to at least one token in */
+    TDBQCFTSPH, /* full-text search with the phrase of */
+    TDBQCFTSAND, /* full-text search with all tokens in */
+    TDBQCFTSOR, /* full-text search with at least one token in */
+    TDBQCFTSEX, /* full-text search with the compound expression of */
+    TDBQCEXIST, /* string|number exists */
+    TDBQTRUE,  /* any field always matched */
+    TDBQCSTRNUMOR, /* string includes at least one number token in */
+    TDBQCNEGATE = 1 << 24, /* negation flag */
+    TDBQCNOIDX = 1 << 25 /* no index flag */
+};
+
+enum { /* enumeration for order types */
+    TDBQOSTRASC, /* string ascending */
+    TDBQOSTRDESC, /* string descending */
+    TDBQONUMASC, /* number ascending */
+    TDBQONUMDESC /* number descending */
+};
+
+enum { /* enumeration for set operation types */
+    TDBMSUNION, /* union */
+    TDBMSISECT, /* intersection */
+    TDBMSDIFF /* difference */
+};
+
+enum { /* enumeration for post treatments */
+    TDBQPPUT = 1 << 0, /* modify the record */
+    TDBQPOUT = 1 << 1, /* remove the record */
+    TDBQPSTOP = 1 << 24 /* stop the iteration */
+};
+
+/* type of the pointer to a iterator function for each table record.
+   `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: `TDBQPPUT' to modify the
+   record, `TDBQPOUT' to remove the record, `TDBQPSTOP' to stop the iteration. */
+typedef int (*TDBQRYPROC)(const void *pkbuf, int pksiz, TCMAP *cols, void *op);
+
+
+/* custom row data loader function.
+   `rowdata' row data (TCMAP data).
+   `rowdata' row data length.
+   `cname' column name.
+   `cnamesz' length of column name.
+    `op' opaque data for this function.
+    `vsz' resulting value size. */
+typedef char* (*TDBRVALOADER)(TCLIST *tokens,
+                             const char *pkbuf, int pksz,
+                             const char *rowdata, int rowdatasz,
+                             const char *cname, int cnamesz, void *op, int *vsz);
+
+
+/* Get the message string corresponding to an error code.
+   `ecode' specifies the error code.
+   The return value is the message string of the error code. */
+const char *tctdberrmsg(int ecode);
+
+
+/* Create a table database object.
+   The return value is the new table database object. */
+TCTDB *tctdbnew(void);
+
+
+/* Delete a table database object.
+   `tdb' specifies the table database object.
+   If the database is not closed, it is closed implicitly.  Note that the deleted object and its
+   derivatives can not be used anymore. */
+void tctdbdel(TCTDB *tdb);
+
+
+/* Get the last happened error code of a table database object.
+   `tdb' specifies the table database object.
+   The return value is the last happened error code.
+   The following error code is defined: `TCESUCCESS' for success, `TCETHREAD' for threading
+   error, `TCEINVALID' for invalid operation, `TCENOFILE' for file not found, `TCENOPERM' for no
+   permission, `TCEMETA' for invalid meta data, `TCERHEAD' for invalid record header, `TCEOPEN'
+   for open error, `TCECLOSE' for close error, `TCETRUNC' for trunc error, `TCESYNC' for sync
+   error, `TCESTAT' for stat error, `TCESEEK' for seek error, `TCEREAD' for read error,
+   `TCEWRITE' for write error, `TCEMMAP' for mmap error, `TCELOCK' for lock error, `TCEUNLINK'
+   for unlink error, `TCERENAME' for rename error, `TCEMKDIR' for mkdir error, `TCERMDIR' for
+   rmdir error, `TCEKEEP' for existing record, `TCENOREC' for no record found, and `TCEMISC' for
+   miscellaneous error. */
+int tctdbecode(TCTDB *tdb);
+
+
+/* Set mutual exclusion control of a table database object for threading.
+   `tdb' specifies the table database object which is not opened.
+   If successful, the return value is true, else, it is false.
+   Note that the mutual exclusion control is needed if the object is shared by plural threads and
+   this function should be called before the database is opened. */
+bool tctdbsetmutex(TCTDB *tdb);
+
+
+/* Set the tuning parameters of a table database object.
+   `tdb' specifies the table database object which is not opened.
+   `bnum' specifies the number of elements of the bucket array.  If it is not more than 0, the
+   default value is specified.  The default value is 131071.  Suggested size of the bucket array
+   is about from 0.5 to 4 times of the number of all records to be stored.
+   `apow' specifies the size of record alignment by power of 2.  If it is negative, the default
+   value is specified.  The default value is 4 standing for 2^4=16.
+   `fpow' specifies the maximum number of elements of the free block pool by power of 2.  If it
+   is negative, the default value is specified.  The default value is 10 standing for 2^10=1024.
+   `opts' specifies options by bitwise-or: `TDBTLARGE' specifies that the size of the database
+   can be larger than 2GB by using 64-bit bucket array, `TDBTDEFLATE' specifies that each record
+   is compressed with Deflate encoding, `TDBTBZIP' specifies that each record is compressed with
+   BZIP2 encoding, `TDBTTCBS' specifies that each record is compressed with TCBS encoding.
+   If successful, the return value is true, else, it is false.
+   Note that the tuning parameters should be set before the database is opened. */
+bool tctdbtune(TCTDB *tdb, int64_t bnum, int8_t apow, int8_t fpow, uint8_t opts);
+
+
+/* Set the caching parameters of a table database object.
+   `tdb' specifies the table database object which is not opened.
+   `rcnum' specifies the maximum number of records to be cached.  If it is not more than 0, the
+   record cache is disabled.  It is disabled by default.
+   `lcnum' specifies the maximum number of leaf nodes to be cached.  If it is not more than 0,
+   the default value is specified.  The default value is 4096.
+   `ncnum' specifies the maximum number of non-leaf nodes to be cached.  If it is not more than 0,
+   the default value is specified.  The default value is 512.
+   If successful, the return value is true, else, it is false.
+   Note that the caching parameters should be set before the database is opened.  Leaf nodes and
+   non-leaf nodes are used in column indices. */
+bool tctdbsetcache(TCTDB *tdb, int32_t rcnum, int32_t lcnum, int32_t ncnum);
+
+
+/* Set the size of the extra mapped memory of a table database object.
+   `tdb' specifies the table database object which is not opened.
+   `xmsiz' specifies the size of the extra mapped memory.  If it is not more than 0, the extra
+   mapped memory is disabled.  The default size is 67108864.
+   If successful, the return value is true, else, it is false.
+   Note that the mapping parameters should be set before the database is opened. */
+bool tctdbsetxmsiz(TCTDB *tdb, int64_t xmsiz);
+
+
+/* Set the unit step number of auto defragmentation of a table database object.
+   `tdb' specifies the table database object which is not opened.
+   `dfunit' specifie the unit step number.  If it is not more than 0, the auto defragmentation
+   is disabled.  It is disabled by default.
+   If successful, the return value is true, else, it is false.
+   Note that the defragmentation parameters should be set before the database is opened. */
+bool tctdbsetdfunit(TCTDB *tdb, int32_t dfunit);
+
+
+/* Open a database file and connect a table database object.
+   `tdb' specifies the table database object which is not opened.
+   `path' specifies the path of the database file.
+   `omode' specifies the connection mode: `TDBOWRITER' as a writer, `TDBOREADER' as a reader.
+   If the mode is `TDBOWRITER', the following may be added by bitwise-or: `TDBOCREAT', which
+   means it creates a new database if not exist, `TDBOTRUNC', which means it creates a new
+   database regardless if one exists, `TDBOTSYNC', which means every transaction synchronizes
+   updated contents with the device.  Both of `TDBOREADER' and `TDBOWRITER' can be added to by
+   bitwise-or: `TDBONOLCK', which means it opens the database file without file locking, or
+   `TDBOLCKNB', which means locking is performed without blocking.
+   If successful, the return value is true, else, it is false. */
+bool tctdbopen(TCTDB *tdb, const char *path, int omode);
+
+
+/* Close a table database object.
+   `tdb' specifies the table database object.
+   If successful, the return value is true, else, it is false.
+   Update of a database is assured to be written when the database is closed.  If a writer opens
+   a database but does not close it appropriately, the database will be broken. */
+bool tctdbclose(TCTDB *tdb);
+
+
+/* Store a record into a table database object.
+   `tdb' specifies the table database object connected as a writer.
+   `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.
+   If a record with the same key exists in the database, it is overwritten. */
+bool tctdbput(TCTDB *tdb, const void *pkbuf, int pksiz, TCMAP *cols);
+
+
+/* Store a string record into a table database object with a zero separated column string.
+   `tdb' specifies the table database object connected as a writer.
+   `pkbuf' specifies the pointer to the region of the primary key.
+   `pksiz' specifies the size of the region of the primary key.
+   `cbuf' specifies the pointer to the region of the zero separated column string where the name
+   and the value of each column are situated one after the other.
+   `csiz' specifies the size of the region of the column string.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the database, it is overwritten. */
+bool tctdbput2(TCTDB *tdb, const void *pkbuf, int pksiz, const void *cbuf, int csiz);
+
+
+/* Store a string record into a table database object with a tab separated column string.
+   `tdb' specifies the table database object connected as a writer.
+   `pkstr' specifies the string of the primary key.
+   `cstr' specifies the string of the the tab separated column string where the name and the
+   value of each column are situated one after the other.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the database, it is overwritten. */
+bool tctdbput3(TCTDB *tdb, const char *pkstr, const char *cstr);
+
+
+/* Store a new record into a table database object.
+   `tdb' specifies the table database object connected as a writer.
+   `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.
+   If a record with the same key exists in the database, this function has no effect. */
+bool tctdbputkeep(TCTDB *tdb, const void *pkbuf, int pksiz, TCMAP *cols);
+
+
+/* Store a new string record into a table database object with a zero separated column string.
+   `tdb' specifies the table database object connected as a writer.
+   `pkbuf' specifies the pointer to the region of the primary key.
+   `pksiz' specifies the size of the region of the primary key.
+   `cbuf' specifies the pointer to the region of the zero separated column string where the name
+   and the value of each column are situated one after the other.
+   `csiz' specifies the size of the region of the column string.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the database, this function has no effect. */
+bool tctdbputkeep2(TCTDB *tdb, const void *pkbuf, int pksiz, const void *cbuf, int csiz);
+
+
+/* Store a new string record into a table database object with a tab separated column string.
+   `tdb' specifies the table database object connected as a writer.
+   `pkstr' specifies the string of the primary key.
+   `cstr' specifies the string of the the tab separated column string where the name and the
+   value of each column are situated one after the other.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the database, this function has no effect. */
+bool tctdbputkeep3(TCTDB *tdb, const char *pkstr, const char *cstr);
+
+
+/* Concatenate columns of the existing record in a table database object.
+   `tdb' specifies the table database object connected as a writer.
+   `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.
+   If there is no corresponding record, a new record is created. */
+bool tctdbputcat(TCTDB *tdb, const void *pkbuf, int pksiz, TCMAP *cols);
+
+
+/* Concatenate columns in a table database object with a zero separated column string.
+   `tdb' specifies the table database object connected as a writer.
+   `pkbuf' specifies the pointer to the region of the primary key.
+   `pksiz' specifies the size of the region of the primary key.
+   `cbuf' specifies the pointer to the region of the zero separated column string where the name
+   and the value of each column are situated one after the other.
+   `csiz' specifies the size of the region of the column string.
+   If successful, the return value is true, else, it is false.
+   If there is no corresponding record, a new record is created. */
+bool tctdbputcat2(TCTDB *tdb, const void *pkbuf, int pksiz, const void *cbuf, int csiz);
+
+
+/* Concatenate columns in a table database object with with a tab separated column string.
+   `tdb' specifies the table database object connected as a writer.
+   `pkstr' specifies the string of the primary key.
+   `cstr' specifies the string of the the tab separated column string where the name and the
+   value of each column are situated one after the other.
+   If successful, the return value is true, else, it is false.
+   If there is no corresponding record, a new record is created. */
+bool tctdbputcat3(TCTDB *tdb, const char *pkstr, const char *cstr);
+
+
+/* Remove a record of a table database object.
+   `tdb' specifies the table database object connected as a writer.
+   `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. */
+bool tctdbout(TCTDB *tdb, const void *pkbuf, int pksiz);
+
+
+/* Remove a string record of a table database object.
+   `tdb' specifies the table database object connected as a writer.
+   `pkstr' specifies the string of the primary key.
+   If successful, the return value is true, else, it is false. */
+bool tctdbout2(TCTDB *tdb, const char *pkstr);
+
+
+/* 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.
+   `NULL' is returned if no record corresponds.
+   Because the object of the return value is created with the function `tcmapnew', it should be
+   deleted with the function `tcmapdel' when it is no longer in use. */
+TCMAP *tctdbget(TCTDB *tdb, const void *pkbuf, int pksiz);
+
+
+/* Retrieve a record in a table database object as a zero separated column string.
+   `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.
+   `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 column string of the
+   corresponding record.  `NULL' is returned if no record corresponds.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when
+   it is no longer in use. */
+char *tctdbget2(TCTDB *tdb, const void *pkbuf, int pksiz, int *sp);
+
+
+/* Retrieve a string record in a table database object as a tab separated column string.
+   `tdb' specifies the table database object.
+   `pkstr' specifies the string of the primary key.
+   If successful, the return value is the tab separated column string of the corresponding
+   record.  `NULL' is returned if no record corresponds.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+char *tctdbget3(TCTDB *tdb, const char *pkstr);
+
+
+/* Get the size of the value of a record in a table database object.
+   `tdb' specifies the table database object.
+   `kbuf' specifies the pointer to the region of the primary key.
+   `ksiz' specifies the size of the region of the primary key.
+   If successful, the return value is the size of the value of the corresponding record, else,
+   it is -1. */
+int tctdbvsiz(TCTDB *tdb, const void *pkbuf, int pksiz);
+
+
+/* Get the size of the value of a string record in a table database object.
+   `tdb' specifies the table database object.
+   `kstr' specifies the string of the primary key.
+   If successful, the return value is the size of the value of the corresponding record, else,
+   it is -1. */
+int tctdbvsiz2(TCTDB *tdb, const char *pkstr);
+
+
+/* Initialize the iterator of a table database object.
+   `tdb' specifies the table database object.
+   If successful, the return value is true, else, it is false.
+   The iterator is used in order to access the primary key of every record stored in a
+   database. */
+bool tctdbiterinit(TCTDB *tdb);
+
+
+/* Get the next primary key of the iterator of a table database object.
+   `tdb' specifies the table 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 primary key, else, it
+   is `NULL'.  `NULL' is returned when no record is to be get out of the iterator.
+   Because an additional zero code is appended at the end of the region of the return value, the
+   return value can be treated as a character string.  Because the region of the return value is
+   allocated with the `malloc' call, it should be released with the `free' call when it is no
+   longer in use.  It is possible to access every record by iteration of calling this function.
+   It is allowed to update or remove records whose keys are fetched while the iteration.
+   However, it is not assured if updating the database is occurred while the iteration.  Besides,
+   the order of this traversal access method is arbitrary, so it is not assured that the order of
+   storing matches the one of the traversal access. */
+void *tctdbiternext(TCTDB *tdb, int *sp);
+
+
+/* Get the next primary key string of the iterator of a table database object.
+   `tdb' specifies the table database object.
+   If successful, the return value is the string of the next primary key, else, it is `NULL'.
+   `NULL' is returned when no record is to be get out of the iterator.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use.  It is possible to access every
+   record by iteration of calling this function.  However, it is not assured if updating the
+   database is occurred while the iteration.  Besides, the order of this traversal access method
+   is arbitrary, so it is not assured that the order of storing matches the one of the traversal
+   access. */
+char *tctdbiternext2(TCTDB *tdb);
+
+
+/* Get the columns of the next record of the iterator of a table database object.
+   `tdb' specifies the table database object.
+   If successful, the return value is a map object of the columns of the next record, else, it is
+   `NULL'.  `NULL' is returned when no record is to be get out of the iterator.  The primary key
+   is added into the map as a column of an empty string key.
+   Because the object of the return value is created with the function `tcmapnew', it should be
+   deleted with the function `tcmapdel' when it is no longer in use.  It is possible to access
+   every record by iteration of calling this function.  However, it is not assured if updating
+   the database is occurred while the iteration.  Besides, the order of this traversal access
+   method is arbitrary, so it is not assured that the order of storing matches the one of the
+   traversal access. */
+TCMAP *tctdbiternext3(TCTDB *tdb);
+
+
+/* Get forward matching primary keys in a table database object.
+   `tdb' specifies the table 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.  This function does never fail.
+   It returns an empty list even if no key corresponds.
+   Because the object of the return value is created with the function `tclistnew', it should be
+   deleted with the function `tclistdel' when it is no longer in use.  Note that this function
+   may be very slow because every key in the database is scanned. */
+TCLIST *tctdbfwmkeys(TCTDB *tdb, const void *pbuf, int psiz, int max);
+
+
+/* Get forward matching string primary keys in a table database object.
+   `tdb' specifies the table database object.
+   `pstr' specifies the string 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.  This function does never fail.
+   It returns an empty list even if no key corresponds.
+   Because the object of the return value is created with the function `tclistnew', it should be
+   deleted with the function `tclistdel' when it is no longer in use.  Note that this function
+   may be very slow because every key in the database is scanned. */
+TCLIST *tctdbfwmkeys2(TCTDB *tdb, const char *pstr, int max);
+
+
+/* Add an integer to a column of a record in a table database object.
+   `tdb' specifies the table database object connected as a writer.
+   `kbuf' specifies the pointer to the region of the primary key.
+   `ksiz' 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 `INT_MIN'.
+   The additional value is stored as a decimal string value of a column whose name is "_num".
+   If no record corresponds, a new record with the additional value is stored. */
+int tctdbaddint(TCTDB *tdb, const void *pkbuf, int pksiz, int num);
+
+
+/* Add a real number to a column of a record in a table database object.
+   `tdb' specifies the table database object connected as a writer.
+   `kbuf' specifies the pointer to the region of the primary key.
+   `ksiz' 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.
+   The additional value is stored as a decimal string value of a column whose name is "_num".
+   If no record corresponds, a new record with the additional value is stored. */
+double tctdbadddouble(TCTDB *tdb, const void *pkbuf, int pksiz, double num);
+
+
+/* Synchronize updated contents of a table database object with the file and the device.
+   `tdb' specifies the table database object connected as a writer.
+   If successful, the return value is true, else, it is false.
+   This function is useful when another process connects to the same database file. */
+bool tctdbsync(TCTDB *tdb);
+
+
+/* Optimize the file of a table database object.
+   `tdb' specifies the table database object connected as a writer.
+   `bnum' specifies the number of elements of the bucket array.  If it is not more than 0, the
+   default value is specified.  The default value is two times of the number of records.
+   `apow' specifies the size of record alignment by power of 2.  If it is negative, the current
+   setting is not changed.
+   `fpow' specifies the maximum number of elements of the free block pool by power of 2.  If it
+   is negative, the current setting is not changed.
+   `opts' specifies options by bitwise-or: `BDBTLARGE' specifies that the size of the database
+   can be larger than 2GB by using 64-bit bucket array, `BDBTDEFLATE' specifies that each record
+   is compressed with Deflate encoding, `BDBTBZIP' specifies that each record is compressed with
+   BZIP2 encoding, `BDBTTCBS' specifies that each record is compressed with TCBS encoding.  If it
+   is `UINT8_MAX', the current setting is not changed.
+   If successful, the return value is true, else, it is false.
+   This function is useful to reduce the size of the database file with data fragmentation by
+   successive updating. */
+bool tctdboptimize(TCTDB *tdb, int64_t bnum, int8_t apow, int8_t fpow, uint8_t opts);
+
+
+/* Remove all records of a table database object.
+   `tdb' specifies the table database object connected as a writer.
+   If successful, the return value is true, else, it is false. */
+bool tctdbvanish(TCTDB *tdb);
+
+
+/* Copy the database file of a table database object.
+   `tdb' specifies the table database object.
+   `path' specifies the path of the destination file.  If it begins with `@', the trailing
+   substring is executed as a command line.
+   If successful, the return value is true, else, it is false.  False is returned if the executed
+   command returns non-zero code.
+   The database file is assured to be kept synchronized and not modified while the copying or
+   executing operation is in progress.  So, this function is useful to create a backup file of
+   the database file. */
+bool tctdbcopy(TCTDB *tdb, const char *path);
+
+
+/* Begin the transaction of a table database object.
+   `tdb' specifies the table database object connected as a writer.
+   If successful, the return value is true, else, it is false.
+   The database is locked by the thread while the transaction so that only one transaction can be
+   activated with a database object at the same time.  Thus, the serializable isolation level is
+   assumed if every database operation is performed in the transaction.  Because all pages are
+   cached on memory while the transaction, the amount of referred records is limited by the
+   memory capacity.  If the database is closed during transaction, the transaction is aborted
+   implicitly. */
+bool tctdbtranbegin(TCTDB *tdb);
+
+
+/* Commit the transaction of a table database object.
+   `tdb' specifies the table database object connected as a writer.
+   If successful, the return value is true, else, it is false.
+   Update in the transaction is fixed when it is committed successfully. */
+bool tctdbtrancommit(TCTDB *tdb);
+
+
+/* Abort the transaction of a table database object.
+   `tdb' specifies the table database object connected as a writer.
+   If successful, the return value is true, else, it is false.
+   Update in the transaction is discarded when it is aborted.  The state of the database is
+   rollbacked to before transaction. */
+bool tctdbtranabort(TCTDB *tdb);
+
+
+/* Get the file path of a table database object.
+   `tdb' specifies the table database object.
+   The return value is the path of the database file or `NULL' if the object does not connect to
+   any database file. */
+const char *tctdbpath(TCTDB *tdb);
+
+
+/* Get the number of records ccccof a table database object.
+   `tdb' specifies the table database object.
+   The return value is the number of records or 0 if the object does not connect to any database
+   file. */
+uint64_t tctdbrnum(TCTDB *tdb);
+
+
+/* Get the size of the database file of a table database object.
+   `tdb' specifies the table database object.
+   The return value is the size of the database file or 0 if the object does not connect to any
+   database file. */
+uint64_t tctdbfsiz(TCTDB *tdb);
+
+
+/* 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.  If the name of an existing index is specified, the
+   index is rebuilt.  An empty string means the primary key.
+   `type' specifies the index type: `TDBITLEXICAL' for lexical string, `TDBITDECIMAL' for decimal
+   string, `TDBITTOKEN' for token inverted index, `TDBITQGRAM' for q-gram inverted index.  If it
+   is `TDBITOPT', the index is optimized.  If it is `TDBITVOID', the index is removed.  If
+   `TDBITKEEP' is added by bitwise-or and the index exists, this function merely returns failure.
+   If successful, the return value is true, else, it is false.
+   Note that the setting indices should be set after the database is opened. */
+bool tctdbsetindex(TCTDB *tdb, const char *name, int type);
+
+bool tctdbsetindexrldr(TCTDB *tdb, const char *name, int type, TDBRVALOADER rvldr, void* rvldrop);
+
+
+/* Generate a unique ID number of a table database object.
+   `tdb' specifies the table database object connected as a writer.
+   The return value is the new unique ID number or -1 on failure. */
+int64_t tctdbgenuid(TCTDB *tdb);
+
+
+/* Create a query object.
+   `tdb' specifies the table database object.
+   The return value is the new query object. */
+TDBQRY *tctdbqrynew(TCTDB *tdb);
+
+
+/* Delete a query object.
+   `qry' specifies the query object. */
+void tctdbqrydel(TDBQRY *qry);
+
+
+/* Add a narrowing condition to a query object.
+   `qry' specifies the query object.
+   `name' specifies the name of a column.  An empty string means the primary key.
+   `op' specifies an operation type: `TDBQCSTREQ' for string which is equal to the expression,
+   `TDBQCSTRINC' for string which is included in the expression, `TDBQCSTRBW' for string which
+   begins with the expression, `TDBQCSTREW' for string which ends with the expression,
+   `TDBQCSTRAND' for string which includes all tokens in the expression, `TDBQCSTROR' for string
+   which includes at least one token in the expression, `TDBQCSTROREQ' for string which is equal
+   to at least one token in the expression, `TDBQCSTRRX' for string which matches regular
+   expressions of the expression, `TDBQCNUMEQ' for number which is equal to the expression,
+   `TDBQCNUMGT' for number which is greater than the expression, `TDBQCNUMGE' for number which is
+   greater than or equal to the expression, `TDBQCNUMLT' for number which is less than the
+   expression, `TDBQCNUMLE' for number which is less than or equal to the expression, `TDBQCNUMBT'
+   for number which is between two tokens of the expression, `TDBQCNUMOREQ' for number which is
+   equal to at least one token in the expression, `TDBQCFTSPH' for full-text search with the
+   phrase of the expression, `TDBQCFTSAND' for full-text search with all tokens in the expression,
+   `TDBQCFTSOR' for full-text search with at least one token in the expression, `TDBQCFTSEX' for
+   full-text search with the compound expression.  All operations can be flagged by bitwise-or:
+   `TDBQCNEGATE' for negation, `TDBQCNOIDX' for using no index.
+   `expr' specifies an operand exression. */
+void tctdbqryaddcond(TDBQRY *qry, const char *name, int op, const char *expr);
+
+
+/* Set the order of a query object.
+   `qry' specifies the query object.
+   `name' specifies the name of a column.  An empty string means the primary key.
+   `type' specifies the order type: `TDBQOSTRASC' for string ascending, `TDBQOSTRDESC' for
+   string descending, `TDBQONUMASC' for number ascending, `TDBQONUMDESC' for number descending. */
+void tctdbqrysetorder(TDBQRY *qry, const char *name, int type);
+
+
+/* Set the limit number of records of the result of a query object.
+   `qry' specifies the query object.
+   `max' specifies the maximum number of records of the result.  If it is negative, no limit is
+   specified.
+   `skip' specifies the number of skipped records of the result.  If it is not more than 0, no
+   record is skipped. */
+void tctdbqrysetlimit(TDBQRY *qry, int max, int skip);
+
+
+/* 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.  This
+   function does never fail.  It returns an empty list even if no record corresponds.
+   Because the object of the return value is created with the function `tclistnew', it should
+   be deleted with the function `tclistdel' when it is no longer in use. */
+TCLIST *tctdbqrysearch(TDBQRY *qry);
+
+
+/* Remove each record corresponding to a query object.
+   `qry' specifies the query object of the database connected as a writer.
+   If successful, the return value is true, else, it is false. */
+bool tctdbqrysearchout(TDBQRY *qry);
+
+
+/* Process each record corresponding to a query object.
+   `qry' specifies the query object of the database connected as a writer.
+   `proc' specifies the pointer to the iterator function called for each record.  It receives
+   four parameters.  The first parameter is the pointer to the region of the primary key.  The
+   second parameter is the size of the region of the primary key.  The third parameter is a map
+   object containing columns.  The fourth parameter is the pointer to the optional opaque object.
+   It returns flags of the post treatment by bitwise-or: `TDBQPPUT' to modify the record,
+   `TDBQPOUT' to remove the record, `TDBQPSTOP' to stop the iteration.
+   `op' specifies an arbitrary pointer to be given as a parameter of the iterator function.  If
+   it is not needed, `NULL' can be specified.
+   If successful, the return value is true, else, it is false. */
+bool tctdbqryproc(TDBQRY *qry, TDBQRYPROC proc, void *op);
+
+
+/* Get the hint string of a query object.
+   `qry' specifies the query object.
+   The return value is the hint string.
+   This function should be called after the query execution by `tctdbqrysearch' and so on.  The
+   region of the return value is overwritten when this function is called again. */
+const char *tctdbqryhint(TDBQRY *qry);
+
+
+/* Retrieve records with multiple query objects and get the set of the result.
+   `qrys' specifies an array of the query objects.
+   `num' specifies the number of elements of the array.
+   `type' specifies a set operation type: `TDBMSUNION' for the union set, `TDBMSISECT' for the
+   intersection set, `TDBMSDIFF' for the difference set.
+   The return value is a list object of the primary keys of the corresponding records.  This
+   function does never fail.  It returns an empty list even if no record corresponds.
+   If the first query object has the order setting, the result array is sorted by the order.
+   Because the object of the return value is created with the function `tclistnew', it should be
+   deleted with the function `tclistdel' when it is no longer in use. */
+TCLIST *tctdbmetasearch(TDBQRY **qrys, int num, int type);
+
+
+
+/*************************************************************************************************
+ * features for experts
+ *************************************************************************************************/
+
+
+/* Set the error code of a table database object.
+   `tdb' specifies the table database object.
+   `ecode' specifies the error code.
+   `file' specifies the file name of the code.
+   `line' specifies the line number of the code.
+   `func' specifies the function name of the code. */
+void tctdbsetecode(TCTDB *tdb, int ecode, const char *filename, int line, const char *func);
+
+
+/* Set the file descriptor for debugging output.
+   `tdb' specifies the table database object.
+   `fd' specifies the file descriptor for debugging output. */
+void tctdbsetdbgfd(TCTDB *tdb, int fd);
+
+
+/* Get the file descriptor for debugging output.
+   `tdb' specifies the table database object.
+   The return value is the file descriptor for debugging output. */
+int tctdbdbgfd(TCTDB *tdb);
+
+
+/* Check whether mutual exclusion control is set to a table database object.
+   `tdb' specifies the table database object.
+   If mutual exclusion control is set, it is true, else it is false. */
+bool tctdbhasmutex(TCTDB *tdb);
+
+
+/* Synchronize updating contents on memory of a table database object.
+   `tdb' specifies the table database object connected as a writer.
+   `phys' specifies whether to synchronize physically.
+   If successful, the return value is true, else, it is false. */
+bool tctdbmemsync(TCTDB *tdb, bool phys);
+
+
+/* Get the number of elements of the bucket array of a table database object.
+   `tdb' specifies the table database object.
+   The return value is the number of elements of the bucket array or 0 if the object does not
+   connect to any database file. */
+uint64_t tctdbbnum(TCTDB *tdb);
+
+
+/* Get the record alignment of a table database object.
+   `tdb' specifies the table database object.
+   The return value is the record alignment or 0 if the object does not connect to any database
+   file. */
+uint32_t tctdbalign(TCTDB *tdb);
+
+
+/* Get the maximum number of the free block pool of a table database object.
+   `tdb' specifies the table database object.
+   The return value is the maximum number of the free block pool or 0 if the object does not
+   connect to any database file. */
+uint32_t tctdbfbpmax(TCTDB *tdb);
+
+
+/* Get the inode number of the database file of a table database object.
+   `tdb' specifies the table database object.
+   The return value is the inode number of the database file or 0 if the object does not connect
+   to any database file. */
+uint64_t tctdbinode(TCTDB *tdb);
+
+
+/* Get the modification time of the database file of a table database object.
+   `tdb' specifies the table database object.
+   The return value is the inode number of the database file or 0 if the object does not connect
+   to any database file. */
+time_t tctdbmtime(TCTDB *tdb);
+
+
+/* Get the additional flags of a table database object.
+   `tdb' specifies the table database object.
+   The return value is the additional flags. */
+uint8_t tctdbflags(TCTDB *tdb);
+
+
+/* Get the options of a table database object.
+   `tdb' specifies the table database object.
+   The return value is the options. */
+uint8_t tctdbopts(TCTDB *tdb);
+
+
+/* Get the pointer to the opaque field of a table database object.
+   `tdb' specifies the table database object.
+   The return value is the pointer to the opaque field whose size is 128 bytes. */
+char *tctdbopaque(TCTDB *tdb);
+
+
+/* Get the number of used elements of the bucket array of a table database object.
+   `tdb' specifies the table database object.
+   The return value is the number of used elements of the bucket array or 0 if the object does not
+   connect to any database file. */
+uint64_t tctdbbnumused(TCTDB *tdb);
+
+
+/* Get the number of column indices of a table database object.
+   `tdb' specifies the table database object.
+   The return value is the number of column indices or 0 if the object does not connect to any
+   database file. */
+int tctdbinum(TCTDB *tdb);
+
+
+/* Get the seed of unique ID unumbers of a table database object.
+   `tdb' specifies the table database object.
+   The return value is the seed of unique ID numbers or -1 on failure. */
+int64_t tctdbuidseed(TCTDB *tdb);
+
+
+/* Set the seed of unique ID unumbers of a table database object.
+   `tdb' specifies the table database object connected as a writer.
+   If successful, the return value is true, else, it is false. */
+bool tctdbsetuidseed(TCTDB *tdb, int64_t seed);
+
+
+/* Set the parameters of the inverted cache of a table database object.
+   `tdb' specifies the table database object.
+   `iccmax' specifies the maximum size.  If it is not more than 0, the default value is
+   specified.  The default value is 67108864.
+   `iccsync' specifies synchronization ratio.  If it is not more than 0, the default value is
+   specified.  The default value is 0.01.
+   If successful, the return value is true, else, it is false.
+   Note that the caching parameters should be set before the database is opened. */
+bool tctdbsetinvcache(TCTDB *tdb, int64_t iccmax, double iccsync);
+
+
+/* Set the custom codec functions of a table database object.
+   `tdb' specifies the table database object.
+   `enc' specifies the pointer to the custom encoding function.  It receives four parameters.
+   The first parameter is the pointer to the region.  The second parameter is the size of the
+   region.  The third parameter is the pointer to the variable into which the size of the region
+   of the return value is assigned.  The fourth parameter is the pointer to the optional opaque
+   object.  It returns the pointer to the result object allocated with `malloc' call if
+   successful, else, it returns `NULL'.
+   `encop' specifies an arbitrary pointer to be given as a parameter of the encoding function.
+   If it is not needed, `NULL' can be specified.
+   `dec' specifies the pointer to the custom decoding function.
+   `decop' specifies an arbitrary pointer to be given as a parameter of the decoding function.
+   If it is not needed, `NULL' can be specified.
+   If successful, the return value is true, else, it is false.
+   Note that the custom codec functions should be set before the database is opened and should be
+   set every time the database is being opened. */
+bool tctdbsetcodecfunc(TCTDB *tdb, TCCODEC enc, void *encop, TCCODEC dec, void *decop);
+
+
+/* Get the unit step number of auto defragmentation of a table database object.
+   `tdb' specifies the table database object.
+   The return value is the unit step number of auto defragmentation. */
+uint32_t tctdbdfunit(TCTDB *tdb);
+
+
+/* Perform dynamic defragmentation of a table database object.
+   `tdb' specifies the table database object connected as a writer.
+   `step' specifie the number of steps.  If it is not more than 0, the whole file is defragmented
+   gradually without keeping a continuous lock.
+   If successful, the return value is true, else, it is false. */
+bool tctdbdefrag(TCTDB *tdb, int64_t step);
+
+
+/* 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. */
+bool tctdbcacheclear(TCTDB *tdb);
+
+
+/* Store a record into a table database object with a duplication handler.
+   `tdb' specifies the table database object connected as a writer.
+   `pkbuf' specifies the pointer to the region of the primary key.
+   `pksiz' specifies the size of the region of the primary key.
+   `cbuf' specifies the pointer to the region of the zero separated column string where the name
+   and the value of each column are situated one after the other.  `NULL' means that record
+   addition is ommited if there is no corresponding record.
+   `csiz' specifies the size of the region of the column string.
+   `proc' specifies the pointer to the callback function to process duplication.  It receives
+   four parameters.  The first parameter is the pointer to the region of the value.  The second
+   parameter is the size of the region of the value.  The third parameter is the pointer to the
+   variable into which the size of the region of the return value is assigned.  The fourth
+   parameter is the pointer to the optional opaque object.  It returns the pointer to the result
+   object allocated with `malloc'.  It is released by the caller.  If it is `NULL', the record is
+   not modified.  If it is `(void *)-1', the record is removed.
+   `op' specifies an arbitrary pointer to be given as a parameter of the callback function.  If
+   it is not needed, `NULL' can be specified.
+   If successful, the return value is true, else, it is false.
+   Note that the callback function can not perform any database operation because the function
+   is called in the critical section guarded by the same locks of database operations. */
+bool tctdbputproc(TCTDB *tdb, const void *pkbuf, int pksiz, const void *cbuf, int csiz,
+        TCPDPROC proc, void *op);
+
+
+/* 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.  `NULL' is returned if no record corresponds or there is no column.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when
+   it is no longer in use. */
+char *tctdbget4(TCTDB *tdb, const void *pkbuf, int pksiz, const void *nbuf, int nsiz, int *sp);
+
+
+/* Move the iterator to the record corresponding a key 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.  False is returned if there is
+   no record corresponding the condition. */
+bool tctdbiterinit2(TCTDB *tdb, const void *pkbuf, int pksiz);
+
+
+/* Move the iterator to the record corresponding a key string of a table database object.
+   `tdb' specifies the table database object.
+   `kstr' specifies the string of the primary key.
+   If successful, the return value is true, else, it is false.  False is returned if there is
+   no record corresponding the condition. */
+bool tctdbiterinit3(TCTDB *tdb, const char *kstr);
+
+
+/* Process each record atomically of a table database object.
+   `tdb' specifies the table database object.
+   `iter' specifies the pointer to the iterator function called for each record.  It receives
+   five parameters.  The first parameter is the pointer to the region of the key.  The second
+   parameter is the size of the region of the key.  The third parameter is the pointer to the
+   region of the value.  The fourth parameter is the size of the region of the value.  The fifth
+   parameter is the pointer to the optional opaque object.  It returns true to continue iteration
+   or false to stop iteration.
+   `op' specifies an arbitrary pointer to be given as a parameter of the iterator function.  If
+   it is not needed, `NULL' can be specified.
+   If successful, the return value is true, else, it is false.
+   Note that the callback function can not perform any database operation because the function
+   is called in the critical section guarded by the same locks of database operations. */
+bool tctdbforeach(TCTDB *tdb, TCITER iter, void *op);
+
+
+/* Process each record corresponding to a query object with non-atomic fashion.
+   `qry' specifies the query object of the database connected as a writer.
+   `proc' specifies the pointer to the iterator function called for each record.  It receives
+   four parameters.  The first parameter is the pointer to the region of the primary key.  The
+   second parameter is the size of the region of the primary key.  The third parameter is a map
+   object containing columns.  The fourth parameter is the pointer to the optional opaque object.
+   It returns flags of the post treatment by bitwise-or: `TDBQPPUT' to modify the record,
+   `TDBQPOUT' to remove the record, `TDBQPSTOP' to stop the iteration.
+   `op' specifies an arbitrary pointer to be given as a parameter of the iterator function.  If
+   it is not needed, `NULL' can be specified.
+   If successful, the return value is true, else, it is false. */
+bool tctdbqryproc2(TDBQRY *qry, TDBQRYPROC proc, void *op);
+
+
+/* Remove each record corresponding to a query object with non-atomic fashion.
+   `qry' specifies the query object of the database connected as a writer.
+   If successful, the return value is true, else, it is false. */
+bool tctdbqrysearchout2(TDBQRY *qry);
+
+
+/* Convert a string into the index type number.
+   `str' specifies a string.
+   The return value is the index type number or -1 on failure. */
+int tctdbstrtoindextype(const char *str);
+
+
+/* Convert a string into the meta search type number.
+   `str' specifies a string.
+   The return value is the meta search type number or -1 on failure. */
+int tctdbstrtometasearcytype(const char *str);
+
+
+/* Get the count of corresponding records of a query object.
+   `qry' specifies the query object.
+   The return value is the count of corresponding records. */
+int tctdbqrycount(TDBQRY *qry);
+
+
+/* Generate keyword-in-context strings from a query object.
+   `qry' specifies the query object.
+   `cols' specifies a map object containing columns.
+   `name' specifies the name of a column.  If it is `NULL', the first column of the query is
+   specified.
+   `width' specifies the width of strings picked up around each keyword.
+   `opts' specifies options by bitwise-or: `TCKWMUTAB' specifies that each keyword is marked up
+   between two tab characters, `TCKWMUCTRL' specifies that each keyword is marked up by the STX
+   (0x02) code and the ETX (0x03) code, `TCKWMUBRCT' specifies that each keyword is marked up by
+   the two square brackets, `TCKWNOOVER' specifies that each context does not overlap,
+   `TCKWPULEAD' specifies that the lead string is picked up forcibly.
+   The return value is the list object whose elements are strings around keywords.
+   Because the object of the return value is created with the function `tclistnew', it should
+   be deleted with the function `tclistdel' when it is no longer in use. */
+TCLIST *tctdbqrykwic(TDBQRY *qry, TCMAP *cols, const char *name, int width, int opts);
+
+
+/* Convert a string into the query operation number.
+   `str' specifies a string.
+   The return value is the query operation number or -1 on failure. */
+int tctdbqrystrtocondop(const char *str);
+
+
+/* Convert a string into the query order type number.
+   `str' specifies a string.
+   The return value is the query order type or -1 on failure. */
+int tctdbqrystrtoordertype(const char *str);
+
+
+/* Convert a string into the set operation type number.
+   `str' specifies a string.
+   The return value is the set operation type or -1 on failure. */
+int tctdbmetastrtosettype(const char *str);
+
+
+/* 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. */
+bool tctdbidxput(TCTDB *tdb, const void *pkbuf, int pksiz, TCMAP *cols);
+bool tctdbidxput2(TCTDB *tdb, const void *pkbuf, int pksiz, TCMAP *cols);
+
+/* 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. */
+bool tctdbidxout(TCTDB *tdb, const void *pkbuf, int pksiz, TCMAP *cols);
+bool tctdbidxout2(TCTDB *tdb, const void *pkbuf, int pksiz, TCMAP *cols);
+
+/* 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. */
+bool tctdbqryidxcurjumpnum(BDBCUR *cur, const char *kbuf, int ksiz, bool first);
+
+/* Convert a string to a real number.
+   `str' specifies the string.
+   The return value is the real number. */
+long double tctdbatof(const char *str);
+
+double tctdbatof2(const char *str);
+
+/* Convert a string to a integer number.
+   `str' specifies the string.
+   The return value is the int64_t number. */
+int64_t tctdbatoi(const char *str);
+
+/* 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. */
+int tdbcmppkeynumasc(const TCLISTDATUM *a, const TCLISTDATUM *b);
+
+
+/* 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. */
+int tdbcmppkeynumdesc(const TCLISTDATUM *a, const TCLISTDATUM *b);
+
+
+/* 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. */
+TCMAP *tctdbidxgetbytokens(TCTDB *tdb, const TDBIDX *idx, const TCLIST *tokens, int op, TCXSTR *hint);
+
+bool tctdbtranbeginimpl(TCTDB *tdb);
+bool tctdbtrancommitimpl(TCTDB *tdb);
+bool tctdbtranabortimpl(TCTDB *tdb);
+
+#define TDBDEFBNUM     131071            // default bucket number
+
+
+__TCTDB_CLINKAGEEND
+#endif                                   /* duplication check */
+
+
+/* END OF FILE */
diff --git a/tcejdb/tctmgr.c b/tcejdb/tctmgr.c
new file mode 100644 (file)
index 0000000..bd7b622
--- /dev/null
@@ -0,0 +1,1241 @@
+/*************************************************************************************************
+ * The command line utility of the table database API
+ *                                                               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 <tctdb.h>
+#include "myconf.h"
+
+
+/* global variables */
+const char *g_progname;                  // program name
+int g_dbgfd;                             // debugging output
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+static void usage(void);
+static void printerr(TCTDB *tdb);
+static int printdata(const char *ptr, int size, bool px);
+static char *mygetline(FILE *ifp);
+static int runcreate(int argc, char **argv);
+static int runinform(int argc, char **argv);
+static int runput(int argc, char **argv);
+static int runout(int argc, char **argv);
+static int runget(int argc, char **argv);
+static int runlist(int argc, char **argv);
+static int runsearch(int argc, char **argv);
+static int runoptimize(int argc, char **argv);
+static int runsetindex(int argc, char **argv);
+static int runimporttsv(int argc, char **argv);
+static int runversion(int argc, char **argv);
+static int proccreate(const char *path, int bnum, int apow, int fpow, int opts);
+static int procinform(const char *path, int omode);
+static int procput(const char *path, const char *pkbuf, int pksiz, TCMAP *cols,
+                   int omode, int dmode);
+static int procout(const char *path, const char *pkbuf, int pksiz, int omode);
+static int procget(const char *path, const char *pkbuf, int pksiz, int omode, bool px, bool pz);
+static int proclist(const char *path, int omode, int max, bool pv, bool px, const char *fmstr);
+static int procsearch(const char *path, TCLIST *conds, const char *oname, const char *otype,
+                      int omode, int max, int skip, bool pv, bool px, bool kw, bool ph, int bt,
+                      bool rm, const char *mtype);
+static int procoptimize(const char *path, int bnum, int apow, int fpow, int opts, int omode,
+                        bool df);
+static int procsetindex(const char *path, const char *name, int omode, int type);
+static int procimporttsv(const char *path, const char *file, int omode, bool sc);
+static int procversion(void);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  g_progname = argv[0];
+  g_dbgfd = -1;
+  const char *ebuf = getenv("TCDBGFD");
+  if(ebuf) g_dbgfd = tcatoix(ebuf);
+  if(argc < 2) usage();
+  int rv = 0;
+  if(!strcmp(argv[1], "create")){
+    rv = runcreate(argc, argv);
+  } else if(!strcmp(argv[1], "inform")){
+    rv = runinform(argc, argv);
+  } else if(!strcmp(argv[1], "put")){
+    rv = runput(argc, argv);
+  } else if(!strcmp(argv[1], "out")){
+    rv = runout(argc, argv);
+  } else if(!strcmp(argv[1], "get")){
+    rv = runget(argc, argv);
+  } else if(!strcmp(argv[1], "list")){
+    rv = runlist(argc, argv);
+  } else if(!strcmp(argv[1], "search")){
+    rv = runsearch(argc, argv);
+  } else if(!strcmp(argv[1], "optimize")){
+    rv = runoptimize(argc, argv);
+  } else if(!strcmp(argv[1], "setindex")){
+    rv = runsetindex(argc, argv);
+  } else if(!strcmp(argv[1], "importtsv")){
+    rv = runimporttsv(argc, argv);
+  } else if(!strcmp(argv[1], "version") || !strcmp(argv[1], "--version")){
+    rv = runversion(argc, argv);
+  } else {
+    usage();
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+static void usage(void){
+  fprintf(stderr, "%s: the command line utility of the table database API\n", g_progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s create [-tl] [-td|-tb|-tt|-tx] path [bnum [apow [fpow]]]\n", g_progname);
+  fprintf(stderr, "  %s inform [-nl|-nb] path\n", g_progname);
+  fprintf(stderr, "  %s put [-nl|-nb] [-sx] [-dk|-dc|-dai|-dad] path pkey [cols...]\n",
+          g_progname);
+  fprintf(stderr, "  %s out [-nl|-nb] [-sx] path pkey\n", g_progname);
+  fprintf(stderr, "  %s get [-nl|-nb] [-sx] [-px] [-pz] path pkey\n", g_progname);
+  fprintf(stderr, "  %s list [-nl|-nb] [-m num] [-pv] [-px] [-fm str] path\n", g_progname);
+  fprintf(stderr, "  %s search [-nl|-nb] [-ord name type] [-m num] [-sk num] [-kw] [-pv] [-px]"
+          " [-ph] [-bt num] [-rm] [-ms type] path [name op expr ...]\n", g_progname);
+  fprintf(stderr, "  %s optimize [-tl] [-td|-tb|-tt|-tx] [-tz] [-nl|-nb] [-df]"
+          " path [bnum [apow [fpow]]]\n", g_progname);
+  fprintf(stderr, "  %s setindex [-nl|-nb] [-it type] path name\n", g_progname);
+  fprintf(stderr, "  %s importtsv [-nl|-nb] [-sc] path [file]\n", g_progname);
+  fprintf(stderr, "  %s version\n", g_progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* print error information */
+static void printerr(TCTDB *tdb){
+  const char *path = tctdbpath(tdb);
+  int ecode = tctdbecode(tdb);
+  fprintf(stderr, "%s: %s: %d: %s\n", g_progname, path ? path : "-", ecode, tctdberrmsg(ecode));
+}
+
+
+/* print record data */
+static int printdata(const char *ptr, int size, bool px){
+  int len = 0;
+  while(size-- > 0){
+    if(px){
+      if(len > 0) putchar(' ');
+      len += printf("%02X", *(unsigned char *)ptr);
+    } else {
+      if(strchr("\t\r\n", *ptr)){
+        putchar(' ');
+      } else {
+        putchar(*ptr);
+      }
+      len++;
+    }
+    ptr++;
+  }
+  return len;
+}
+
+
+/* read a line from a file descriptor */
+static char *mygetline(FILE *ifp){
+  int len = 0;
+  int blen = 1024;
+  char *buf = tcmalloc(blen + 1);
+  bool end = true;
+  int c;
+  while((c = fgetc(ifp)) != EOF){
+    end = false;
+    if(c == '\0') continue;
+    if(blen <= len){
+      blen *= 2;
+      buf = tcrealloc(buf, blen + 1);
+    }
+    if(c == '\n' || c == '\r') c = '\0';
+    buf[len++] = c;
+    if(c == '\0') break;
+  }
+  if(end){
+    tcfree(buf);
+    return NULL;
+  }
+  buf[len] = '\0';
+  return buf;
+}
+
+
+/* parse arguments of create command */
+static int runcreate(int argc, char **argv){
+  char *path = NULL;
+  char *bstr = NULL;
+  char *astr = NULL;
+  char *fstr = NULL;
+  int opts = 0;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-tl")){
+        opts |= TDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= TDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= TDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= TDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= TDBTEXCODEC;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else if(!astr){
+      astr = argv[i];
+    } else if(!fstr){
+      fstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int apow = astr ? tcatoix(astr) : -1;
+  int fpow = fstr ? tcatoix(fstr) : -1;
+  int rv = proccreate(path, bnum, apow, fpow, opts);
+  return rv;
+}
+
+
+/* parse arguments of inform command */
+static int runinform(int argc, char **argv){
+  char *path = NULL;
+  int omode = 0;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= TDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= TDBOLCKNB;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int rv = procinform(path, omode);
+  return rv;
+}
+
+
+/* parse arguments of put command */
+static int runput(int argc, char **argv){
+  char *path = NULL;
+  char *pkey = NULL;
+  TCLIST *vals = tcmpoollistnew(tcmpoolglobal());
+  int omode = 0;
+  int dmode = 0;
+  bool sx = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= TDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= TDBOLCKNB;
+      } else if(!strcmp(argv[i], "-dk")){
+        dmode = -1;
+      } else if(!strcmp(argv[i], "-dc")){
+        dmode = 1;
+      } else if(!strcmp(argv[i], "-dai")){
+        dmode = 10;
+      } else if(!strcmp(argv[i], "-dad")){
+        dmode = 11;
+      } else if(!strcmp(argv[i], "-sx")){
+        sx = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!pkey){
+      pkey = argv[i];
+    } else {
+      tclistpush2(vals, argv[i]);
+    }
+  }
+  if(!path || !pkey) usage();
+  TCMAP *cols = tcmapnew();
+  char *pkbuf;
+  int pksiz;
+  if(sx){
+    pkbuf = tchexdecode(pkey, &pksiz);
+    for(int i = 0; i < tclistnum(vals) - 1; i += 2){
+      const char *name = tclistval2(vals, i);
+      const char *value = tclistval2(vals, i + 1);
+      int nsiz;
+      char *nbuf = tchexdecode(name, &nsiz);
+      int vsiz;
+      char *vbuf = tchexdecode(value, &vsiz);
+      tcmapput(cols, nbuf, nsiz, vbuf, vsiz);
+      tcfree(vbuf);
+      tcfree(nbuf);
+    }
+  } else {
+    pksiz = strlen(pkey);
+    pkbuf = tcmemdup(pkey, pksiz);
+    for(int i = 0; i < tclistnum(vals) - 1; i += 2){
+      const char *name = tclistval2(vals, i);
+      const char *value = tclistval2(vals, i + 1);
+      tcmapput2(cols, name, value);
+    }
+  }
+  int rv = procput(path, pkbuf, pksiz, cols, omode, dmode);
+  tcmapdel(cols);
+  tcfree(pkbuf);
+  return rv;
+}
+
+
+/* parse arguments of out command */
+static int runout(int argc, char **argv){
+  char *path = NULL;
+  char *pkey = NULL;
+  int omode = 0;
+  bool sx = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= TDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= TDBOLCKNB;
+      } else if(!strcmp(argv[i], "-sx")){
+        sx = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!pkey){
+      pkey = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !pkey) usage();
+  int pksiz;
+  char *pkbuf;
+  if(sx){
+    pkbuf = tchexdecode(pkey, &pksiz);
+  } else {
+    pksiz = strlen(pkey);
+    pkbuf = tcmemdup(pkey, pksiz);
+  }
+  int rv = procout(path, pkbuf, pksiz, omode);
+  tcfree(pkbuf);
+  return rv;
+}
+
+
+/* parse arguments of get command */
+static int runget(int argc, char **argv){
+  char *path = NULL;
+  char *pkey = NULL;
+  int omode = 0;
+  bool sx = false;
+  bool px = false;
+  bool pz = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= TDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= TDBOLCKNB;
+      } else if(!strcmp(argv[i], "-sx")){
+        sx = true;
+      } else if(!strcmp(argv[i], "-px")){
+        px = true;
+      } else if(!strcmp(argv[i], "-pz")){
+        pz = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!pkey){
+      pkey = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !pkey) usage();
+  int pksiz;
+  char *pkbuf;
+  if(sx){
+    pkbuf = tchexdecode(pkey, &pksiz);
+  } else {
+    pksiz = strlen(pkey);
+    pkbuf = tcmemdup(pkey, pksiz);
+  }
+  int rv = procget(path, pkbuf, pksiz, omode, px, pz);
+  tcfree(pkbuf);
+  return rv;
+}
+
+
+/* parse arguments of list command */
+static int runlist(int argc, char **argv){
+  char *path = NULL;
+  int omode = 0;
+  int max = -1;
+  bool pv = false;
+  bool px = false;
+  char *fmstr = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= TDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= TDBOLCKNB;
+      } else if(!strcmp(argv[i], "-m")){
+        if(++i >= argc) usage();
+        max = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-pv")){
+        pv = true;
+      } else if(!strcmp(argv[i], "-px")){
+        px = true;
+      } else if(!strcmp(argv[i], "-fm")){
+        if(++i >= argc) usage();
+        fmstr = argv[i];
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int rv = proclist(path, omode, max, pv, px, fmstr);
+  return rv;
+}
+
+
+/* parse arguments of search command */
+static int runsearch(int argc, char **argv){
+  char *path = NULL;
+  TCLIST *conds = tcmpoollistnew(tcmpoolglobal());
+  char *oname = NULL;
+  char *otype = NULL;
+  int omode = 0;
+  int max = -1;
+  int skip = -1;
+  bool pv = false;
+  bool px = false;
+  bool kw = false;
+  bool ph = false;
+  int bt = 0;
+  bool rm = false;
+  char *mtype = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= TDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= TDBOLCKNB;
+      } else if(!strcmp(argv[i], "-ord")){
+        if(++i >= argc) usage();
+        oname = argv[i];
+        if(++i >= argc) usage();
+        otype = argv[i];
+      } else if(!strcmp(argv[i], "-m")){
+        if(++i >= argc) usage();
+        max = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-sk")){
+        if(++i >= argc) usage();
+        skip = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-kw")){
+        kw = true;
+      } else if(!strcmp(argv[i], "-pv")){
+        pv = true;
+      } else if(!strcmp(argv[i], "-px")){
+        px = true;
+      } else if(!strcmp(argv[i], "-ph")){
+        ph = true;
+      } else if(!strcmp(argv[i], "-bt")){
+        if(++i >= argc) usage();
+        bt = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-rm")){
+        rm = true;
+      } else if(!strcmp(argv[i], "-ms")){
+        if(++i >= argc) usage();
+        mtype = argv[i];
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      tclistpush2(conds, argv[i]);
+    }
+  }
+  if(!path || tclistnum(conds) % 3 != 0) usage();
+  int rv = procsearch(path, conds, oname, otype, omode, max, skip,
+                      pv, px, kw, ph, bt, rm, mtype);
+  return rv;
+}
+
+
+/* parse arguments of optimize command */
+static int runoptimize(int argc, char **argv){
+  char *path = NULL;
+  char *bstr = NULL;
+  char *astr = NULL;
+  char *fstr = NULL;
+  int opts = UINT8_MAX;
+  int omode = 0;
+  bool df = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-tl")){
+        if(opts == UINT8_MAX) opts = 0;
+        opts |= TDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        if(opts == UINT8_MAX) opts = 0;
+        opts |= TDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        if(opts == UINT8_MAX) opts = 0;
+        opts |= TDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        if(opts == UINT8_MAX) opts = 0;
+        opts |= TDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        if(opts == UINT8_MAX) opts = 0;
+        opts |= TDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-tz")){
+        if(opts == UINT8_MAX) opts = 0;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= TDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= TDBOLCKNB;
+      } else if(!strcmp(argv[i], "-df")){
+        df = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else if(!astr){
+      astr = argv[i];
+    } else if(!fstr){
+      fstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int apow = astr ? tcatoix(astr) : -1;
+  int fpow = fstr ? tcatoix(fstr) : -1;
+  int rv = procoptimize(path, bnum, apow, fpow, opts, omode, df);
+  return rv;
+}
+
+
+/* parse arguments of setindex command */
+static int runsetindex(int argc, char **argv){
+  char *path = NULL;
+  char *name = NULL;
+  int omode = 0;
+  int type = TDBITLEXICAL;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= TDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= TDBOLCKNB;
+      } else if(!strcmp(argv[i], "-it")){
+        if(++i >= argc) usage();
+        type = tctdbstrtoindextype(argv[i]);
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!name){
+      name = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !name) usage();
+  if(type < 0) usage();
+  int rv = procsetindex(path, name, omode, type);
+  return rv;
+}
+
+
+/* parse arguments of importtsv command */
+static int runimporttsv(int argc, char **argv){
+  char *path = NULL;
+  char *file = NULL;
+  int omode = 0;
+  bool sc = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-nl")){
+        omode |= TDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= TDBOLCKNB;
+      } else if(!strcmp(argv[i], "-sc")){
+        sc = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!file){
+      file = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int rv = procimporttsv(path, file, omode, sc);
+  return rv;
+}
+
+
+/* parse arguments of version command */
+static int runversion(int argc, char **argv){
+  int rv = procversion();
+  return rv;
+}
+
+
+/* perform create command */
+static int proccreate(const char *path, int bnum, int apow, int fpow, int opts){
+  TCTDB *tdb = tctdbnew();
+  if(g_dbgfd >= 0) tctdbsetdbgfd(tdb, g_dbgfd);
+  if(!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(tdb);
+  if(!tctdbtune(tdb, bnum, apow, fpow, opts)){
+    printerr(tdb);
+    tctdbdel(tdb);
+    return 1;
+  }
+  if(!tctdbopen(tdb, path, TDBOWRITER | TDBOCREAT | TDBOTRUNC)){
+    printerr(tdb);
+    tctdbdel(tdb);
+    return 1;
+  }
+  bool err = false;
+  if(!tctdbclose(tdb)){
+    printerr(tdb);
+    err = true;
+  }
+  tctdbdel(tdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform inform command */
+static int procinform(const char *path, int omode){
+  TCTDB *tdb = tctdbnew();
+  if(g_dbgfd >= 0) tctdbsetdbgfd(tdb, g_dbgfd);
+  tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL);
+  if(!tctdbopen(tdb, path, TDBOREADER | omode)){
+    printerr(tdb);
+    tctdbdel(tdb);
+    return 1;
+  }
+  bool err = false;
+  const char *npath = tctdbpath(tdb);
+  if(!npath) npath = "(unknown)";
+  printf("path: %s\n", npath);
+  printf("database type: table\n");
+  uint8_t flags = tctdbflags(tdb);
+  printf("additional flags:");
+  if(flags & TDBFOPEN) printf(" open");
+  if(flags & TDBFFATAL) printf(" fatal");
+  printf("\n");
+  printf("bucket number: %llu\n", (unsigned long long)tctdbbnum(tdb));
+  if(tdb->hdb->cnt_writerec >= 0)
+    printf("used bucket number: %lld\n", (long long)tctdbbnumused(tdb));
+  printf("alignment: %u\n", tctdbalign(tdb));
+  printf("free block pool: %u\n", tctdbfbpmax(tdb));
+  printf("index number: %d\n", tctdbinum(tdb));
+  TDBIDX *idxs = tdb->idxs;
+  int inum = tdb->inum;
+  for(int i = 0; i < inum; i++){
+    TDBIDX *idxp = idxs + i;
+    switch(idxp->type){
+      case TDBITLEXICAL:
+        printf("  name=%s, type=lexical, rnum=%lld, fsiz=%lld\n",
+               idxp->name, (long long)tcbdbrnum(idxp->db), (long long)tcbdbfsiz(idxp->db));
+        break;
+      case TDBITDECIMAL:
+        printf("  name=%s, type=decimal, rnum=%lld, fsiz=%lld\n",
+               idxp->name, (long long)tcbdbrnum(idxp->db), (long long)tcbdbfsiz(idxp->db));
+        break;
+      case TDBITTOKEN:
+        printf("  name=%s, type=token, rnum=%lld, fsiz=%lld\n",
+               idxp->name, (long long)tcbdbrnum(idxp->db), (long long)tcbdbfsiz(idxp->db));
+        break;
+      case TDBITQGRAM:
+        printf("  name=%s, type=qgram, rnum=%lld, fsiz=%lld\n",
+               idxp->name, (long long)tcbdbrnum(idxp->db), (long long)tcbdbfsiz(idxp->db));
+        break;
+    }
+  }
+  printf("unique ID seed: %lld\n", (long long)tctdbuidseed(tdb));
+  printf("inode number: %lld\n", (long long)tctdbinode(tdb));
+  char date[48];
+  tcdatestrwww(tctdbmtime(tdb), INT_MAX, date);
+  printf("modified time: %s\n", date);
+  uint8_t opts = tctdbopts(tdb);
+  printf("options:");
+  if(opts & TDBTLARGE) printf(" large");
+  if(opts & TDBTDEFLATE) printf(" deflate");
+  if(opts & TDBTBZIP) printf(" bzip");
+  if(opts & TDBTTCBS) printf(" tcbs");
+  if(opts & TDBTEXCODEC) printf(" excodec");
+  printf("\n");
+  printf("record number: %llu\n", (unsigned long long)tctdbrnum(tdb));
+  printf("file size: %llu\n", (unsigned long long)tctdbfsiz(tdb));
+  if(!tctdbclose(tdb)){
+    if(!err) printerr(tdb);
+    err = true;
+  }
+  tctdbdel(tdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform put command */
+static int procput(const char *path, const char *pkbuf, int pksiz, TCMAP *cols,
+                   int omode, int dmode){
+  TCTDB *tdb = tctdbnew();
+  if(g_dbgfd >= 0) tctdbsetdbgfd(tdb, g_dbgfd);
+  if(!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(tdb);
+  if(!tctdbopen(tdb, path, TDBOWRITER | omode)){
+    printerr(tdb);
+    tctdbdel(tdb);
+    return 1;
+  }
+  bool err = false;
+  char pknumbuf[TCNUMBUFSIZ];
+  if(pksiz < 1){
+    pksiz = sprintf(pknumbuf, "%lld", (long long)tctdbgenuid(tdb));
+    pkbuf = pknumbuf;
+  }
+  const char *vbuf;
+  switch(dmode){
+    case -1:
+      if(!tctdbputkeep(tdb, pkbuf, pksiz, cols)){
+        printerr(tdb);
+        err = true;
+      }
+      break;
+    case 1:
+      if(!tctdbputcat(tdb, pkbuf, pksiz, cols)){
+        printerr(tdb);
+        err = true;
+      }
+      break;
+    case 10:
+      vbuf = tcmapget2(cols, "_num");
+      if(!vbuf) vbuf = "1";
+      if(tctdbaddint(tdb, pkbuf, pksiz, tcatoi(vbuf)) == INT_MIN){
+        printerr(tdb);
+        err = true;
+      }
+      break;
+    case 11:
+      vbuf = tcmapget2(cols, "_num");
+      if(!vbuf) vbuf = "1.0";
+      if(isnan(tctdbadddouble(tdb, pkbuf, pksiz, tcatof(vbuf)))){
+        printerr(tdb);
+        err = true;
+      }
+      break;
+    default:
+      if(!tctdbput(tdb, pkbuf, pksiz, cols)){
+        printerr(tdb);
+        err = true;
+      }
+      break;
+  }
+  if(!tctdbclose(tdb)){
+    if(!err) printerr(tdb);
+    err = true;
+  }
+  tctdbdel(tdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform out command */
+static int procout(const char *path, const char *pkbuf, int pksiz, int omode){
+  TCTDB *tdb = tctdbnew();
+  if(g_dbgfd >= 0) tctdbsetdbgfd(tdb, g_dbgfd);
+  if(!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(tdb);
+  if(!tctdbopen(tdb, path, TDBOWRITER | omode)){
+    printerr(tdb);
+    tctdbdel(tdb);
+    return 1;
+  }
+  bool err = false;
+  if(!tctdbout(tdb, pkbuf, pksiz)){
+    printerr(tdb);
+    err = true;
+  }
+  if(!tctdbclose(tdb)){
+    if(!err) printerr(tdb);
+    err = true;
+  }
+  tctdbdel(tdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform get command */
+static int procget(const char *path, const char *pkbuf, int pksiz, int omode, bool px, bool pz){
+  TCTDB *tdb = tctdbnew();
+  if(g_dbgfd >= 0) tctdbsetdbgfd(tdb, g_dbgfd);
+  if(!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(tdb);
+  if(!tctdbopen(tdb, path, TDBOREADER | omode)){
+    printerr(tdb);
+    tctdbdel(tdb);
+    return 1;
+  }
+  bool err = false;
+  TCMAP *cols = tctdbget(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);
+      printdata(kbuf, ksiz, px);
+      putchar('\t');
+      printdata(vbuf, vsiz, px);
+      putchar(pz ? '\t' : '\n');
+    }
+    tcmapdel(cols);
+  } else {
+    printerr(tdb);
+    err = true;
+  }
+  if(!tctdbclose(tdb)){
+    if(!err) printerr(tdb);
+    err = true;
+  }
+  tctdbdel(tdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform list command */
+static int proclist(const char *path, int omode, int max, bool pv, bool px, const char *fmstr){
+  TCTDB *tdb = tctdbnew();
+  if(g_dbgfd >= 0) tctdbsetdbgfd(tdb, g_dbgfd);
+  if(!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(tdb);
+  if(!tctdbopen(tdb, path, TDBOREADER | omode)){
+    printerr(tdb);
+    tctdbdel(tdb);
+    return 1;
+  }
+  bool err = false;
+  if(fmstr){
+    TCLIST *pkeys = tctdbfwmkeys2(tdb, fmstr, max);
+    for(int i = 0; i < tclistnum(pkeys); i++){
+      int pksiz;
+      const char *pkbuf = tclistval(pkeys, i, &pksiz);
+      printdata(pkbuf, pksiz, px);
+      if(pv){
+        TCMAP *cols = tctdbget(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);
+            putchar('\t');
+            printdata(kbuf, ksiz, px);
+            putchar('\t');
+            printdata(vbuf, vsiz, px);
+          }
+          tcmapdel(cols);
+        }
+      }
+      putchar('\n');
+    }
+    tclistdel(pkeys);
+  } else {
+    if(!tctdbiterinit(tdb)){
+      printerr(tdb);
+      err = true;
+    }
+    int cnt = 0;
+    TCMAP *cols;
+    while((cols = tctdbiternext3(tdb)) != NULL){
+      int pksiz;
+      const char *pkbuf = tcmapget(cols, "", 0, &pksiz);
+      if(pkbuf){
+        printdata(pkbuf, pksiz, px);
+        if(pv){
+          tcmapiterinit(cols);
+          const char *kbuf;
+          int ksiz;
+          while((kbuf = tcmapiternext(cols, &ksiz)) != NULL){
+            if(*kbuf == '\0') continue;
+            int vsiz;
+            const char *vbuf = tcmapiterval(kbuf, &vsiz);
+            putchar('\t');
+            printdata(kbuf, ksiz, px);
+            putchar('\t');
+            printdata(vbuf, vsiz, px);
+          }
+        }
+      }
+      tcmapdel(cols);
+      putchar('\n');
+      if(max >= 0 && ++cnt >= max) break;
+    }
+  }
+  if(!tctdbclose(tdb)){
+    if(!err) printerr(tdb);
+    err = true;
+  }
+  tctdbdel(tdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform search command */
+static int procsearch(const char *path, TCLIST *conds, const char *oname, const char *otype,
+                      int omode, int max, int skip, bool pv, bool px, bool kw, bool ph, int bt,
+                      bool rm, const char *mtype){
+  TCTDB *tdb = tctdbnew();
+  if(g_dbgfd >= 0) tctdbsetdbgfd(tdb, g_dbgfd);
+  if(!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(tdb);
+  if(!tctdbopen(tdb, path, (rm ? TDBOWRITER : TDBOREADER) | omode)){
+    printerr(tdb);
+    tctdbdel(tdb);
+    return 1;
+  }
+  bool err = false;
+  TDBQRY *qry = tctdbqrynew(tdb);
+  int cnum = tclistnum(conds);
+  for(int i = 0; i < cnum - 2; i += 3){
+    const char *name = tclistval2(conds, i);
+    const char *opstr = tclistval2(conds, i + 1);
+    const char *expr  = tclistval2(conds, i + 2);
+    int op = tctdbqrystrtocondop(opstr);
+    if(op >= 0) tctdbqryaddcond(qry, name, op, expr);
+  }
+  if(oname){
+    int type = tctdbqrystrtoordertype(otype);
+    if(type >= 0) tctdbqrysetorder(qry, oname, type);
+  }
+  tctdbqrysetlimit(qry, max, skip);
+  if(rm){
+    double stime = tctime();
+    if(!tctdbqrysearchout(qry)){
+      printerr(tdb);
+      err = true;
+    }
+    double etime = tctime();
+    if(ph){
+      TCLIST *hints = tcstrsplit(tctdbqryhint(qry), "\n");
+      int hnum = tclistnum(hints);
+      for(int i = 0; i < hnum; i++){
+        const char *hint = tclistval2(hints, i);
+        if(*hint == '\0') continue;
+        printf("\t:::: %s\n", hint);
+      }
+      tclistdel(hints);
+      printf("\t:::: number of records: %d\n", tctdbqrycount(qry));
+      printf("\t:::: elapsed time: %.5f\n", etime - stime);
+    }
+  } else if(bt > 0){
+    double sum = 0;
+    for(int i = 1; i <= bt; i++){
+      double stime = tctime();
+      TCLIST *res = tctdbqrysearch(qry);
+      double etime = tctime();
+      tclistdel(res);
+      printf("%d: %.5f sec.\n", i, etime - stime);
+      sum += etime - stime;
+    }
+    printf("----\n");
+    printf("total: %.5f sec. (%.5f s/q = %.5f q/s)\n", sum, sum / bt, bt / sum);
+  } else {
+    double stime = tctime();
+    TCLIST *res;
+    TCLIST *hints;
+    int count;
+    int mtnum = mtype ? tctdbmetastrtosettype(mtype) : -1;
+    if(mtnum >= 0){
+      TDBQRY *qrys[cnum/3+1];
+      int qnum = 0;
+      for(int i = 0; i < cnum - 2; i += 3){
+        const char *name = tclistval2(conds, i);
+        const char *opstr = tclistval2(conds, i + 1);
+        const char *expr  = tclistval2(conds, i + 2);
+        int op = tctdbqrystrtocondop(opstr);
+        if(op >= 0){
+          qrys[qnum] = tctdbqrynew(tdb);
+          tctdbqryaddcond(qrys[qnum], name, op, expr);
+          if(oname){
+            int type = tctdbqrystrtoordertype(otype);
+            if(type >= 0) tctdbqrysetorder(qrys[qnum], oname, type);
+          }
+          tctdbqrysetlimit(qrys[qnum], max, skip);
+          qnum++;
+        }
+      }
+      res = tctdbmetasearch(qrys, qnum, mtnum);
+      hints = qnum > 0 ? tcstrsplit(tctdbqryhint(qrys[0]), "\n") : tclistnew2(1);
+      count = qnum > 0 ? tctdbqrycount(qrys[0]) : 0;
+      for(int i = 0; i < qnum; i++){
+        tctdbqrydel(qrys[i]);
+      }
+    } else {
+      res = tctdbqrysearch(qry);
+      hints = tcstrsplit(tctdbqryhint(qry), "\n");
+      count = tctdbqrycount(qry);
+    }
+    double etime = tctime();
+    if(max < 0) max = INT_MAX;
+    int rnum = tclistnum(res);
+    for(int i = 0; i < rnum && max > 0; i++){
+      int pksiz;
+      const char *pkbuf = tclistval(res, i, &pksiz);
+      if(kw){
+        TCMAP *cols = tctdbget(tdb, pkbuf, pksiz);
+        if(cols){
+          TCLIST *texts = tctdbqrykwic(qry, cols, NULL, 16, TCKWMUTAB);
+          int tnum = tclistnum(texts);
+          for(int j = 0; j < tnum && max > 0; j++){
+            int tsiz;
+            const char *text = tclistval(texts, j, &tsiz);
+            printdata(pkbuf, pksiz, px);
+            putchar('\t');
+            printdata(text, tsiz, px);
+            putchar('\n');
+            max--;
+          }
+          tclistdel(texts);
+          tcmapdel(cols);
+        }
+      } else {
+        printdata(pkbuf, pksiz, px);
+        if(pv){
+          TCMAP *cols = tctdbget(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);
+              putchar('\t');
+              printdata(kbuf, ksiz, px);
+              putchar('\t');
+              printdata(vbuf, vsiz, px);
+            }
+            tcmapdel(cols);
+          }
+        }
+        putchar('\n');
+        max--;
+      }
+    }
+    if(ph){
+      int hnum = tclistnum(hints);
+      for(int i = 0; i < hnum; i++){
+        const char *hint = tclistval2(hints, i);
+        if(*hint == '\0') continue;
+        printf("\t:::: %s\n", hint);
+      }
+      printf("\t:::: number of records: %d\n", count);
+      printf("\t:::: elapsed time: %.5f\n", etime - stime);
+    }
+    tclistdel(hints);
+    tclistdel(res);
+  }
+  tctdbqrydel(qry);
+  if(!tctdbclose(tdb)){
+    if(!err) printerr(tdb);
+    err = true;
+  }
+  tctdbdel(tdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform optimize command */
+static int procoptimize(const char *path, int bnum, int apow, int fpow, int opts, int omode,
+                        bool df){
+  TCTDB *tdb = tctdbnew();
+  if(g_dbgfd >= 0) tctdbsetdbgfd(tdb, g_dbgfd);
+  if(!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(tdb);
+  int64_t msiz = 0;
+  TCMAP *info = tcsysinfo();
+  if(info){
+    msiz = tcatoi(tcmapget4(info, "total", "0"));
+    tcmapdel(info);
+  }
+  if(!tctdbsetinvcache(tdb, msiz >= (1 << 30) ? msiz / 4 : 0, 1.0)) printerr(tdb);
+  if(!tctdbopen(tdb, path, TDBOWRITER | omode)){
+    printerr(tdb);
+    tctdbdel(tdb);
+    return 1;
+  }
+  bool err = false;
+  if(df){
+    if(!tctdbdefrag(tdb, INT64_MAX)){
+      printerr(tdb);
+      err = true;
+    }
+  } else {
+    if(!tctdboptimize(tdb, bnum, apow, fpow, opts)){
+      printerr(tdb);
+      err = true;
+    }
+  }
+  if(!tctdbclose(tdb)){
+    if(!err) printerr(tdb);
+    err = true;
+  }
+  tctdbdel(tdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform setindex command */
+static int procsetindex(const char *path, const char *name, int omode, int type){
+  TCTDB *tdb = tctdbnew();
+  if(g_dbgfd >= 0) tctdbsetdbgfd(tdb, g_dbgfd);
+  if(!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(tdb);
+  int64_t msiz = 0;
+  TCMAP *info = tcsysinfo();
+  if(info){
+    msiz = tcatoi(tcmapget4(info, "total", "0"));
+    tcmapdel(info);
+  }
+  if(!tctdbsetinvcache(tdb, msiz >= (1 << 30) ? msiz / 4 : 0, 1.0)) printerr(tdb);
+  if(!tctdbopen(tdb, path, TDBOWRITER | omode)){
+    printerr(tdb);
+    tctdbdel(tdb);
+    return 1;
+  }
+  bool err = false;
+  if(!tctdbsetindex(tdb, name, type)){
+    printerr(tdb);
+    err = true;
+  }
+  if(!tctdbclose(tdb)){
+    if(!err) printerr(tdb);
+    err = true;
+  }
+  tctdbdel(tdb);
+  return err ? 1 : 0;
+}
+
+
+/* perform importtsv command */
+static int procimporttsv(const char *path, const char *file, int omode, bool sc){
+  FILE *ifp = file ? fopen(file, "rb") : stdin;
+  if(!ifp){
+    fprintf(stderr, "%s: could not open\n", file ? file : "(stdin)");
+    return 1;
+  }
+  TCTDB *tdb = tctdbnew();
+  if(g_dbgfd >= 0) tctdbsetdbgfd(tdb, g_dbgfd);
+  if(!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)) printerr(tdb);
+  int64_t msiz = 0;
+  TCMAP *info = tcsysinfo();
+  if(info){
+    msiz = tcatoi(tcmapget4(info, "total", "0"));
+    tcmapdel(info);
+  }
+  if(!tctdbsetinvcache(tdb, msiz >= (1 << 30) ? msiz / 4 : 0, 1.0)) printerr(tdb);
+  if(!tctdbopen(tdb, path, TDBOWRITER | TDBOCREAT | omode)){
+    printerr(tdb);
+    tctdbdel(tdb);
+    if(ifp != stdin) fclose(ifp);
+    return 1;
+  }
+  bool err = false;
+  char *line, numbuf[TCNUMBUFSIZ];
+  int cnt = 0;
+  while(!err && (line = mygetline(ifp)) != NULL){
+    char *pv = strchr(line, '\t');
+    if(!pv){
+      tcfree(line);
+      continue;
+    }
+    *pv = '\0';
+    if(sc) tcstrutfnorm(line, TCUNSPACE | TCUNLOWER | TCUNNOACC | TCUNWIDTH);
+    const char *pkey;
+    if(*line == '\0'){
+      sprintf(numbuf, "%lld", (long long)tctdbgenuid(tdb));
+      pkey = numbuf;
+    } else {
+      pkey = line;
+    }
+    if(!tctdbput3(tdb, pkey, pv + 1)){
+      printerr(tdb);
+      err = true;
+    }
+    tcfree(line);
+    if(cnt > 0 && cnt % 100 == 0){
+      putchar('.');
+      fflush(stdout);
+      if(cnt % 5000 == 0) printf(" (%08d)\n", cnt);
+    }
+    cnt++;
+  }
+  printf(" (%08d)\n", cnt);
+  if(!tctdbclose(tdb)){
+    if(!err) printerr(tdb);
+    err = true;
+  }
+  tctdbdel(tdb);
+  if(ifp != stdin) fclose(ifp);
+  return err ? 1 : 0;
+}
+
+
+/* perform version command */
+static int procversion(void){
+  printf("Tokyo Cabinet version %s (%d:%s) for %s\n",
+         tcversion, _TC_LIBVER, _TC_FORMATVER, TCSYSNAME);
+  printf("Copyright (C) 2006-2012 FAL Labs\n");
+  return 0;
+}
+
+
+
+// END OF FILE
diff --git a/tcejdb/tctmttest.c b/tcejdb/tctmttest.c
new file mode 100644 (file)
index 0000000..4c9d351
--- /dev/null
@@ -0,0 +1,1563 @@
+/*************************************************************************************************
+ * The test cases of the table database API
+ *                                                               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 <tctdb.h>
+#include "myconf.h"
+
+#define RECBUFSIZ      48                // buffer for records
+
+typedef struct {                         // type of structure for write thread
+  TCTDB *tdb;
+  int rnum;
+  bool rnd;
+  int id;
+} TARGWRITE;
+
+typedef struct {                         // type of structure for read thread
+  TCTDB *tdb;
+  int rnum;
+  bool rnd;
+  int id;
+} TARGREAD;
+
+typedef struct {                         // type of structure for remove thread
+  TCTDB *tdb;
+  int rnum;
+  bool rnd;
+  int id;
+} TARGREMOVE;
+
+typedef struct {                         // type of structure for wicked thread
+  TCTDB *tdb;
+  int rnum;
+  int id;
+} TARGWICKED;
+
+typedef struct {                         // type of structure for typical thread
+  TCTDB *tdb;
+  int rnum;
+  int rratio;
+  int id;
+} TARGTYPICAL;
+
+
+/* global variables */
+const char *g_progname;                  // program name
+unsigned int g_randseed;                 // random seed
+int g_dbgfd;                             // debugging output
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+static void usage(void);
+static void iprintf(const char *format, ...);
+static void iputchar(int c);
+static void eprint(TCTDB *tdb, int line, const char *func);
+static void sysprint(void);
+static int myrand(int range);
+static int myrandnd(int range);
+static int runwrite(int argc, char **argv);
+static int runread(int argc, char **argv);
+static int runremove(int argc, char **argv);
+static int runwicked(int argc, char **argv);
+static int runtypical(int argc, char **argv);
+static int procwrite(const char *path, int tnum, int rnum, int bnum, int apow, int fpow,
+                     int opts, int rcnum, int lcnum, int ncnum, int xmsiz, int dfunit,
+                     int iflags, int omode, bool rnd);
+static int procread(const char *path, int tnum, int rcnum, int lcnum, int ncnum,
+                    int xmsiz, int dfunit, int omode, bool rnd);
+static int procremove(const char *path, int tnum, int rcnum, int lcnum, int ncnum,
+                      int xmsiz, int dfunit, int omode, bool rnd);
+static int procwicked(const char *path, int tnum, int rnum, int opts, int omode);
+static int proctypical(const char *path, int tnum, int rnum, int bnum, int apow, int fpow,
+                       int opts, int rcnum, int lcnum, int ncnum, int xmsiz, int dfunit,
+                       int omode, int rratio);
+static void *threadwrite(void *targ);
+static void *threadread(void *targ);
+static void *threadremove(void *targ);
+static void *threadwicked(void *targ);
+static void *threadtypical(void *targ);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  g_progname = argv[0];
+  const char *ebuf = getenv("TCRNDSEED");
+  g_randseed = ebuf ? tcatoix(ebuf) : tctime() * 1000;
+  srand(g_randseed);
+  ebuf = getenv("TCDBGFD");
+  g_dbgfd = ebuf ? tcatoix(ebuf) : UINT16_MAX;
+  if(argc < 2) usage();
+  int rv = 0;
+  if(!strcmp(argv[1], "write")){
+    rv = runwrite(argc, argv);
+  } else if(!strcmp(argv[1], "read")){
+    rv = runread(argc, argv);
+  } else if(!strcmp(argv[1], "remove")){
+    rv = runremove(argc, argv);
+  } else if(!strcmp(argv[1], "wicked")){
+    rv = runwicked(argc, argv);
+  } else if(!strcmp(argv[1], "typical")){
+    rv = runtypical(argc, argv);
+  } else {
+    usage();
+  }
+  if(rv != 0){
+    printf("FAILED: TCRNDSEED=%u PID=%d", g_randseed, (int)getpid());
+    for(int i = 0; i < argc; i++){
+      printf(" %s", argv[i]);
+    }
+    printf("\n\n");
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+static void usage(void){
+  fprintf(stderr, "%s: test cases of the table database API of Tokyo Cabinet\n", g_progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s write [-tl] [-td|-tb|-tt|-tx] [-rc num] [-lc num] [-nc num]"
+          " [-xm num] [-df num] [-ip] [-is] [-in] [-it] [-if] [-ix] [-nl|-nb] [-rnd]"
+          " path tnum rnum [bnum [apow [fpow]]]\n", g_progname);
+  fprintf(stderr, "  %s read [-rc num] [-lc num] [-nc num] [-xm num] [-df num] [-nl|-nb] [-rnd]"
+          " path tnum\n", g_progname);
+  fprintf(stderr, "  %s remove [-rc num] [-lc num] [-nc num] [-xm num] [-df num]"
+          " [-nl|-nb] [-rnd] path tnum\n", g_progname);
+  fprintf(stderr, "  %s wicked [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] path tnum rnum\n", g_progname);
+  fprintf(stderr, "  %s typical [-tl] [-td|-tb|-tt|-tx] [-rc num] [-lc num] [-nc num]"
+          " [-xm num] [-df num] [-nl|-nb] [-rr num] path tnum rnum [bnum [apow [fpow]]]\n",
+          g_progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* print formatted information string and flush the buffer */
+static void iprintf(const char *format, ...){
+  va_list ap;
+  va_start(ap, format);
+  vprintf(format, ap);
+  fflush(stdout);
+  va_end(ap);
+}
+
+
+/* print a character and flush the buffer */
+static void iputchar(int c){
+  putchar(c);
+  fflush(stdout);
+}
+
+
+/* print error message of table database */
+static void eprint(TCTDB *tdb, int line, const char *func){
+  const char *path = tctdbpath(tdb);
+  int ecode = tctdbecode(tdb);
+  fprintf(stderr, "%s: %s: %d: %s: error: %d: %s\n",
+          g_progname, path ? path : "-", line, func, ecode, tctdberrmsg(ecode));
+}
+
+
+/* print system information */
+static void sysprint(void){
+  TCMAP *info = tcsysinfo();
+  if(info){
+    tcmapiterinit(info);
+    const char *kbuf;
+    while((kbuf = tcmapiternext2(info)) != NULL){
+      iprintf("sys_%s: %s\n", kbuf, tcmapiterval2(kbuf));
+    }
+    tcmapdel(info);
+  }
+}
+
+
+/* get a random number */
+static int myrand(int range){
+  if(range < 2) return 0;
+  int high = (unsigned int)rand() >> 4;
+  int low = range * (rand() / (RAND_MAX + 1.0));
+  low &= (unsigned int)INT_MAX >> 4;
+  return (high + low) % range;
+}
+
+
+/* get a random number based on normal distribution */
+static int myrandnd(int range){
+  int num = (int)tcdrandnd(range >> 1, range / 10);
+  return (num < 0 || num >= range) ? 0 : num;
+}
+
+
+/* parse arguments of write command */
+static int runwrite(int argc, char **argv){
+  char *path = NULL;
+  char *tstr = NULL;
+  char *rstr = NULL;
+  char *bstr = NULL;
+  char *astr = NULL;
+  char *fstr = NULL;
+  int opts = 0;
+  int rcnum = 0;
+  int lcnum = 0;
+  int ncnum = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int iflags = 0;
+  int omode = 0;
+  bool rnd = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-tl")){
+        opts |= TDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= TDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= TDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= TDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= TDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-rc")){
+        if(++i >= argc) usage();
+        rcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-lc")){
+        if(++i >= argc) usage();
+        lcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nc")){
+        if(++i >= argc) usage();
+        ncnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-ip")){
+        iflags |= 1 << 0;
+      } else if(!strcmp(argv[i], "-is")){
+        iflags |= 1 << 1;
+      } else if(!strcmp(argv[i], "-in")){
+        iflags |= 1 << 2;
+      } else if(!strcmp(argv[i], "-it")){
+        iflags |= 1 << 3;
+      } else if(!strcmp(argv[i], "-if")){
+        iflags |= 1 << 4;
+      } else if(!strcmp(argv[i], "-ix")){
+        iflags |= 1 << 5;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= TDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= TDBOLCKNB;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else if(!astr){
+      astr = argv[i];
+    } else if(!fstr){
+      fstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !tstr || !rstr) usage();
+  int tnum = tcatoix(tstr);
+  int rnum = tcatoix(rstr);
+  if(tnum < 1 || rnum < 1) usage();
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int apow = astr ? tcatoix(astr) : -1;
+  int fpow = fstr ? tcatoix(fstr) : -1;
+  int rv = procwrite(path, tnum, rnum, bnum, apow, fpow, opts, rcnum, lcnum, ncnum,
+                     xmsiz, dfunit, iflags, omode, rnd);
+  return rv;
+}
+
+
+/* parse arguments of read command */
+static int runread(int argc, char **argv){
+  char *path = NULL;
+  char *tstr = NULL;
+  int rcnum = 0;
+  int lcnum = 0;
+  int ncnum = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int omode = 0;
+  bool rnd = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-rc")){
+        if(++i >= argc) usage();
+        rcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-lc")){
+        if(++i >= argc) usage();
+        lcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nc")){
+        if(++i >= argc) usage();
+        ncnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= TDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= TDBOLCKNB;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !tstr) usage();
+  int tnum = tcatoix(tstr);
+  if(tnum < 1) usage();
+  int rv = procread(path, tnum, rcnum, lcnum, ncnum, xmsiz, dfunit, omode, rnd);
+  return rv;
+}
+
+
+/* parse arguments of remove command */
+static int runremove(int argc, char **argv){
+  char *path = NULL;
+  char *tstr = NULL;
+  int rcnum = 0;
+  int lcnum = 0;
+  int ncnum = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int omode = 0;
+  bool rnd = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-rc")){
+        if(++i >= argc) usage();
+        rcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-lc")){
+        if(++i >= argc) usage();
+        lcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nc")){
+        if(++i >= argc) usage();
+        ncnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= TDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= TDBOLCKNB;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !tstr) usage();
+  int tnum = tcatoix(tstr);
+  if(tnum < 1) usage();
+  int rv = procremove(path, tnum, rcnum, lcnum, ncnum, xmsiz, dfunit, omode, rnd);
+  return rv;
+}
+
+
+/* parse arguments of wicked command */
+static int runwicked(int argc, char **argv){
+  char *path = NULL;
+  char *tstr = NULL;
+  char *rstr = NULL;
+  int opts = 0;
+  int omode = 0;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-tl")){
+        opts |= TDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= TDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= TDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= TDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= TDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= TDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= TDBOLCKNB;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !tstr || !rstr) usage();
+  int tnum = tcatoix(tstr);
+  int rnum = tcatoix(rstr);
+  if(tnum < 1 || rnum < 1) usage();
+  int rv = procwicked(path, tnum, rnum, opts, omode);
+  return rv;
+}
+
+
+/* parse arguments of typical command */
+static int runtypical(int argc, char **argv){
+  char *path = NULL;
+  char *tstr = NULL;
+  char *rstr = NULL;
+  char *bstr = NULL;
+  char *astr = NULL;
+  char *fstr = NULL;
+  int opts = 0;
+  int rcnum = 0;
+  int lcnum = 0;
+  int ncnum = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int omode = 0;
+  int rratio = -1;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-tl")){
+        opts |= TDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= TDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= TDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= TDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= TDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-rc")){
+        if(++i >= argc) usage();
+        rcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-lc")){
+        if(++i >= argc) usage();
+        lcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nc")){
+        if(++i >= argc) usage();
+        ncnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= TDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= TDBOLCKNB;
+      } else if(!strcmp(argv[i], "-rr")){
+        if(++i >= argc) usage();
+        rratio = tcatoix(argv[i]);
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!tstr){
+      tstr = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else if(!astr){
+      astr = argv[i];
+    } else if(!fstr){
+      fstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !tstr || !rstr) usage();
+  int tnum = tcatoix(tstr);
+  int rnum = tcatoix(rstr);
+  if(tnum < 1 || rnum < 1) usage();
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int apow = astr ? tcatoix(astr) : -1;
+  int fpow = fstr ? tcatoix(fstr) : -1;
+  int rv = proctypical(path, tnum, rnum, bnum, apow, fpow, opts, rcnum, lcnum, ncnum,
+                       xmsiz, dfunit, omode, rratio);
+  return rv;
+}
+
+
+/* perform write command */
+static int procwrite(const char *path, int tnum, int rnum, int bnum, int apow, int fpow,
+                     int opts, int rcnum, int lcnum, int ncnum, int xmsiz, int dfunit,
+                     int iflags, int omode, bool rnd){
+  iprintf("<Writing Test>\n  seed=%u  path=%s  tnum=%d  rnum=%d  bnum=%d  apow=%d  fpow=%d"
+          "  opts=%d  rcnum=%d  lcnum=%d  ncnum=%d  xmsiz=%d  dfunit=%d  iflags=%d"
+          "  omode=%d  rnd=%d\n\n",
+          g_randseed, path, tnum, rnum, bnum, apow, fpow, opts, rcnum, lcnum, ncnum,
+          xmsiz, dfunit, iflags, omode, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCTDB *tdb = tctdbnew();
+  if(g_dbgfd >= 0) tctdbsetdbgfd(tdb, g_dbgfd);
+  if(!tctdbsetmutex(tdb)){
+    eprint(tdb, __LINE__, "tctdbsetmutex");
+    err = true;
+  }
+  if(!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(tdb, __LINE__, "tctdbsetcodecfunc");
+    err = true;
+  }
+  if(!tctdbtune(tdb, bnum, apow, fpow, opts)){
+    eprint(tdb, __LINE__, "tctdbtune");
+    err = true;
+  }
+  if(!tctdbsetcache(tdb, rcnum, lcnum, ncnum)){
+    eprint(tdb, __LINE__, "tctdbsetcache");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tctdbsetxmsiz(tdb, xmsiz)){
+    eprint(tdb, __LINE__, "tctdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tctdbsetdfunit(tdb, dfunit)){
+    eprint(tdb, __LINE__, "tctdbsetdfunit");
+    err = true;
+  }
+  if(!tctdbopen(tdb, path, TDBOWRITER | TDBOCREAT | TDBOTRUNC | omode)){
+    eprint(tdb, __LINE__, "tctdbopen");
+    err = true;
+  }
+  if((iflags & (1 << 0)) && !tctdbsetindex(tdb, "", TDBITDECIMAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if((iflags & (1 << 1)) && !tctdbsetindex(tdb, "str", TDBITLEXICAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if((iflags & (1 << 2)) && !tctdbsetindex(tdb, "num", TDBITDECIMAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if((iflags & (1 << 3)) && !tctdbsetindex(tdb, "type", TDBITDECIMAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if((iflags & (1 << 4)) && !tctdbsetindex(tdb, "flag", TDBITTOKEN)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if((iflags & (1 << 5)) && !tctdbsetindex(tdb, "text", TDBITQGRAM)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  TARGWRITE targs[tnum];
+  pthread_t threads[tnum];
+  if(tnum == 1){
+    targs[0].tdb = tdb;
+    targs[0].rnum = rnum;
+    targs[0].rnd = rnd;
+    targs[0].id = 0;
+    if(threadwrite(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].tdb = tdb;
+      targs[i].rnum = rnum;
+      targs[i].rnd = rnd;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadwrite, targs + i) != 0){
+        eprint(tdb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(tdb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tctdbrnum(tdb));
+  iprintf("size: %llu\n", (unsigned long long)tctdbfsiz(tdb));
+  sysprint();
+  if(!tctdbclose(tdb)){
+    eprint(tdb, __LINE__, "tctdbclose");
+    err = true;
+  }
+  tctdbdel(tdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform read command */
+static int procread(const char *path, int tnum, int rcnum, int lcnum, int ncnum,
+                    int xmsiz, int dfunit, int omode, bool rnd){
+  iprintf("<Reading Test>\n  seed=%u  path=%s  tnum=%d  rcnum=%d  lcnum=%d  ncnum=%d"
+          "  xmsiz=%d  dfunit=%d  omode=%d  rnd=%d\n\n",
+          g_randseed, path, tnum, rcnum, lcnum, ncnum, xmsiz, dfunit, omode, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCTDB *tdb = tctdbnew();
+  if(g_dbgfd >= 0) tctdbsetdbgfd(tdb, g_dbgfd);
+  if(!tctdbsetmutex(tdb)){
+    eprint(tdb, __LINE__, "tctdbsetmutex");
+    err = true;
+  }
+  if(!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(tdb, __LINE__, "tctdbsetcodecfunc");
+    err = true;
+  }
+  if(!tctdbsetcache(tdb, rcnum, lcnum, ncnum)){
+    eprint(tdb, __LINE__, "tctdbsetcache");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tctdbsetxmsiz(tdb, xmsiz)){
+    eprint(tdb, __LINE__, "tctdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tctdbsetdfunit(tdb, dfunit)){
+    eprint(tdb, __LINE__, "tctdbsetdfunit");
+    err = true;
+  }
+  if(!tctdbopen(tdb, path, TDBOREADER | omode)){
+    eprint(tdb, __LINE__, "tctdbopen");
+    err = true;
+  }
+  int rnum = tctdbrnum(tdb) / tnum;
+  TARGREAD targs[tnum];
+  pthread_t threads[tnum];
+  if(tnum == 1){
+    targs[0].tdb = tdb;
+    targs[0].rnum = rnum;
+    targs[0].rnd = rnd;
+    targs[0].id = 0;
+    if(threadread(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].tdb = tdb;
+      targs[i].rnum = rnum;
+      targs[i].rnd = rnd;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadread, targs + i) != 0){
+        eprint(tdb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(tdb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tctdbrnum(tdb));
+  iprintf("size: %llu\n", (unsigned long long)tctdbfsiz(tdb));
+  sysprint();
+  if(!tctdbclose(tdb)){
+    eprint(tdb, __LINE__, "tctdbclose");
+    err = true;
+  }
+  tctdbdel(tdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform remove command */
+static int procremove(const char *path, int tnum, int rcnum, int lcnum, int ncnum,
+                      int xmsiz, int dfunit, int omode, bool rnd){
+  iprintf("<Removing Test>\n  seed=%u  path=%s  tnum=%d  rcnum=%d  lcnum=%d  ncnum=%d"
+          "  xmsiz=%d  dfunit=%d  omode=%d  rnd=%d\n\n",
+          g_randseed, path, tnum, rcnum, lcnum, ncnum, xmsiz, dfunit, omode, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCTDB *tdb = tctdbnew();
+  if(g_dbgfd >= 0) tctdbsetdbgfd(tdb, g_dbgfd);
+  if(!tctdbsetmutex(tdb)){
+    eprint(tdb, __LINE__, "tctdbsetmutex");
+    err = true;
+  }
+  if(!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(tdb, __LINE__, "tctdbsetcodecfunc");
+    err = true;
+  }
+  if(!tctdbsetcache(tdb, rcnum, lcnum, ncnum)){
+    eprint(tdb, __LINE__, "tctdbsetcache");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tctdbsetxmsiz(tdb, xmsiz)){
+    eprint(tdb, __LINE__, "tctdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tctdbsetdfunit(tdb, dfunit)){
+    eprint(tdb, __LINE__, "tctdbsetdfunit");
+    err = true;
+  }
+  if(!tctdbopen(tdb, path, TDBOWRITER | omode)){
+    eprint(tdb, __LINE__, "tctdbopen");
+    err = true;
+  }
+  int rnum = tctdbrnum(tdb) / tnum;
+  TARGREMOVE targs[tnum];
+  pthread_t threads[tnum];
+  if(tnum == 1){
+    targs[0].tdb = tdb;
+    targs[0].rnum = rnum;
+    targs[0].rnd = rnd;
+    targs[0].id = 0;
+    if(threadremove(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].tdb = tdb;
+      targs[i].rnum = rnum;
+      targs[i].rnd = rnd;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadremove, targs + i) != 0){
+        eprint(tdb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(tdb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tctdbrnum(tdb));
+  iprintf("size: %llu\n", (unsigned long long)tctdbfsiz(tdb));
+  sysprint();
+  if(!tctdbclose(tdb)){
+    eprint(tdb, __LINE__, "tctdbclose");
+    err = true;
+  }
+  tctdbdel(tdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform wicked command */
+static int procwicked(const char *path, int tnum, int rnum, int opts, int omode){
+  iprintf("<Writing Test>\n  seed=%u  path=%s  tnum=%d  rnum=%d  opts=%d  omode=%d\n\n",
+          g_randseed, path, tnum, rnum, opts, omode);
+  bool err = false;
+  double stime = tctime();
+  TCTDB *tdb = tctdbnew();
+  if(g_dbgfd >= 0) tctdbsetdbgfd(tdb, g_dbgfd);
+  if(!tctdbsetmutex(tdb)){
+    eprint(tdb, __LINE__, "tctdbsetmutex");
+    err = true;
+  }
+  if(!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(tdb, __LINE__, "tctdbsetcodecfunc");
+    err = true;
+  }
+  if(!tctdbtune(tdb, rnum / 50, 2, -1, opts)){
+    eprint(tdb, __LINE__, "tctdbtune");
+    err = true;
+  }
+  if(!tctdbsetcache(tdb, rnum / 10, 128, 256)){
+    eprint(tdb, __LINE__, "tctdbsetcache");
+    err = true;
+  }
+  if(!tctdbsetxmsiz(tdb, rnum * sizeof(int))){
+    eprint(tdb, __LINE__, "tctdbsetxmsiz");
+    err = true;
+  }
+  if(!tctdbsetdfunit(tdb, 8)){
+    eprint(tdb, __LINE__, "tctdbsetdfunit");
+    err = true;
+  }
+  if(!tctdbsetinvcache(tdb, -1, 0.5)){
+    eprint(tdb, __LINE__, "tctdbsetinvcache");
+    err = true;
+  }
+  if(!tctdbopen(tdb, path, TDBOWRITER | TDBOCREAT | TDBOTRUNC | omode)){
+    eprint(tdb, __LINE__, "tctdbopen");
+    err = true;
+  }
+  if(!tctdbsetindex(tdb, "", TDBITDECIMAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if(!tctdbsetindex(tdb, "str", TDBITLEXICAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if(!tctdbsetindex(tdb, "num", TDBITDECIMAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if(!tctdbsetindex(tdb, "type", TDBITDECIMAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if(!tctdbsetindex(tdb, "flag", TDBITTOKEN)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if(!tctdbsetindex(tdb, "text", TDBITQGRAM)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if(!tctdbiterinit(tdb)){
+    eprint(tdb, __LINE__, "tctdbiterinit");
+    err = true;
+  }
+  TARGWICKED targs[tnum];
+  pthread_t threads[tnum];
+  if(tnum == 1){
+    targs[0].tdb = tdb;
+    targs[0].rnum = rnum;
+    targs[0].id = 0;
+    if(threadwicked(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].tdb = tdb;
+      targs[i].rnum = rnum;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadwicked, targs + i) != 0){
+        eprint(tdb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(tdb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tctdbrnum(tdb));
+  iprintf("size: %llu\n", (unsigned long long)tctdbfsiz(tdb));
+  sysprint();
+  if(!tctdbclose(tdb)){
+    eprint(tdb, __LINE__, "tctdbclose");
+    err = true;
+  }
+  tctdbdel(tdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform typical command */
+static int proctypical(const char *path, int tnum, int rnum, int bnum, int apow, int fpow,
+                       int opts, int rcnum, int lcnum, int ncnum, int xmsiz, int dfunit,
+                       int omode, int rratio){
+  iprintf("<Typical Access Test>\n  seed=%u  path=%s  tnum=%d  rnum=%d  bnum=%d  apow=%d"
+          "  fpow=%d  opts=%d  rcnum=%d  lcnum=%d  ncnum=%d  xmsiz=%d  dfunit=%d"
+          "  omode=%d  rratio=%d\n\n",
+          g_randseed, path, tnum, rnum, bnum, apow, fpow, opts, rcnum, lcnum, ncnum,
+          xmsiz, dfunit, omode, rratio);
+  bool err = false;
+  double stime = tctime();
+  TCTDB *tdb = tctdbnew();
+  if(g_dbgfd >= 0) tctdbsetdbgfd(tdb, g_dbgfd);
+  if(!tctdbsetmutex(tdb)){
+    eprint(tdb, __LINE__, "tctdbsetmutex");
+    err = true;
+  }
+  if(!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(tdb, __LINE__, "tctdbsetcodecfunc");
+    err = true;
+  }
+  if(!tctdbtune(tdb, bnum, apow, fpow, opts)){
+    eprint(tdb, __LINE__, "tctdbtune");
+    err = true;
+  }
+  if(!tctdbsetcache(tdb, rcnum, lcnum, ncnum)){
+    eprint(tdb, __LINE__, "tctdbsetcache");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tctdbsetxmsiz(tdb, xmsiz)){
+    eprint(tdb, __LINE__, "tctdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tctdbsetdfunit(tdb, dfunit)){
+    eprint(tdb, __LINE__, "tctdbsetdfunit");
+    err = true;
+  }
+  if(!tctdbopen(tdb, path, TDBOWRITER | TDBOCREAT | TDBOTRUNC | omode)){
+    eprint(tdb, __LINE__, "tctdbopen");
+    err = true;
+  }
+  if(!tctdbsetindex(tdb, "", TDBITDECIMAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if(!tctdbsetindex(tdb, "str", TDBITLEXICAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if(!tctdbsetindex(tdb, "num", TDBITDECIMAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if(!tctdbsetindex(tdb, "type", TDBITDECIMAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if(!tctdbsetindex(tdb, "flag", TDBITTOKEN)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if(!tctdbsetindex(tdb, "text", TDBITQGRAM)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  TARGTYPICAL targs[tnum];
+  pthread_t threads[tnum];
+  if(tnum == 1){
+    targs[0].tdb = tdb;
+    targs[0].rnum = rnum;
+    targs[0].rratio = rratio;
+    targs[0].id = 0;
+    if(threadtypical(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].tdb = tdb;
+      targs[i].rnum = rnum;
+      targs[i].rratio= rratio;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadtypical, targs + i) != 0){
+        eprint(tdb, __LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(tdb, __LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tctdbrnum(tdb));
+  iprintf("size: %llu\n", (unsigned long long)tctdbfsiz(tdb));
+  sysprint();
+  if(!tctdbclose(tdb)){
+    eprint(tdb, __LINE__, "tctdbclose");
+    err = true;
+  }
+  tctdbdel(tdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* thread the write function */
+static void *threadwrite(void *targ){
+  TCTDB *tdb = ((TARGWRITE *)targ)->tdb;
+  int rnum = ((TARGWRITE *)targ)->rnum;
+  bool rnd = ((TARGWRITE *)targ)->rnd;
+  int id = ((TARGWRITE *)targ)->id;
+  bool err = false;
+  int base = id * rnum;
+  for(int i = 1; i <= rnum; i++){
+    int uid = rnd ? (base + myrand(i) + 1) : tctdbgenuid(tdb);
+    char pkbuf[RECBUFSIZ];
+    int pksiz = sprintf(pkbuf, "%d", uid);
+    TCMAP *cols = tcmapnew2(7);
+    char vbuf[RECBUFSIZ*5];
+    int vsiz = sprintf(vbuf, "%d", uid);
+    tcmapput(cols, "str", 3, vbuf, vsiz);
+    if(myrand(3) == 0){
+      vsiz = sprintf(vbuf, "%.2f", (myrand(i * 100) + 1) / 100.0);
+    } else {
+      vsiz = sprintf(vbuf, "%d", myrand(i) + 1);
+    }
+    tcmapput(cols, "num", 3, vbuf, vsiz);
+    vsiz = sprintf(vbuf, "%d", myrand(32) + 1);
+    tcmapput(cols, "type", 4, vbuf, vsiz);
+    int num = myrand(5);
+    int pt = 0;
+    char *wp = vbuf;
+    for(int j = 0; j < num; j++){
+      pt += myrand(5) + 1;
+      if(wp > vbuf) *(wp++) = ',';
+      wp += sprintf(wp, "%d", pt);
+    }
+    *wp = '\0';
+    if(*vbuf != '\0'){
+      tcmapput(cols, "flag", 4, vbuf, wp - vbuf);
+      tcmapput(cols, "text", 4, vbuf, wp - vbuf);
+    }
+    if(!tctdbput(tdb, pkbuf, pksiz, cols)){
+      eprint(tdb, __LINE__, "tctdbput");
+      err = true;
+      break;
+    }
+    tcmapdel(cols);
+    if(id == 0 && rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  return err ? "error" : NULL;
+}
+
+
+/* thread the read function */
+static void *threadread(void *targ){
+  TCTDB *tdb = ((TARGREAD *)targ)->tdb;
+  int rnum = ((TARGREAD *)targ)->rnum;
+  bool rnd = ((TARGREAD *)targ)->rnd;
+  int id = ((TARGREAD *)targ)->id;
+  bool err = false;
+  int base = id * rnum;
+  for(int i = 1; i <= rnum && !err; i++){
+    char pkbuf[RECBUFSIZ];
+    int pksiz = sprintf(pkbuf, "%d", base + (rnd ? myrandnd(i) : i));
+    TCMAP *cols = tctdbget(tdb, pkbuf, pksiz);
+    if(cols){
+      tcmapdel(cols);
+    } else if(!rnd || tctdbecode(tdb) != TCENOREC){
+      eprint(tdb, __LINE__, "tctdbget");
+      err = true;
+    }
+    if(id == 0 && rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  return err ? "error" : NULL;
+}
+
+
+/* thread the remove function */
+static void *threadremove(void *targ){
+  TCTDB *tdb = ((TARGREMOVE *)targ)->tdb;
+  int rnum = ((TARGREMOVE *)targ)->rnum;
+  bool rnd = ((TARGREMOVE *)targ)->rnd;
+  int id = ((TARGREMOVE *)targ)->id;
+  bool err = false;
+  int base = id * rnum;
+  for(int i = 1; i <= rnum; i++){
+    char pkbuf[RECBUFSIZ];
+    int pksiz = sprintf(pkbuf, "%d", base + (rnd ? myrand(i + 1) : i));
+    if(!tctdbout(tdb, pkbuf, pksiz) && (!rnd || tctdbecode(tdb) != TCENOREC)){
+      eprint(tdb, __LINE__, "tctdbout");
+      err = true;
+      break;
+    }
+    if(id == 0 && rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  return err ? "error" : NULL;
+}
+
+
+/* thread the wicked function */
+static void *threadwicked(void *targ){
+  TCTDB *tdb = ((TARGWICKED *)targ)->tdb;
+  int rnum = ((TARGWICKED *)targ)->rnum;
+  int id = ((TARGWICKED *)targ)->id;
+  bool err = false;
+  const char *names[] = { "", "str", "num", "type", "flag", "c1" };
+  int ops[] = { TDBQCSTREQ, TDBQCSTRINC, TDBQCSTRBW, TDBQCSTREW, TDBQCSTRAND, TDBQCSTROR,
+                TDBQCSTROREQ, TDBQCSTRRX, TDBQCNUMEQ, TDBQCNUMGT, TDBQCNUMGE, TDBQCNUMLT,
+                TDBQCNUMLE, TDBQCNUMBT, TDBQCNUMOREQ };
+  int ftsops[] = { TDBQCFTSPH, TDBQCFTSAND, TDBQCFTSOR, TDBQCFTSEX };
+  int types[] = { TDBQOSTRASC, TDBQOSTRDESC, TDBQONUMASC, TDBQONUMDESC };
+  for(int i = 1; i <= rnum && !err; i++){
+    char pkbuf[RECBUFSIZ];
+    int pksiz = sprintf(pkbuf, "%d", myrand(rnum * (id + 1)));
+    TCMAP *cols = tcmapnew2(7);
+    char vbuf[RECBUFSIZ*5];
+    int vsiz = sprintf(vbuf, "%d", id);
+    tcmapput(cols, "str", 3, vbuf, vsiz);
+    if(myrand(3) == 0){
+      vsiz = sprintf(vbuf, "%.2f", (myrand(i * 100) + 1) / 100.0);
+    } else {
+      vsiz = sprintf(vbuf, "%d", myrand(i) + 1);
+    }
+    tcmapput(cols, "num", 3, vbuf, vsiz);
+    vsiz = sprintf(vbuf, "%d", myrand(32) + 1);
+    tcmapput(cols, "type", 4, vbuf, vsiz);
+    int num = myrand(5);
+    int pt = 0;
+    char *wp = vbuf;
+    for(int j = 0; j < num; j++){
+      pt += myrand(5) + 1;
+      if(wp > vbuf) *(wp++) = ',';
+      wp += sprintf(wp, "%d", pt);
+    }
+    *wp = '\0';
+    if(*vbuf != '\0'){
+      tcmapput(cols, "flag", 4, vbuf, wp - vbuf);
+      tcmapput(cols, "text", 4, vbuf, wp - vbuf);
+    }
+    char nbuf[RECBUFSIZ];
+    int nsiz = sprintf(nbuf, "c%d", myrand(i) + 1);
+    vsiz = sprintf(vbuf, "%d", myrand(i) + 1);
+    tcmapput(cols, nbuf, nsiz, vbuf, vsiz);
+    char *cbuf;
+    int csiz;
+    TCMAP *ncols;
+    TDBQRY *qry;
+    switch(myrand(17)){
+      case 0:
+        if(id == 0) iputchar('0');
+        if(!tctdbput(tdb, pkbuf, pksiz, cols)){
+          eprint(tdb, __LINE__, "tctdbput");
+          err = true;
+        }
+        break;
+      case 1:
+        if(id == 0) iputchar('1');
+        cbuf = tcstrjoin4(cols, &csiz);
+        if(!tctdbput2(tdb, pkbuf, pksiz, cbuf, csiz)){
+          eprint(tdb, __LINE__, "tctdbput2");
+          err = true;
+        }
+        tcfree(cbuf);
+        break;
+      case 2:
+        if(id == 0) iputchar('2');
+        cbuf = tcstrjoin3(cols, '\t');
+        if(!tctdbput3(tdb, pkbuf, cbuf)){
+          eprint(tdb, __LINE__, "tctdbput3");
+          err = true;
+        }
+        tcfree(cbuf);
+        break;
+      case 3:
+        if(id == 0) iputchar('3');
+        if(!tctdbputkeep(tdb, pkbuf, pksiz, cols) && tctdbecode(tdb) != TCEKEEP){
+          eprint(tdb, __LINE__, "tctdbputkeep");
+          err = true;
+        }
+        break;
+      case 4:
+        if(id == 0) iputchar('4');
+        cbuf = tcstrjoin4(cols, &csiz);
+        if(!tctdbputkeep2(tdb, pkbuf, pksiz, cbuf, csiz) && tctdbecode(tdb) != TCEKEEP){
+          eprint(tdb, __LINE__, "tctdbputkeep2");
+          err = true;
+        }
+        tcfree(cbuf);
+        break;
+      case 5:
+        if(id == 0) iputchar('5');
+        cbuf = tcstrjoin3(cols, '\t');
+        if(!tctdbputkeep3(tdb, pkbuf, cbuf) && tctdbecode(tdb) != TCEKEEP){
+          eprint(tdb, __LINE__, "tctdbputkeep3");
+          err = true;
+        }
+        tcfree(cbuf);
+        break;
+      case 6:
+        if(id == 0) iputchar('6');
+        if(!tctdbputcat(tdb, pkbuf, pksiz, cols)){
+          eprint(tdb, __LINE__, "tctdbputcat");
+          err = true;
+        }
+        break;
+      case 7:
+        if(id == 0) iputchar('7');
+        cbuf = tcstrjoin4(cols, &csiz);
+        if(!tctdbputcat2(tdb, pkbuf, pksiz, cbuf, csiz)){
+          eprint(tdb, __LINE__, "tctdbputcat2");
+          err = true;
+        }
+        tcfree(cbuf);
+        break;
+      case 8:
+        if(id == 0) iputchar('8');
+        cbuf = tcstrjoin3(cols, '\t');
+        if(!tctdbputcat3(tdb, pkbuf, cbuf)){
+          eprint(tdb, __LINE__, "tctdbputcat3");
+          err = true;
+        }
+        tcfree(cbuf);
+        break;
+      case 9:
+        if(id == 0) iputchar('9');
+        if(myrand(2) == 0){
+          if(!tctdbout(tdb, pkbuf, pksiz) && tctdbecode(tdb) != TCENOREC){
+            eprint(tdb, __LINE__, "tctdbout");
+            err = true;
+          }
+        }
+        break;
+      case 10:
+        if(id == 0) iputchar('A');
+        if(myrand(2) == 0){
+          if(!tctdbout2(tdb, pkbuf) && tctdbecode(tdb) != TCENOREC){
+            eprint(tdb, __LINE__, "tctdbout2");
+            err = true;
+          }
+        }
+        break;
+      case 11:
+        if(id == 0) iputchar('B');
+        ncols = tctdbget(tdb, pkbuf, pksiz);
+        if(ncols){
+          tcmapdel(ncols);
+        } else if(tctdbecode(tdb) != TCENOREC){
+          eprint(tdb, __LINE__, "tctdbget");
+          err = true;
+        }
+        break;
+      case 12:
+        if(id == 0) iputchar('C');
+        cbuf = tctdbget2(tdb, pkbuf, pksiz, &csiz);
+        if(cbuf){
+          tcfree(cbuf);
+        } else if(tctdbecode(tdb) != TCENOREC){
+          eprint(tdb, __LINE__, "tctdbget2");
+          err = true;
+        }
+        break;
+      case 13:
+        if(id == 0) iputchar('D');
+        cbuf = tctdbget3(tdb, pkbuf);
+        if(cbuf){
+          tcfree(cbuf);
+        } else if(tctdbecode(tdb) != TCENOREC){
+          eprint(tdb, __LINE__, "tctdbget3");
+          err = true;
+        }
+        break;
+      case 14:
+        if(id == 0) iputchar('E');
+        if(myrand(rnum / 50) == 0){
+          if(!tctdbiterinit(tdb)){
+            eprint(tdb, __LINE__, "tctdbiterinit");
+            err = true;
+          }
+        }
+        for(int j = myrand(rnum) / 1000 + 1; j >= 0; j--){
+          int iksiz;
+          char *ikbuf = tctdbiternext(tdb, &iksiz);
+          if(ikbuf){
+            tcfree(ikbuf);
+          } else {
+            int ecode = tctdbecode(tdb);
+            if(ecode != TCEINVALID && ecode != TCENOREC){
+              eprint(tdb, __LINE__, "tctdbiternext");
+              err = true;
+            }
+          }
+        }
+        break;
+      case 15:
+        if(id == 0) iputchar('F');
+        qry = tctdbqrynew(tdb);
+        if(myrand(10) != 0){
+          char expr[RECBUFSIZ];
+          sprintf(expr, "%d", myrand(i) + 1);
+          switch(myrand(6)){
+            default:
+              tctdbqryaddcond(qry, "str", TDBQCSTREQ, expr);
+              break;
+            case 1:
+              tctdbqryaddcond(qry, "str", TDBQCSTRBW, expr);
+              break;
+            case 2:
+              tctdbqryaddcond(qry, "str", TDBQCSTROREQ, expr);
+              break;
+            case 3:
+              tctdbqryaddcond(qry, "num", TDBQCNUMEQ, expr);
+              break;
+            case 4:
+              tctdbqryaddcond(qry, "num", TDBQCNUMGT, expr);
+              break;
+            case 5:
+              tctdbqryaddcond(qry, "num", TDBQCNUMLT, expr);
+              break;
+          }
+          switch(myrand(5)){
+            case 0:
+              tctdbqrysetorder(qry, "str", TDBQOSTRASC);
+              break;
+            case 1:
+              tctdbqrysetorder(qry, "str", TDBQOSTRDESC);
+              break;
+            case 2:
+              tctdbqrysetorder(qry, "num", TDBQONUMASC);
+              break;
+            case 3:
+              tctdbqrysetorder(qry, "num", TDBQONUMDESC);
+              break;
+          }
+          tctdbqrysetlimit(qry, 10, myrand(10));
+        } else {
+          int cnum = myrand(4);
+          if(cnum < 1 && myrand(5) != 0) cnum = 1;
+          for(int j = 0; j < cnum; j++){
+            const char *name = names[myrand(sizeof(names) / sizeof(*names))];
+            int op = ops[myrand(sizeof(ops) / sizeof(*ops))];
+            if(myrand(10) == 0) op = ftsops[myrand(sizeof(ftsops) / sizeof(*ftsops))];
+            if(myrand(20) == 0) op |= TDBQCNEGATE;
+            if(myrand(20) == 0) op |= TDBQCNOIDX;
+            char expr[RECBUFSIZ*3];
+            char *wp = expr;
+            if(myrand(3) == 0){
+              wp += sprintf(expr, "%f", myrand(i * 100) / 100.0);
+            } else {
+              wp += sprintf(expr, "%d", myrand(i));
+            }
+            if(myrand(10) == 0) wp += sprintf(wp, ",%d", myrand(i));
+            if(myrand(10) == 0) wp += sprintf(wp, ",%d", myrand(i));
+            tctdbqryaddcond(qry, name, op, expr);
+          }
+          if(myrand(3) != 0){
+            const char *name = names[myrand(sizeof(names) / sizeof(*names))];
+            int type = types[myrand(sizeof(types) / sizeof(*types))];
+            tctdbqrysetorder(qry, name, type);
+          }
+          if(myrand(3) != 0) tctdbqrysetlimit(qry, myrand(i), myrand(10));
+        }
+        if(myrand(10) == 0){
+          TCLIST *res = tctdbqrysearch(qry);
+          tclistdel(res);
+        }
+        tctdbqrydel(qry);
+        break;
+      default:
+        if(id == 0) iputchar('@');
+        if(tctdbtranbegin(tdb)){
+          if(myrand(2) == 0){
+            if(!tctdbput(tdb, pkbuf, pksiz, cols)){
+              eprint(tdb, __LINE__, "tctdbput");
+              err = true;
+            }
+          } else {
+            if(!tctdbout(tdb, pkbuf, pksiz) && tctdbecode(tdb) != TCENOREC){
+              eprint(tdb, __LINE__, "tctdbout");
+              err = true;
+            }
+          }
+          if(myrand(2) == 0){
+            if(!tctdbtranabort(tdb)){
+              eprint(tdb, __LINE__, "tctdbtranabort");
+              err = true;
+            }
+          } else {
+            if(!tctdbtrancommit(tdb)){
+              eprint(tdb, __LINE__, "tctdbtrancommit");
+              err = true;
+            }
+          }
+        } else {
+          eprint(tdb, __LINE__, "tctdbtranbegin");
+          err = true;
+        }
+        if(myrand(10000) == 0) srand((unsigned int)(tctime() * 1000) % UINT_MAX);
+        break;
+    }
+    tcmapdel(cols);
+    if(id == 0){
+      if(i % 50 == 0) iprintf(" (%08d)\n", i);
+      if(id == 0 && i == rnum / 4){
+        if(!tctdboptimize(tdb, rnum / 50, -1, -1, -1) && tctdbecode(tdb) != TCEINVALID){
+          eprint(tdb, __LINE__, "tctdboptimize");
+          err = true;
+        }
+        if(!tctdbiterinit(tdb)){
+          eprint(tdb, __LINE__, "tctdbiterinit");
+          err = true;
+        }
+      }
+    }
+  }
+  return err ? "error" : NULL;
+}
+
+
+/* thread the typical function */
+static void *threadtypical(void *targ){
+  TCTDB *tdb = ((TARGTYPICAL *)targ)->tdb;
+  int rnum = ((TARGTYPICAL *)targ)->rnum;
+  int rratio = ((TARGTYPICAL *)targ)->rratio;
+  int id = ((TARGTYPICAL *)targ)->id;
+  bool err = false;
+  int base = id * rnum;
+  int mrange = tclmax(50 + rratio, 100);
+  for(int i = 1; !err && i <= rnum; i++){
+    char pkbuf[RECBUFSIZ];
+    int pksiz = sprintf(pkbuf, "%08d", base + myrandnd(i));
+    int rnd = myrand(mrange);
+    if(rnd < 20){
+      TCMAP *cols = tcmapnew2(7);
+      char vbuf[RECBUFSIZ*5];
+      int vsiz = sprintf(vbuf, "%d", id);
+      tcmapput(cols, "str", 3, vbuf, vsiz);
+      if(myrand(3) == 0){
+        vsiz = sprintf(vbuf, "%.2f", (myrand(i * 100) + 1) / 100.0);
+      } else {
+        vsiz = sprintf(vbuf, "%d", myrand(i) + 1);
+      }
+      tcmapput(cols, "num", 3, vbuf, vsiz);
+      vsiz = sprintf(vbuf, "%d", myrand(32) + 1);
+      tcmapput(cols, "type", 4, vbuf, vsiz);
+      int num = myrand(5);
+      int pt = 0;
+      char *wp = vbuf;
+      for(int j = 0; j < num; j++){
+        pt += myrand(5) + 1;
+        if(wp > vbuf) *(wp++) = ',';
+        wp += sprintf(wp, "%d", pt);
+      }
+      *wp = '\0';
+      if(*vbuf != '\0'){
+        tcmapput(cols, "flag", 4, vbuf, wp - vbuf);
+        tcmapput(cols, "text", 4, vbuf, wp - vbuf);
+      }
+      char nbuf[RECBUFSIZ];
+      int nsiz = sprintf(nbuf, "c%d", myrand(i) + 1);
+      vsiz = sprintf(vbuf, "%d", myrand(i) + 1);
+      tcmapput(cols, nbuf, nsiz, vbuf, vsiz);
+      if(myrand(2) == 0){
+        if(!tctdbput(tdb, pkbuf, pksiz, cols)){
+          eprint(tdb, __LINE__, "tctdbput");
+          err = true;
+        }
+      } else {
+        if(!tctdbput(tdb, pkbuf, pksiz, cols) && tctdbecode(tdb) && tctdbecode(tdb) != TCEKEEP){
+          eprint(tdb, __LINE__, "tctdbput");
+          err = true;
+        }
+      }
+      tcmapdel(cols);
+    } else if(rnd < 30){
+      if(!tctdbout(tdb, pkbuf, pksiz) && tctdbecode(tdb) && tctdbecode(tdb) != TCENOREC){
+        eprint(tdb, __LINE__, "tctdbout");
+        err = true;
+      }
+    } else if(rnd < 31){
+      if(myrand(10) == 0 && !tctdbiterinit(tdb) && tctdbecode(tdb) != TCENOREC){
+        eprint(tdb, __LINE__, "tctdbiterinit");
+        err = true;
+      }
+      for(int j = 0; !err && j < 10; j++){
+        int ksiz;
+        char *kbuf = tctdbiternext(tdb, &ksiz);
+        if(kbuf){
+          tcfree(kbuf);
+        } else if(tctdbecode(tdb) != TCEINVALID && tctdbecode(tdb) != TCENOREC){
+          eprint(tdb, __LINE__, "tctdbiternext");
+          err = true;
+        }
+      }
+    } else {
+      TDBQRY *qry = tctdbqrynew(tdb);
+      char expr[RECBUFSIZ];
+      sprintf(expr, "%d", myrand(i) + 1);
+      switch(myrand(6) * 1){
+        default:
+          tctdbqryaddcond(qry, "str", TDBQCSTREQ, expr);
+          break;
+        case 1:
+          tctdbqryaddcond(qry, "str", TDBQCSTRBW, expr);
+          break;
+        case 2:
+          tctdbqryaddcond(qry, "str", TDBQCSTROREQ, expr);
+          break;
+        case 3:
+          tctdbqryaddcond(qry, "num", TDBQCNUMEQ, expr);
+          break;
+        case 4:
+          tctdbqryaddcond(qry, "num", TDBQCNUMGT, expr);
+          break;
+        case 5:
+          tctdbqryaddcond(qry, "num", TDBQCNUMLT, expr);
+          break;
+      }
+      tctdbqrysetlimit(qry, 10, myrand(5) * 10);
+      TCLIST *res = tctdbqrysearch(qry);
+      tclistdel(res);
+      tctdbqrydel(qry);
+    }
+    if(id == 0 && rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  return err ? "error" : NULL;
+}
+
+
+
+// END OF FILE
diff --git a/tcejdb/tcttest.c b/tcejdb/tcttest.c
new file mode 100644 (file)
index 0000000..7feed4a
--- /dev/null
@@ -0,0 +1,2062 @@
+/*************************************************************************************************
+ * The test cases of the table database API
+ *                                                               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 <tctdb.h>
+#include "myconf.h"
+
+#define RECBUFSIZ      48                // buffer for records
+
+
+/* global variables */
+const char *g_progname;                  // program name
+unsigned int g_randseed;                 // random seed
+int g_dbgfd;                             // debugging output
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+static void usage(void);
+static void iprintf(const char *format, ...);
+static void iputchar(int c);
+static void eprint(TCTDB *tdb, int line, const char *func);
+static void sysprint(void);
+static int myrand(int range);
+static void *pdprocfunc(const void *vbuf, int vsiz, int *sp, void *op);
+static bool iterfunc(const void *kbuf, int ksiz, const void *vbuf, int vsiz, void *op);
+static int runwrite(int argc, char **argv);
+static int runread(int argc, char **argv);
+static int runremove(int argc, char **argv);
+static int runrcat(int argc, char **argv);
+static int runmisc(int argc, char **argv);
+static int runwicked(int argc, char **argv);
+static int procwrite(const char *path, int rnum, int bnum, int apow, int fpow,
+                     bool mt, int opts, int rcnum, int lcnum, int ncnum, int xmsiz, int dfunit,
+                     int iflags, int omode, bool rnd);
+static int procread(const char *path, bool mt, int rcnum, int lcnum, int ncnum,
+                    int xmsiz, int dfunit, int omode, bool rnd);
+static int procremove(const char *path, bool mt, int rcnum, int lcnum, int ncnum,
+                      int xmsiz, int dfunit, int omode, bool rnd);
+static int procrcat(const char *path, int rnum, int bnum, int apow, int fpow,
+                    bool mt, int opts, int rcnum, int lcnum, int ncnum, int xmsiz, int dfunit,
+                    int iflags, int omode, int pnum, bool dai, bool dad, bool rl, bool ru);
+static int procmisc(const char *path, int rnum, bool mt, int opts, int omode);
+static int procwicked(const char *path, int rnum, bool mt, int opts, int omode);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  g_progname = argv[0];
+  const char *ebuf = getenv("TCRNDSEED");
+  g_randseed = ebuf ? tcatoix(ebuf) : tctime() * 1000;
+  srand(g_randseed);
+  ebuf = getenv("TCDBGFD");
+  g_dbgfd = ebuf ? tcatoix(ebuf) : UINT16_MAX;
+  if(argc < 2) usage();
+  int rv = 0;
+  if(!strcmp(argv[1], "write")){
+    rv = runwrite(argc, argv);
+  } else if(!strcmp(argv[1], "read")){
+    rv = runread(argc, argv);
+  } else if(!strcmp(argv[1], "remove")){
+    rv = runremove(argc, argv);
+  } else if(!strcmp(argv[1], "rcat")){
+    rv = runrcat(argc, argv);
+  } else if(!strcmp(argv[1], "misc")){
+    rv = runmisc(argc, argv);
+  } else if(!strcmp(argv[1], "wicked")){
+    rv = runwicked(argc, argv);
+  } else {
+    usage();
+  }
+  if(rv != 0){
+    printf("FAILED: TCRNDSEED=%u PID=%d", g_randseed, (int)getpid());
+    for(int i = 0; i < argc; i++){
+      printf(" %s", argv[i]);
+    }
+    printf("\n\n");
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+static void usage(void){
+  fprintf(stderr, "%s: test cases of the table database API of Tokyo Cabinet\n", g_progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s write [-mt] [-tl] [-td|-tb|-tt|-tx] [-rc num] [-lc num] [-nc num]"
+          " [-xm num] [-df num] [-ip] [-is] [-in] [-it] [-if] [-ix] [-nl|-nb] [-rnd]"
+          " path rnum [bnum [apow [fpow]]]\n", g_progname);
+  fprintf(stderr, "  %s read [-mt] [-rc num] [-lc num] [-nc num] [-xm num] [-df num]"
+          " [-nl|-nb] [-rnd] path\n", g_progname);
+  fprintf(stderr, "  %s remove [-mt] [-rc num] [-lc num] [-nc num] [-xm num] [-df num]"
+          " [-nl|-nb] [-rnd] path\n", g_progname);
+  fprintf(stderr, "  %s rcat [-mt] [-tl] [-td|-tb|-tt|-tx] [-rc num] [-lc num] [-nc num]"
+          " [-xm num] [-df num] [-ip] [-is] [-in] [-it] [-if] [-ix] [-nl|-nb] [-pn num]"
+          " [-dai|-dad|-rl|-ru] path rnum [bnum [apow [fpow]]]\n", g_progname);
+  fprintf(stderr, "  %s misc [-mt] [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] path rnum\n", g_progname);
+  fprintf(stderr, "  %s wicked [-mt] [-tl] [-td|-tb|-tt|-tx] [-nl|-nb] path rnum\n", g_progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* print formatted information string and flush the buffer */
+static void iprintf(const char *format, ...){
+  va_list ap;
+  va_start(ap, format);
+  vprintf(format, ap);
+  fflush(stdout);
+  va_end(ap);
+}
+
+
+/* print a character and flush the buffer */
+static void iputchar(int c){
+  putchar(c);
+  fflush(stdout);
+}
+
+
+/* print error message of table database */
+static void eprint(TCTDB *tdb, int line, const char *func){
+  const char *path = tctdbpath(tdb);
+  int ecode = tctdbecode(tdb);
+  fprintf(stderr, "%s: %s: %d: %s: error: %d: %s\n",
+          g_progname, path ? path : "-", line, func, ecode, tctdberrmsg(ecode));
+}
+
+
+/* print system information */
+static void sysprint(void){
+  TCMAP *info = tcsysinfo();
+  if(info){
+    tcmapiterinit(info);
+    const char *kbuf;
+    while((kbuf = tcmapiternext2(info)) != NULL){
+      iprintf("sys_%s: %s\n", kbuf, tcmapiterval2(kbuf));
+    }
+    tcmapdel(info);
+  }
+}
+
+
+/* get a random number */
+static int myrand(int range){
+  if(range < 2) return 0;
+  int high = (unsigned int)rand() >> 4;
+  int low = range * (rand() / (RAND_MAX + 1.0));
+  low &= (unsigned int)INT_MAX >> 4;
+  return (high + low) % range;
+}
+
+
+/* duplication callback function */
+static void *pdprocfunc(const void *vbuf, int vsiz, int *sp, void *op){
+  if(myrand(4) == 0) return (void *)-1;
+  if(myrand(2) == 0) return NULL;
+  int len = myrand(RECBUFSIZ - 5);
+  char buf[RECBUFSIZ];
+  memcpy(buf, "proc", 5);
+  memset(buf + 5, '*', len);
+  *sp = len + 5;
+  return tcmemdup(buf, len + 5);
+}
+
+
+/* iterator function */
+static bool iterfunc(const void *kbuf, int ksiz, const void *vbuf, int vsiz, void *op){
+  unsigned int sum = 0;
+  while(--ksiz >= 0){
+    sum += ((char *)kbuf)[ksiz];
+  }
+  while(--vsiz >= 0){
+    sum += ((char *)vbuf)[vsiz];
+  }
+  return myrand(100 + (sum & 0xff)) > 0;
+}
+
+
+/* parse arguments of write command */
+static int runwrite(int argc, char **argv){
+  char *path = NULL;
+  char *rstr = NULL;
+  char *bstr = NULL;
+  char *astr = NULL;
+  char *fstr = NULL;
+  bool mt = false;
+  int opts = 0;
+  int rcnum = 0;
+  int lcnum = 0;
+  int ncnum = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int iflags = 0;
+  int omode = 0;
+  bool rnd = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-tl")){
+        opts |= TDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= TDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= TDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= TDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= TDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-rc")){
+        if(++i >= argc) usage();
+        rcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-lc")){
+        if(++i >= argc) usage();
+        lcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nc")){
+        if(++i >= argc) usage();
+        ncnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-ip")){
+        iflags |= 1 << 0;
+      } else if(!strcmp(argv[i], "-is")){
+        iflags |= 1 << 1;
+      } else if(!strcmp(argv[i], "-in")){
+        iflags |= 1 << 2;
+      } else if(!strcmp(argv[i], "-it")){
+        iflags |= 1 << 3;
+      } else if(!strcmp(argv[i], "-if")){
+        iflags |= 1 << 4;
+      } else if(!strcmp(argv[i], "-ix")){
+        iflags |= 1 << 5;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= TDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= TDBOLCKNB;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else if(!astr){
+      astr = argv[i];
+    } else if(!fstr){
+      fstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int apow = astr ? tcatoix(astr) : -1;
+  int fpow = fstr ? tcatoix(fstr) : -1;
+  int rv = procwrite(path, rnum, bnum, apow, fpow, mt, opts, rcnum, lcnum, ncnum, xmsiz, dfunit,
+                     iflags, omode, rnd);
+  return rv;
+}
+
+
+/* parse arguments of read command */
+static int runread(int argc, char **argv){
+  char *path = NULL;
+  bool mt = false;
+  int rcnum = 0;
+  int lcnum = 0;
+  int ncnum = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int omode = 0;
+  bool rnd = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-rc")){
+        if(++i >= argc) usage();
+        rcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-lc")){
+        if(++i >= argc) usage();
+        lcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nc")){
+        if(++i >= argc) usage();
+        ncnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= TDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= TDBOLCKNB;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int rv = procread(path, mt, rcnum, lcnum, ncnum, xmsiz, dfunit, omode, rnd);
+  return rv;
+}
+
+
+/* parse arguments of remove command */
+static int runremove(int argc, char **argv){
+  char *path = NULL;
+  bool mt = false;
+  int rcnum = 0;
+  int lcnum = 0;
+  int ncnum = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int omode = 0;
+  bool rnd = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-rc")){
+        if(++i >= argc) usage();
+        rcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-lc")){
+        if(++i >= argc) usage();
+        lcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nc")){
+        if(++i >= argc) usage();
+        ncnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= TDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= TDBOLCKNB;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path) usage();
+  int rv = procremove(path, mt, rcnum, lcnum, ncnum, xmsiz, dfunit, omode, rnd);
+  return rv;
+}
+
+
+/* parse arguments of rcat command */
+static int runrcat(int argc, char **argv){
+  char *path = NULL;
+  char *rstr = NULL;
+  char *bstr = NULL;
+  char *astr = NULL;
+  char *fstr = NULL;
+  bool mt = false;
+  int opts = 0;
+  int rcnum = 0;
+  int lcnum = 0;
+  int ncnum = 0;
+  int xmsiz = -1;
+  int dfunit = 0;
+  int iflags = 0;
+  int omode = 0;
+  int pnum = 0;
+  bool dai = false;
+  bool dad = false;
+  bool rl = false;
+  bool ru = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-tl")){
+        opts |= TDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= TDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= TDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= TDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= TDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-xm")){
+        if(++i >= argc) usage();
+        xmsiz = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-df")){
+        if(++i >= argc) usage();
+        dfunit = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-rc")){
+        if(++i >= argc) usage();
+        rcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-lc")){
+        if(++i >= argc) usage();
+        lcnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-nc")){
+        if(++i >= argc) usage();
+        ncnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-ip")){
+        iflags |= 1 << 0;
+      } else if(!strcmp(argv[i], "-is")){
+        iflags |= 1 << 1;
+      } else if(!strcmp(argv[i], "-in")){
+        iflags |= 1 << 2;
+      } else if(!strcmp(argv[i], "-it")){
+        iflags |= 1 << 3;
+      } else if(!strcmp(argv[i], "-if")){
+        iflags |= 1 << 4;
+      } else if(!strcmp(argv[i], "-ix")){
+        iflags |= 1 << 5;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= TDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= TDBOLCKNB;
+      } else if(!strcmp(argv[i], "-pn")){
+        if(++i >= argc) usage();
+        pnum = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-dai")){
+        dai = true;
+      } else if(!strcmp(argv[i], "-dad")){
+        dad = true;
+      } else if(!strcmp(argv[i], "-rl")){
+        rl = true;
+      } else if(!strcmp(argv[i], "-ru")){
+        ru = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else if(!astr){
+      astr = argv[i];
+    } else if(!fstr){
+      fstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int apow = astr ? tcatoix(astr) : -1;
+  int fpow = fstr ? tcatoix(fstr) : -1;
+  int rv = procrcat(path, rnum, bnum, apow, fpow, mt, opts, rcnum, lcnum, ncnum, xmsiz, dfunit,
+                    iflags, omode, pnum, dai, dad, rl, ru);
+  return rv;
+}
+
+
+/* parse arguments of misc command */
+static int runmisc(int argc, char **argv){
+  char *path = NULL;
+  char *rstr = NULL;
+  bool mt = false;
+  int opts = 0;
+  int omode = 0;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-tl")){
+        opts |= TDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= TDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= TDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= TDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= TDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= TDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= TDBOLCKNB;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int rv = procmisc(path, rnum, mt, opts, omode);
+  return rv;
+}
+
+
+/* parse arguments of wicked command */
+static int runwicked(int argc, char **argv){
+  char *path = NULL;
+  char *rstr = NULL;
+  bool mt = false;
+  int opts = 0;
+  int omode = 0;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-mt")){
+        mt = true;
+      } else if(!strcmp(argv[i], "-tl")){
+        opts |= TDBTLARGE;
+      } else if(!strcmp(argv[i], "-td")){
+        opts |= TDBTDEFLATE;
+      } else if(!strcmp(argv[i], "-tb")){
+        opts |= TDBTBZIP;
+      } else if(!strcmp(argv[i], "-tt")){
+        opts |= TDBTTCBS;
+      } else if(!strcmp(argv[i], "-tx")){
+        opts |= TDBTEXCODEC;
+      } else if(!strcmp(argv[i], "-nl")){
+        omode |= TDBONOLCK;
+      } else if(!strcmp(argv[i], "-nb")){
+        omode |= TDBOLCKNB;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!path || !rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int rv = procwicked(path, rnum, mt, opts, omode);
+  return rv;
+}
+
+
+/* perform write command */
+static int procwrite(const char *path, int rnum, int bnum, int apow, int fpow,
+                     bool mt, int opts, int rcnum, int lcnum, int ncnum, int xmsiz, int dfunit,
+                     int iflags, int omode, bool rnd){
+  iprintf("<Writing Test>\n  seed=%u  path=%s  rnum=%d  bnum=%d  apow=%d  fpow=%d  mt=%d"
+          "  opts=%d  rcnum=%d  lcnum=%d  ncnum=%d  xmsiz=%d  dfunit=%d  iflags=%d"
+          "  omode=%d  rnd=%d\n\n",
+          g_randseed, path, rnum, bnum, apow, fpow, mt, opts, rcnum, lcnum, ncnum, xmsiz, dfunit,
+          iflags, omode, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCTDB *tdb = tctdbnew();
+  if(g_dbgfd >= 0) tctdbsetdbgfd(tdb, g_dbgfd);
+  if(mt && !tctdbsetmutex(tdb)){
+    eprint(tdb, __LINE__, "tctdbsetmutex");
+    err = true;
+  }
+  if(!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(tdb, __LINE__, "tctdbsetcodecfunc");
+    err = true;
+  }
+  if(!tctdbtune(tdb, bnum, apow, fpow, opts)){
+    eprint(tdb, __LINE__, "tctdbtune");
+    err = true;
+  }
+  if(!tctdbsetcache(tdb, rcnum, lcnum, ncnum)){
+    eprint(tdb, __LINE__, "tctdbsetcache");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tctdbsetxmsiz(tdb, xmsiz)){
+    eprint(tdb, __LINE__, "tctdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tctdbsetdfunit(tdb, dfunit)){
+    eprint(tdb, __LINE__, "tctdbsetdfunit");
+    err = true;
+  }
+  if(!rnd) omode |= TDBOTRUNC;
+  if(!tctdbopen(tdb, path, TDBOWRITER | TDBOCREAT | omode)){
+    eprint(tdb, __LINE__, "tctdbopen");
+    err = true;
+  }
+  if((iflags & (1 << 0)) && !tctdbsetindex(tdb, "", TDBITDECIMAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if((iflags & (1 << 1)) && !tctdbsetindex(tdb, "str", TDBITLEXICAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if((iflags & (1 << 2)) && !tctdbsetindex(tdb, "num", TDBITDECIMAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if((iflags & (1 << 3)) && !tctdbsetindex(tdb, "type", TDBITDECIMAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if((iflags & (1 << 4)) && !tctdbsetindex(tdb, "flag", TDBITTOKEN)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if((iflags & (1 << 5)) && !tctdbsetindex(tdb, "text", TDBITQGRAM)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  for(int i = 1; i <= rnum; i++){
+    int id = rnd ? myrand(rnum) + 1 : (int)tctdbgenuid(tdb);
+    char pkbuf[RECBUFSIZ];
+    int pksiz = sprintf(pkbuf, "%d", id);
+    TCMAP *cols = tcmapnew2(7);
+    char vbuf[RECBUFSIZ*5];
+    int vsiz = sprintf(vbuf, "%d", id);
+    tcmapput(cols, "str", 3, vbuf, vsiz);
+    if(myrand(3) == 0){
+      vsiz = sprintf(vbuf, "%.2f", (myrand(i * 100) + 1) / 100.0);
+    } else {
+      vsiz = sprintf(vbuf, "%d", myrand(i) + 1);
+    }
+    tcmapput(cols, "num", 3, vbuf, vsiz);
+    vsiz = sprintf(vbuf, "%d", myrand(32) + 1);
+    tcmapput(cols, "type", 4, vbuf, vsiz);
+    int num = myrand(5);
+    int pt = 0;
+    char *wp = vbuf;
+    for(int j = 0; j < num; j++){
+      pt += myrand(5) + 1;
+      if(wp > vbuf) *(wp++) = ',';
+      wp += sprintf(wp, "%d", pt);
+    }
+    *wp = '\0';
+    if(*vbuf != '\0'){
+      tcmapput(cols, "flag", 4, vbuf, wp - vbuf);
+      tcmapput(cols, "text", 4, vbuf, wp - vbuf);
+    }
+    if(!tctdbput(tdb, pkbuf, pksiz, cols)){
+      eprint(tdb, __LINE__, "tctdbput");
+      err = true;
+      break;
+    }
+    tcmapdel(cols);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tctdbrnum(tdb));
+  iprintf("size: %llu\n", (unsigned long long)tctdbfsiz(tdb));
+  sysprint();
+  if(!tctdbclose(tdb)){
+    eprint(tdb, __LINE__, "tctdbclose");
+    err = true;
+  }
+  tctdbdel(tdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform read command */
+static int procread(const char *path, bool mt, int rcnum, int lcnum, int ncnum,
+                    int xmsiz, int dfunit, int omode, bool rnd){
+  iprintf("<Reading Test>\n  seed=%u  path=%s  mt=%d  rcnum=%d  lcnum=%d  ncnum=%d"
+          "  xmsiz=%d  dfunit=%d  omode=%d  rnd=%d\n\n",
+          g_randseed, path, mt, rcnum, lcnum, ncnum, xmsiz, dfunit, omode, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCTDB *tdb = tctdbnew();
+  if(g_dbgfd >= 0) tctdbsetdbgfd(tdb, g_dbgfd);
+  if(mt && !tctdbsetmutex(tdb)){
+    eprint(tdb, __LINE__, "tctdbsetmutex");
+    err = true;
+  }
+  if(!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(tdb, __LINE__, "tctdbsetcodecfunc");
+    err = true;
+  }
+  if(!tctdbsetcache(tdb, rcnum, lcnum, ncnum)){
+    eprint(tdb, __LINE__, "tctdbsetcache");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tctdbsetxmsiz(tdb, xmsiz)){
+    eprint(tdb, __LINE__, "tctdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tctdbsetdfunit(tdb, dfunit)){
+    eprint(tdb, __LINE__, "tctdbsetdfunit");
+    err = true;
+  }
+  if(!tctdbopen(tdb, path, TDBOREADER | omode)){
+    eprint(tdb, __LINE__, "tctdbopen");
+    err = true;
+  }
+  int rnum = tctdbrnum(tdb);
+  for(int i = 1; i <= rnum; i++){
+    char pkbuf[RECBUFSIZ];
+    int pksiz = sprintf(pkbuf, "%d", rnd ? myrand(rnum) + 1 : i);
+    TCMAP *cols = tctdbget(tdb, pkbuf, pksiz);
+    if(cols){
+      tcmapdel(cols);
+    } else if(!rnd || tctdbecode(tdb) != TCENOREC){
+      eprint(tdb, __LINE__, "tctdbget");
+      err = true;
+      break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tctdbrnum(tdb));
+  iprintf("size: %llu\n", (unsigned long long)tctdbfsiz(tdb));
+  sysprint();
+  if(!tctdbclose(tdb)){
+    eprint(tdb, __LINE__, "tctdbclose");
+    err = true;
+  }
+  tctdbdel(tdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform remove command */
+static int procremove(const char *path, bool mt, int rcnum, int lcnum, int ncnum,
+                      int xmsiz, int dfunit, int omode, bool rnd){
+  iprintf("<Removing Test>\n  seed=%u  path=%s  mt=%d  rcnum=%d  lcnum=%d  ncnum=%d"
+          "  xmsiz=%d  dfunit=%d  omode=%d  rnd=%d\n\n",
+          g_randseed, path, mt, rcnum, lcnum, ncnum, xmsiz, dfunit, omode, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCTDB *tdb = tctdbnew();
+  if(g_dbgfd >= 0) tctdbsetdbgfd(tdb, g_dbgfd);
+  if(mt && !tctdbsetmutex(tdb)){
+    eprint(tdb, __LINE__, "tctdbsetmutex");
+    err = true;
+  }
+  if(!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(tdb, __LINE__, "tctdbsetcodecfunc");
+    err = true;
+  }
+  if(!tctdbsetcache(tdb, rcnum, lcnum, ncnum)){
+    eprint(tdb, __LINE__, "tctdbsetcache");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tctdbsetxmsiz(tdb, xmsiz)){
+    eprint(tdb, __LINE__, "tctdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tctdbsetdfunit(tdb, dfunit)){
+    eprint(tdb, __LINE__, "tctdbsetdfunit");
+    err = true;
+  }
+  if(!tctdbopen(tdb, path, TDBOWRITER | omode)){
+    eprint(tdb, __LINE__, "tctdbopen");
+    err = true;
+  }
+  int rnum = tctdbrnum(tdb);
+  for(int i = 1; i <= rnum; i++){
+    char pkbuf[RECBUFSIZ];
+    int pksiz = sprintf(pkbuf, "%d", rnd ? myrand(rnum) + 1 : i);
+    if(!tctdbout(tdb, pkbuf, pksiz) && !(rnd && tctdbecode(tdb) == TCENOREC)){
+      eprint(tdb, __LINE__, "tctdbout");
+      err = true;
+      break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tctdbrnum(tdb));
+  iprintf("size: %llu\n", (unsigned long long)tctdbfsiz(tdb));
+  sysprint();
+  if(!tctdbclose(tdb)){
+    eprint(tdb, __LINE__, "tctdbclose");
+    err = true;
+  }
+  tctdbdel(tdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform rcat command */
+static int procrcat(const char *path, int rnum, int bnum, int apow, int fpow,
+                    bool mt, int opts, int rcnum, int lcnum, int ncnum, int xmsiz, int dfunit,
+                    int iflags, int omode, int pnum, bool dai, bool dad, bool rl, bool ru){
+  iprintf("<Random Concatenating Test>\n"
+          "  seed=%u  path=%s  rnum=%d  bnum=%d  apow=%d  fpow=%d  mt=%d  opts=%d"
+          "  rcnum=%d  lcnum=%d  ncnum=%d  xmsiz=%d  dfunit=%d  iflags=%d"
+          "  omode=%d  pnum=%d  dai=%d  dad=%d  rl=%d  ru=%d\n\n",
+          g_randseed, path, rnum, bnum, apow, fpow, mt, opts, rcnum, lcnum, rcnum, xmsiz, dfunit,
+          iflags, omode, pnum, dai, dad, rl, ru);
+  if(pnum < 1) pnum = rnum;
+  bool err = false;
+  double stime = tctime();
+  TCTDB *tdb = tctdbnew();
+  if(g_dbgfd >= 0) tctdbsetdbgfd(tdb, g_dbgfd);
+  if(mt && !tctdbsetmutex(tdb)){
+    eprint(tdb, __LINE__, "tctdbsetmutex");
+    err = true;
+  }
+  if(!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(tdb, __LINE__, "tctdbsetcodecfunc");
+    err = true;
+  }
+  if(!tctdbtune(tdb, bnum, apow, fpow, opts)){
+    eprint(tdb, __LINE__, "tctdbtune");
+    err = true;
+  }
+  if(!tctdbsetcache(tdb, rcnum, lcnum, ncnum)){
+    eprint(tdb, __LINE__, "tctdbsetcache");
+    err = true;
+  }
+  if(xmsiz >= 0 && !tctdbsetxmsiz(tdb, xmsiz)){
+    eprint(tdb, __LINE__, "tctdbsetxmsiz");
+    err = true;
+  }
+  if(dfunit >= 0 && !tctdbsetdfunit(tdb, dfunit)){
+    eprint(tdb, __LINE__, "tctdbsetdfunit");
+    err = true;
+  }
+  if(!tctdbopen(tdb, path, TDBOWRITER | TDBOCREAT | TDBOTRUNC | omode)){
+    eprint(tdb, __LINE__, "tctdbopen");
+    err = true;
+  }
+  if((iflags & (1 << 0)) && !tctdbsetindex(tdb, "", TDBITDECIMAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if((iflags & (1 << 1)) && !tctdbsetindex(tdb, "str", TDBITLEXICAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if((iflags & (1 << 2)) && !tctdbsetindex(tdb, "num", TDBITDECIMAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if((iflags & (1 << 3)) && !tctdbsetindex(tdb, "type", TDBITDECIMAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if((iflags & (1 << 4)) && !tctdbsetindex(tdb, "flag", TDBITTOKEN)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if((iflags & (1 << 5)) && !tctdbsetindex(tdb, "text", TDBITQGRAM)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  for(int i = 1; i <= rnum; i++){
+    int id = myrand(pnum) + 1;
+    char pkbuf[RECBUFSIZ];
+    int pksiz = sprintf(pkbuf, "%d", id);
+    TCMAP *cols = tcmapnew2(7);
+    char vbuf[RECBUFSIZ*5];
+    int vsiz = sprintf(vbuf, "%d", id);
+    tcmapput(cols, "str", 3, vbuf, vsiz);
+    if(myrand(3) == 0){
+      vsiz = sprintf(vbuf, "%.2f", (myrand(i * 100) + 1) / 100.0);
+    } else {
+      vsiz = sprintf(vbuf, "%d", myrand(i) + 1);
+    }
+    tcmapput(cols, "num", 3, vbuf, vsiz);
+    vsiz = sprintf(vbuf, "%d", myrand(32) + 1);
+    tcmapput(cols, "type", 4, vbuf, vsiz);
+    int num = myrand(5);
+    int pt = 0;
+    char *wp = vbuf;
+    for(int j = 0; j < num; j++){
+      pt += myrand(5) + 1;
+      if(wp > vbuf) *(wp++) = ',';
+      wp += sprintf(wp, "%d", pt);
+    }
+    *wp = '\0';
+    if(*vbuf != '\0'){
+      tcmapput(cols, "flag", 4, vbuf, wp - vbuf);
+      tcmapput(cols, "text", 4, vbuf, wp - vbuf);
+    }
+    char nbuf[RECBUFSIZ];
+    int nsiz = sprintf(nbuf, "c%d", myrand(pnum) + 1);
+    vsiz = sprintf(vbuf, "%d", myrand(rnum) + 1);
+    tcmapput(cols, nbuf, nsiz, vbuf, vsiz);
+    if(ru){
+      switch(myrand(8)){
+        case 0:
+          if(!tctdbput(tdb, pkbuf, pksiz, cols)){
+            eprint(tdb, __LINE__, "tctdbput");
+            err = true;
+          }
+          break;
+        case 1:
+          if(!tctdbputkeep(tdb, pkbuf, pksiz, cols) && tctdbecode(tdb) != TCEKEEP){
+            eprint(tdb, __LINE__, "tctdbputkeep");
+            err = true;
+          }
+          break;
+        case 2:
+          if(!tctdbout(tdb, pkbuf, pksiz) && tctdbecode(tdb) != TCENOREC){
+            eprint(tdb, __LINE__, "tctdbout");
+            err = true;
+          }
+          break;
+        case 3:
+          if(tctdbaddint(tdb, pkbuf, pksiz, 1) == INT_MIN && tctdbecode(tdb) != TCEKEEP){
+            eprint(tdb, __LINE__, "tctdbaddint");
+            err = true;
+          }
+          break;
+        case 4:
+          if(isnan(tctdbadddouble(tdb, pkbuf, pksiz, 1.0)) && tctdbecode(tdb) != TCEKEEP){
+            eprint(tdb, __LINE__, "tctdbadddouble");
+            err = true;
+          }
+          break;
+        case 5:
+          if(myrand(2) == 0){
+            if(!tctdbputproc(tdb, pkbuf, pksiz, pkbuf, pksiz, pdprocfunc, NULL) &&
+               tctdbecode(tdb) != TCEKEEP){
+              eprint(tdb, __LINE__, "tctdbputproc");
+              err = true;
+            }
+          } else {
+            if(!tctdbputproc(tdb, pkbuf, pksiz, NULL, 0, pdprocfunc, NULL) &&
+               tctdbecode(tdb) != TCEKEEP && tctdbecode(tdb) != TCENOREC){
+              eprint(tdb, __LINE__, "tctdbputproc");
+              err = true;
+            }
+          }
+          break;
+        default:
+          if(!tctdbputcat(tdb, pkbuf, pksiz, cols)){
+            eprint(tdb, __LINE__, "tctdbputcat");
+            err = true;
+          }
+          break;
+      }
+      if(err) break;
+    } else {
+      if(dai){
+        if(tctdbaddint(tdb, pkbuf, pksiz, myrand(3)) == INT_MIN){
+          eprint(tdb, __LINE__, "tctdbaddint");
+          err = true;
+          break;
+        }
+      } else if(dad){
+        if(isnan(tctdbadddouble(tdb, pkbuf, pksiz, myrand(30) / 10.0))){
+          eprint(tdb, __LINE__, "tctdbadddouble");
+          err = true;
+          break;
+        }
+      } else if(rl){
+        char fbuf[PATH_MAX];
+        int fsiz = myrand(PATH_MAX);
+        for(int j = 0; j < fsiz; j++){
+          fbuf[j] = myrand(0x100);
+        }
+        tcmapput(cols, "bin", 3, fbuf, fsiz);
+        if(!tctdbputcat(tdb, pkbuf, pksiz, cols)){
+          eprint(tdb, __LINE__, "tctdbputcat");
+          err = true;
+          break;
+        }
+      } else {
+        if(!tctdbputcat(tdb, pkbuf, pksiz, cols)){
+          eprint(tdb, __LINE__, "tctdbputcat");
+          err = true;
+          break;
+        }
+      }
+    }
+    tcmapdel(cols);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tctdbrnum(tdb));
+  iprintf("size: %llu\n", (unsigned long long)tctdbfsiz(tdb));
+  sysprint();
+  if(!tctdbclose(tdb)){
+    eprint(tdb, __LINE__, "tctdbclose");
+    err = true;
+  }
+  tctdbdel(tdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform misc command */
+static int procmisc(const char *path, int rnum, bool mt, int opts, int omode){
+  iprintf("<Miscellaneous Test>\n  seed=%u  path=%s  rnum=%d  mt=%d  opts=%d  omode=%d\n\n",
+          g_randseed, path, rnum, mt, opts, omode);
+  bool err = false;
+  double stime = tctime();
+  TCTDB *tdb = tctdbnew();
+  if(g_dbgfd >= 0) tctdbsetdbgfd(tdb, g_dbgfd);
+  if(mt && !tctdbsetmutex(tdb)){
+    eprint(tdb, __LINE__, "tctdbsetmutex");
+    err = true;
+  }
+  if(!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(tdb, __LINE__, "tctdbsetcodecfunc");
+    err = true;
+  }
+  if(!tctdbtune(tdb, rnum / 50, 2, -1, opts)){
+    eprint(tdb, __LINE__, "tctdbtune");
+    err = true;
+  }
+  if(!tctdbsetcache(tdb, rnum / 10, 128, 256)){
+    eprint(tdb, __LINE__, "tctdbsetcache");
+    err = true;
+  }
+  if(!tctdbsetxmsiz(tdb, rnum * sizeof(int))){
+    eprint(tdb, __LINE__, "tctdbsetxmsiz");
+    err = true;
+  }
+  if(!tctdbsetdfunit(tdb, 8)){
+    eprint(tdb, __LINE__, "tctdbsetdfunit");
+    err = true;
+  }
+  if(!tctdbsetinvcache(tdb, -1, 0.5)){
+    eprint(tdb, __LINE__, "tctdbsetinvcache");
+    err = true;
+  }
+  if(!tctdbopen(tdb, path, TDBOWRITER | TDBOCREAT | TDBOTRUNC | omode)){
+    eprint(tdb, __LINE__, "tctdbopen");
+    err = true;
+  }
+  if(!tctdbsetindex(tdb, "", TDBITDECIMAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if(!tctdbsetindex(tdb, "str", TDBITLEXICAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if(!tctdbsetindex(tdb, "num", TDBITDECIMAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if(!tctdbsetindex(tdb, "type", TDBITDECIMAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if(!tctdbsetindex(tdb, "flag", TDBITTOKEN)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if(!tctdbsetindex(tdb, "text", TDBITQGRAM)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if(TCUSEPTHREAD){
+    TCTDB *tdbdup = tctdbnew();
+    if(tctdbopen(tdbdup, path, TDBOREADER)){
+      eprint(tdb, __LINE__, "(validation)");
+      err = true;
+    } else if(tctdbecode(tdbdup) != TCETHREAD){
+      eprint(tdb, __LINE__, "(validation)");
+      err = true;
+    }
+    tctdbdel(tdbdup);
+  }
+  iprintf("writing:\n");
+  for(int i = 1; i <= rnum; i++){
+    int id = (int)tctdbgenuid(tdb);
+    char pkbuf[RECBUFSIZ];
+    int pksiz = sprintf(pkbuf, "%d", id);
+    TCMAP *cols = tcmapnew2(7);
+    char vbuf[RECBUFSIZ*5];
+    int vsiz = sprintf(vbuf, "%d", id);
+    tcmapput(cols, "str", 3, vbuf, vsiz);
+    if(myrand(3) == 0){
+      vsiz = sprintf(vbuf, "%.2f", (myrand(i * 100) + 1) / 100.0);
+    } else {
+      vsiz = sprintf(vbuf, "%d", myrand(i) + 1);
+    }
+    tcmapput(cols, "num", 3, vbuf, vsiz);
+    vsiz = sprintf(vbuf, "%d", myrand(32) + 1);
+    tcmapput(cols, "type", 4, vbuf, vsiz);
+    int num = myrand(5);
+    int pt = 0;
+    char *wp = vbuf;
+    for(int j = 0; j < num; j++){
+      pt += myrand(5) + 1;
+      if(wp > vbuf) *(wp++) = ',';
+      wp += sprintf(wp, "%d", pt);
+    }
+    *wp = '\0';
+    if(*vbuf != '\0'){
+      tcmapput(cols, "flag", 4, vbuf, wp - vbuf);
+      tcmapput(cols, "text", 4, vbuf, wp - vbuf);
+    }
+    char nbuf[RECBUFSIZ];
+    int nsiz = sprintf(nbuf, "c%d", myrand(32) + 1);
+    vsiz = sprintf(vbuf, "%d", myrand(32) + 1);
+    tcmapput(cols, nbuf, nsiz, vbuf, vsiz);
+    if(i % 3 == 0){
+      if(!tctdbputkeep(tdb, pkbuf, pksiz, cols)){
+        eprint(tdb, __LINE__, "tctdbputkeep");
+        err = true;
+        break;
+      }
+    } else {
+      if(!tctdbputcat(tdb, pkbuf, pksiz, cols)){
+        eprint(tdb, __LINE__, "tctdbputcat");
+        err = true;
+        break;
+      }
+    }
+    tcmapdel(cols);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("reading:\n");
+  for(int i = 1; i <= rnum; i++){
+    char pkbuf[RECBUFSIZ];
+    int pksiz = sprintf(pkbuf, "%d", i);
+    TCMAP *cols = tctdbget(tdb, pkbuf, pksiz);
+    if(cols){
+      const char *vbuf = tcmapget2(cols, "str");
+      if(!vbuf || strcmp(vbuf, pkbuf)){
+        eprint(tdb, __LINE__, "(validation)");
+        tcmapdel(cols);
+        err = true;
+        break;
+      }
+      tcmapdel(cols);
+    } else {
+      eprint(tdb, __LINE__, "tctdbget");
+      err = true;
+      break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("erasing:\n");
+  for(int i = 1; i <= rnum; i++){
+    if(i % 2 == 1){
+      char pkbuf[RECBUFSIZ];
+      int pksiz = sprintf(pkbuf, "%d", i);
+      if(!tctdbout(tdb, pkbuf, pksiz)){
+        eprint(tdb, __LINE__, "tctdbout");
+        err = true;
+        break;
+      }
+      if(tctdbout(tdb, pkbuf, pksiz) || tctdbecode(tdb) != TCENOREC){
+        eprint(tdb, __LINE__, "tctdbout");
+        err = true;
+        break;
+      }
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(tctdbrnum(tdb) != rnum / 2){
+    eprint(tdb, __LINE__, "tctdbrnum");
+    err = true;
+  }
+  if(tctdbuidseed(tdb) != rnum){
+    eprint(tdb, __LINE__, "tctdbuidseed");
+    err = true;
+  }
+  iprintf("searching:\n");
+  TDBQRY *qry = tctdbqrynew(tdb);
+  TCLIST *res = tctdbqrysearch(qry);
+  if(tclistnum(res) != rnum / 2){
+    eprint(tdb, __LINE__, "tctdbqrysearch");
+    err = true;
+  }
+  tclistdel(res);
+  tctdbqrydel(qry);
+  const char *names[] = { "", "str", "num", "type", "flag", "text", "c1" };
+  int ops[] = { TDBQCSTREQ, TDBQCSTRINC, TDBQCSTRBW, TDBQCSTREW, TDBQCSTRAND, TDBQCSTROR,
+                TDBQCSTROREQ, TDBQCSTRRX, TDBQCNUMEQ, TDBQCNUMGT, TDBQCNUMGE, TDBQCNUMLT,
+                TDBQCNUMLE, TDBQCNUMBT, TDBQCNUMOREQ };
+  int ftsops[] = { TDBQCFTSPH, TDBQCFTSAND, TDBQCFTSOR, TDBQCFTSEX };
+  int types[] = { TDBQOSTRASC, TDBQOSTRDESC, TDBQONUMASC, TDBQONUMDESC };
+  qry = tctdbqrynew(tdb);
+  for(int i = 1; i <= rnum; i++){
+    if(myrand(100) != 0){
+      tctdbqrydel(qry);
+      qry = tctdbqrynew(tdb);
+      if(myrand(10) != 0){
+        char expr[RECBUFSIZ];
+        sprintf(expr, "%d", myrand(i) + 1);
+        switch(myrand(6)){
+          default:
+            tctdbqryaddcond(qry, "str", TDBQCSTREQ, expr);
+            break;
+          case 1:
+            tctdbqryaddcond(qry, "str", TDBQCSTRBW, expr);
+            break;
+          case 2:
+            tctdbqryaddcond(qry, "str", TDBQCSTROREQ, expr);
+            break;
+          case 3:
+            tctdbqryaddcond(qry, "num", TDBQCNUMEQ, expr);
+            break;
+          case 4:
+            tctdbqryaddcond(qry, "num", TDBQCNUMGT, expr);
+            break;
+          case 5:
+            tctdbqryaddcond(qry, "num", TDBQCNUMLT, expr);
+            break;
+        }
+        switch(myrand(5)){
+          case 0:
+            tctdbqrysetorder(qry, "str", TDBQOSTRASC);
+            break;
+          case 1:
+            tctdbqrysetorder(qry, "str", TDBQOSTRDESC);
+            break;
+          case 2:
+            tctdbqrysetorder(qry, "num", TDBQONUMASC);
+            break;
+          case 3:
+            tctdbqrysetorder(qry, "num", TDBQONUMDESC);
+            break;
+        }
+        tctdbqrysetlimit(qry, 10, myrand(5) * 10);
+      } else {
+        int cnum = myrand(4);
+        if(cnum < 1 && myrand(5) != 0) cnum = 1;
+        for(int j = 0; j < cnum; j++){
+          const char *name = names[myrand(sizeof(names) / sizeof(*names))];
+          int op = ops[myrand(sizeof(ops) / sizeof(*ops))];
+          if(myrand(10) == 0) op = ftsops[myrand(sizeof(ftsops) / sizeof(*ftsops))];
+          if(myrand(20) == 0) op |= TDBQCNEGATE;
+          if(myrand(20) == 0) op |= TDBQCNOIDX;
+          char expr[RECBUFSIZ*3];
+          char *wp = expr;
+          if(myrand(3) == 0){
+            wp += sprintf(expr, "%f", myrand(i * 100) / 100.0);
+          } else {
+            wp += sprintf(expr, "%d", myrand(i));
+          }
+          if(myrand(10) == 0) wp += sprintf(wp, ",%d", myrand(i));
+          if(myrand(10) == 0) wp += sprintf(wp, ",%d", myrand(i));
+          tctdbqryaddcond(qry, name, op, expr);
+        }
+        if(myrand(3) != 0){
+          const char *name = names[myrand(sizeof(names) / sizeof(*names))];
+          int type = types[myrand(sizeof(types) / sizeof(*types))];
+          tctdbqrysetorder(qry, name, type);
+        }
+        if(myrand(3) != 0) tctdbqrysetlimit(qry, myrand(i), myrand(10));
+      }
+    }
+    if(myrand(10) == 0){
+      TDBQRY *qrys[4];
+      int num = myrand(5);
+      for(int j = 0; j < num; j++){
+        qrys[j] = qry;
+      }
+      TCLIST *res = tctdbmetasearch(qrys, num, TDBMSUNION + myrand(3));
+      if(num > 0){
+        for(int j = 0; j < 3 && j < tclistnum(res); j++){
+          int pksiz;
+          const char *pkbuf = tclistval(res, j, &pksiz);
+          TCMAP *cols = tctdbget(tdb, pkbuf, pksiz);
+          if(cols){
+            TCLIST *texts = tctdbqrykwic(qrys[0], cols, NULL, myrand(10), TCKWNOOVER);
+            tclistdel(texts);
+            tcmapdel(cols);
+          } else {
+            eprint(tdb, __LINE__, "tctdbget");
+            err = true;
+          }
+        }
+      }
+      tclistdel(res);
+    } else {
+      TCLIST *res = tctdbqrysearch(qry);
+      tclistdel(res);
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  tctdbqrydel(qry);
+  iprintf("random writing and reopening:\n");
+  for(int i = 1; i <= rnum; i++){
+    char pkbuf[RECBUFSIZ];
+    int pksiz = sprintf(pkbuf, "%d", myrand(rnum) + 1);
+    switch(myrand(4)){
+      default:
+        if(!tctdbout(tdb, pkbuf, pksiz) && tctdbecode(tdb) != TCENOREC){
+          eprint(tdb, __LINE__, "tctdbout");
+          err = true;
+        }
+        break;
+      case 1:
+        if(!tctdbaddint(tdb, pkbuf, pksiz, 1)){
+          eprint(tdb, __LINE__, "tctdbaddint");
+          err = true;
+        }
+        break;
+      case 2:
+        if(!tctdbadddouble(tdb, pkbuf, pksiz, 1.0)){
+          eprint(tdb, __LINE__, "tctdbadddouble");
+          err = true;
+        }
+        break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(!tctdbclose(tdb)){
+    eprint(tdb, __LINE__, "tctdbclose");
+    err = true;
+  }
+  if(!tctdbopen(tdb, path, TDBOWRITER | omode)){
+    eprint(tdb, __LINE__, "tctdbopen");
+    err = true;
+  }
+  iprintf("random updating:\n");
+  for(int i = 1; i <= rnum; i++){
+    if(myrand(2) == 0){
+      char pkbuf[RECBUFSIZ];
+      int pksiz = sprintf(pkbuf, "%X", myrand(rnum) + 1);
+      TCMAP *cols = tcmapnew2(7);
+      char vbuf[RECBUFSIZ*2];
+      int vsiz = sprintf(vbuf, "%d", myrand(i) + 1);
+      tcmapput(cols, "c1", 3, vbuf, vsiz);
+      vsiz = sprintf(vbuf, " %d, %d ", myrand(i) + 1, rnum / (myrand(rnum) + 1));
+      tcmapput(cols, "flag", 4, vbuf, vsiz);
+      tcmapput(cols, "text", 4, vbuf, vsiz);
+      if(!tctdbputcat(tdb, pkbuf, pksiz, cols)){
+        eprint(tdb, __LINE__, "tctdbputcat");
+        err = true;
+        break;
+      }
+      tcmapdel(cols);
+    } else {
+      char pkbuf[RECBUFSIZ];
+      int pksiz = sprintf(pkbuf, "%X", myrand(rnum) + 1);
+      if(!tctdbout(tdb, pkbuf, pksiz) && tctdbecode(tdb) != TCENOREC){
+        eprint(tdb, __LINE__, "tctdbout");
+        err = true;
+        break;
+      }
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("checking iterator:\n");
+  int inum = 0;
+  if(!tctdbiterinit(tdb)){
+    eprint(tdb, __LINE__, "tctdbiterinit");
+    err = true;
+  }
+  char *pkbuf;
+  int pksiz;
+  for(int i = 1; (pkbuf = tctdbiternext(tdb, &pksiz)) != NULL; i++, inum++){
+    TCMAP *cols = tctdbget(tdb, pkbuf, pksiz);
+    if(!cols){
+      eprint(tdb, __LINE__, "tctdbget");
+      err = true;
+      tcfree(pkbuf);
+      break;
+    }
+    tcmapdel(cols);
+    tcfree(pkbuf);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(rnum > 250) iprintf(" (%08d)\n", inum);
+  if(tctdbecode(tdb) != TCENOREC || inum != tctdbrnum(tdb)){
+    eprint(tdb, __LINE__, "(validation)");
+    err = true;
+  }
+  iprintf("checking search consistency:\n");
+  for(int i = 1; i <= rnum; i++){
+    TDBQRY *myqry = tctdbqrynew(tdb);
+    qry = tctdbqrynew(tdb);
+    int cnum = myrand(4);
+    if(cnum < 1) cnum = 1;
+    for(int j = 0; j < cnum; j++){
+      const char *name = names[myrand(sizeof(names) / sizeof(*names))];
+      int op = ops[myrand(sizeof(ops) / sizeof(*ops))];
+      if(myrand(10) == 0) op = ftsops[myrand(sizeof(ftsops) / sizeof(*ftsops))];
+      char expr[RECBUFSIZ*3];
+      char *wp = expr;
+      if(myrand(3) == 0){
+        wp += sprintf(expr, "%f", myrand(i * 100) / 100.0);
+      } else {
+        wp += sprintf(expr, "%d", myrand(i));
+      }
+      if(myrand(10) == 0) wp += sprintf(wp, ",%d", myrand(i));
+      if(myrand(10) == 0) wp += sprintf(wp, ",%d", myrand(i));
+      tctdbqryaddcond(myqry, name, op | (myrand(2) == 0 ? TDBQCNOIDX : 0), expr);
+      tctdbqryaddcond(qry, name, op | (myrand(2) == 0 ? TDBQCNOIDX : 0), expr);
+    }
+    int max = (myrand(10) == 0) ? 0 : myrand(10) + 1;
+    if(max > 0){
+      tctdbqrysetlimit(myqry, max, 0);
+      tctdbqrysetlimit(qry, max, 0);
+      TCLIST *myres = tctdbqrysearch(myqry);
+      res = tctdbqrysearch(qry);
+      if(tclistnum(myres) != tclistnum(res)){
+        eprint(tdb, __LINE__, "(validation)");
+        err = true;
+      }
+      tclistdel(res);
+      tclistdel(myres);
+    } else {
+      TCLIST *myres = tctdbqrysearch(myqry);
+      res = tctdbqrysearch(qry);
+      if(tclistnum(myres) == tclistnum(res)){
+        tclistsort(myres);
+        tclistsort(res);
+        int rnum = tclistnum(myres);
+        for(int j = 0; j < rnum; j++){
+          int myrsiz;
+          const char *myrbuf = tclistval(myres, j, &myrsiz);
+          int rsiz;
+          const char *rbuf = tclistval(res, j, &rsiz);
+          if(myrsiz != rsiz || memcmp(myrbuf, rbuf, myrsiz)){
+            eprint(tdb, __LINE__, "(validation)");
+            err = true;
+            break;
+          }
+        }
+      } else {
+        eprint(tdb, __LINE__, "(validation)");
+        err = true;
+      }
+      tclistdel(res);
+      tclistdel(myres);
+    }
+    tctdbqrydel(qry);
+    tctdbqrydel(myqry);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  qry = tctdbqrynew(tdb);
+  tctdbqryaddcond(qry, "", TDBQCSTRBW, "1");
+  if(!tctdbqrysearchout(qry)){
+    eprint(tdb, __LINE__, "tctdbqrysearchout");
+    err = true;
+  }
+  tctdbqrydel(qry);
+  qry = tctdbqrynew(tdb);
+  tctdbqryaddcond(qry, "", TDBQCSTRBW, "2");
+  if(!tctdbqrysearchout2(qry)){
+    eprint(tdb, __LINE__, "tctdbqrysearchout2");
+    err = true;
+  }
+  tctdbqrydel(qry);
+  if(myrand(4) == 0 && !tctdbdefrag(tdb, 0)){
+    eprint(tdb, __LINE__, "tctdbdefrag");
+    err = true;
+  }
+  if(myrand(4) == 0 && !tctdbcacheclear(tdb)){
+    eprint(tdb, __LINE__, "tctdbcacheclear");
+    err = true;
+  }
+  iprintf("checking transaction commit:\n");
+  if(!tctdbtranbegin(tdb)){
+    eprint(tdb, __LINE__, "tctdbtranbegin");
+    err = true;
+  }
+  for(int i = 1; i <= rnum; i++){
+    char pkbuf[RECBUFSIZ];
+    int pksiz = sprintf(pkbuf, "%d", myrand(rnum));
+    switch(myrand(4)){
+      default:
+        if(!tctdbout(tdb, pkbuf, pksiz) && tctdbecode(tdb) != TCENOREC){
+          eprint(tdb, __LINE__, "tctdbout");
+          err = true;
+        }
+        break;
+      case 1:
+        if(!tctdbaddint(tdb, pkbuf, pksiz, 1)){
+          eprint(tdb, __LINE__, "tctdbaddint");
+          err = true;
+        }
+        break;
+      case 2:
+        if(!tctdbadddouble(tdb, pkbuf, pksiz, 1.0)){
+          eprint(tdb, __LINE__, "tctdbadddouble");
+          err = true;
+        }
+        break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(!tctdbtrancommit(tdb)){
+    eprint(tdb, __LINE__, "tctdbtrancommit");
+    err = true;
+  }
+  iprintf("checking transaction abort:\n");
+  uint64_t ornum = tctdbrnum(tdb);
+  uint64_t ofsiz = tctdbfsiz(tdb);
+  if(!tctdbtranbegin(tdb)){
+    eprint(tdb, __LINE__, "tctdbtranbegin");
+    err = true;
+  }
+  for(int i = 1; i <= rnum; i++){
+    char pkbuf[RECBUFSIZ];
+    int pksiz = sprintf(pkbuf, "%d", myrand(rnum));
+    switch(myrand(4)){
+      default:
+        if(!tctdbout(tdb, pkbuf, pksiz) && tctdbecode(tdb) != TCENOREC){
+          eprint(tdb, __LINE__, "tctdbout");
+          err = true;
+        }
+        break;
+      case 1:
+        if(!tctdbaddint(tdb, pkbuf, pksiz, 1)){
+          eprint(tdb, __LINE__, "tctdbaddint");
+          err = true;
+        }
+        break;
+      case 2:
+        if(!tctdbadddouble(tdb, pkbuf, pksiz, 1.0)){
+          eprint(tdb, __LINE__, "tctdbadddouble");
+          err = true;
+        }
+        break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(!tctdbtranabort(tdb)){
+    eprint(tdb, __LINE__, "tctdbtranabort");
+    err = true;
+  }
+  if(tctdbrnum(tdb) != ornum || tctdbfsiz(tdb) != ofsiz){
+    eprint(tdb, __LINE__, "(validation)");
+    err = true;
+  }
+  if(!tctdbvanish(tdb)){
+    eprint(tdb, __LINE__, "tctdbvanish");
+    err = true;
+  }
+  if(!tctdbput3(tdb, "mikio", "str\tMIKIO\tbirth\t19780211")){
+    eprint(tdb, __LINE__, "tctdbput3");
+    err = true;
+  }
+  if(!tctdbtranbegin(tdb)){
+    eprint(tdb, __LINE__, "tctdbtranbegin");
+    err = true;
+  }
+  if(!tctdbput3(tdb, "mikio", "str\tMEKEO\tsex\tmale")){
+    eprint(tdb, __LINE__, "tctdbput3");
+    err = true;
+  }
+  for(int i = 0; i < 10; i++){
+    char pkbuf[RECBUFSIZ];
+    sprintf(pkbuf, "%d", myrand(10) + 1);
+    char vbuf[RECBUFSIZ*2];
+    sprintf(vbuf, "%d\t%d", myrand(10) + 1, myrand(rnum) + 1);
+    if(!tctdbput3(tdb, pkbuf, vbuf)){
+      eprint(tdb, __LINE__, "tctdbput");
+      err = true;
+    }
+  }
+  if(!tctdbforeach(tdb, iterfunc, NULL)){
+    eprint(tdb, __LINE__, "tctdbforeach");
+    err = true;
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tctdbrnum(tdb));
+  iprintf("size: %llu\n", (unsigned long long)tctdbfsiz(tdb));
+  sysprint();
+  if(!tctdbclose(tdb)){
+    eprint(tdb, __LINE__, "tctdbclose");
+    err = true;
+  }
+  tctdbdel(tdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform wicked command */
+static int procwicked(const char *path, int rnum, bool mt, int opts, int omode){
+  iprintf("<Wicked Writing Test>\n  seed=%u  path=%s  rnum=%d  mt=%d  opts=%d  omode=%d\n\n",
+          g_randseed, path, rnum, mt, opts, omode);
+  bool err = false;
+  double stime = tctime();
+  TCTDB *tdb = tctdbnew();
+  if(g_dbgfd >= 0) tctdbsetdbgfd(tdb, g_dbgfd);
+  if(mt && !tctdbsetmutex(tdb)){
+    eprint(tdb, __LINE__, "tctdbsetmutex");
+    err = true;
+  }
+  if(!tctdbsetcodecfunc(tdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+    eprint(tdb, __LINE__, "tctdbsetcodecfunc");
+    err = true;
+  }
+  if(!tctdbtune(tdb, rnum / 50, 2, -1, opts)){
+    eprint(tdb, __LINE__, "tctdbtune");
+    err = true;
+  }
+  if(!tctdbsetcache(tdb, rnum / 10, 128, 256)){
+    eprint(tdb, __LINE__, "tctdbsetcache");
+    err = true;
+  }
+  if(!tctdbsetxmsiz(tdb, rnum * sizeof(int))){
+    eprint(tdb, __LINE__, "tctdbsetxmsiz");
+    err = true;
+  }
+  if(!tctdbsetinvcache(tdb, -1, 0.5)){
+    eprint(tdb, __LINE__, "tctdbsetinvcache");
+    err = true;
+  }
+  if(!tctdbsetdfunit(tdb, 8)){
+    eprint(tdb, __LINE__, "tctdbsetdfunit");
+    err = true;
+  }
+  if(!tctdbopen(tdb, path, TDBOWRITER | TDBOCREAT | TDBOTRUNC | omode)){
+    eprint(tdb, __LINE__, "tctdbopen");
+    err = true;
+  }
+  if(!tctdbsetindex(tdb, "", TDBITDECIMAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if(!tctdbsetindex(tdb, "str", TDBITLEXICAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if(!tctdbsetindex(tdb, "num", TDBITDECIMAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if(!tctdbsetindex(tdb, "type", TDBITDECIMAL)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if(!tctdbsetindex(tdb, "flag", TDBITTOKEN)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  if(!tctdbsetindex(tdb, "text", TDBITQGRAM)){
+    eprint(tdb, __LINE__, "tctdbsetindex");
+    err = true;
+  }
+  const char *names[] = { "", "str", "num", "type", "flag", "text", "c1" };
+  int ops[] = { TDBQCSTREQ, TDBQCSTRINC, TDBQCSTRBW, TDBQCSTREW, TDBQCSTRAND, TDBQCSTROR,
+                TDBQCSTROREQ, TDBQCSTRRX, TDBQCNUMEQ, TDBQCNUMGT, TDBQCNUMGE, TDBQCNUMLT,
+                TDBQCNUMLE, TDBQCNUMBT, TDBQCNUMOREQ };
+  int ftsops[] = { TDBQCFTSPH, TDBQCFTSAND, TDBQCFTSOR, TDBQCFTSEX };
+  int types[] = { TDBQOSTRASC, TDBQOSTRDESC, TDBQONUMASC, TDBQONUMDESC };
+  for(int i = 1; i <= rnum; i++){
+    int id = myrand(2) == 0 ? myrand(rnum) + 1 : (int)tctdbgenuid(tdb);
+    char pkbuf[RECBUFSIZ];
+    int pksiz = sprintf(pkbuf, "%d", id);
+    TCMAP *cols = tcmapnew2(7);
+    char vbuf[RECBUFSIZ*5];
+    int vsiz = sprintf(vbuf, "%d", id);
+    tcmapput(cols, "str", 3, vbuf, vsiz);
+    if(myrand(3) == 0){
+      vsiz = sprintf(vbuf, "%.2f", (myrand(i * 100) + 1) / 100.0);
+    } else {
+      vsiz = sprintf(vbuf, "%d", myrand(i) + 1);
+    }
+    tcmapput(cols, "num", 3, vbuf, vsiz);
+    vsiz = sprintf(vbuf, "%d", myrand(32) + 1);
+    tcmapput(cols, "type", 4, vbuf, vsiz);
+    int num = myrand(5);
+    int pt = 0;
+    char *wp = vbuf;
+    for(int j = 0; j < num; j++){
+      pt += myrand(5) + 1;
+      if(wp > vbuf) *(wp++) = ',';
+      wp += sprintf(wp, "%d", pt);
+    }
+    *wp = '\0';
+    if(*vbuf != '\0'){
+      tcmapput(cols, "flag", 4, vbuf, wp - vbuf);
+      tcmapput(cols, "text", 4, vbuf, wp - vbuf);
+    }
+    char nbuf[RECBUFSIZ];
+    int nsiz = sprintf(nbuf, "c%d", myrand(i) + 1);
+    vsiz = sprintf(vbuf, "%d", myrand(i) + 1);
+    tcmapput(cols, nbuf, nsiz, vbuf, vsiz);
+    char *cbuf;
+    int csiz;
+    TCMAP *ncols;
+    TDBQRY *qry;
+    TCLIST *res;
+    switch(myrand(17)){
+      case 0:
+        iputchar('0');
+        if(!tctdbput(tdb, pkbuf, pksiz, cols)){
+          eprint(tdb, __LINE__, "tctdbput");
+          err = true;
+        }
+        break;
+      case 1:
+        iputchar('1');
+        cbuf = tcstrjoin4(cols, &csiz);
+        if(!tctdbput2(tdb, pkbuf, pksiz, cbuf, csiz)){
+          eprint(tdb, __LINE__, "tctdbput2");
+          err = true;
+        }
+        tcfree(cbuf);
+        break;
+      case 2:
+        iputchar('2');
+        cbuf = tcstrjoin3(cols, '\t');
+        if(!tctdbput3(tdb, pkbuf, cbuf)){
+          eprint(tdb, __LINE__, "tctdbput3");
+          err = true;
+        }
+        tcfree(cbuf);
+        break;
+      case 3:
+        iputchar('3');
+        if(!tctdbputkeep(tdb, pkbuf, pksiz, cols) && tctdbecode(tdb) != TCEKEEP){
+          eprint(tdb, __LINE__, "tctdbputkeep");
+          err = true;
+        }
+        break;
+      case 4:
+        iputchar('4');
+        cbuf = tcstrjoin4(cols, &csiz);
+        if(!tctdbputkeep2(tdb, pkbuf, pksiz, cbuf, csiz) && tctdbecode(tdb) != TCEKEEP){
+          eprint(tdb, __LINE__, "tctdbputkeep2");
+          err = true;
+        }
+        tcfree(cbuf);
+        break;
+      case 5:
+        iputchar('5');
+        cbuf = tcstrjoin3(cols, '\t');
+        if(!tctdbputkeep3(tdb, pkbuf, cbuf) && tctdbecode(tdb) != TCEKEEP){
+          eprint(tdb, __LINE__, "tctdbputkeep3");
+          err = true;
+        }
+        tcfree(cbuf);
+        break;
+      case 6:
+        iputchar('6');
+        if(!tctdbputcat(tdb, pkbuf, pksiz, cols)){
+          eprint(tdb, __LINE__, "tctdbputcat");
+          err = true;
+        }
+        break;
+      case 7:
+        iputchar('7');
+        cbuf = tcstrjoin4(cols, &csiz);
+        if(!tctdbputcat2(tdb, pkbuf, pksiz, cbuf, csiz)){
+          eprint(tdb, __LINE__, "tctdbputcat2");
+          err = true;
+        }
+        tcfree(cbuf);
+        break;
+      case 8:
+        iputchar('8');
+        cbuf = tcstrjoin3(cols, '\t');
+        if(!tctdbputcat3(tdb, pkbuf, cbuf)){
+          eprint(tdb, __LINE__, "tctdbputcat3");
+          err = true;
+        }
+        tcfree(cbuf);
+        break;
+      case 9:
+        iputchar('9');
+        if(!tctdbout(tdb, pkbuf, pksiz) && tctdbecode(tdb) != TCENOREC){
+          eprint(tdb, __LINE__, "tctdbout");
+          err = true;
+        }
+        break;
+      case 10:
+        iputchar('A');
+        if(!tctdbout2(tdb, pkbuf) && tctdbecode(tdb) != TCENOREC){
+          eprint(tdb, __LINE__, "tctdbout2");
+          err = true;
+        }
+        break;
+      case 11:
+        iputchar('B');
+        ncols = tctdbget(tdb, pkbuf, pksiz);
+        if(ncols){
+          tcmapdel(ncols);
+        } else if(tctdbecode(tdb) != TCENOREC){
+          eprint(tdb, __LINE__, "tctdbget");
+          err = true;
+        }
+        break;
+      case 12:
+        iputchar('C');
+        cbuf = tctdbget2(tdb, pkbuf, pksiz, &csiz);
+        if(cbuf){
+          tcfree(cbuf);
+        } else if(tctdbecode(tdb) != TCENOREC){
+          eprint(tdb, __LINE__, "tctdbget2");
+          err = true;
+        }
+        break;
+      case 13:
+        iputchar('D');
+        cbuf = tctdbget3(tdb, pkbuf);
+        if(cbuf){
+          tcfree(cbuf);
+        } else if(tctdbecode(tdb) != TCENOREC){
+          eprint(tdb, __LINE__, "tctdbget3");
+          err = true;
+        }
+        break;
+      case 14:
+        iputchar('E');
+        if(myrand(rnum / 128) == 0){
+          if(myrand(2) == 0){
+            if(!tctdbiterinit(tdb)){
+              eprint(tdb, __LINE__, "tctdbiterinit");
+              err = true;
+            }
+          } else {
+            if(!tctdbiterinit2(tdb, pkbuf, pksiz) && tctdbecode(tdb) != TCENOREC){
+              eprint(tdb, __LINE__, "tctdbiterinit2");
+              err = true;
+            }
+          }
+        }
+        for(int j = myrand(rnum) / 1000 + 1; j >= 0; j--){
+          if(j % 3 == 0){
+            ncols = tctdbiternext3(tdb);
+            if(ncols){
+              tcmapdel(ncols);
+            } else {
+              int ecode = tctdbecode(tdb);
+              if(ecode != TCEINVALID && ecode != TCENOREC){
+                eprint(tdb, __LINE__, "tctdbiternext3");
+                err = true;
+              }
+            }
+          } else {
+            int iksiz;
+            char *ikbuf = tctdbiternext(tdb, &iksiz);
+            if(ikbuf){
+              tcfree(ikbuf);
+            } else {
+              int ecode = tctdbecode(tdb);
+              if(ecode != TCEINVALID && ecode != TCENOREC){
+                eprint(tdb, __LINE__, "tctdbiternext");
+                err = true;
+              }
+            }
+          }
+        }
+        break;
+      case 15:
+        iputchar('F');
+        qry = tctdbqrynew(tdb);
+        if(myrand(10) != 0){
+          char expr[RECBUFSIZ];
+          sprintf(expr, "%d", myrand(i) + 1);
+          switch(myrand(6)){
+            default:
+              tctdbqryaddcond(qry, "str", TDBQCSTREQ, expr);
+              break;
+            case 1:
+              tctdbqryaddcond(qry, "str", TDBQCSTRBW, expr);
+              break;
+            case 2:
+              tctdbqryaddcond(qry, "str", TDBQCSTROREQ, expr);
+              break;
+            case 3:
+              tctdbqryaddcond(qry, "num", TDBQCNUMEQ, expr);
+              break;
+            case 4:
+              tctdbqryaddcond(qry, "num", TDBQCNUMGT, expr);
+              break;
+            case 5:
+              tctdbqryaddcond(qry, "num", TDBQCNUMLT, expr);
+              break;
+          }
+          switch(myrand(5)){
+            case 0:
+              tctdbqrysetorder(qry, "str", TDBQOSTRASC);
+              break;
+            case 1:
+              tctdbqrysetorder(qry, "str", TDBQOSTRDESC);
+              break;
+            case 2:
+              tctdbqrysetorder(qry, "num", TDBQONUMASC);
+              break;
+            case 3:
+              tctdbqrysetorder(qry, "num", TDBQONUMDESC);
+              break;
+          }
+          tctdbqrysetlimit(qry, 10, myrand(10));
+        } else {
+          int cnum = myrand(4);
+          if(cnum < 1 && myrand(5) != 0) cnum = 1;
+          for(int j = 0; j < cnum; j++){
+            const char *name = names[myrand(sizeof(names) / sizeof(*names))];
+            int op = ops[myrand(sizeof(ops) / sizeof(*ops))];
+            if(myrand(10) == 0) op = ftsops[myrand(sizeof(ftsops) / sizeof(*ftsops))];
+            if(myrand(20) == 0) op |= TDBQCNEGATE;
+            if(myrand(20) == 0) op |= TDBQCNOIDX;
+            char expr[RECBUFSIZ*3];
+            char *wp = expr;
+            if(myrand(3) == 0){
+              wp += sprintf(expr, "%f", myrand(i * 100) / 100.0);
+            } else {
+              wp += sprintf(expr, "%d", myrand(i));
+            }
+            if(myrand(10) == 0) wp += sprintf(wp, ",%d", myrand(i));
+            if(myrand(10) == 0) wp += sprintf(wp, ",%d", myrand(i));
+            tctdbqryaddcond(qry, name, op, expr);
+          }
+          if(myrand(3) != 0){
+            const char *name = names[myrand(sizeof(names) / sizeof(*names))];
+            int type = types[myrand(sizeof(types) / sizeof(*types))];
+            tctdbqrysetorder(qry, name, type);
+          }
+          if(myrand(3) != 0) tctdbqrysetlimit(qry, myrand(i), myrand(10));
+        }
+        res = tctdbqrysearch(qry);
+        tclistdel(res);
+        tctdbqrydel(qry);
+        break;
+      default:
+        iputchar('@');
+        if(myrand(10000) == 0) srand((unsigned int)(tctime() * 1000) % UINT_MAX);
+        break;
+    }
+    tcmapdel(cols);
+    if(i % 50 == 0) iprintf(" (%08d)\n", i);
+    if(i == rnum / 2){
+      if(!tctdbclose(tdb)){
+        eprint(tdb, __LINE__, "tctdbclose");
+        err = true;
+      }
+      if(!tctdbopen(tdb, path, TDBOWRITER | omode)){
+        eprint(tdb, __LINE__, "tctdbopen");
+        err = true;
+      }
+    } else if(i == rnum / 4){
+      char *npath = tcsprintf("%s-tmp", path);
+      if(!tctdbcopy(tdb, npath)){
+        eprint(tdb, __LINE__, "tctdbcopy");
+        err = true;
+      }
+      TCTDB *ntdb = tctdbnew();
+      if(!tctdbsetcodecfunc(ntdb, _tc_recencode, NULL, _tc_recdecode, NULL)){
+        eprint(ntdb, __LINE__, "tctdbsetcodecfunc");
+        err = true;
+      }
+      if(!tctdbopen(ntdb, npath, TDBOREADER | omode)){
+        eprint(ntdb, __LINE__, "tctdbopen");
+        err = true;
+      }
+      tctdbdel(ntdb);
+      unlink(npath);
+      tcfree(npath);
+      if(!tctdboptimize(tdb, rnum / 50, -1, -1, -1)){
+        eprint(tdb, __LINE__, "tctdboptimize");
+        err = true;
+      }
+      if(!tctdbiterinit(tdb)){
+        eprint(tdb, __LINE__, "tctdbiterinit");
+        err = true;
+      }
+    } else if(i == rnum / 8){
+      if(!tctdbtranbegin(tdb)){
+        eprint(tdb, __LINE__, "tctdbtranbegin");
+        err = true;
+      }
+    } else if(i == rnum / 8 + rnum / 16){
+      if(!tctdbtrancommit(tdb)){
+        eprint(tdb, __LINE__, "tctdbtrancommit");
+        err = true;
+      }
+    }
+  }
+  if(!tctdbsync(tdb)){
+    eprint(tdb, __LINE__, "tctdbsync");
+    err = true;
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tctdbrnum(tdb));
+  iprintf("size: %llu\n", (unsigned long long)tctdbfsiz(tdb));
+  sysprint();
+  if(!tctdbclose(tdb)){
+    eprint(tdb, __LINE__, "tctdbclose");
+    err = true;
+  }
+  tctdbdel(tdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+
+// END OF FILE
diff --git a/tcejdb/tcucodec.c b/tcejdb/tcucodec.c
new file mode 100644 (file)
index 0000000..149dac3
--- /dev/null
@@ -0,0 +1,1357 @@
+/*************************************************************************************************
+ * Popular encoders and decoders of the utility API
+ *                                                               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"
+
+
+/* global variables */
+const char *g_progname;                  // program name
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+static void usage(void);
+static void eprintf(const char *format, ...);
+static int runurl(int argc, char **argv);
+static int runbase(int argc, char **argv);
+static int runquote(int argc, char **argv);
+static int runmime(int argc, char **argv);
+static int runhex(int argc, char **argv);
+static int runpack(int argc, char **argv);
+static int runtcbs(int argc, char **argv);
+static int runzlib(int argc, char **argv);
+static int runbzip(int argc, char **argv);
+static int runxml(int argc, char **argv);
+static int runcstr(int argc, char **argv);
+static int runucs(int argc, char **argv);
+static int runhash(int argc, char **argv);
+static int runcipher(int argc, char **argv);
+static int rundate(int argc, char **argv);
+static int runtmpl(int argc, char **argv);
+static int runconf(int argc, char **argv);
+static int procurl(const char *ibuf, int isiz, bool dec, bool br, const char *base);
+static int procbase(const char *ibuf, int isiz, bool dec);
+static int procquote(const char *ibuf, int isiz, bool dec);
+static int procmime(const char *ibuf, int isiz, bool dec, const char *ename, bool qb, bool on,
+                    bool hd, bool bd, int part);
+static int prochex(const char *ibuf, int isiz, bool dec);
+static int procpack(const char *ibuf, int isiz, bool dec, bool bwt);
+static int proctcbs(const char *ibuf, int isiz, bool dec);
+static int proczlib(const char *ibuf, int isiz, bool dec, bool gz);
+static int procbzip(const char *ibuf, int isiz, bool dec);
+static int procxml(const char *ibuf, int isiz, bool dec, bool br);
+static int proccstr(const char *ibuf, int isiz, bool dec, bool js);
+static int procucs(const char *ibuf, int isiz, bool dec, bool un, const char *kw);
+static int prochash(const char *ibuf, int isiz, bool crc, int ch);
+static int proccipher(const char *ibuf, int isiz, const char *key);
+static int procdate(const char *str, int jl, bool wf, bool rf);
+static int proctmpl(const char *ibuf, int isiz, TCMAP *vars);
+static int procconf(int mode);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  g_progname = argv[0];
+  if(argc < 2) usage();
+  int rv = 0;
+  if(!strcmp(argv[1], "url")){
+    rv = runurl(argc, argv);
+  } else if(!strcmp(argv[1], "base")){
+    rv = runbase(argc, argv);
+  } else if(!strcmp(argv[1], "quote")){
+    rv = runquote(argc, argv);
+  } else if(!strcmp(argv[1], "mime")){
+    rv = runmime(argc, argv);
+  } else if(!strcmp(argv[1], "hex")){
+    rv = runhex(argc, argv);
+  } else if(!strcmp(argv[1], "pack")){
+    rv = runpack(argc, argv);
+  } else if(!strcmp(argv[1], "tcbs")){
+    rv = runtcbs(argc, argv);
+  } else if(!strcmp(argv[1], "zlib")){
+    rv = runzlib(argc, argv);
+  } else if(!strcmp(argv[1], "bzip")){
+    rv = runbzip(argc, argv);
+  } else if(!strcmp(argv[1], "xml")){
+    rv = runxml(argc, argv);
+  } else if(!strcmp(argv[1], "cstr")){
+    rv = runcstr(argc, argv);
+  } else if(!strcmp(argv[1], "ucs")){
+    rv = runucs(argc, argv);
+  } else if(!strcmp(argv[1], "hash")){
+    rv = runhash(argc, argv);
+  } else if(!strcmp(argv[1], "cipher")){
+    rv = runcipher(argc, argv);
+  } else if(!strcmp(argv[1], "date")){
+    rv = rundate(argc, argv);
+  } else if(!strcmp(argv[1], "tmpl")){
+    rv = runtmpl(argc, argv);
+  } else if(!strcmp(argv[1], "conf")){
+    rv = runconf(argc, argv);
+  } else {
+    usage();
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+static void usage(void){
+  fprintf(stderr, "%s: popular encoders and decoders of Tokyo Cabinet\n", g_progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s url [-d] [-br] [-rs base] [file]\n", g_progname);
+  fprintf(stderr, "  %s base [-d] [file]\n", g_progname);
+  fprintf(stderr, "  %s quote [-d] [file]\n", g_progname);
+  fprintf(stderr, "  %s mime [-d] [-en name] [-q] [-on] [-hd] [-bd] [-part num] [file]\n",
+          g_progname);
+  fprintf(stderr, "  %s hex [-d] [file]\n", g_progname);
+  fprintf(stderr, "  %s pack [-d] [-bwt] [file]\n", g_progname);
+  fprintf(stderr, "  %s tcbs [-d] [file]\n", g_progname);
+  fprintf(stderr, "  %s zlib [-d] [-gz] [file]\n", g_progname);
+  fprintf(stderr, "  %s bzip [-d] [file]\n", g_progname);
+  fprintf(stderr, "  %s xml [-d] [-br] [file]\n", g_progname);
+  fprintf(stderr, "  %s cstr [-d] [-js] [file]\n", g_progname);
+  fprintf(stderr, "  %s ucs [-d] [-un] [file]\n", g_progname);
+  fprintf(stderr, "  %s hash [-crc] [-ch num] [file]\n", g_progname);
+  fprintf(stderr, "  %s cipher [-key str] [file]\n", g_progname);
+  fprintf(stderr, "  %s date [-ds str] [-jl num] [-wf] [-rf]\n", g_progname);
+  fprintf(stderr, "  %s tmpl [-var name val] [file]\n", g_progname);
+  fprintf(stderr, "  %s conf [-v|-i|-l|-p]\n", g_progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* print formatted error string */
+static void eprintf(const char *format, ...){
+  va_list ap;
+  va_start(ap, format);
+  fprintf(stderr, "%s: ", g_progname);
+  vfprintf(stderr, format, ap);
+  fprintf(stderr, "\n");
+  va_end(ap);
+}
+
+
+/* parse arguments of url command */
+static int runurl(int argc, char **argv){
+  char *path = NULL;
+  bool dec = false;
+  bool br = false;
+  char *base = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-d")){
+        dec = true;
+      } else if(!strcmp(argv[i], "-br")){
+        br = true;
+      } else if(!strcmp(argv[i], "-rs")){
+        if(++i >= argc) usage();
+        base = argv[i];
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  char *ibuf;
+  int isiz;
+  if(path && path[0] == '@'){
+    isiz = strlen(path) - 1;
+    ibuf = tcmemdup(path + 1, isiz);
+  } else {
+    ibuf = tcreadfile(path, -1, &isiz);
+  }
+  if(!ibuf){
+    eprintf("%s: cannot open", path ? path : "(stdin)");
+    return 1;
+  }
+  int rv = procurl(ibuf, isiz, dec, br, base);
+  if(path && path[0] == '@' && !br) printf("\n");
+  tcfree(ibuf);
+  return rv;
+}
+
+
+/* parse arguments of base command */
+static int runbase(int argc, char **argv){
+  char *path = NULL;
+  bool dec = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-d")){
+        dec = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  char *ibuf;
+  int isiz;
+  if(path && path[0] == '@'){
+    isiz = strlen(path) - 1;
+    ibuf = tcmemdup(path + 1, isiz);
+  } else {
+    ibuf = tcreadfile(path, -1, &isiz);
+  }
+  if(!ibuf){
+    eprintf("%s: cannot open", path ? path : "(stdin)");
+    return 1;
+  }
+  int rv = procbase(ibuf, isiz, dec);
+  if(path && path[0] == '@') printf("\n");
+  tcfree(ibuf);
+  return rv;
+}
+
+
+/* parse arguments of quote command */
+static int runquote(int argc, char **argv){
+  char *path = NULL;
+  bool dec = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-d")){
+        dec = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  char *ibuf;
+  int isiz;
+  if(path && path[0] == '@'){
+    isiz = strlen(path) - 1;
+    ibuf = tcmemdup(path + 1, isiz);
+  } else {
+    ibuf = tcreadfile(path, -1, &isiz);
+  }
+  if(!ibuf){
+    eprintf("%s: cannot open", path ? path : "(stdin)");
+    return 1;
+  }
+  int rv = procquote(ibuf, isiz, dec);
+  if(path && path[0] == '@') printf("\n");
+  tcfree(ibuf);
+  return rv;
+}
+
+
+/* parse arguments of mime command */
+static int runmime(int argc, char **argv){
+  char *path = NULL;
+  bool dec = false;
+  char *ename = NULL;
+  bool qb = false;
+  bool on = false;
+  bool hd = false;
+  bool bd = false;
+  int part = -1;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-d")){
+        dec = true;
+      } else if(!strcmp(argv[i], "-en")){
+        if(++i >= argc) usage();
+        ename = argv[i];
+      } else if(!strcmp(argv[i], "-q")){
+        qb = true;
+      } else if(!strcmp(argv[i], "-on")){
+        on = true;
+      } else if(!strcmp(argv[i], "-hd")){
+        hd = true;
+      } else if(!strcmp(argv[i], "-bd")){
+        bd = true;
+      } else if(!strcmp(argv[i], "-part")){
+        if(++i >= argc) usage();
+        part = tcatoix(argv[i]);
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  char *ibuf;
+  int isiz;
+  if(path && path[0] == '@'){
+    isiz = strlen(path) - 1;
+    ibuf = tcmemdup(path + 1, isiz);
+  } else {
+    ibuf = tcreadfile(path, -1, &isiz);
+  }
+  if(!ibuf){
+    eprintf("%s: cannot open", path ? path : "(stdin)");
+    return 1;
+  }
+  if(!ename) ename = "UTF-8";
+  int rv = procmime(ibuf, isiz, dec, ename, qb, on, hd, bd, part);
+  if(path && path[0] == '@') printf("\n");
+  tcfree(ibuf);
+  return rv;
+}
+
+
+/* parse arguments of hex command */
+static int runhex(int argc, char **argv){
+  char *path = NULL;
+  bool dec = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-d")){
+        dec = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  char *ibuf;
+  int isiz;
+  if(path && path[0] == '@'){
+    isiz = strlen(path) - 1;
+    ibuf = tcmemdup(path + 1, isiz);
+  } else {
+    ibuf = tcreadfile(path, -1, &isiz);
+  }
+  if(!ibuf){
+    eprintf("%s: cannot open", path ? path : "(stdin)");
+    return 1;
+  }
+  int rv = prochex(ibuf, isiz, dec);
+  if(path && path[0] == '@') printf("\n");
+  tcfree(ibuf);
+  return rv;
+}
+
+
+/* parse arguments of pack command */
+static int runpack(int argc, char **argv){
+  char *path = NULL;
+  bool dec = false;
+  bool bwt = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-d")){
+        dec = true;
+      } else if(!strcmp(argv[i], "-bwt")){
+        bwt = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  char *ibuf;
+  int isiz;
+  if(path && path[0] == '@'){
+    isiz = strlen(path) - 1;
+    ibuf = tcmemdup(path + 1, isiz);
+  } else {
+    ibuf = tcreadfile(path, -1, &isiz);
+  }
+  if(!ibuf){
+    eprintf("%s: cannot open", path ? path : "(stdin)");
+    return 1;
+  }
+  int rv = procpack(ibuf, isiz, dec, bwt);
+  if(path && path[0] == '@') printf("\n");
+  tcfree(ibuf);
+  return rv;
+}
+
+
+/* parse arguments of tcbs command */
+static int runtcbs(int argc, char **argv){
+  char *path = NULL;
+  bool dec = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-d")){
+        dec = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  char *ibuf;
+  int isiz;
+  if(path && path[0] == '@'){
+    isiz = strlen(path) - 1;
+    ibuf = tcmemdup(path + 1, isiz);
+  } else {
+    ibuf = tcreadfile(path, -1, &isiz);
+  }
+  if(!ibuf){
+    eprintf("%s: cannot open", path ? path : "(stdin)");
+    return 1;
+  }
+  int rv = proctcbs(ibuf, isiz, dec);
+  if(path && path[0] == '@') printf("\n");
+  tcfree(ibuf);
+  return rv;
+}
+
+
+/* parse arguments of zlib command */
+static int runzlib(int argc, char **argv){
+  char *path = NULL;
+  bool dec = false;
+  bool gz = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-d")){
+        dec = true;
+      } else if(!strcmp(argv[i], "-gz")){
+        gz = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  char *ibuf;
+  int isiz;
+  if(path && path[0] == '@'){
+    isiz = strlen(path) - 1;
+    ibuf = tcmemdup(path + 1, isiz);
+  } else {
+    ibuf = tcreadfile(path, -1, &isiz);
+  }
+  if(!ibuf){
+    eprintf("%s: cannot open", path ? path : "(stdin)");
+    return 1;
+  }
+  int rv = proczlib(ibuf, isiz, dec, gz);
+  if(path && path[0] == '@') printf("\n");
+  tcfree(ibuf);
+  return rv;
+}
+
+
+/* parse arguments of bzip command */
+static int runbzip(int argc, char **argv){
+  char *path = NULL;
+  bool dec = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-d")){
+        dec = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  char *ibuf;
+  int isiz;
+  if(path && path[0] == '@'){
+    isiz = strlen(path) - 1;
+    ibuf = tcmemdup(path + 1, isiz);
+  } else {
+    ibuf = tcreadfile(path, -1, &isiz);
+  }
+  if(!ibuf){
+    eprintf("%s: cannot open", path ? path : "(stdin)");
+    return 1;
+  }
+  int rv = procbzip(ibuf, isiz, dec);
+  if(path && path[0] == '@') printf("\n");
+  tcfree(ibuf);
+  return rv;
+}
+
+
+/* parse arguments of xml command */
+static int runxml(int argc, char **argv){
+  char *path = NULL;
+  bool dec = false;
+  bool br = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-d")){
+        dec = true;
+      } else if(!strcmp(argv[i], "-br")){
+        br = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  char *ibuf;
+  int isiz;
+  if(path && path[0] == '@'){
+    isiz = strlen(path) - 1;
+    ibuf = tcmemdup(path + 1, isiz);
+  } else {
+    ibuf = tcreadfile(path, -1, &isiz);
+  }
+  if(!ibuf){
+    eprintf("%s: cannot open", path ? path : "(stdin)");
+    return 1;
+  }
+  int rv = procxml(ibuf, isiz, dec, br);
+  if(path && path[0] == '@') printf("\n");
+  tcfree(ibuf);
+  return rv;
+}
+
+
+/* parse arguments of cstr command */
+static int runcstr(int argc, char **argv){
+  char *path = NULL;
+  bool dec = false;
+  bool js = false;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-d")){
+        dec = true;
+      } else if(!strcmp(argv[i], "-js")){
+        js = true;
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  char *ibuf;
+  int isiz;
+  if(path && path[0] == '@'){
+    isiz = strlen(path) - 1;
+    ibuf = tcmemdup(path + 1, isiz);
+  } else {
+    ibuf = tcreadfile(path, -1, &isiz);
+  }
+  if(!ibuf){
+    eprintf("%s: cannot open", path ? path : "(stdin)");
+    return 1;
+  }
+  int rv = proccstr(ibuf, isiz, dec, js);
+  if(path && path[0] == '@') printf("\n");
+  tcfree(ibuf);
+  return rv;
+}
+
+
+/* parse arguments of ucs command */
+static int runucs(int argc, char **argv){
+  char *path = NULL;
+  bool dec = false;
+  bool un = false;
+  char *kw = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-d")){
+        dec = true;
+      } else if(!strcmp(argv[i], "-un")){
+        un = true;
+      } else if(!strcmp(argv[i], "-kw")){
+        if(++i >= argc) usage();
+        kw = argv[i];
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  char *ibuf;
+  int isiz;
+  if(path && path[0] == '@'){
+    isiz = strlen(path) - 1;
+    ibuf = tcmemdup(path + 1, isiz);
+  } else {
+    ibuf = tcreadfile(path, -1, &isiz);
+  }
+  if(!ibuf){
+    eprintf("%s: cannot open", path ? path : "(stdin)");
+    return 1;
+  }
+  int rv = procucs(ibuf, isiz, dec, un, kw);
+  if(path && path[0] == '@') printf("\n");
+  tcfree(ibuf);
+  return rv;
+}
+
+
+/* parse arguments of hash command */
+static int runhash(int argc, char **argv){
+  char *path = NULL;
+  bool crc = false;
+  int ch = 0;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-crc")){
+        crc = true;
+      } else if(!strcmp(argv[i], "-ch")){
+        if(++i >= argc) usage();
+        ch = tcatoix(argv[i]);
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  char *ibuf;
+  int isiz;
+  if(path && path[0] == '@'){
+    isiz = strlen(path) - 1;
+    ibuf = tcmemdup(path + 1, isiz);
+  } else {
+    ibuf = tcreadfile(path, -1, &isiz);
+  }
+  if(!ibuf){
+    eprintf("%s: cannot open", path ? path : "(stdin)");
+    return 1;
+  }
+  int rv = prochash(ibuf, isiz, crc, ch);
+  tcfree(ibuf);
+  return rv;
+}
+
+
+/* parse arguments of cipher command */
+static int runcipher(int argc, char **argv){
+  char *path = NULL;
+  char *key = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-key")){
+        if(++i >= argc) usage();
+        key = argv[i];
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  char *ibuf;
+  int isiz;
+  if(path && path[0] == '@'){
+    isiz = strlen(path) - 1;
+    ibuf = tcmemdup(path + 1, isiz);
+  } else {
+    ibuf = tcreadfile(path, -1, &isiz);
+  }
+  if(!ibuf){
+    eprintf("%s: cannot open", path ? path : "(stdin)");
+    return 1;
+  }
+  int rv = proccipher(ibuf, isiz, key);
+  if(path && path[0] == '@') printf("\n");
+  tcfree(ibuf);
+  return rv;
+}
+
+
+/* parse arguments of date command */
+static int rundate(int argc, char **argv){
+  char *str = NULL;
+  int jl = INT_MAX;
+  bool wf = false;
+  bool rf = false;
+  for(int i = 2; i < argc; i++){
+    if(argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-ds")){
+        if(++i >= argc) usage();
+        str = argv[i];
+      } else if(!strcmp(argv[i], "-jl")){
+        if(++i >= argc) usage();
+        jl = tcatoix(argv[i]);
+      } else if(!strcmp(argv[i], "-wf")){
+        wf = true;
+      } else if(!strcmp(argv[i], "-rf")){
+        rf = true;
+      } else {
+        usage();
+      }
+    } else {
+      usage();
+    }
+  }
+  int rv = procdate(str, jl, wf, rf);
+  return rv;
+}
+
+
+/* parse arguments of tmpl command */
+static int runtmpl(int argc, char **argv){
+  char *path = NULL;
+  TCMAP *vars = tcmpoolmapnew(tcmpoolglobal());
+  for(int i = 2; i < argc; i++){
+    if(!path && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-var")){
+        if(++i >= argc) usage();
+        const char *name = argv[i];
+        if(++i >= argc) usage();
+        const char *value = argv[i];
+        tcmapput2(vars, name, value);
+      } else {
+        usage();
+      }
+    } else if(!path){
+      path = argv[i];
+    } else {
+      usage();
+    }
+  }
+  char *ibuf;
+  int isiz;
+  if(path && path[0] == '@'){
+    isiz = strlen(path) - 1;
+    ibuf = tcmemdup(path + 1, isiz);
+  } else {
+    ibuf = tcreadfile(path, -1, &isiz);
+  }
+  if(!ibuf){
+    eprintf("%s: cannot open", path ? path : "(stdin)");
+    return 1;
+  }
+  int rv = proctmpl(ibuf, isiz, vars);
+  if(path && path[0] == '@') printf("\n");
+  tcfree(ibuf);
+  return rv;
+}
+
+
+/* parse arguments of conf command */
+static int runconf(int argc, char **argv){
+  int mode = 0;
+  for(int i = 2; i < argc; i++){
+    if(argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-v")){
+        mode = 'v';
+      } else if(!strcmp(argv[i], "-i")){
+        mode = 'i';
+      } else if(!strcmp(argv[i], "-l")){
+        mode = 'l';
+      } else if(!strcmp(argv[i], "-p")){
+        mode = 'p';
+      } else {
+        usage();
+      }
+    } else {
+      usage();
+    }
+  }
+  int rv = procconf(mode);
+  return rv;
+}
+
+
+/* perform url command */
+static int procurl(const char *ibuf, int isiz, bool dec, bool br, const char *base){
+  if(base){
+    char *obuf = tcurlresolve(base, ibuf);
+    printf("%s", obuf);
+    tcfree(obuf);
+  } else if(br){
+    TCMAP *elems = tcurlbreak(ibuf);
+    const char *elem;
+    if((elem = tcmapget2(elems, "self")) != NULL) printf("self: %s\n", elem);
+    if((elem = tcmapget2(elems, "scheme")) != NULL) printf("scheme: %s\n", elem);
+    if((elem = tcmapget2(elems, "host")) != NULL) printf("host: %s\n", elem);
+    if((elem = tcmapget2(elems, "port")) != NULL) printf("port: %s\n", elem);
+    if((elem = tcmapget2(elems, "authority")) != NULL) printf("authority: %s\n", elem);
+    if((elem = tcmapget2(elems, "path")) != NULL) printf("path: %s\n", elem);
+    if((elem = tcmapget2(elems, "file")) != NULL) printf("file: %s\n", elem);
+    if((elem = tcmapget2(elems, "query")) != NULL) printf("query: %s\n", elem);
+    if((elem = tcmapget2(elems, "fragment")) != NULL) printf("fragment: %s\n", elem);
+    tcmapdel(elems);
+  } else if(dec){
+    int osiz;
+    char *obuf = tcurldecode(ibuf, &osiz);
+    fwrite(obuf, 1, osiz, stdout);
+    tcfree(obuf);
+  } else {
+    char *obuf = tcurlencode(ibuf, isiz);
+    fwrite(obuf, 1, strlen(obuf), stdout);
+    tcfree(obuf);
+  }
+  return 0;
+}
+
+
+/* perform base command */
+static int procbase(const char *ibuf, int isiz, bool dec){
+  if(dec){
+    int osiz;
+    char *obuf = tcbasedecode(ibuf, &osiz);
+    fwrite(obuf, 1, osiz, stdout);
+    tcfree(obuf);
+  } else {
+    char *obuf = tcbaseencode(ibuf, isiz);
+    fwrite(obuf, 1, strlen(obuf), stdout);
+    tcfree(obuf);
+  }
+  return 0;
+}
+
+
+/* perform quote command */
+static int procquote(const char *ibuf, int isiz, bool dec){
+  if(dec){
+    int osiz;
+    char *obuf = tcquotedecode(ibuf, &osiz);
+    fwrite(obuf, 1, osiz, stdout);
+    tcfree(obuf);
+  } else {
+    char *obuf = tcquoteencode(ibuf, isiz);
+    fwrite(obuf, 1, strlen(obuf), stdout);
+    tcfree(obuf);
+  }
+  return 0;
+}
+
+
+/* perform mime command */
+static int procmime(const char *ibuf, int isiz, bool dec, const char *ename, bool qb, bool on,
+                    bool hd, bool bd, int part){
+  if(hd || bd || part > 0){
+    TCMAP *hmap = tcmapnew2(16);
+    int osiz;
+    char *obuf = tcmimebreak(ibuf, isiz, hmap, &osiz);
+    if(part > 0){
+      const char *value;
+      if(!(value = tcmapget2(hmap, "TYPE")) || !tcstrifwm(value, "multipart/") ||
+         !(value = tcmapget2(hmap, "BOUNDARY"))){
+        eprintf("not multipart");
+      } else {
+        TCLIST *parts = tcmimeparts(obuf, osiz, value);
+        if(part <= tclistnum(parts)){
+          int bsiz;
+          const char *body = tclistval(parts, part - 1, &bsiz);
+          fwrite(body, 1, bsiz, stdout);
+        } else {
+          eprintf("no such part");
+        }
+        tclistdel(parts);
+      }
+    } else {
+      if(hd){
+        TCLIST *names = tcmapkeys(hmap);
+        tclistsort(names);
+        int num = tclistnum(names);
+        for(int i = 0; i < num; i++){
+          const char *name = tclistval2(names, i);
+          printf("%s\t%s\n", name, tcmapget2(hmap, name));
+        }
+        tclistdel(names);
+        if(bd) putchar('\n');
+      }
+      if(bd) fwrite(obuf, 1, osiz, stdout);
+    }
+    tcfree(obuf);
+    tcmapdel(hmap);
+  } else if(dec){
+    char ebuf[32];
+    char *obuf = tcmimedecode(ibuf, ebuf);
+    if(on){
+      fwrite(ebuf, 1, strlen(ebuf), stdout);
+    } else {
+      fwrite(obuf, 1, strlen(obuf), stdout);
+    }
+    tcfree(obuf);
+  } else {
+    char *obuf = tcmimeencode(ibuf, ename, !qb);
+    fwrite(obuf, 1, strlen(obuf), stdout);
+    tcfree(obuf);
+  }
+  return 0;
+}
+
+
+/* perform hex command */
+static int prochex(const char *ibuf, int isiz, bool dec){
+  if(dec){
+    int osiz;
+    char *obuf = tchexdecode(ibuf, &osiz);
+    fwrite(obuf, 1, osiz, stdout);
+    tcfree(obuf);
+  } else {
+    char *obuf = tchexencode(ibuf, isiz);
+    fwrite(obuf, 1, strlen(obuf), stdout);
+    tcfree(obuf);
+  }
+  return 0;
+}
+
+
+/* perform pack command */
+static int procpack(const char *ibuf, int isiz, bool dec, bool bwt){
+  if(dec){
+    int osiz;
+    char *obuf = tcpackdecode(ibuf, isiz, &osiz);
+    if(bwt && osiz > 0){
+      int idx, step;
+      TCREADVNUMBUF(obuf, idx, step);
+      char *tbuf = tcbwtdecode(obuf + step, osiz - step, idx);
+      fwrite(tbuf, 1, osiz - step, stdout);
+      tcfree(tbuf);
+    } else {
+      fwrite(obuf, 1, osiz, stdout);
+    }
+    tcfree(obuf);
+  } else {
+    char *tbuf = NULL;
+    if(bwt){
+      int idx;
+      tbuf = tcbwtencode(ibuf, isiz, &idx);
+      char vnumbuf[sizeof(int)+1];
+      int step;
+      TCSETVNUMBUF(step, vnumbuf, idx);
+      tbuf = tcrealloc(tbuf, isiz + step + 1);
+      memmove(tbuf + step, tbuf, isiz);
+      memcpy(tbuf, vnumbuf, step);
+      isiz += step;
+      ibuf = tbuf;
+    }
+    int osiz;
+    char *obuf = tcpackencode(ibuf, isiz, &osiz);
+    fwrite(obuf, 1, osiz, stdout);
+    tcfree(obuf);
+    tcfree(tbuf);
+  }
+  return 0;
+}
+
+
+/* perform tcbs command */
+static int proctcbs(const char *ibuf, int isiz, bool dec){
+  if(dec){
+    int osiz;
+    char *obuf = tcbsdecode(ibuf, isiz, &osiz);
+    fwrite(obuf, 1, osiz, stdout);
+    tcfree(obuf);
+  } else {
+    int osiz;
+    char *obuf = tcbsencode(ibuf, isiz, &osiz);
+    fwrite(obuf, 1, osiz, stdout);
+    tcfree(obuf);
+  }
+  return 0;
+}
+
+
+/* perform zlib command */
+static int proczlib(const char *ibuf, int isiz, bool dec, bool gz){
+  if(dec){
+    int osiz;
+    char *obuf = gz ? tcgzipdecode(ibuf, isiz, &osiz) : tcinflate(ibuf, isiz, &osiz);
+    if(obuf){
+      fwrite(obuf, 1, osiz, stdout);
+      tcfree(obuf);
+    } else {
+      eprintf("inflate failure");
+    }
+  } else {
+    int osiz;
+    char *obuf = gz ? tcgzipencode(ibuf, isiz, &osiz) : tcdeflate(ibuf, isiz, &osiz);
+    if(obuf){
+      fwrite(obuf, 1, osiz, stdout);
+      tcfree(obuf);
+    } else {
+      eprintf("deflate failure");
+    }
+  }
+  return 0;
+}
+
+
+/* perform bzip command */
+static int procbzip(const char *ibuf, int isiz, bool dec){
+  if(dec){
+    int osiz;
+    char *obuf = tcbzipdecode(ibuf, isiz, &osiz);
+    if(obuf){
+      fwrite(obuf, 1, osiz, stdout);
+      tcfree(obuf);
+    } else {
+      eprintf("inflate failure");
+    }
+  } else {
+    int osiz;
+    char *obuf = tcbzipencode(ibuf, isiz, &osiz);
+    if(obuf){
+      fwrite(obuf, 1, osiz, stdout);
+      tcfree(obuf);
+    } else {
+      eprintf("deflate failure");
+    }
+  }
+  return 0;
+}
+
+
+/* perform xml command */
+static int procxml(const char *ibuf, int isiz, bool dec, bool br){
+  if(br){
+    TCLIST *elems = tcxmlbreak(ibuf);
+    for(int i = 0; i < tclistnum(elems); i++){
+      int esiz;
+      const char *elem = tclistval(elems, i, &esiz);
+      char *estr = tcmemdup(elem, esiz);
+      tcstrsubchr(estr, "\t\n\r", "  ");
+      tcstrtrim(estr);
+      if(*estr != '\0'){
+        if(*elem == '<'){
+          if(tcstrfwm(estr, "<!--")){
+            printf("COMMENT\t%s\n", estr);
+          } else if(tcstrfwm(estr, "<![CDATA[")){
+            printf("CDATA\t%s\n", estr);
+          } else if(tcstrfwm(estr, "<!")){
+            printf("DECL\t%s\n", estr);
+          } else if(tcstrfwm(estr, "<?")){
+            printf("XMLDECL\t%s\n", estr);
+          } else {
+            TCMAP *attrs = tcxmlattrs(estr);
+            tcmapiterinit(attrs);
+            const char *name;
+            if((name = tcmapget2(attrs, "")) != NULL){
+              if(tcstrfwm(estr, "</")){
+                printf("END");
+              } else if(tcstrbwm(estr, "/>")){
+                printf("EMPTY");
+              } else {
+                printf("BEGIN");
+              }
+              printf("\t%s", name);
+              while((name = tcmapiternext2(attrs)) != NULL){
+                if(*name == '\0') continue;
+                printf("\t%s\t%s", name, tcmapiterval2(name));
+              }
+              printf("\n");
+            }
+            tcmapdel(attrs);
+          }
+        } else {
+          char *dstr = tcxmlunescape(estr);
+          printf("TEXT\t%s\n", dstr);
+          tcfree(dstr);
+        }
+      }
+      tcfree(estr);
+    }
+    tclistdel(elems);
+  } else if(dec){
+    char *obuf = tcxmlunescape(ibuf);
+    fwrite(obuf, 1, strlen(obuf), stdout);
+    tcfree(obuf);
+  } else {
+    char *obuf = tcxmlescape(ibuf);
+    fwrite(obuf, 1, strlen(obuf), stdout);
+    tcfree(obuf);
+  }
+  return 0;
+}
+
+
+/* perform cstr command */
+static int proccstr(const char *ibuf, int isiz, bool dec, bool js){
+  if(js){
+    if(dec){
+      char *ostr = tcjsonunescape(ibuf);
+      printf("%s", ostr);
+      tcfree(ostr);
+    } else {
+      char *ostr = tcjsonescape(ibuf);
+      printf("%s", ostr);
+      tcfree(ostr);
+    }
+  } else {
+    if(dec){
+      char *ostr = tccstrunescape(ibuf);
+      printf("%s", ostr);
+      tcfree(ostr);
+    } else {
+      char *ostr = tccstrescape(ibuf);
+      printf("%s", ostr);
+      tcfree(ostr);
+    }
+  }
+  return 0;
+}
+
+
+/* perform ucs command */
+static int procucs(const char *ibuf, int isiz, bool dec, bool un, const char *kw){
+  if(un){
+    uint16_t *ary = tcmalloc(isiz * sizeof(uint16_t) + 1);
+    int anum;
+    tcstrutftoucs(ibuf, ary, &anum);
+    anum = tcstrucsnorm(ary, anum, TCUNSPACE | TCUNLOWER | TCUNNOACC | TCUNWIDTH);
+    char *str = tcmalloc(anum * 3 + 1);
+    tcstrucstoutf(ary, anum, str);
+    printf("%s", str);
+    tcfree(str);
+    tcfree(ary);
+  } else if(kw){
+    TCLIST *words = tcstrtokenize(kw);
+    TCLIST *texts = tcstrkwic(ibuf, words, 10, TCKWMUTAB);
+    for(int i = 0; i < tclistnum(texts); i++){
+      printf("%s\n", tclistval2(texts, i));
+    }
+    tclistdel(texts);
+    tclistdel(words);
+  } else if(dec){
+    uint16_t *ary = tcmalloc(isiz + 1);
+    int anum = 0;
+    for(int i = 0; i < isiz; i += 2){
+      ary[anum++] = (((unsigned char *)ibuf)[i] << 8) + ((unsigned char *)ibuf)[i+1];
+    }
+    char *str = tcmalloc(isiz * 3 + 1);
+    tcstrucstoutf(ary, anum, str);
+    printf("%s", str);
+    tcfree(str);
+    tcfree(ary);
+  } else {
+    uint16_t *ary = tcmalloc(isiz * sizeof(uint16_t) + 1);
+    int anum;
+    tcstrutftoucs(ibuf, ary, &anum);
+    for(int i = 0; i < anum; i++){
+      int c = ary[i];
+      putchar(c >> 8);
+      putchar(c & 0xff);
+    }
+    tcfree(ary);
+  }
+  return 0;
+}
+
+
+/* perform hash command */
+static int prochash(const char *ibuf, int isiz, bool crc, int ch){
+  if(crc){
+    printf("%08x\n", tcgetcrc(ibuf, isiz));
+  } else if(ch > 0){
+    TCCHIDX *chidx = tcchidxnew(ch);
+    printf("%d\n", tcchidxhash(chidx, ibuf, isiz));
+    tcchidxdel(chidx);
+  } else {
+    char buf[48];
+    tcmd5hash(ibuf, isiz, buf);
+    printf("%s\n", buf);
+  }
+  return 0;
+}
+
+
+/* perform cipher command */
+static int proccipher(const char *ibuf, int isiz, const char *key){
+  char *obuf = tcmalloc(isiz + 1);
+  const char *kbuf = "";
+  int ksiz = 0;
+  if(key){
+    kbuf = key;
+    ksiz = strlen(key);
+  }
+  tcarccipher(ibuf, isiz, kbuf, ksiz, obuf);
+  fwrite(obuf, 1, isiz, stdout);
+  tcfree(obuf);
+  return 0;
+}
+
+
+/* perform date command */
+static int procdate(const char *str, int jl, bool wf, bool rf){
+  int64_t t = str ? tcstrmktime(str) : time(NULL);
+  if(wf){
+    char buf[48];
+    tcdatestrwww(t, jl, buf);
+    printf("%s\n", buf);
+  } else if(rf){
+    char buf[48];
+    tcdatestrhttp(t, jl, buf);
+    printf("%s\n", buf);
+  } else {
+    printf("%lld\n", (long long int)t);
+  }
+  return 0;
+}
+
+
+/* perform tmpl command */
+static int proctmpl(const char *ibuf, int isiz, TCMAP *vars){
+  TCTMPL *tmpl = tctmplnew();
+  tctmplload(tmpl, ibuf);
+  char *str = tctmpldump(tmpl, vars);
+  printf("%s", str);
+  tcfree(str);
+  tctmpldel(tmpl);
+  return 0;
+}
+
+
+/* perform conf command */
+static int procconf(int mode){
+  switch(mode){
+    case 'v':
+      printf("%s\n", tcversion);
+      break;
+    case 'i':
+      printf("%s\n", _TC_APPINC);
+      break;
+    case 'l':
+      printf("%s\n", _TC_APPLIBS);
+      break;
+    case 'p':
+      printf("%s\n", _TC_BINDIR);
+      break;
+    default:
+      printf("myconf(version): %s\n", tcversion);
+      printf("myconf(sysname): %s\n", TCSYSNAME);
+      printf("myconf(libver): %d\n", _TC_LIBVER);
+      printf("myconf(formatver): %s\n", _TC_FORMATVER);
+      printf("myconf(prefix): %s\n", _TC_PREFIX);
+      printf("myconf(includedir): %s\n", _TC_INCLUDEDIR);
+      printf("myconf(libdir): %s\n", _TC_LIBDIR);
+      printf("myconf(bindir): %s\n", _TC_BINDIR);
+      printf("myconf(libexecdir): %s\n", _TC_LIBEXECDIR);
+      printf("myconf(appinc): %s\n", _TC_APPINC);
+      printf("myconf(applibs): %s\n", _TC_APPLIBS);
+      printf("myconf(bigend): %d\n", TCBIGEND);
+      printf("myconf(usezlib): %d\n", TCUSEZLIB);
+      printf("myconf(usebzip): %d\n", TCUSEBZIP);
+      printf("type(bool): size=%d align=%d offset=%d max=%llu\n",
+             (int)sizeof(bool), (int)_alignof(bool), TCALIGNOF(bool),
+             (unsigned long long)true);
+      printf("type(char): size=%d align=%d offset=%d max=%llu\n",
+             (int)sizeof(char), (int)_alignof(char), TCALIGNOF(char),
+             (unsigned long long)CHAR_MAX);
+      printf("type(short): size=%d align=%d offset=%d max=%llu\n",
+             (int)sizeof(short), (int)_alignof(short), TCALIGNOF(short),
+             (unsigned long long)SHRT_MAX);
+      printf("type(int): size=%d align=%d offset=%d max=%llu\n",
+             (int)sizeof(int), (int)_alignof(int), TCALIGNOF(int),
+             (unsigned long long)INT_MAX);
+      printf("type(long): size=%d align=%d offset=%d max=%llu\n",
+             (int)sizeof(long), (int)_alignof(long), TCALIGNOF(long),
+             (unsigned long long)LONG_MAX);
+      printf("type(long long): size=%d align=%d offset=%d max=%llu\n",
+             (int)sizeof(long long), (int)_alignof(long long), TCALIGNOF(long long),
+             (unsigned long long)LLONG_MAX);
+      printf("type(float): size=%d align=%d offset=%d max=%g\n",
+             (int)sizeof(float), (int)_alignof(float), TCALIGNOF(float),
+             (double)FLT_MAX);
+      printf("type(double): size=%d align=%d offset=%d max=%g\n",
+             (int)sizeof(double), (int)_alignof(double), TCALIGNOF(double),
+             (double)DBL_MAX);
+      printf("type(long double): size=%d align=%d offset=%d max=%Lg\n",
+             (int)sizeof(long double), (int)_alignof(long double), TCALIGNOF(long double),
+             (long double)LDBL_MAX);
+      printf("type(void *): size=%d align=%d offset=%d\n",
+             (int)sizeof(void *), (int)_alignof(void *), TCALIGNOF(void *));
+      printf("type(intptr_t): size=%d align=%d offset=%d max=%llu\n",
+             (int)sizeof(intptr_t), (int)_alignof(intptr_t), TCALIGNOF(intptr_t),
+             (unsigned long long)INTPTR_MAX);
+      printf("type(size_t): size=%d align=%d offset=%d max=%llu\n",
+             (int)sizeof(size_t), (int)_alignof(size_t), TCALIGNOF(size_t),
+             (unsigned long long)SIZE_MAX);
+      printf("type(ptrdiff_t): size=%d align=%d offset=%d max=%llu\n",
+             (int)sizeof(ptrdiff_t), (int)_alignof(ptrdiff_t), TCALIGNOF(ptrdiff_t),
+             (unsigned long long)PTRDIFF_MAX);
+      printf("type(wchar_t): size=%d align=%d offset=%d max=%llu\n",
+             (int)sizeof(wchar_t), (int)_alignof(wchar_t), TCALIGNOF(wchar_t),
+             (unsigned long long)WCHAR_MAX);
+      printf("type(sig_atomic_t): size=%d align=%d offset=%d max=%llu\n",
+             (int)sizeof(sig_atomic_t), (int)_alignof(sig_atomic_t), TCALIGNOF(sig_atomic_t),
+             (unsigned long long)SIG_ATOMIC_MAX);
+      printf("type(time_t): size=%d align=%d offset=%d max=%llu\n",
+             (int)sizeof(time_t), (int)_alignof(time_t), TCALIGNOF(time_t),
+             (unsigned long long)_maxof(time_t));
+      printf("type(off_t): size=%d align=%d offset=%d max=%llu\n",
+             (int)sizeof(off_t), (int)_alignof(off_t), TCALIGNOF(off_t),
+             (unsigned long long)_maxof(off_t));
+      printf("type(ino_t): size=%d align=%d offset=%d max=%llu\n",
+             (int)sizeof(ino_t), (int)_alignof(ino_t), TCALIGNOF(ino_t),
+             (unsigned long long)_maxof(ino_t));
+      printf("type(tcgeneric_t): size=%d align=%d offset=%d\n",
+             (int)sizeof(tcgeneric_t), (int)_alignof(tcgeneric_t), TCALIGNOF(tcgeneric_t));
+      printf("macro(RAND_MAX): %llu\n", (unsigned long long)RAND_MAX);
+      printf("macro(PATH_MAX): %llu\n", (unsigned long long)PATH_MAX);
+      printf("macro(NAME_MAX): %llu\n", (unsigned long long)NAME_MAX);
+      printf("macro(P_tmpdir): %s\n", P_tmpdir);
+      printf("sysconf(_SC_CLK_TCK): %ld\n", sysconf(_SC_CLK_TCK));
+      printf("sysconf(_SC_OPEN_MAX): %ld\n", sysconf(_SC_OPEN_MAX));
+      printf("sysconf(_SC_PAGESIZE): %ld\n", sysconf(_SC_PAGESIZE));
+      TCMAP *info = tcsysinfo();
+      if(info){
+        tcmapiterinit(info);
+        const char *name;
+        while((name = tcmapiternext2(info)) != NULL){
+          printf("sysinfo(%s): %s\n", name, tcmapiterval2(name));
+        }
+        tcmapdel(info);
+      }
+      struct stat sbuf;
+      if(stat(MYCDIRSTR, &sbuf) == 0){
+        printf("stat(st_uid): %d\n", (int)sbuf.st_uid);
+        printf("stat(st_gid): %d\n", (int)sbuf.st_gid);
+        printf("stat(st_blksize): %d\n", (int)sbuf.st_blksize);
+      }
+  }
+  return 0;
+}
+
+
+
+// END OF FILE
diff --git a/tcejdb/tcumttest.c b/tcejdb/tcumttest.c
new file mode 100644 (file)
index 0000000..46de087
--- /dev/null
@@ -0,0 +1,578 @@
+/*************************************************************************************************
+ * The test cases of the on-memory database API
+ *                                                               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"
+
+#define RECBUFSIZ      48                // buffer for records
+
+typedef struct {                         // type of structure for combo thread
+  TCMDB *mdb;
+  TCNDB *ndb;
+  int rnum;
+  bool rnd;
+  int id;
+} TARGCOMBO;
+
+typedef struct {                         // type of structure for typical thread
+  TCMDB *mdb;
+  TCNDB *ndb;
+  int rnum;
+  bool nc;
+  int rratio;
+  int id;
+} TARGTYPICAL;
+
+
+/* global variables */
+const char *g_progname;                  // program name
+unsigned int g_randseed;                 // random seed
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+static void usage(void);
+static void iprintf(const char *format, ...);
+static void iputchar(int c);
+static void eprint(int line, const char *func);
+static int myrand(int range);
+static int myrandnd(int range);
+static int runcombo(int argc, char **argv);
+static int runtypical(int argc, char **argv);
+static int proccombo(int tnum, int rnum, int bnum, bool tr, bool rnd);
+static int proctypical(int tnum, int rnum, int bnum, bool tr, bool nc, int rratio);
+static void *threadwrite(void *targ);
+static void *threadread(void *targ);
+static void *threadremove(void *targ);
+static void *threadtypical(void *targ);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  g_progname = argv[0];
+  const char *ebuf = getenv("TCRNDSEED");
+  g_randseed = ebuf ? tcatoix(ebuf) : tctime() * 1000;
+  srand(g_randseed);
+  if(argc < 2) usage();
+  int rv = 0;
+  if(!strcmp(argv[1], "combo")){
+    rv = runcombo(argc, argv);
+  } else if(!strcmp(argv[1], "typical")){
+    rv = runtypical(argc, argv);
+  } else {
+    usage();
+  }
+  if(rv != 0){
+    printf("FAILED: TCRNDSEED=%u PID=%d", g_randseed, (int)getpid());
+    for(int i = 0; i < argc; i++){
+      printf(" %s", argv[i]);
+    }
+    printf("\n\n");
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+static void usage(void){
+  fprintf(stderr, "%s: test cases of the on-memory database API of Tokyo Cabinet\n", g_progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s combo [-tr] [-rnd] tnum rnum [bnum]\n", g_progname);
+  fprintf(stderr, "  %s typical [-tr] [-nc] [-rr num] tnum rnum [bnum]\n", g_progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* print formatted information string and flush the buffer */
+static void iprintf(const char *format, ...){
+  va_list ap;
+  va_start(ap, format);
+  vprintf(format, ap);
+  fflush(stdout);
+  va_end(ap);
+}
+
+
+/* print a character and flush the buffer */
+static void iputchar(int c){
+  putchar(c);
+  fflush(stdout);
+}
+
+
+/* print error message of on-memory database */
+static void eprint(int line, const char *func){
+  fprintf(stderr, "%s: %d: %s: error\n", g_progname, line, func);
+}
+
+
+/* get a random number */
+static int myrand(int range){
+  if(range < 2) return 0;
+  int high = (unsigned int)rand() >> 4;
+  int low = range * (rand() / (RAND_MAX + 1.0));
+  low &= (unsigned int)INT_MAX >> 4;
+  return (high + low) % range;
+}
+
+
+/* get a random number based on normal distribution */
+static int myrandnd(int range){
+  int num = (int)tcdrandnd(range >> 1, range / 10);
+  return (num < 0 || num >= range) ? 0 : num;
+}
+
+
+/* parse arguments of combo command */
+static int runcombo(int argc, char **argv){
+  char *tstr = NULL;
+  char *rstr = NULL;
+  char *bstr = NULL;
+  bool tr = false;
+  bool rnd = false;
+  for(int i = 2; i < argc; i++){
+    if(!tstr && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-tr")){
+        tr = true;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else {
+        usage();
+      }
+    } else if(!tstr){
+      tstr = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!tstr || !rstr) usage();
+  int tnum = tcatoix(tstr);
+  int rnum = tcatoix(rstr);
+  if(tnum < 1 || rnum < 1) usage();
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int rv = proccombo(tnum, rnum, bnum, tr, rnd);
+  return rv;
+}
+
+
+/* parse arguments of typical command */
+static int runtypical(int argc, char **argv){
+  char *tstr = NULL;
+  char *rstr = NULL;
+  char *bstr = NULL;
+  bool tr = false;
+  int rratio = -1;
+  bool nc = false;
+  for(int i = 2; i < argc; i++){
+    if(!tstr && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-tr")){
+        tr = true;
+      } else if(!strcmp(argv[i], "-nc")){
+        nc = true;
+      } else if(!strcmp(argv[i], "-rr")){
+        if(++i >= argc) usage();
+        rratio = tcatoix(argv[i]);
+      } else {
+        usage();
+      }
+    } else if(!tstr){
+      tstr = argv[i];
+    } else if(!rstr){
+      rstr = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!tstr || !rstr) usage();
+  int tnum = tcatoix(tstr);
+  int rnum = tcatoix(rstr);
+  if(tnum < 1 || rnum < 1) usage();
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int rv = proctypical(tnum, rnum, bnum, tr, nc, rratio);
+  return rv;
+}
+
+
+/* perform combo command */
+static int proccombo(int tnum, int rnum, int bnum, bool tr, bool rnd){
+  iprintf("<Combination Test>\n  seed=%u  tnum=%d  rnum=%d  bnum=%d  tr=%d  rnd=%d\n\n",
+          g_randseed, tnum, rnum, bnum, tr, rnd);
+  bool err = false;
+  double stime = tctime();
+  TCMDB *mdb = (bnum > 0) ? tcmdbnew2(bnum) : tcmdbnew();
+  TCNDB *ndb = tcndbnew();
+  TARGCOMBO targs[tnum];
+  pthread_t threads[tnum];
+  if(tnum == 1){
+    targs[0].mdb = mdb;
+    targs[0].ndb = tr ? ndb : NULL;
+    targs[0].rnum = rnum;
+    targs[0].rnd = rnd;
+    targs[0].id = 0;
+    if(threadwrite(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].mdb = mdb;
+      targs[i].ndb = tr ? ndb : NULL;
+      targs[i].rnum = rnum;
+      targs[i].rnd = rnd;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadwrite, targs + i) != 0){
+        eprint(__LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(__LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  if(tnum == 1){
+    targs[0].mdb = mdb;
+    targs[0].ndb = tr ? ndb : NULL;
+    targs[0].rnum = rnum;
+    targs[0].rnd = rnd;
+    targs[0].id = 0;
+    if(threadread(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].mdb = mdb;
+      targs[i].ndb = tr ? ndb : NULL;
+      targs[i].rnum = rnum;
+      targs[i].rnd = rnd;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadread, targs + i) != 0){
+        eprint(__LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(__LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  if(tnum == 1){
+    targs[0].mdb = mdb;
+    targs[0].ndb = tr ? ndb : NULL;
+    targs[0].rnum = rnum;
+    targs[0].rnd = rnd;
+    targs[0].id = 0;
+    if(threadremove(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].mdb = mdb;
+      targs[i].ndb = tr ? ndb : NULL;
+      targs[i].rnum = rnum;
+      targs[i].rnd = rnd;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadremove, targs + i) != 0){
+        eprint(__LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(__LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  if(tr){
+    iprintf("record number: %llu\n", (unsigned long long)tcndbrnum(ndb));
+    iprintf("size: %llu\n", (unsigned long long)tcndbmsiz(ndb));
+  } else {
+    iprintf("record number: %llu\n", (unsigned long long)tcmdbrnum(mdb));
+    iprintf("size: %llu\n", (unsigned long long)tcmdbmsiz(mdb));
+  }
+  tcndbdel(ndb);
+  tcmdbdel(mdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* perform typical command */
+static int proctypical(int tnum, int rnum, int bnum, bool tr, bool nc, int rratio){
+  iprintf("<Typical Access Test>\n  seed=%u  tnum=%d  rnum=%d  bnum=%d  tr=%d  nc=%d"
+          "  rratio=%d\n\n", g_randseed, tnum, rnum, bnum, tr, nc, rratio);
+  bool err = false;
+  double stime = tctime();
+  TCMDB *mdb = (bnum > 0) ? tcmdbnew2(bnum) : tcmdbnew();
+  TCNDB *ndb = tcndbnew();
+  TARGTYPICAL targs[tnum];
+  pthread_t threads[tnum];
+  if(tnum == 1){
+    targs[0].mdb = mdb;
+    targs[0].ndb = tr ? ndb : NULL;
+    targs[0].rnum = rnum;
+    targs[0].nc = nc;
+    targs[0].rratio = rratio;
+    targs[0].id = 0;
+    if(threadtypical(targs) != NULL) err = true;
+  } else {
+    for(int i = 0; i < tnum; i++){
+      targs[i].mdb = mdb;
+      targs[i].ndb = tr ? ndb : NULL;
+      targs[i].rnum = rnum;
+      targs[i].nc = nc;
+      targs[i].rratio= rratio;
+      targs[i].id = i;
+      if(pthread_create(threads + i, NULL, threadtypical, targs + i) != 0){
+        eprint(__LINE__, "pthread_create");
+        targs[i].id = -1;
+        err = true;
+      }
+    }
+    for(int i = 0; i < tnum; i++){
+      if(targs[i].id == -1) continue;
+      void *rv;
+      if(pthread_join(threads[i], &rv) != 0){
+        eprint(__LINE__, "pthread_join");
+        err = true;
+      } else if(rv){
+        err = true;
+      }
+    }
+  }
+  if(tr){
+    iprintf("record number: %llu\n", (unsigned long long)tcndbrnum(ndb));
+    iprintf("size: %llu\n", (unsigned long long)tcndbmsiz(ndb));
+  } else {
+    iprintf("record number: %llu\n", (unsigned long long)tcmdbrnum(mdb));
+    iprintf("size: %llu\n", (unsigned long long)tcmdbmsiz(mdb));
+  }
+  tcndbdel(ndb);
+  tcmdbdel(mdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("%s\n\n", err ? "error" : "ok");
+  return err ? 1 : 0;
+}
+
+
+/* thread the write function */
+static void *threadwrite(void *targ){
+  TCMDB *mdb = ((TARGCOMBO *)targ)->mdb;
+  TCNDB *ndb = ((TARGCOMBO *)targ)->ndb;
+  int rnum = ((TARGCOMBO *)targ)->rnum;
+  bool rnd = ((TARGCOMBO *)targ)->rnd;
+  int id = ((TARGCOMBO *)targ)->id;
+  double stime = tctime();
+  if(id == 0) iprintf("writing:\n");
+  int base = id * rnum;
+  for(int i = 1; i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", base + (rnd ? myrand(i) : i));
+    if(ndb){
+      tcndbput(ndb, buf, len, buf, len);
+    } else {
+      tcmdbput(mdb, buf, len, buf, len);
+    }
+    if(id == 0 && rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(id == 0) iprintf("time: %.3f\n", tctime() - stime);
+  return NULL;
+}
+
+
+/* thread the read function */
+static void *threadread(void *targ){
+  TCMDB *mdb = ((TARGCOMBO *)targ)->mdb;
+  TCNDB *ndb = ((TARGCOMBO *)targ)->ndb;
+  int rnum = ((TARGCOMBO *)targ)->rnum;
+  bool rnd = ((TARGCOMBO *)targ)->rnd;
+  int id = ((TARGCOMBO *)targ)->id;
+  double stime = tctime();
+  if(id == 0) iprintf("reading:\n");
+  int base = id * rnum;
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%08d", base + (rnd ? myrand(i) : i));
+    int vsiz;
+    char *vbuf = ndb ? tcndbget(ndb, kbuf, ksiz, &vsiz) : tcmdbget(mdb, kbuf, ksiz, &vsiz);
+    if(vbuf) tcfree(vbuf);
+    if(id == 0 && rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(id == 0) iprintf("time: %.3f\n", tctime() - stime);
+  return NULL;
+}
+
+
+/* thread the remove function */
+static void *threadremove(void *targ){
+  TCMDB *mdb = ((TARGCOMBO *)targ)->mdb;
+  TCNDB *ndb = ((TARGCOMBO *)targ)->ndb;
+  int rnum = ((TARGCOMBO *)targ)->rnum;
+  bool rnd = ((TARGCOMBO *)targ)->rnd;
+  int id = ((TARGCOMBO *)targ)->id;
+  double stime = tctime();
+  if(id == 0) iprintf("removing:\n");
+  int base = id * rnum;
+  for(int i = 1; i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", base + (rnd ? myrand(i) : i));
+    if(ndb){
+      tcndbout(ndb, buf, len);
+    } else {
+      tcmdbout(mdb, buf, len);
+    }
+    if(id == 0 && rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(id == 0) iprintf("time: %.3f\n", tctime() - stime);
+  return NULL;
+}
+
+
+/* thread the typical function */
+static void *threadtypical(void *targ){
+  TCMDB *mdb = ((TARGTYPICAL *)targ)->mdb;
+  TCNDB *ndb = ((TARGCOMBO *)targ)->ndb;
+  int rnum = ((TARGTYPICAL *)targ)->rnum;
+  bool nc = ((TARGTYPICAL *)targ)->nc;
+  int rratio = ((TARGTYPICAL *)targ)->rratio;
+  int id = ((TARGTYPICAL *)targ)->id;
+  bool err = false;
+  TCMAP *map = (!nc && id == 0) ? tcmapnew2(rnum + 1) : NULL;
+  int base = id * rnum;
+  int mrange = tclmax(50 + rratio, 100);
+  for(int i = 1; !err && i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", base + myrandnd(i));
+    int rnd = myrand(mrange);
+    if(rnd < 10){
+      if(ndb){
+        tcndbput(ndb, buf, len, buf, len);
+      } else {
+        tcmdbput(mdb, buf, len, buf, len);
+      }
+      if(map) tcmapput(map, buf, len, buf, len);
+    } else if(rnd < 15){
+      if(ndb){
+        tcndbputkeep(ndb, buf, len, buf, len);
+      } else {
+        tcmdbputkeep(mdb, buf, len, buf, len);
+      }
+      if(map) tcmapputkeep(map, buf, len, buf, len);
+    } else if(rnd < 20){
+      if(ndb){
+        tcndbputcat(ndb, buf, len, buf, len);
+      } else {
+        tcmdbputcat(mdb, buf, len, buf, len);
+      }
+      if(map) tcmapputcat(map, buf, len, buf, len);
+    } else if(rnd < 30){
+      if(ndb){
+        tcndbout(ndb, buf, len);
+      } else {
+        tcmdbout(mdb, buf, len);
+      }
+      if(map) tcmapout(map, buf, len);
+    } else if(rnd < 31){
+      if(myrand(10) == 0) tcmdbiterinit(mdb);
+      for(int j = 0; !err && j < 10; j++){
+        int ksiz;
+        char *kbuf = ndb ? tcndbiternext(ndb, &ksiz) : tcmdbiternext(mdb, &ksiz);
+        if(kbuf) tcfree(kbuf);
+      }
+    } else {
+      int vsiz;
+      char *vbuf = ndb ? tcndbget(ndb, buf, len, &vsiz) : tcmdbget(mdb, buf, len, &vsiz);
+      if(vbuf){
+        if(map){
+          int msiz;
+          const char *mbuf = tcmapget(map, buf, len, &msiz);
+          if(msiz != vsiz || memcmp(mbuf, vbuf, vsiz)){
+            eprint(__LINE__, "(validation)");
+            err = true;
+          }
+        }
+        tcfree(vbuf);
+      } else {
+        if(map && tcmapget(map, buf, len, &vsiz)){
+          eprint(__LINE__, "(validation)");
+          err = true;
+        }
+      }
+    }
+    if(id == 0 && rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(map){
+    tcmapiterinit(map);
+    int ksiz;
+    const char *kbuf;
+    while(!err && (kbuf = tcmapiternext(map, &ksiz)) != NULL){
+      int vsiz;
+      char *vbuf = ndb ? tcndbget(ndb, kbuf, ksiz, &vsiz) : tcmdbget(mdb, kbuf, ksiz, &vsiz);
+      if(vbuf){
+        int msiz;
+        const char *mbuf = tcmapget(map, kbuf, ksiz, &msiz);
+        if(!mbuf || msiz != vsiz || memcmp(mbuf, vbuf, vsiz)){
+          eprint(__LINE__, "(validation)");
+          err = true;
+        }
+        tcfree(vbuf);
+      } else {
+        eprint(__LINE__, "(validation)");
+        err = true;
+      }
+    }
+    tcmapdel(map);
+  }
+  return err ? "error" : NULL;
+}
+
+
+
+// END OF FILE
diff --git a/tcejdb/tcutest.c b/tcejdb/tcutest.c
new file mode 100644 (file)
index 0000000..bf2cb4d
--- /dev/null
@@ -0,0 +1,1875 @@
+/*************************************************************************************************
+ * The test cases of the utility API
+ *                                                               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"
+
+#define RECBUFSIZ      48                // buffer for records
+
+
+/* global variables */
+const char *g_progname;                  // program name
+unsigned int g_randseed;                 // random seed
+
+
+/* function prototypes */
+int main(int argc, char **argv);
+static void usage(void);
+static void iprintf(const char *format, ...);
+static void iputchar(int c);
+static void sysprint(void);
+static int myrand(int range);
+static void *pdprocfunc(const void *vbuf, int vsiz, int *sp, void *op);
+static bool iterfunc(const void *kbuf, int ksiz, const void *vbuf, int vsiz, void *op);
+static int intcompar(const void *ap, const void *bp);
+static int runxstr(int argc, char **argv);
+static int runlist(int argc, char **argv);
+static int runmap(int argc, char **argv);
+static int runtree(int argc, char **argv);
+static int runmdb(int argc, char **argv);
+static int runndb(int argc, char **argv);
+static int runmisc(int argc, char **argv);
+static int runwicked(int argc, char **argv);
+static int procxstr(int rnum);
+static int proclist(int rnum, int anum, bool rd);
+static int procmap(int rnum, int bnum, bool rd, bool tr, bool rnd, int dmode);
+static int proctree(int rnum, bool rd, bool tr, bool rnd, int dmode);
+static int procmdb(int rnum, int bnum, bool rd, bool tr, bool rnd, int dmode);
+static int procndb(int rnum, bool rd, bool tr, bool rnd, int dmode);
+static int procmisc(int rnum);
+static int procwicked(int rnum);
+
+
+/* main routine */
+int main(int argc, char **argv){
+  g_progname = argv[0];
+  const char *ebuf = getenv("TCRNDSEED");
+  g_randseed = ebuf ? tcatoix(ebuf) : tctime() * 1000;
+  srand(g_randseed);
+  if(argc < 2) usage();
+  int rv = 0;
+  if(!strcmp(argv[1], "xstr")){
+    rv = runxstr(argc, argv);
+  } else if(!strcmp(argv[1], "list")){
+    rv = runlist(argc, argv);
+  } else if(!strcmp(argv[1], "map")){
+    rv = runmap(argc, argv);
+  } else if(!strcmp(argv[1], "tree")){
+    rv = runtree(argc, argv);
+  } else if(!strcmp(argv[1], "mdb")){
+    rv = runmdb(argc, argv);
+  } else if(!strcmp(argv[1], "ndb")){
+    rv = runndb(argc, argv);
+  } else if(!strcmp(argv[1], "misc")){
+    rv = runmisc(argc, argv);
+  } else if(!strcmp(argv[1], "wicked")){
+    rv = runwicked(argc, argv);
+  } else {
+    usage();
+  }
+  if(rv != 0){
+    printf("FAILED: TCRNDSEED=%u PID=%d", g_randseed, (int)getpid());
+    for(int i = 0; i < argc; i++){
+      printf(" %s", argv[i]);
+    }
+    printf("\n\n");
+  }
+  return rv;
+}
+
+
+/* print the usage and exit */
+static void usage(void){
+  fprintf(stderr, "%s: test cases of the utility API of Tokyo Cabinet\n", g_progname);
+  fprintf(stderr, "\n");
+  fprintf(stderr, "usage:\n");
+  fprintf(stderr, "  %s xstr rnum\n", g_progname);
+  fprintf(stderr, "  %s list [-rd] rnum [anum]\n", g_progname);
+  fprintf(stderr, "  %s map [-rd] [-tr] [-rnd] [-dk|-dc|-dai|-dad|-dpr] rnum [bnum]\n",
+          g_progname);
+  fprintf(stderr, "  %s tree [-rd] [-tr] [-rnd] [-dk|-dc|-dai|-dad|-dpr] rnum\n",
+          g_progname);
+  fprintf(stderr, "  %s mdb [-rd] [-tr] [-rnd] [-dk|-dc|-dai|-dad|-dpr] rnum [bnum]\n",
+          g_progname);
+  fprintf(stderr, "  %s ndb [-rd] [-tr] [-rnd] [-dk|-dc|-dai|-dad|-dpr] rnum\n",
+          g_progname);
+  fprintf(stderr, "  %s misc rnum\n", g_progname);
+  fprintf(stderr, "  %s wicked rnum\n", g_progname);
+  fprintf(stderr, "\n");
+  exit(1);
+}
+
+
+/* print formatted information string and flush the buffer */
+static void iprintf(const char *format, ...){
+  va_list ap;
+  va_start(ap, format);
+  vprintf(format, ap);
+  fflush(stdout);
+  va_end(ap);
+}
+
+
+/* print a character and flush the buffer */
+static void iputchar(int c){
+  putchar(c);
+  fflush(stdout);
+}
+
+
+/* print system information */
+static void sysprint(void){
+  TCMAP *info = tcsysinfo();
+  if(info){
+    tcmapiterinit(info);
+    const char *kbuf;
+    while((kbuf = tcmapiternext2(info)) != NULL){
+      iprintf("sys_%s: %s\n", kbuf, tcmapiterval2(kbuf));
+    }
+    tcmapdel(info);
+  }
+}
+
+
+/* get a random number */
+static int myrand(int range){
+  if(range < 2) return 0;
+  int high = (unsigned int)rand() >> 4;
+  int low = range * (rand() / (RAND_MAX + 1.0));
+  low &= (unsigned int)INT_MAX >> 4;
+  return (high + low) % range;
+}
+
+
+/* duplication callback function */
+static void *pdprocfunc(const void *vbuf, int vsiz, int *sp, void *op){
+  if(myrand(4) == 0) return (void *)-1;
+  if(myrand(2) == 0) return NULL;
+  int len = myrand(RECBUFSIZ);
+  char buf[RECBUFSIZ];
+  memset(buf, '*', len);
+  *sp = len;
+  return tcmemdup(buf, len);
+}
+
+
+/* iterator function */
+static bool iterfunc(const void *kbuf, int ksiz, const void *vbuf, int vsiz, void *op){
+  unsigned int sum = 0;
+  while(--ksiz >= 0){
+    sum += ((char *)kbuf)[ksiz];
+  }
+  while(--vsiz >= 0){
+    sum += ((char *)vbuf)[vsiz];
+  }
+  return myrand(100 + (sum & 0xff)) > 0;
+}
+
+
+/* compare two integers */
+static int intcompar(const void *ap, const void *bp){
+  return *(int *)ap - *(int *)bp;
+}
+
+
+/* parse arguments of xstr command */
+static int runxstr(int argc, char **argv){
+  char *rstr = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!rstr && argv[i][0] == '-'){
+      usage();
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int rv = procxstr(rnum);
+  return rv;
+}
+
+
+/* parse arguments of list command */
+static int runlist(int argc, char **argv){
+  char *rstr = NULL;
+  char *astr = NULL;
+  bool rd = false;
+  for(int i = 2; i < argc; i++){
+    if(!rstr && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-rd")){
+        rd = true;
+      } else {
+        usage();
+      }
+    } else if(!rstr){
+      rstr = argv[i];
+    } else if(!astr){
+      astr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int anum = astr ? tcatoix(astr) : -1;
+  int rv = proclist(rnum, anum, rd);
+  return rv;
+}
+
+
+/* parse arguments of map command */
+static int runmap(int argc, char **argv){
+  char *rstr = NULL;
+  char *bstr = NULL;
+  bool rd = false;
+  bool tr = false;
+  bool rnd = false;
+  int dmode = 0;
+  for(int i = 2; i < argc; i++){
+    if(!rstr && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-rd")){
+        rd = true;
+      } else if(!strcmp(argv[i], "-tr")){
+        tr = true;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else if(!strcmp(argv[i], "-dk")){
+        dmode = -1;
+      } else if(!strcmp(argv[i], "-dc")){
+        dmode = 1;
+      } else if(!strcmp(argv[i], "-dai")){
+        dmode = 10;
+      } else if(!strcmp(argv[i], "-dad")){
+        dmode = 11;
+      } else if(!strcmp(argv[i], "-dpr")){
+        dmode = 12;
+      } else {
+        usage();
+      }
+    } else if(!rstr){
+      rstr = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int rv = procmap(rnum, bnum, rd, tr, rnd, dmode);
+  return rv;
+}
+
+
+/* parse arguments of tree command */
+static int runtree(int argc, char **argv){
+  char *rstr = NULL;
+  bool rd = false;
+  bool tr = false;
+  bool rnd = false;
+  int dmode = 0;
+  for(int i = 2; i < argc; i++){
+    if(!rstr && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-rd")){
+        rd = true;
+      } else if(!strcmp(argv[i], "-tr")){
+        tr = true;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else if(!strcmp(argv[i], "-dk")){
+        dmode = -1;
+      } else if(!strcmp(argv[i], "-dc")){
+        dmode = 1;
+      } else if(!strcmp(argv[i], "-dai")){
+        dmode = 10;
+      } else if(!strcmp(argv[i], "-dad")){
+        dmode = 11;
+      } else if(!strcmp(argv[i], "-dpr")){
+        dmode = 12;
+      } else {
+        usage();
+      }
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int rv = proctree(rnum, rd, tr, rnd, dmode);
+  return rv;
+}
+
+
+/* parse arguments of mdb command */
+static int runmdb(int argc, char **argv){
+  char *rstr = NULL;
+  char *bstr = NULL;
+  bool rd = false;
+  bool tr = false;
+  bool rnd = false;
+  int dmode = 0;
+  for(int i = 2; i < argc; i++){
+    if(!rstr && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-rd")){
+        rd = true;
+      } else if(!strcmp(argv[i], "-tr")){
+        tr = true;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else if(!strcmp(argv[i], "-dk")){
+        dmode = -1;
+      } else if(!strcmp(argv[i], "-dc")){
+        dmode = 1;
+      } else if(!strcmp(argv[i], "-dai")){
+        dmode = 10;
+      } else if(!strcmp(argv[i], "-dad")){
+        dmode = 11;
+      } else if(!strcmp(argv[i], "-dpr")){
+        dmode = 12;
+      } else {
+        usage();
+      }
+    } else if(!rstr){
+      rstr = argv[i];
+    } else if(!bstr){
+      bstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int bnum = bstr ? tcatoix(bstr) : -1;
+  int rv = procmdb(rnum, bnum, rd, tr, rnd, dmode);
+  return rv;
+}
+
+
+/* parse arguments of ndb command */
+static int runndb(int argc, char **argv){
+  char *rstr = NULL;
+  bool rd = false;
+  bool tr = false;
+  bool rnd = false;
+  int dmode = 0;
+  for(int i = 2; i < argc; i++){
+    if(!rstr && argv[i][0] == '-'){
+      if(!strcmp(argv[i], "-rd")){
+        rd = true;
+      } else if(!strcmp(argv[i], "-tr")){
+        tr = true;
+      } else if(!strcmp(argv[i], "-rnd")){
+        rnd = true;
+      } else if(!strcmp(argv[i], "-dk")){
+        dmode = -1;
+      } else if(!strcmp(argv[i], "-dc")){
+        dmode = 1;
+      } else if(!strcmp(argv[i], "-dai")){
+        dmode = 10;
+      } else if(!strcmp(argv[i], "-dad")){
+        dmode = 11;
+      } else if(!strcmp(argv[i], "-dpr")){
+        dmode = 12;
+      } else {
+        usage();
+      }
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int rv = procndb(rnum, rd, tr, rnd, dmode);
+  return rv;
+}
+
+
+/* parse arguments of misc command */
+static int runmisc(int argc, char **argv){
+  char *rstr = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!rstr && argv[i][0] == '-'){
+      usage();
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int rv = procmisc(rnum);
+  return rv;
+}
+
+
+/* parse arguments of wicked command */
+static int runwicked(int argc, char **argv){
+  char *rstr = NULL;
+  for(int i = 2; i < argc; i++){
+    if(!rstr && argv[i][0] == '-'){
+      usage();
+    } else if(!rstr){
+      rstr = argv[i];
+    } else {
+      usage();
+    }
+  }
+  if(!rstr) usage();
+  int rnum = tcatoix(rstr);
+  if(rnum < 1) usage();
+  int rv = procwicked(rnum);
+  return rv;
+}
+
+
+/* perform xstr command */
+static int procxstr(int rnum){
+  iprintf("<Extensible String Writing Test>\n  seed=%u  rnum=%d\n\n", g_randseed, rnum);
+  double stime = tctime();
+  TCXSTR *xstr = tcxstrnew();
+  for(int i = 1; i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", i);
+    tcxstrcat(xstr, buf, len);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("size: %u\n", tcxstrsize(xstr));
+  sysprint();
+  tcxstrdel(xstr);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("ok\n\n");
+  return 0;
+}
+
+
+/* perform list command */
+static int proclist(int rnum, int anum, bool rd){
+  iprintf("<List Writing Test>\n  seed=%u  rnum=%d  anum=%d  rd=%d\n\n",
+          g_randseed, rnum, anum, rd);
+  double stime = tctime();
+  TCLIST *list = (anum > 0) ? tclistnew2(anum) : tclistnew();
+  for(int i = 1; i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", i);
+    tclistpush(list, buf, len);
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(rd){
+    double itime = tctime();
+    iprintf("time: %.3f\n", itime - stime);
+    stime = itime;
+    for(int i = 1; i <= rnum; i++){
+      int len;
+      tclistval(list, i, &len);
+      if(rnum > 250 && i % (rnum / 250) == 0){
+        iputchar('.');
+        if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+      }
+    }
+  }
+  iprintf("record number: %u\n", tclistnum(list));
+  sysprint();
+  tclistdel(list);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("ok\n\n");
+  return 0;
+}
+
+
+/* perform map command */
+static int procmap(int rnum, int bnum, bool rd, bool tr, bool rnd, int dmode){
+  iprintf("<Map Writing Test>\n  seed=%u  rnum=%d  bnum=%d  rd=%d  tr=%d  rnd=%d  dmode=%d\n\n",
+          g_randseed, rnum, bnum, rd, tr, rnd, dmode);
+  double stime = tctime();
+  TCMAP *map = (bnum > 0) ? tcmapnew2(bnum) : tcmapnew();
+  for(int i = 1; i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", rnd ? myrand(rnum) + 1 : i);
+    switch(dmode){
+      case -1:
+        tcmapputkeep(map, buf, len, buf, len);
+        break;
+      case 1:
+        tcmapputcat(map, buf, len, buf, len);
+        break;
+      case 10:
+        tcmapaddint(map, buf, len, myrand(3));
+        break;
+      case 11:
+        tcmapadddouble(map, buf, len, myrand(3));
+        break;
+      case 12:
+        tcmapputproc(map, buf, len, buf, len, pdprocfunc, NULL);
+        break;
+      default:
+        tcmapput(map, buf, len, buf, len);
+        break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(rd){
+    double itime = tctime();
+    iprintf("time: %.3f\n", itime - stime);
+    stime = itime;
+    for(int i = 1; i <= rnum; i++){
+      char buf[RECBUFSIZ];
+      int len = sprintf(buf, "%08d", rnd ? myrand(rnum) + 1 : i);
+      tcmapget(map, buf, len, &len);
+      if(rnum > 250 && i % (rnum / 250) == 0){
+        iputchar('.');
+        if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+      }
+    }
+  }
+  if(tr){
+    double itime = tctime();
+    iprintf("time: %.3f\n", itime - stime);
+    stime = itime;
+    tcmapiterinit(map);
+    int ksiz;
+    const char *kbuf;
+    int inum = 1;
+    while((kbuf = tcmapiternext(map, &ksiz)) != NULL){
+      tcmapiterval2(kbuf);
+      if(rnum > 250 && inum % (rnum / 250) == 0){
+        iputchar('.');
+        if(inum == rnum || inum % (rnum / 10) == 0) iprintf(" (%08d)\n", inum);
+      }
+      inum++;
+    }
+    if(rnd && rnum > 250) iprintf(" (%08d)\n", inum);
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcmaprnum(map));
+  iprintf("size: %llu\n", (unsigned long long)tcmapmsiz(map));
+  sysprint();
+  tcmapdel(map);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("ok\n\n");
+  return 0;
+}
+
+
+/* perform tree command */
+static int proctree(int rnum, bool rd, bool tr, bool rnd, int dmode){
+  iprintf("<Tree Writing Test>\n  seed=%u  rnum=%d  rd=%d  tr=%d  rnd=%d  dmode=%d\n\n",
+          g_randseed, rnum, rd, tr, rnd, dmode);
+  double stime = tctime();
+  TCTREE *tree = tctreenew();
+  for(int i = 1; i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", rnd ? myrand(rnum) + 1 : i);
+    switch(dmode){
+      case -1:
+        tctreeputkeep(tree, buf, len, buf, len);
+        break;
+      case 1:
+        tctreeputcat(tree, buf, len, buf, len);
+        break;
+      case 10:
+        tctreeaddint(tree, buf, len, myrand(3));
+        break;
+      case 11:
+        tctreeadddouble(tree, buf, len, myrand(3));
+        break;
+      case 12:
+        tctreeputproc(tree, buf, len, buf, len, pdprocfunc, NULL);
+        break;
+      default:
+        tctreeput(tree, buf, len, buf, len);
+        break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(rd){
+    double itime = tctime();
+    iprintf("time: %.3f\n", itime - stime);
+    stime = itime;
+    for(int i = 1; i <= rnum; i++){
+      char buf[RECBUFSIZ];
+      int len = sprintf(buf, "%08d", rnd ? myrand(rnum) + 1 : i);
+      tctreeget(tree, buf, len, &len);
+      if(rnum > 250 && i % (rnum / 250) == 0){
+        iputchar('.');
+        if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+      }
+    }
+  }
+  if(tr){
+    double itime = tctime();
+    iprintf("time: %.3f\n", itime - stime);
+    stime = itime;
+    tctreeiterinit(tree);
+    int ksiz;
+    const char *kbuf;
+    int inum = 1;
+    while((kbuf = tctreeiternext(tree, &ksiz)) != NULL){
+      tctreeiterval2(kbuf);
+      if(rnum > 250 && inum % (rnum / 250) == 0){
+        iputchar('.');
+        if(inum == rnum || inum % (rnum / 10) == 0) iprintf(" (%08d)\n", inum);
+      }
+      inum++;
+    }
+    if(rnd && rnum > 250) iprintf(" (%08d)\n", inum);
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tctreernum(tree));
+  iprintf("size: %llu\n", (unsigned long long)tctreemsiz(tree));
+  sysprint();
+  tctreedel(tree);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("ok\n\n");
+  return 0;
+}
+
+
+/* perform mdb command */
+static int procmdb(int rnum, int bnum, bool rd, bool tr, bool rnd, int dmode){
+  iprintf("<On-memory Hash Database Writing Test>\n  seed=%u  rnum=%d  bnum=%d  rd=%d  tr=%d"
+          "  rnd=%d  dmode=%d\n\n", g_randseed, rnum, bnum, rd, tr, rnd, dmode);
+  double stime = tctime();
+  TCMDB *mdb = (bnum > 0) ? tcmdbnew2(bnum) : tcmdbnew();
+  for(int i = 1; i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", rnd ? myrand(rnum) + 1 : i);
+    switch(dmode){
+      case -1:
+        tcmdbputkeep(mdb, buf, len, buf, len);
+        break;
+      case 1:
+        tcmdbputcat(mdb, buf, len, buf, len);
+        break;
+      case 10:
+        tcmdbaddint(mdb, buf, len, myrand(3));
+        break;
+      case 11:
+        tcmdbadddouble(mdb, buf, len, myrand(3));
+        break;
+      case 12:
+        tcmdbputproc(mdb, buf, len, buf, len, pdprocfunc, NULL);
+        break;
+      default:
+        tcmdbput(mdb, buf, len, buf, len);
+        break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(rd){
+    double itime = tctime();
+    iprintf("time: %.3f\n", itime - stime);
+    stime = itime;
+    for(int i = 1; i <= rnum; i++){
+      char buf[RECBUFSIZ];
+      int len = sprintf(buf, "%08d", rnd ? myrand(rnum) + 1 : i);
+      tcfree(tcmdbget(mdb, buf, len, &len));
+      if(rnum > 250 && i % (rnum / 250) == 0){
+        iputchar('.');
+        if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+      }
+    }
+  }
+  if(tr){
+    double itime = tctime();
+    iprintf("time: %.3f\n", itime - stime);
+    stime = itime;
+    tcmdbiterinit(mdb);
+    int ksiz;
+    char *kbuf;
+    int inum = 1;
+    while((kbuf = tcmdbiternext(mdb, &ksiz)) != NULL){
+      tcfree(kbuf);
+      if(rnum > 250 && inum % (rnum / 250) == 0){
+        iputchar('.');
+        if(inum == rnum || inum % (rnum / 10) == 0) iprintf(" (%08d)\n", inum);
+      }
+      inum++;
+    }
+    if(rnd && rnum > 250) iprintf(" (%08d)\n", inum);
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcmdbrnum(mdb));
+  iprintf("size: %llu\n", (unsigned long long)tcmdbmsiz(mdb));
+  sysprint();
+  tcmdbdel(mdb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("ok\n\n");
+  return 0;
+}
+
+
+/* perform ndb command */
+static int procndb(int rnum, bool rd, bool tr, bool rnd, int dmode){
+  iprintf("<On-memory Tree Database Writing Test>\n  seed=%u  rnum=%d  rd=%d  tr=%d"
+          "  rnd=%d  dmode=%d\n\n", g_randseed, rnum, rd, tr, rnd, dmode);
+  double stime = tctime();
+  TCNDB *ndb = tcndbnew();
+  for(int i = 1; i <= rnum; i++){
+    char buf[RECBUFSIZ];
+    int len = sprintf(buf, "%08d", rnd ? myrand(rnum) + 1 : i);
+    switch(dmode){
+      case -1:
+        tcndbputkeep(ndb, buf, len, buf, len);
+        break;
+      case 1:
+        tcndbputcat(ndb, buf, len, buf, len);
+        break;
+      case 10:
+        tcndbaddint(ndb, buf, len, myrand(3));
+        break;
+      case 11:
+        tcndbadddouble(ndb, buf, len, myrand(3));
+        break;
+      case 12:
+        tcndbputproc(ndb, buf, len, buf, len, pdprocfunc, NULL);
+        break;
+      default:
+        tcndbput(ndb, buf, len, buf, len);
+        break;
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  if(rd){
+    double itime = tctime();
+    iprintf("time: %.3f\n", itime - stime);
+    stime = itime;
+    for(int i = 1; i <= rnum; i++){
+      char buf[RECBUFSIZ];
+      int len = sprintf(buf, "%08d", rnd ? myrand(rnum) + 1 : i);
+      tcfree(tcndbget(ndb, buf, len, &len));
+      if(rnum > 250 && i % (rnum / 250) == 0){
+        iputchar('.');
+        if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+      }
+    }
+  }
+  if(tr){
+    double itime = tctime();
+    iprintf("time: %.3f\n", itime - stime);
+    stime = itime;
+    tcndbiterinit(ndb);
+    int ksiz;
+    char *kbuf;
+    int inum = 1;
+    while((kbuf = tcndbiternext(ndb, &ksiz)) != NULL){
+      tcfree(kbuf);
+      if(rnum > 250 && inum % (rnum / 250) == 0){
+        iputchar('.');
+        if(inum == rnum || inum % (rnum / 10) == 0) iprintf(" (%08d)\n", inum);
+      }
+      inum++;
+    }
+    if(rnd && rnum > 250) iprintf(" (%08d)\n", inum);
+  }
+  iprintf("record number: %llu\n", (unsigned long long)tcndbrnum(ndb));
+  iprintf("size: %llu\n", (unsigned long long)tcndbmsiz(ndb));
+  sysprint();
+  tcndbdel(ndb);
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("ok\n\n");
+  return 0;
+}
+
+
+/* perform misc command */
+static int procmisc(int rnum){
+  iprintf("<Miscellaneous Test>\n  seed=%u  rnum=%d\n\n", g_randseed, rnum);
+  double stime = tctime();
+  bool err = false;
+  for(int i = 1; i <= rnum && !err; i++){
+    const char *str = "5%2+3-1=4 \"Yes/No\" <a&b>";
+    int slen = strlen(str);
+    char *buf, *dec;
+    int bsiz, dsiz, jl;
+    time_t date, ddate;
+    TCXSTR *xstr;
+    TCLIST *list;
+    TCMAP *map;
+    TCTREE *tree;
+    TCPTRLIST *ptrlist;
+    buf = tcmemdup(str, slen);
+    xstr = tcxstrfrommalloc(buf, slen);
+    buf = tcxstrtomalloc(xstr);
+    if(strcmp(buf, str)) err = true;
+    tcfree(buf);
+    if(tclmax(1, 2) != 2) err = true;
+    if(tclmin(1, 2) != 1) err = true;
+    tclrand();
+    if(tcdrand() >= 1.0) err = true;
+    tcdrandnd(50, 10);
+    if(tcstricmp("abc", "ABC") != 0) err = true;
+    if(!tcstrfwm("abc", "ab") || !tcstrifwm("abc", "AB")) err = true;
+    if(!tcstrbwm("abc", "bc") || !tcstribwm("abc", "BC")) err = true;
+    if(tcstrdist("abcde", "abdfgh") != 4 || tcstrdist("abdfgh", "abcde") != 4) err = true;
+    if(tcstrdistutf("abcde", "abdfgh") != 4 || tcstrdistutf("abdfgh", "abcde") != 4) err = true;
+    buf = tcmemdup("abcde", 5);
+    tcstrtoupper(buf);
+    if(strcmp(buf, "ABCDE")) err = true;
+    tcstrtolower(buf);
+    if(strcmp(buf, "abcde")) err = true;
+    tcfree(buf);
+    buf = tcmemdup("  ab  cd  ", 10);
+    tcstrtrim(buf);
+    if(strcmp(buf, "ab  cd")) err = true;
+    tcstrsqzspc(buf);
+    if(strcmp(buf, "ab cd")) err = true;
+    tcstrsubchr(buf, "cd", "C");
+    if(strcmp(buf, "ab C")) err = true;
+    if(tcstrcntutf(buf) != 4) err = true;
+    tcstrcututf(buf, 2);
+    if(strcmp(buf, "ab")) err = true;
+    tcfree(buf);
+    if(i % 10 == 1){
+      int anum = myrand(30);
+      uint16_t ary[anum+1];
+      for(int j = 0; j < anum; j++){
+        ary[j] = myrand(65535) + 1;
+      }
+      char ustr[anum*3+1];
+      tcstrucstoutf(ary, anum, ustr);
+      uint16_t dary[anum+1];
+      int danum;
+      tcstrutftoucs(ustr, dary, &danum);
+      if(danum != anum){
+        err = true;
+      } else {
+        for(int j = 0; j < danum; j++){
+          if(dary[j] != dary[j]) err = true;
+        }
+      }
+      tcstrutfnorm(ustr, TCUNSPACE | TCUNLOWER | TCUNNOACC | TCUNWIDTH);
+      if(tcstrucsnorm(dary, danum, TCUNSPACE | TCUNLOWER | TCUNNOACC | TCUNWIDTH) > danum)
+        err = true;
+      list = tcstrtokenize("a ab abc b bc bcd abcde \"I'm nancy\" x \"xx");
+      if(tclistnum(list) != 10) err = true;
+      int opts = myrand(TCKWMUBRCT + 1);
+      if(myrand(2) == 0) opts |= TCKWNOOVER;
+      if(myrand(2) == 0) opts |= TCKWPULEAD;
+      TCLIST *texts = tcstrkwic(ustr, list, myrand(10), opts);
+      tclistdel(texts);
+      tclistdel(list);
+      list = tclistnew3("hop", "step", "jump", "touchdown", NULL);
+      if(tclistnum(list) != 4) err = true;
+      tclistprintf(list, "%s:%010d:%7.3f", "game set", 123456789, 12345.6789);
+      tclistdel(list);
+      map = tcmapnew3("hop", "step", "jump", "touchdown", NULL);
+      if(tcmaprnum(map) != 2) err = true;
+      tcmapprintf(map, "joker", "%s:%010d:%7.3f", "game set", 123456789, 12345.6789);
+      tcmapdel(map);
+      list = tcstrsplit(",a,b..c,d,", ",.");
+      if(tclistnum(list) != 7) err = true;
+      buf = tcstrjoin(list, ':');
+      if(strcmp(buf, ":a:b::c:d:")) err = true;
+      tcfree(buf);
+      tclistdel(list);
+      char zbuf[RECBUFSIZ];
+      memcpy(zbuf, "abc\0def\0ghij\0kl\0m", 17);
+      list = tcstrsplit2(zbuf, 17);
+      if(tclistnum(list) != 5) err = true;
+      buf = tcstrjoin2(list, &bsiz);
+      if(bsiz != 17 || memcmp(buf, "abc\0def\0ghij\0kl\0m", 17)) err = true;
+      tcfree(buf);
+      tclistdel(list);
+      map = tcstrsplit3("abc.def,ghij.kl,", ",.");
+      if(tcmaprnum(map) != 2) err = true;
+      buf = tcstrjoin3(map, ':');
+      if(strcmp(buf, "abc:def:ghij:kl")) err = true;
+      tcfree(buf);
+      tcmapdel(map);
+      memcpy(zbuf, "abc\0def\0ghij\0kl\0m", 17);
+      map = tcstrsplit4(zbuf, 17);
+      if(tcmaprnum(map) != 2) err = true;
+      buf = tcstrjoin4(map, &bsiz);
+      if(bsiz != 15 || memcmp(buf, "abc\0def\0ghij\0kl", 15)) err = true;
+      tcfree(buf);
+      tcmapdel(map);
+      if(!tcregexmatch("ABCDEFGHI", "*(b)c[d-f]*g(h)")) err = true;
+      buf = tcregexreplace("ABCDEFGHI", "*(b)c[d-f]*g(h)", "[\\1][\\2][&]");
+      if(strcmp(buf, "A[B][H][BCDEFGH]I")) err = true;
+      tcfree(buf);
+      buf = tcmalloc(48);
+      for(int i = 0; i < 10; i++){
+        char kbuf[RECBUFSIZ];
+        int ksiz = sprintf(kbuf, "%d", myrand(1000000));
+        tcmd5hash(kbuf, ksiz, buf);
+      }
+      tcfree(buf);
+      anum = myrand(30) + 1;
+      int tary[anum], qary[anum];
+      for(int j = 0; j < anum; j++){
+        int val = myrand(anum * 2 + 1);
+        tary[j] = val;
+        qary[j] = val;
+      }
+      int tnum = myrand(anum);
+      tctopsort(tary, anum, sizeof(*tary), tnum, intcompar);
+      qsort(qary, anum, sizeof(*qary), intcompar);
+      for(int j = 0; j < tnum; j++){
+        if(tary[j] != qary[j]) err = true;
+      }
+      qsort(tary, anum, sizeof(*tary), intcompar);
+      for(int j = 0; j < anum; j++){
+        if(tary[j] != qary[j]) err = true;
+      }
+      TCCHIDX *chidx = tcchidxnew(5);
+      for(int i = 0; i < 10; i++){
+        char kbuf[RECBUFSIZ];
+        int ksiz = sprintf(kbuf, "%d", myrand(1000000));
+        tcchidxhash(chidx, kbuf, ksiz);
+      }
+      tcchidxdel(chidx);
+      char kbuf[TCNUMBUFSIZ];
+      int ksiz = sprintf(kbuf, "%d", myrand(200));
+      char *enc = tcmalloc(slen + 1);
+      tcarccipher(str, slen, kbuf, ksiz, enc);
+      tcarccipher(enc, slen, kbuf, ksiz, enc);
+      if(memcmp(enc, str, slen)) err = true;
+      tcfree(enc);
+      buf = tczeromap(myrand(1024*256) + 1);
+      tczerounmap(buf);
+    }
+    buf = tcmalloc(48);
+    date = myrand(INT_MAX - 1000000);
+    jl = 3600 * (myrand(23) - 11);
+    tcdatestrwww(date, jl, buf);
+    ddate = tcstrmktime(buf);
+    if(ddate != date) err = true;
+    tcdatestrhttp(date, jl, buf);
+    ddate = tcstrmktime(buf);
+    if(ddate != date) err = true;
+    tcfree(buf);
+    if(i % 100 == 1){
+      map = myrand(2) == 0 ? tcmapnew() : tcmapnew2(myrand(10));
+      tree = tctreenew();
+      for(int j = 0; j < 10; j++){
+        char kbuf[RECBUFSIZ];
+        int ksiz = sprintf(kbuf, "%d", myrand(10));
+        tcmapaddint(map, kbuf, ksiz, 1);
+        const char *vbuf = tcmapget2(map, kbuf);
+        if(*(int *)vbuf < 1) err = true;
+        tctreeaddint(tree, kbuf, ksiz, 1);
+        vbuf = tctreeget2(tree, kbuf);
+        if(*(int *)vbuf < 1) err = true;
+      }
+      tcmapclear(map);
+      tctreeclear(tree);
+      for(int j = 0; j < 10; j++){
+        char kbuf[RECBUFSIZ];
+        int ksiz = sprintf(kbuf, "%d", myrand(10));
+        tcmapadddouble(map, kbuf, ksiz, 1.0);
+        const char *vbuf = tcmapget2(map, kbuf);
+        if(*(double *)vbuf < 1.0) err = true;
+        tctreeadddouble(tree, kbuf, ksiz, 1.0);
+        vbuf = tctreeget2(tree, kbuf);
+        if(*(double *)vbuf < 1.0) err = true;
+      }
+      for(int j = 0; j < 10; j++){
+        char kbuf[RECBUFSIZ];
+        sprintf(kbuf, "%d", myrand(10));
+        tcmapprintf(map, kbuf, "%s:%f", kbuf, (double)i * j);
+        tctreeprintf(tree, kbuf, "%s:%f", kbuf, (double)i * j);
+      }
+      if(!tcmapget4(map, "ace", "dummy")) err = true;
+      if(!tctreeget4(tree, "ace", "dummy")) err = true;
+      tctreedel(tree);
+      tcmapdel(map);
+    }
+    if(i % 100 == 1){
+      ptrlist = myrand(2) == 0 ? tcptrlistnew() : tcptrlistnew2(myrand(10));
+      for(int j = 0; j < 10; j++){
+        tcptrlistpush(ptrlist, tcsprintf("%d", j));
+        tcptrlistunshift(ptrlist, tcsprintf("::%d", j));
+      }
+      for(int j = 0; j < 5; j++){
+        tcfree(tcptrlistpop(ptrlist));
+        tcfree(tcptrlistshift(ptrlist));
+      }
+      for(int j = 0; j < tcptrlistnum(ptrlist); j++){
+        tcfree(tcptrlistval(ptrlist, j));
+      }
+      tcptrlistdel(ptrlist);
+    }
+    buf = tcurlencode(str, slen);
+    if(strcmp(buf, "5%252%2B3-1%3D4%20%22Yes%2FNo%22%20%3Ca%26b%3E")) err = true;
+    dec = tcurldecode(buf, &dsiz);
+    if(dsiz != slen || strcmp(dec, str)) err = true;
+    tcfree(dec);
+    tcfree(buf);
+    if(i % 10 == 1){
+      map = tcurlbreak("http://mikio:oikim@estraier.net:1978/foo/bar/baz.cgi?ab=cd&ef=jkl#quux");
+      const char *elem;
+      if(!(elem = tcmapget2(map, "self")) ||
+         strcmp(elem, "http://mikio:oikim@estraier.net:1978/foo/bar/baz.cgi?ab=cd&ef=jkl#quux"))
+        err = true;
+      if(!(elem = tcmapget2(map, "scheme")) || strcmp(elem, "http")) err = true;
+      if(!(elem = tcmapget2(map, "host")) || strcmp(elem, "estraier.net")) err = true;
+      if(!(elem = tcmapget2(map, "port")) || strcmp(elem, "1978")) err = true;
+      if(!(elem = tcmapget2(map, "authority")) || strcmp(elem, "mikio:oikim")) err = true;
+      if(!(elem = tcmapget2(map, "path")) || strcmp(elem, "/foo/bar/baz.cgi")) err = true;
+      if(!(elem = tcmapget2(map, "file")) || strcmp(elem, "baz.cgi")) err = true;
+      if(!(elem = tcmapget2(map, "query")) || strcmp(elem, "ab=cd&ef=jkl")) err = true;
+      if(!(elem = tcmapget2(map, "fragment")) || strcmp(elem, "quux")) err = true;
+      tcmapdel(map);
+      buf = tcurlresolve("http://a:b@c.d:1/e/f/g.h?i=j#k", "http://A:B@C.D:1/E/F/G.H?I=J#K");
+      if(strcmp(buf, "http://A:B@c.d:1/E/F/G.H?I=J#K")) err = true;
+      tcfree(buf);
+      buf = tcurlresolve("http://a:b@c.d:1/e/f/g.h?i=j#k", "/E/F/G.H?I=J#K");
+      if(strcmp(buf, "http://a:b@c.d:1/E/F/G.H?I=J#K")) err = true;
+      tcfree(buf);
+      buf = tcurlresolve("http://a:b@c.d:1/e/f/g.h?i=j#k", "G.H?I=J#K");
+      if(strcmp(buf, "http://a:b@c.d:1/e/f/G.H?I=J#K")) err = true;
+      tcfree(buf);
+      buf = tcurlresolve("http://a:b@c.d:1/e/f/g.h?i=j#k", "?I=J#K");
+      if(strcmp(buf, "http://a:b@c.d:1/e/f/g.h?I=J#K")) err = true;
+      tcfree(buf);
+      buf = tcurlresolve("http://a:b@c.d:1/e/f/g.h?i=j#k", "#K");
+      if(strcmp(buf, "http://a:b@c.d:1/e/f/g.h?i=j#K")) err = true;
+      tcfree(buf);
+    }
+    buf = tcbaseencode(str, slen);
+    if(strcmp(buf, "NSUyKzMtMT00ICJZZXMvTm8iIDxhJmI+")) err = true;
+    dec = tcbasedecode(buf, &dsiz);
+    if(dsiz != slen || strcmp(dec, str)) err = true;
+    tcfree(dec);
+    tcfree(buf);
+    buf = tcquoteencode(str, slen);
+    if(strcmp(buf, "5%2+3-1=3D4 \"Yes/No\" <a&b>")) err = true;
+    dec = tcquotedecode(buf, &dsiz);
+    if(dsiz != slen || strcmp(dec, str)) err = true;
+    tcfree(dec);
+    tcfree(buf);
+    buf = tcmimeencode(str, "UTF-8", true);
+    if(strcmp(buf, "=?UTF-8?B?NSUyKzMtMT00ICJZZXMvTm8iIDxhJmI+?=")) err = true;
+    char encname[32];
+    dec = tcmimedecode(buf, encname);
+    if(strcmp(dec, str) || strcmp(encname, "UTF-8")) err = true;
+    tcfree(dec);
+    tcfree(buf);
+    if(i % 10 == 1){
+      const char *mstr = "Subject: Hello\r\nContent-Type: multipart/mixed; boundary=____\r\n\r\n"
+        "\r\n--____\r\nThis is a pen.\r\n--____\r\nIs this your bag?\r\n--____--\r\n";
+      map = tcmapnew2(10);
+      char *buf = tcmimebreak(mstr, strlen(mstr), map, &bsiz);
+      const char *boundary = tcmapget2(map, "BOUNDARY");
+      if(boundary){
+        list = tcmimeparts(buf, bsiz, boundary);
+        if(tclistnum(list) == 2){
+          if(strcmp(tclistval2(list, 0), "This is a pen.")) err = true;
+          if(strcmp(tclistval2(list, 1), "Is this your bag?")) err = true;
+        } else {
+          err = true;
+        }
+        tclistdel(list);
+      } else {
+        err = true;
+      }
+      tcfree(buf);
+      tcmapdel(map);
+    }
+    buf = tcpackencode(str, slen, &bsiz);
+    dec = tcpackdecode(buf, bsiz, &dsiz);
+    if(dsiz != slen || strcmp(dec, str)) err = true;
+    tcfree(dec);
+    tcfree(buf);
+    buf = tcbsencode(str, slen, &bsiz);
+    dec = tcbsdecode(buf, bsiz, &dsiz);
+    if(dsiz != slen || strcmp(dec, str)) err = true;
+    tcfree(dec);
+    tcfree(buf);
+    int idx;
+    buf = tcbwtencode(str, slen, &idx);
+    if(memcmp(buf, "4\"o 5a23s-%+=> 1b/\"<&YNe", slen) || idx != 13) err = true;
+    dec = tcbwtdecode(buf, slen, idx);
+    if(memcmp(dec, str, slen)) err = true;
+    tcfree(dec);
+    tcfree(buf);
+    if(_tc_deflate){
+      if((buf = tcdeflate(str, slen, &bsiz)) != NULL){
+        if((dec = tcinflate(buf, bsiz, &dsiz)) != NULL){
+          if(slen != dsiz || memcmp(str, dec, dsiz)) err = true;
+          tcfree(dec);
+        } else {
+          err = true;
+        }
+        tcfree(buf);
+      } else {
+        err = true;
+      }
+      if((buf = tcgzipencode(str, slen, &bsiz)) != NULL){
+        if((dec = tcgzipdecode(buf, bsiz, &dsiz)) != NULL){
+          if(slen != dsiz || memcmp(str, dec, dsiz)) err = true;
+          tcfree(dec);
+        } else {
+          err = true;
+        }
+        tcfree(buf);
+      } else {
+        err = true;
+      }
+      if(tcgetcrc("hoge", 4) % 10000 != 7034) err = true;
+    }
+    if(_tc_bzcompress){
+      if((buf = tcbzipencode(str, slen, &bsiz)) != NULL){
+        if((dec = tcbzipdecode(buf, bsiz, &dsiz)) != NULL){
+          if(slen != dsiz || memcmp(str, dec, dsiz)) err = true;
+          tcfree(dec);
+        } else {
+          err = true;
+        }
+        tcfree(buf);
+      } else {
+        err = true;
+      }
+    }
+    int anum = myrand(50)+1;
+    unsigned int ary[anum];
+    for(int j = 0; j < anum; j++){
+      ary[j] = myrand(INT_MAX);
+    }
+    buf = tcberencode(ary, anum, &bsiz);
+    int dnum;
+    unsigned int *dary = tcberdecode(buf, bsiz, &dnum);
+    if(anum != dnum || memcmp(ary, dary, sizeof(*dary) * dnum)) err = true;
+    tcfree(dary);
+    tcfree(buf);
+    buf = tcxmlescape(str);
+    if(strcmp(buf, "5%2+3-1=4 &quot;Yes/No&quot; &lt;a&amp;b&gt;")) err = true;
+    dec = tcxmlunescape(buf);
+    if(strcmp(dec, str)) err = true;
+    tcfree(dec);
+    tcfree(buf);
+    buf = tccstrescape(str);
+    if(strcmp(buf, "5%2+3-1=4 \\x22Yes/No\\x22 <a&b>")) err = true;
+    dec = tccstrunescape(buf);
+    if(strcmp(dec, str)) err = true;
+    tcfree(dec);
+    tcfree(buf);
+    buf = tcjsonescape(str);
+    if(strcmp(buf, "5%2+3-1=4 \\u0022Yes/No\\u0022 <a&b>")) err = true;
+    dec = tcjsonunescape(buf);
+    if(strcmp(dec, str)) err = true;
+    tcfree(dec);
+    tcfree(buf);
+    if(i % 10 == 1){
+      TCMAP *params = tcmapnew3("one", "=first=", "two", "&second&", "three", "#third#", NULL);
+      char *estr = tcwwwformencode(params);
+      TCMAP *nparams = tcmapnew2(1);
+      tcwwwformdecode(estr, nparams);
+      if(strcmp(estr, "one=%3Dfirst%3D&two=%26second%26&three=%23third%23") ||
+         tcmaprnum(nparams) != tcmaprnum(params) || !tcmapget2(nparams, "two")) err = true;
+      tcmapdel(nparams);
+      tcfree(estr);
+      tcmapdel(params);
+      list = tcxmlbreak("<abc de=\"foo&amp;\" gh='&lt;bar&gt;'>xyz<br>\na<!--<mikio>--></abc>");
+      for(int j = 0; j < tclistnum(list); j++){
+        const char *elem = tclistval2(list, j);
+        TCMAP *attrs = tcxmlattrs(elem);
+        tcmapdel(attrs);
+      }
+      tclistdel(list);
+    }
+    if(i % 100 == 1){
+      TCTMPL *tmpl = tctmplnew();
+      const char *str =
+        "{{ title XML }}{{UNKNOWN COMMAND}}{{CONF author 'Mikio Hirabayashi'}}\n"
+        "{{ FOREACH docs \\}}\n"
+        "{{ IF void }}===={{void}}{{ ELSE }}----{{void.void}}{{ END }}\n"
+        "{{ IF .id }}ID:{{ .id }}{{ END }}\n"
+        "{{ IF .title }}Title:{{ .title }}{{ END }}\n"
+        "{{ IF author }}Author:{{ author }}{{ END }}\n"
+        "{{ IF .coms }}{{ SET addr 'Setagaya, Tokyo' }}--\n"
+        "{{ FOREACH .coms com }}{{ com.author MD5 }}: {{ com.body }}\n"
+        "{{ END \\}}\n"
+        "{{ END \\}}\n"
+        "{{ END \\}}\n";
+      tctmplsetsep(tmpl, "{{", "}}");
+      tctmplload(tmpl, str);
+      const char *cval = tctmplconf(tmpl, "author");
+      if(!cval || strcmp(cval, "Mikio Hirabayashi")) err = true;
+      TCMPOOL *mpool = tcmpoolnew();
+      TCMAP *vars = tcmpoolmapnew(mpool);
+      tcmapput2(vars, "title", "I LOVE YOU");
+      TCLIST *docs = tcmpoollistnew(mpool);
+      for(int j = 0; j < 3; j++){
+        TCMAP *doc = tcmpoolmapnew(mpool);
+        char vbuf[TCNUMBUFSIZ];
+        sprintf(vbuf, "%d", i + j);
+        tcmapput2(doc, "id", vbuf);
+        sprintf(vbuf, "[%08d]", i + j);
+        tcmapput2(doc, "title", vbuf);
+        TCLIST *coms = tcmpoollistnew(mpool);
+        for(int k = 0; k < 3; k++){
+          TCMAP *com = tcmpoolmapnew(mpool);
+          sprintf(vbuf, "u%d", k);
+          tcmapput2(com, "author", vbuf);
+          sprintf(vbuf, "this is the %dth pen.", (j + 1) * (k + 1) + i);
+          tcmapput2(com, "body", vbuf);
+          tclistpushmap(coms, com);
+        }
+        tcmapputlist(doc, "coms", coms);
+        tclistpushmap(docs, doc);
+      }
+      tcmapputlist(vars, "docs", docs);
+      char *res = tctmpldump(tmpl, vars);
+      tcfree(res);
+      tcmpoolclear(mpool, true);
+      tcmpoolmalloc(mpool, 1);
+      tcmpoollistnew(mpool);
+      tcmpooldel(mpool);
+      tctmpldel(tmpl);
+    }
+    if(i % 10 == 1){
+      for(int16_t j = 1; j <= 0x2000; j *= 2){
+        for(int16_t num = j - 1; num <= j + 1; num++){
+          int16_t nnum = TCHTOIS(num);
+          if(num != TCITOHS(nnum)) err = true;
+        }
+      }
+      for(int32_t j = 1; j <= 0x20000000; j *= 2){
+        for(int32_t num = j - 1; num <= j + 1; num++){
+          int32_t nnum = TCHTOIL(num);
+          if(num != TCITOHL(nnum)) err = true;
+          char buf[TCNUMBUFSIZ];
+          int step, nstep;
+          TCSETVNUMBUF(step, buf, num);
+          TCREADVNUMBUF(buf, nnum, nstep);
+          if(num != nnum || step != nstep) err = true;
+        }
+      }
+      for(int64_t j = 1; j <= 0x2000000000000000; j *= 2){
+        for(int64_t num = j - 1; num <= j + 1; num++){
+          int64_t nnum = TCHTOILL(num);
+          if(num != TCITOHLL(nnum)) err = true;
+          char buf[TCNUMBUFSIZ];
+          int step, nstep;
+          TCSETVNUMBUF64(step, buf, num);
+          TCREADVNUMBUF64(buf, nnum, nstep);
+          if(num != nnum || step != nstep) err = true;
+        }
+      }
+      TCBITMAP *bitmap = TCBITMAPNEW(100);
+      for(int j = 0; j < 100; j++){
+        if(j % 3 == 0) TCBITMAPON(bitmap, j);
+        if(j % 5 == 0) TCBITMAPOFF(bitmap, j);
+      }
+      for(int j = 0; j < 100; j++){
+        if(j % 5 == 0){
+          if(TCBITMAPCHECK(bitmap, j)) err = true;
+        } else if(j % 3 == 0){
+          if(!TCBITMAPCHECK(bitmap, j)) err = true;
+        }
+      }
+      TCBITMAPDEL(bitmap);
+      buf = tcmalloc(i / 8 + 2);
+      TCBITSTRM strm;
+      TCBITSTRMINITW(strm, buf);
+      for(int j = 0; j < i; j++){
+        int sign = j % 3 == 0 || j % 7 == 0;
+        TCBITSTRMCAT(strm, sign);
+      }
+      TCBITSTRMSETEND(strm);
+      int bnum = TCBITSTRMNUM(strm);
+      if(bnum != i) err = true;
+      TCBITSTRMINITR(strm, buf, bsiz);
+      for(int j = 0; j < i; j++){
+        int sign;
+        TCBITSTRMREAD(strm, sign);
+        if(sign != (j % 3 == 0 || j % 7 == 0)) err = true;
+      }
+      tcfree(buf);
+    }
+    if(i % 100 == 1){
+      char path[RECBUFSIZ];
+      sprintf(path, "%d", myrand(10));
+      if(tcpathlock(path)){
+        if(!tcpathunlock(path)) err = true;
+      } else {
+        err = true;
+      }
+    }
+    if(rnum > 250 && i % (rnum / 250) == 0){
+      iputchar('.');
+      if(i == rnum || i % (rnum / 10) == 0) iprintf(" (%08d)\n", i);
+    }
+  }
+  iprintf("time: %.3f\n", tctime() - stime);
+  if(err){
+    iprintf("error\n\n");
+    return 1;
+  }
+  iprintf("ok\n\n");
+  return 0;
+}
+
+
+/* perform wicked command */
+static int procwicked(int rnum){
+  iprintf("<Wicked Writing Test>\n  seed=%u  rnum=%d\n\n", g_randseed, rnum);
+  double stime = tctime();
+  TCMPOOL *mpool = tcmpoolglobal();
+  TCXSTR *xstr = myrand(2) > 0 ? tcxstrnew() : tcxstrnew2("hello world");
+  tcmpoolpushxstr(mpool, xstr);
+  TCLIST *list = myrand(2) > 0 ? tclistnew() : tclistnew2(myrand(rnum) + rnum / 2);
+  tcmpoolpushlist(mpool, list);
+  TCPTRLIST *ptrlist = myrand(2) > 0 ? tcptrlistnew() : tcptrlistnew2(myrand(rnum) + rnum / 2);
+  tcmpoolpush(mpool, ptrlist, (void (*)(void*))tcptrlistdel);
+  TCMAP *map = myrand(2) > 0 ? tcmapnew() : tcmapnew2(myrand(rnum) + rnum / 2);
+  tcmpoolpushmap(mpool, map);
+  TCTREE *tree = myrand(2) > 0 ? tctreenew() : tctreenew2(tccmpdecimal, NULL);
+  tcmpoolpushtree(mpool, tree);
+  TCMDB *mdb = myrand(2) > 0 ? tcmdbnew() : tcmdbnew2(myrand(rnum) + rnum / 2);
+  tcmpoolpush(mpool, mdb, (void (*)(void*))tcmdbdel);
+  TCNDB *ndb = myrand(2) > 0 ? tcndbnew() : tcndbnew2(tccmpdecimal, NULL);
+  tcmpoolpush(mpool, ndb, (void (*)(void*))tcndbdel);
+  for(int i = 1; i <= rnum; i++){
+    char kbuf[RECBUFSIZ];
+    int ksiz = sprintf(kbuf, "%d", myrand(i));
+    char vbuf[RECBUFSIZ];
+    int vsiz = sprintf(vbuf, "%d", myrand(i));
+    char *tmp;
+    switch(myrand(70)){
+      case 0:
+        iputchar('0');
+        tcxstrcat(xstr, kbuf, ksiz);
+        break;
+      case 1:
+        iputchar('1');
+        tcxstrcat2(xstr, kbuf);
+        break;
+      case 2:
+        iputchar('2');
+        if(myrand(rnum / 100 + 1) == 0) tcxstrclear(xstr);
+        break;
+      case 3:
+        iputchar('3');
+        tcxstrprintf(xstr, "[%s:%d:%llu:%b:%llb]\n",
+                     kbuf, i, (long long)i * 65521, i, (unsigned long long)i * 65521);
+        break;
+      case 4:
+        iputchar('4');
+        tclistpush(list, kbuf, ksiz);
+        tcptrlistpush(ptrlist, tcmemdup(kbuf, ksiz));
+        break;
+      case 5:
+        iputchar('5');
+        tclistpush2(list, kbuf);
+        break;
+      case 6:
+        iputchar('6');
+        tmp = tcmemdup(kbuf, ksiz);
+        tclistpushmalloc(list, tmp, strlen(tmp));
+        break;
+      case 7:
+        iputchar('7');
+        if(myrand(10) == 0){
+          tcfree(tclistpop(list, &ksiz));
+          tcfree(tcptrlistpop(ptrlist));
+        }
+        break;
+      case 8:
+        iputchar('8');
+        if(myrand(10) == 0) tcfree(tclistpop2(list));
+        break;
+      case 9:
+        iputchar('9');
+        tclistunshift(list, kbuf, ksiz);
+        tcptrlistunshift(ptrlist, tcmemdup(kbuf, ksiz));
+        break;
+      case 10:
+        iputchar('A');
+        tclistunshift2(list, kbuf);
+        break;
+      case 11:
+        iputchar('B');
+        if(myrand(10) == 0){
+          tcfree(tclistshift(list, &ksiz));
+          tcfree(tcptrlistshift(ptrlist));
+        }
+        break;
+      case 12:
+        iputchar('C');
+        if(myrand(10) == 0) tcfree(tclistshift2(list));
+        break;
+      case 13:
+        iputchar('D');
+        tclistinsert(list, i / 10, kbuf, ksiz);
+        if(tcptrlistnum(ptrlist) > i / 10)
+          tcptrlistinsert(ptrlist, i / 10, tcmemdup(kbuf, ksiz));
+        break;
+      case 14:
+        iputchar('E');
+        tclistinsert2(list, i / 10, kbuf);
+        break;
+      case 15:
+        iputchar('F');
+        if(myrand(10) == 0){
+          tcfree(tclistremove(list, i / 10, &ksiz));
+          tcfree(tcptrlistremove(ptrlist, i / 10));
+        }
+        break;
+      case 16:
+        iputchar('G');
+        if(myrand(10) == 0) tcfree(tclistremove2(list, i / 10));
+        break;
+      case 17:
+        iputchar('H');
+        tclistover(list, i / 10, kbuf, ksiz);
+        if(tcptrlistnum(ptrlist) > i / 10){
+          tcfree(tcptrlistval(ptrlist, i / 10));
+          tcptrlistover(ptrlist, i / 10, tcmemdup(kbuf, ksiz));
+        }
+        break;
+      case 18:
+        iputchar('I');
+        tclistover2(list, i / 10, kbuf);
+        break;
+      case 19:
+        iputchar('J');
+        if(myrand(rnum / 1000 + 1) == 0) tclistsort(list);
+        break;
+      case 20:
+        iputchar('K');
+        if(myrand(rnum / 1000 + 1) == 0) tclistsortci(list);
+        break;
+      case 21:
+        iputchar('L');
+        if(myrand(rnum / 1000 + 1) == 0) tclistlsearch(list, kbuf, ksiz);
+        break;
+      case 22:
+        iputchar('M');
+        if(myrand(rnum / 1000 + 1) == 0) tclistbsearch(list, kbuf, ksiz);
+        break;
+      case 23:
+        iputchar('N');
+        if(myrand(rnum / 100 + 1) == 0){
+          tclistclear(list);
+          for(int j = 0; j < tcptrlistnum(ptrlist); j++){
+            tcfree(tcptrlistval(ptrlist, j));
+          }
+          tcptrlistclear(ptrlist);
+        }
+        break;
+      case 24:
+        iputchar('O');
+        if(myrand(rnum / 100 + 1) == 0){
+          int dsiz;
+          char *dbuf = tclistdump(list, &dsiz);
+          tclistdel(tclistload(dbuf, dsiz));
+          tcfree(dbuf);
+        }
+        break;
+      case 25:
+        iputchar('P');
+        if(myrand(100) == 0){
+          if(myrand(2) == 0){
+            for(int j = 0; j < tclistnum(list); j++){
+              int rsiz;
+              tclistval(list, j, &rsiz);
+              tcptrlistval(ptrlist, j);
+            }
+          } else {
+            for(int j = 0; j < tclistnum(list); j++){
+              tclistval2(list, j);
+            }
+          }
+        }
+        break;
+      case 26:
+        iputchar('Q');
+        tcmapput(map, kbuf, ksiz, vbuf, vsiz);
+        tctreeput(tree, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 27:
+        iputchar('R');
+        tcmapput2(map, kbuf, vbuf);
+        tctreeput2(tree, kbuf, vbuf);
+        break;
+      case 28:
+        iputchar('S');
+        tcmapputkeep(map, kbuf, ksiz, vbuf, vsiz);
+        tctreeputkeep(tree, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 29:
+        iputchar('T');
+        tcmapputkeep2(map, kbuf, vbuf);
+        tctreeputkeep2(tree, kbuf, vbuf);
+        break;
+      case 30:
+        iputchar('U');
+        tcmapputcat(map, kbuf, ksiz, vbuf, vsiz);
+        tctreeputcat(tree, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 31:
+        iputchar('V');
+        tcmapputcat2(map, kbuf, vbuf);
+        tctreeputcat2(tree, kbuf, vbuf);
+        break;
+      case 32:
+        iputchar('W');
+        if(myrand(2) == 0){
+          tcmapput3(map, kbuf, ksiz, vbuf, vsiz);
+          tctreeput3(tree, kbuf, ksiz, vbuf, vsiz);
+        }
+        if(myrand(2) == 0){
+          tcmapput4(map, kbuf, ksiz, vbuf, vsiz, vbuf, vsiz);
+          tctreeputkeep3(tree, kbuf, ksiz, vbuf, vsiz);
+        }
+        if(myrand(2) == 0){
+          tcmapputcat3(map, kbuf, ksiz, vbuf, vsiz);
+          tctreeputcat3(tree, kbuf, ksiz, vbuf, vsiz);
+        }
+        break;
+      case 33:
+        iputchar('X');
+        if(myrand(10) == 0){
+          tcmapout(map, kbuf, ksiz);
+          tctreeout(tree, kbuf, ksiz);
+        }
+        break;
+      case 34:
+        iputchar('Y');
+        if(myrand(10) == 0){
+          tcmapout2(map, kbuf);
+          tctreeout2(tree, kbuf);
+        }
+        break;
+      case 35:
+        iputchar('Z');
+        tcmapget3(map, kbuf, ksiz, &vsiz);
+        tctreeget3(tree, kbuf, ksiz, &vsiz);
+        break;
+      case 36:
+        iputchar('a');
+        tcmapmove(map, kbuf, ksiz, true);
+        break;
+      case 37:
+        iputchar('b');
+        tcmapmove(map, kbuf, ksiz, false);
+        break;
+      case 38:
+        iputchar('c');
+        tcmapmove2(map, kbuf, true);
+        break;
+      case 39:
+        iputchar('d');
+        if(myrand(100) == 0){
+          if(myrand(2) == 0){
+            tcmapiterinit(map);
+            tctreeiterinit(tree);
+          } else {
+            tcmapiterinit2(map, kbuf, ksiz);
+            tctreeiterinit2(tree, kbuf, ksiz);
+          }
+        }
+        break;
+      case 40:
+        iputchar('e');
+        tcmapiternext(map, &vsiz);
+        tctreeiternext(tree, &vsiz);
+        break;
+      case 41:
+        iputchar('f');
+        tcmapiternext2(map);
+        tctreeiternext2(tree);
+        break;
+      case 42:
+        iputchar('g');
+        if(myrand(100) == 0){
+          int anum;
+          switch(myrand(4)){
+            case 0:
+              tclistdel(tcmapkeys(map));
+              tclistdel(tctreekeys(tree));
+              break;
+            case 1:
+              tcfree(tcmapkeys2(map, &anum));
+              tcfree(tctreekeys2(tree, &anum));
+              break;
+            case 2:
+              tclistdel(tcmapvals(map));
+              tclistdel(tctreevals(tree));
+              break;
+            default:
+              tcfree(tcmapvals2(map, &anum));
+              tcfree(tctreevals2(tree, &anum));
+              break;
+          }
+        }
+        break;
+      case 43:
+        iputchar('h');
+        if(myrand(rnum / 100 + 1) == 0){
+          tcmapclear(map);
+          tctreeclear(tree);
+        }
+        break;
+      case 44:
+        iputchar('i');
+        if(myrand(20) == 0){
+          tcmapcutfront(map, myrand(10));
+          tctreecutfringe(tree, myrand(10));
+        }
+        break;
+      case 45:
+        iputchar('j');
+        if(myrand(rnum / 100 + 1) == 0){
+          int dsiz;
+          char *dbuf = tcmapdump(map, &dsiz);
+          tcfree(tcmaploadone(dbuf, dsiz, kbuf, ksiz, &vsiz));
+          tcmapdel(tcmapload(dbuf, dsiz));
+          tcfree(dbuf);
+          dbuf = tctreedump(tree, &dsiz);
+          tcfree(tctreeloadone(dbuf, dsiz, kbuf, ksiz, &vsiz));
+          tctreedel(tctreeload(dbuf, dsiz, tccmplexical, NULL));
+          tcfree(dbuf);
+        }
+        break;
+      case 46:
+        iputchar('k');
+        tcmdbput(mdb, kbuf, ksiz, vbuf, vsiz);
+        tcndbput(ndb, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 47:
+        iputchar('l');
+        tcmdbput2(mdb, kbuf, vbuf);
+        tcndbput2(ndb, kbuf, vbuf);
+        break;
+      case 48:
+        iputchar('m');
+        tcmdbputkeep(mdb, kbuf, ksiz, vbuf, vsiz);
+        tcndbputkeep(ndb, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 49:
+        iputchar('n');
+        tcmdbputkeep2(mdb, kbuf, vbuf);
+        tcndbputkeep2(ndb, kbuf, vbuf);
+        break;
+      case 50:
+        iputchar('o');
+        tcmdbputcat(mdb, kbuf, ksiz, vbuf, vsiz);
+        tcndbputcat(ndb, kbuf, ksiz, vbuf, vsiz);
+        break;
+      case 51:
+        iputchar('p');
+        tcmdbputcat2(mdb, kbuf, vbuf);
+        tcndbputcat2(ndb, kbuf, vbuf);
+        break;
+      case 52:
+        iputchar('q');
+        if(myrand(2) == 0){
+          tcmdbput3(mdb, kbuf, ksiz, vbuf, vsiz);
+          tcndbput3(ndb, kbuf, ksiz, vbuf, vsiz);
+        }
+        if(myrand(2) == 0){
+          tcmdbput4(mdb, kbuf, ksiz, vbuf, vsiz, vbuf, vsiz);
+          tcndbputkeep3(ndb, kbuf, ksiz, vbuf, vsiz);
+        }
+        if(myrand(2) == 0){
+          tcmdbputcat3(mdb, kbuf, ksiz, vbuf, vsiz);
+          tcndbputcat3(ndb, kbuf, ksiz, vbuf, vsiz);
+        }
+        break;
+      case 53:
+        iputchar('r');
+        if(myrand(10) == 0){
+          tcmdbout(mdb, kbuf, ksiz);
+          tcndbout(ndb, kbuf, ksiz);
+        }
+        break;
+      case 54:
+        iputchar('s');
+        if(myrand(10) == 0){
+          tcmdbout2(mdb, kbuf);
+          tcndbout2(ndb, kbuf);
+        }
+        break;
+      case 55:
+        iputchar('t');
+        tcfree(tcmdbget(mdb, kbuf, ksiz, &vsiz));
+        tcfree(tcndbget(ndb, kbuf, ksiz, &vsiz));
+        break;
+      case 56:
+        iputchar('u');
+        tcfree(tcmdbget3(mdb, kbuf, ksiz, &vsiz));
+        tcfree(tcndbget3(ndb, kbuf, ksiz, &vsiz));
+        break;
+      case 57:
+        iputchar('v');
+        if(myrand(100) == 0){
+          if(myrand(2) == 0){
+            tcmdbiterinit(mdb);
+            tcndbiterinit(ndb);
+          } else {
+            tcmdbiterinit2(mdb, kbuf, ksiz);
+            tcndbiterinit2(ndb, kbuf, ksiz);
+          }
+        }
+        break;
+      case 58:
+        iputchar('w');
+        tcfree(tcmdbiternext(mdb, &vsiz));
+        tcfree(tcndbiternext(ndb, &vsiz));
+        break;
+      case 59:
+        iputchar('x');
+        tcfree(tcmdbiternext2(mdb));
+        tcfree(tcndbiternext2(ndb));
+        break;
+      case 60:
+        iputchar('y');
+        if(myrand(rnum / 100 + 1) == 0){
+          tcmdbvanish(mdb);
+          tcndbvanish(ndb);
+        }
+        break;
+      case 61:
+        iputchar('z');
+        if(myrand(200) == 0){
+          tcmdbcutfront(mdb, myrand(100));
+          tcndbcutfringe(ndb, myrand(100));
+        }
+        break;
+      case 62:
+        iputchar('+');
+        if(myrand(200) == 0){
+          tcmdbforeach(mdb, iterfunc, NULL);
+          tcndbforeach(ndb, iterfunc, NULL);
+        }
+        break;
+      case 63:
+        iputchar('+');
+        if(myrand(100) == 0){
+          char *tptr = tcmpoolmalloc(mpool, 1);
+          switch(myrand(5)){
+            case 0:
+              tcfree(tptr);
+              tcmpoolpop(mpool, false);
+              break;
+            case 1:
+              tcmpoolpop(mpool, true);
+              break;
+          }
+        }
+        break;
+      case 64:
+        iputchar('+');
+        if(myrand(100) == 0){
+          TCXSTR *txstr = tcmpoolxstrnew(mpool);
+          switch(myrand(5)){
+            case 0:
+              tcxstrdel(txstr);
+              tcmpoolpop(mpool, false);
+              break;
+            case 1:
+              tcmpoolpop(mpool, true);
+              break;
+          }
+        }
+        break;
+      case 65:
+        iputchar('+');
+        if(myrand(100) == 0){
+          TCLIST *tlist = tcmpoollistnew(mpool);
+          switch(myrand(5)){
+            case 0:
+              tclistdel(tlist);
+              tcmpoolpop(mpool, false);
+              break;
+            case 1:
+              tcmpoolpop(mpool, true);
+              break;
+          }
+        }
+        break;
+      case 66:
+        iputchar('+');
+        if(myrand(100) == 0){
+          TCMAP *tmap = tcmpoolmapnew(mpool);
+          switch(myrand(5)){
+            case 0:
+              tcmapdel(tmap);
+              tcmpoolpop(mpool, false);
+              break;
+            case 1:
+              tcmpoolpop(mpool, true);
+              break;
+          }
+        }
+        break;
+      case 67:
+        iputchar('+');
+        if(myrand(100) == 0){
+          TCTREE *ttree = tcmpooltreenew(mpool);
+          switch(myrand(5)){
+            case 0:
+              tctreedel(ttree);
+              tcmpoolpop(mpool, false);
+              break;
+            case 1:
+              tcmpoolpop(mpool, true);
+              break;
+          }
+        }
+        break;
+      default:
+        iputchar('@');
+        if(myrand(10000) == 0) srand((unsigned int)(tctime() * 1000) % UINT_MAX);
+        break;
+    }
+    if(i % 50 == 0) iprintf(" (%08d)\n", i);
+  }
+  if(rnum % 50 > 0) iprintf(" (%08d)\n", rnum);
+  for(int i = 0; i < tcptrlistnum(ptrlist); i++){
+    tcfree(tcptrlistval(ptrlist, i));
+  }
+  iprintf("time: %.3f\n", tctime() - stime);
+  iprintf("ok\n\n");
+  return 0;
+}
+
+
+
+// END OF FILE
diff --git a/tcejdb/tcutil.c b/tcejdb/tcutil.c
new file mode 100644 (file)
index 0000000..b3bbbc6
--- /dev/null
@@ -0,0 +1,10542 @@
+/*************************************************************************************************
+ * 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 time in milleconds since Epoch. */
+unsigned long tcmstime(void) {
+  struct timeval tv;
+  if(gettimeofday(&tv, NULL) == -1) return 0;
+  return (unsigned long long) (tv.tv_sec) * 1000 + (unsigned long long) (tv.tv_usec) / 1000;
+}
+
+
+/* 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
diff --git a/tcejdb/tcutil.h b/tcejdb/tcutil.h
new file mode 100644 (file)
index 0000000..fa72f49
--- /dev/null
@@ -0,0 +1,4188 @@
+/*************************************************************************************************
+ * 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.
+ *************************************************************************************************/
+
+
+#ifndef _TCUTIL_H                        /* duplication check */
+#define _TCUTIL_H
+
+#if defined(__cplusplus)
+#define __TCUTIL_CLINKAGEBEGIN extern "C" {
+#define __TCUTIL_CLINKAGEEND }
+#else
+#define __TCUTIL_CLINKAGEBEGIN
+#define __TCUTIL_CLINKAGEEND
+#endif
+__TCUTIL_CLINKAGEBEGIN
+
+
+#include <stdlib.h>
+#if ! defined(__cplusplus)
+#include <stdbool.h>
+#endif
+#include <stdint.h>
+#include <time.h>
+#include <limits.h>
+#include <math.h>
+
+
+
+/*************************************************************************************************
+ * basic utilities
+ *************************************************************************************************/
+
+
+/* String containing the version information. */
+extern const char *tcversion;
+
+
+/* Pointer to the call back function for handling a fatal error.
+   The argument specifies the error message.
+   The initial value of this variable is `NULL'.  If the value is `NULL', the default function is
+   called when a fatal error occurs.  A fatal error occurs when memory allocation is failed. */
+extern void (*tcfatalfunc)(const char *);
+
+
+/* Allocate a region on memory.
+   `size' specifies the size of the region.
+   The return value is the pointer to the allocated region.
+   This function handles failure of memory allocation implicitly.  Because the region of the
+   return value is allocated with the `malloc' call, it should be released with the `free' call
+   when it is no longer in use. */
+void *tcmalloc(size_t size);
+
+
+/* Allocate a nullified region on memory.
+   `nmemb' specifies the number of elements.
+   `size' specifies the size of each element.
+   The return value is the pointer to the allocated nullified region.
+   This function handles failure of memory allocation implicitly.  Because the region of the
+   return value is allocated with the `calloc' call, it should be released with the `free' call
+   when it is no longer in use. */
+void *tccalloc(size_t nmemb, size_t size);
+
+
+/* Re-allocate a region on memory.
+   `ptr' specifies the pointer to the region.
+   `size' specifies the size of the region.
+   The return value is the pointer to the re-allocated region.
+   This function handles failure of memory allocation implicitly.  Because the region of the
+   return value is allocated with the `realloc' call, it should be released with the `free' call
+   when it is no longer in use. */
+void *tcrealloc(void *ptr, size_t size);
+
+
+/* Duplicate a region on memory.
+   `ptr' specifies the pointer to the region.
+   `size' specifies the size of the region.
+   The return value is the pointer to the allocated region of the duplicate.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when
+   it is no longer in use. */
+void *tcmemdup(const void *ptr, size_t size);
+
+
+/* Duplicate a string on memory.
+   `str' specifies the string.
+   The return value is the allocated string equivalent to the specified string.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+char *tcstrdup(const void *str);
+
+
+/* Free a region on memory.
+   `ptr' specifies the pointer to the region.  If it is `NULL', this function has no effect.
+   Although this function is just a wrapper of `free' call, this is useful in applications using
+   another package of the `malloc' series. */
+void tcfree(void *ptr);
+
+
+
+/*************************************************************************************************
+ * basic utilities (for experts)
+ *************************************************************************************************/
+
+
+/* type of the pointer to a comparison function.
+   `aptr' specifies the pointer to the region of one key.
+   `asiz' specifies the size of the region of one key.
+   `bptr' specifies the pointer to the region of the other key.
+   `bsiz' specifies the size of the region of the other key.
+   `op' specifies the pointer to the optional opaque object.
+   The return value is positive if the former is big, negative if the latter is big, 0 if both
+   are equivalent. */
+typedef int (*TCCMP)(const char *aptr, int asiz, const char *bptr, int bsiz, void *op);
+
+/* type of the pointer to a encoding or decoding function.
+   `ptr' specifies the pointer to the region.
+   `size' specifies the size of the region.
+   `sp' specifies the pointer to the variable into which the size of the region of the return
+   value is assigned.
+   `op' specifies the pointer to the optional opaque object.
+   If successful, the return value is the pointer to the result object allocated with `malloc'
+   call, else, it is `NULL'. */
+typedef void *(*TCCODEC)(const void *ptr, int size, int *sp, void *op);
+
+/* type of the pointer to a callback function to process record duplication.
+   `vbuf' specifies the pointer to the region of the value.
+   `vsiz' specifies the size of the region of the value.
+   `sp' specifies the pointer to the variable into which the size of the region of the return
+   value is assigned.
+   `op' specifies the pointer to the optional opaque object.
+   The return value is the pointer to the result object allocated with `malloc'.  It is
+   released by the caller.  If it is `NULL', the record is not modified. */
+typedef void *(*TCPDPROC)(const void *vbuf, int vsiz, int *sp, void *op);
+
+/* type of the pointer to a iterator function.
+   `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. */
+typedef bool (*TCITER)(const void *kbuf, int ksiz, const void *vbuf, int vsiz, void *op);
+
+
+
+/*************************************************************************************************
+ * extensible string
+ *************************************************************************************************/
+
+
+typedef struct {                         /* type of structure for an extensible string object */
+  char *ptr;                             /* pointer to the region */
+  int size;                              /* size of the region */
+  int asize;                             /* size of the allocated region */
+} TCXSTR;
+
+
+/* Create an extensible string object.
+   The return value is the new extensible string object. */
+TCXSTR *tcxstrnew(void);
+
+
+/* Create an extensible string object from a character string.
+   `str' specifies the string of the initial content.
+   The return value is the new extensible string object containing the specified string. */
+TCXSTR *tcxstrnew2(const char *str);
+
+
+/* Create an extensible string object with the initial allocation size.
+   `asiz' specifies the initial allocation size.
+   The return value is the new extensible string object. */
+TCXSTR *tcxstrnew3(int asiz);
+
+
+/* Copy an extensible string object.
+   `xstr' specifies the extensible string object.
+   The return value is the new extensible string object equivalent to the specified object. */
+TCXSTR *tcxstrdup(const TCXSTR *xstr);
+
+
+/* Delete an extensible string object.
+   `xstr' specifies the extensible string object.
+   Note that the deleted object and its derivatives can not be used anymore. */
+void tcxstrdel(TCXSTR *xstr);
+
+
+/* Concatenate a region to the end of an extensible string object.
+   `xstr' specifies the extensible string object.
+   `ptr' specifies the pointer to the region to be appended.
+   `size' specifies the size of the region. */
+void tcxstrcat(TCXSTR *xstr, const void *ptr, int size);
+
+
+/* Concatenate a character string to the end of an extensible string object.
+   `xstr' specifies the extensible string object.
+   `str' specifies the string to be appended. */
+void tcxstrcat2(TCXSTR *xstr, const char *str);
+
+
+/* Get the pointer of the region of an extensible string object.
+   `xstr' specifies the extensible string object.
+   The return value is the pointer of the region of the object.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string. */
+const void *tcxstrptr(const TCXSTR *xstr);
+
+
+/* Get the size of the region of an extensible string object.
+   `xstr' specifies the extensible string object.
+   The return value is the size of the region of the object. */
+int tcxstrsize(const TCXSTR *xstr);
+
+
+/* Clear an extensible string object.
+   `xstr' specifies the extensible string object.
+   The internal buffer of the object is cleared and the size is set zero. */
+void tcxstrclear(TCXSTR *xstr);
+
+
+/* Perform formatted output into an extensible string object.
+   `xstr' specifies the extensible string object.
+   `format' specifies the printf-like format string.  The conversion character `%' can be used
+   with such flag characters as `s', `d', `o', `u', `x', `X', `c', `e', `E', `f', `g', `G', `@',
+   `?', `b', and `%'.  `@' works as with `s' but escapes meta characters of XML.  `?' works as
+   with `s' but escapes meta characters of URL.  `b' converts an integer to the string as binary
+   numbers.  The other conversion character work as with each original.
+   The other arguments are used according to the format string. */
+void tcxstrprintf(TCXSTR *xstr, const char *format, ...);
+
+
+/* Allocate a formatted string on memory.
+   `format' specifies the printf-like format string.  The conversion character `%' can be used
+   with such flag characters as `s', `d', `o', `u', `x', `X', `c', `e', `E', `f', `g', `G', `@',
+   `?', `b', and `%'.  `@' works as with `s' but escapes meta characters of XML.  `?' works as
+   with `s' but escapes meta characters of URL.  `b' converts an integer to the string as binary
+   numbers.  The other conversion character work as with each original.
+   The other arguments are used according to the format string.
+   The return value is the pointer to the region of the result string.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+char *tcsprintf(const char *format, ...);
+
+
+
+/*************************************************************************************************
+ * extensible string (for experts)
+ *************************************************************************************************/
+
+
+/* Convert an extensible string object into a usual allocated region.
+   `xstr' specifies the extensible string object.
+   The return value is the pointer to the allocated region of the object.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when it
+   is no longer in use.  Because the region of the original object is deleted, it should not be
+   deleted again. */
+void *tcxstrtomalloc(TCXSTR *xstr);
+
+
+/* Create an extensible string object from an allocated region.
+   `ptr' specifies the pointer to the region allocated with `malloc' call.
+   `size' specifies the size of the region.
+   The return value is the new extensible string object wrapping the specified region.
+   Note that the specified region is released when the object is deleted. */
+TCXSTR *tcxstrfrommalloc(void *ptr, int size);
+
+
+
+/*************************************************************************************************
+ * array list
+ *************************************************************************************************/
+
+
+typedef struct {                         /* type of structure for an element of a list */
+  char *ptr;                             /* pointer to the region */
+  int size;                              /* size of the effective region */
+} TCLISTDATUM;
+
+typedef struct {                         /* type of structure for an array list */
+  TCLISTDATUM *array;                    /* array of data */
+  int anum;                              /* number of the elements of the array */
+  int start;                             /* start index of used elements */
+  int num;                               /* number of used elements */
+} TCLIST;
+
+
+/* Create a list object.
+   The return value is the new list object. */
+TCLIST *tclistnew(void);
+
+
+/* Create a list object with expecting the number of elements.
+   `anum' specifies the number of elements expected to be stored in the list.
+   The return value is the new list object. */
+TCLIST *tclistnew2(int anum);
+
+
+/* Create a list object with initial string elements.
+   `str' specifies the string of the first element.
+   The other arguments are other elements.  They should be trailed by a `NULL' argument.
+   The return value is the new list object. */
+TCLIST *tclistnew3(const char *str, ...);
+
+
+/* Copy a list object.
+   `list' specifies the list object.
+   The return value is the new list object equivalent to the specified object. */
+TCLIST *tclistdup(const TCLIST *list);
+
+
+/* Delete a list object.
+   `list' specifies the list object.
+   Note that the deleted object and its derivatives can not be used anymore. */
+void tclistdel(TCLIST *list);
+
+
+/* Get the number of elements of a list object.
+   `list' specifies the list object.
+   The return value is the number of elements of the list. */
+int tclistnum(const TCLIST *list);
+
+
+/* Get the pointer to the region of an element of a list object.
+   `list' specifies the list object.
+   `index' specifies the index of the element.
+   `sp' specifies the pointer to the variable into which the size of the region of the return
+   value is assigned.
+   The return value is the pointer to the region of the value.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  If `index' is equal to or more than
+   the number of elements, the return value is `NULL'. */
+const void *tclistval(const TCLIST *list, int index, int *sp);
+
+
+/* Get the string of an element of a list object.
+   `list' specifies the list object.
+   `index' specifies the index of the element.
+   The return value is the string of the value.
+   If `index' is equal to or more than the number of elements, the return value is `NULL'. */
+const char *tclistval2(const TCLIST *list, int index);
+
+
+/* Add an element at the end of a list object.
+   `list' specifies the list object.
+   `ptr' specifies the pointer to the region of the new element.
+   `size' specifies the size of the region. */
+void tclistpush(TCLIST *list, const void *ptr, int size);
+
+
+/* Add a string element at the end of a list object.
+   `list' specifies the list object.
+   `str' specifies the string of the new element. */
+void tclistpush2(TCLIST *list, const char *str);
+
+
+/* Remove an element of the end of a list object.
+   `list' specifies the list object.
+   `sp' specifies the pointer to the variable into which the size of the region of the return
+   value is assigned.
+   The return value is the pointer to the region of the removed element.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when it
+   is no longer in use.  If the list is empty, the return value is `NULL'. */
+void *tclistpop(TCLIST *list, int *sp);
+
+
+/* Remove a string element of the end of a list object.
+   `list' specifies the list object.
+   The return value is the string of the removed element.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use.  If the list is empty, the return
+   value is `NULL'. */
+char *tclistpop2(TCLIST *list);
+
+
+/* Add an element at the top of a list object.
+   `list' specifies the list object.
+   `ptr' specifies the pointer to the region of the new element.
+   `size' specifies the size of the region. */
+void tclistunshift(TCLIST *list, const void *ptr, int size);
+
+
+/* Add a string element at the top of a list object.
+   `list' specifies the list object.
+   `str' specifies the string of the new element. */
+void tclistunshift2(TCLIST *list, const char *str);
+
+
+/* Remove an element of the top of a list object.
+   `list' specifies the list object.
+   `sp' specifies the pointer to the variable into which the size of the region of the return
+   value is assigned.
+   The return value is the pointer to the region of the removed element.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when it
+   is no longer in use.  If the list is empty, the return value is `NULL'. */
+void *tclistshift(TCLIST *list, int *sp);
+
+
+/* Remove a string element of the top of a list object.
+   `list' specifies the list object.
+   The return value is the string of the removed element.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use.  If the list is empty, the return
+   value is `NULL'. */
+char *tclistshift2(TCLIST *list);
+
+
+/* Add an element at the specified location of a list object.
+   `list' specifies the list object.
+   `index' specifies the index of the new element.
+   `ptr' specifies the pointer to the region of the new element.
+   `size' specifies the size of the region.
+   If `index' is equal to or more than the number of elements, this function has no effect. */
+void tclistinsert(TCLIST *list, int index, const void *ptr, int size);
+
+
+/* Add a string element at the specified location of a list object.
+   `list' specifies the list object.
+   `index' specifies the index of the new element.
+   `str' specifies the string of the new element.
+   If `index' is equal to or more than the number of elements, this function has no effect. */
+void tclistinsert2(TCLIST *list, int index, const char *str);
+
+
+/* Remove an element at the specified location of a list object.
+   `list' specifies the list object.
+   `index' specifies the index of the element to be removed.
+   `sp' specifies the pointer to the variable into which the size of the region of the return
+   value is assigned.
+   The return value is the pointer to the region of the removed element.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when it
+   is no longer in use.  If `index' is equal to or more than the number of elements, no element
+   is removed and the return value is `NULL'. */
+void *tclistremove(TCLIST *list, int index, int *sp);
+
+
+/* Remove a string element at the specified location of a list object.
+   `list' specifies the list object.
+   `index' specifies the index of the element to be removed.
+   The return value is the string of the removed element.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use.  If `index' is equal to or more
+   than the number of elements, no element is removed and the return value is `NULL'. */
+char *tclistremove2(TCLIST *list, int index);
+
+
+/* Overwrite an element at the specified location of a list object.
+   `list' specifies the list object.
+   `index' specifies the index of the element to be overwritten.
+   `ptr' specifies the pointer to the region of the new content.
+   `size' specifies the size of the new content.
+   If `index' is equal to or more than the number of elements, this function has no effect. */
+void tclistover(TCLIST *list, int index, const void *ptr, int size);
+
+
+/* Overwrite a string element at the specified location of a list object.
+   `list' specifies the list object.
+   `index' specifies the index of the element to be overwritten.
+   `str' specifies the string of the new content.
+   If `index' is equal to or more than the number of elements, this function has no effect. */
+void tclistover2(TCLIST *list, int index, const char *str);
+
+
+/* Sort elements of a list object in lexical order.
+   `list' specifies the list object. */
+void tclistsort(TCLIST *list);
+
+
+/* Search a list object for an element using liner search.
+   `list' specifies the list object.
+   `ptr' specifies the pointer to the region of the key.
+   `size' specifies the size of the region.
+   The return value is the index of a corresponding element or -1 if there is no corresponding
+   element.
+   If two or more elements correspond, the former returns. */
+int tclistlsearch(const TCLIST *list, const void *ptr, int size);
+
+
+/* Search a list object for an element using binary search.
+   `list' specifies the list object.  It should be sorted in lexical order.
+   `ptr' specifies the pointer to the region of the key.
+   `size' specifies the size of the region.
+   The return value is the index of a corresponding element or -1 if there is no corresponding
+   element.
+   If two or more elements correspond, which returns is not defined. */
+int tclistbsearch(const TCLIST *list, const void *ptr, int size);
+
+
+/* Clear a list object.
+   `list' specifies the list object.
+   All elements are removed. */
+void tclistclear(TCLIST *list);
+
+
+/* Serialize a list object into a byte array.
+   `list' specifies the list object.
+   `sp' specifies the pointer to the variable into which the size of the region of the return
+   value is assigned.
+   The return value is the pointer to the region of the result serial region.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+void *tclistdump(const TCLIST *list, int *sp);
+
+
+/* Create a list object from a serialized byte array.
+   `ptr' specifies the pointer to the region of serialized byte array.
+   `size' specifies the size of the region.
+   The return value is a new list object.
+   Because the object of the return value is created with the function `tclistnew', it should
+   be deleted with the function `tclistdel' when it is no longer in use. */
+TCLIST *tclistload(const void *ptr, int size);
+
+
+
+/*************************************************************************************************
+ * array list (for experts)
+ *************************************************************************************************/
+
+
+/* Add an allocated element at the end of a list object.
+   `list' specifies the list object.
+   `ptr' specifies the pointer to the region allocated with `malloc' call.
+   `size' specifies the size of the region.
+   Note that the specified region is released when the object is deleted. */
+void tclistpushmalloc(TCLIST *list, void *ptr, int size);
+
+
+/* Sort elements of a list object in case-insensitive lexical order.
+   `list' specifies the list object. */
+void tclistsortci(TCLIST *list);
+
+
+/* Sort elements of a list object by an arbitrary comparison function.
+   `list' specifies the list object.
+   `cmp' specifies the pointer to the comparison function.  The structure TCLISTDATUM has the
+   member "ptr" which is the pointer to the region of the element, and the member "size" which is
+   the size of the region. */
+void tclistsortex(TCLIST *list, int (*cmp)(const TCLISTDATUM *, const TCLISTDATUM *));
+
+
+/* Invert elements of a list object.
+   `list' specifies the list object. */
+void tclistinvert(TCLIST *list);
+
+
+/* Perform formatted output into a list object.
+   `list' specifies the list object.
+   `format' specifies the printf-like format string.  The conversion character `%' can be used
+   with such flag characters as `s', `d', `o', `u', `x', `X', `c', `e', `E', `f', `g', `G', `@',
+   `?', `b', and `%'.  `@' works as with `s' but escapes meta characters of XML.  `?' works as
+   with `s' but escapes meta characters of URL.  `b' converts an integer to the string as binary
+   numbers.  The other conversion character work as with each original.
+   The other arguments are used according to the format string. */
+void tclistprintf(TCLIST *list, const char *format, ...);
+
+
+
+/*************************************************************************************************
+ * hash map
+ *************************************************************************************************/
+
+
+typedef struct _TCMAPREC {               /* type of structure for an element of a map */
+  int32_t ksiz;                          /* size of the region of the key */
+  int32_t vsiz;                          /* size of the region of the value */
+  struct _TCMAPREC *left;                /* pointer to the left child */
+  struct _TCMAPREC *right;               /* pointer to the right child */
+  struct _TCMAPREC *prev;                /* pointer to the previous element */
+  struct _TCMAPREC *next;                /* pointer to the next element */
+} TCMAPREC;
+
+typedef struct {                         /* type of structure for a map */
+  TCMAPREC **buckets;                    /* bucket array */
+  TCMAPREC *first;                       /* pointer to the first element */
+  TCMAPREC *last;                        /* pointer to the last element */
+  TCMAPREC *cur;                         /* pointer to the current element */
+  uint32_t bnum;                         /* number of buckets */
+  uint64_t rnum;                         /* number of records */
+  uint64_t msiz;                         /* total size of records */
+} TCMAP;
+
+
+/* Create a map object.
+   The return value is the new map object. */
+TCMAP *tcmapnew(void);
+
+
+/* Create a map object with specifying the number of the buckets.
+   `bnum' specifies the number of the buckets.
+   The return value is the new map object. */
+TCMAP *tcmapnew2(uint32_t bnum);
+
+
+/* Create a map object with initial string elements.
+   `str' specifies the string of the first element.
+   The other arguments are other elements.  They should be trailed by a `NULL' argument.
+   The return value is the new map object.
+   The key and the value of each record are situated one after the other. */
+TCMAP *tcmapnew3(const char *str, ...);
+
+
+/* Copy a map object.
+   `map' specifies the map object.
+   The return value is the new map object equivalent to the specified object. */
+TCMAP *tcmapdup(const TCMAP *map);
+
+
+/* Delete a map object.
+   `map' specifies the map object.
+   Note that the deleted object and its derivatives can not be used anymore. */
+void tcmapdel(TCMAP *map);
+
+
+/* Store a record into a map object.
+   `map' specifies the map 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 a record with the same key exists in the map, it is overwritten. */
+void tcmapput(TCMAP *map, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Store a string record into a map object.
+   `map' specifies the map object.
+   `kstr' specifies the string of the key.
+   `vstr' specifies the string of the value.
+   If a record with the same key exists in the map, it is overwritten. */
+void tcmapput2(TCMAP *map, const char *kstr, const char *vstr);
+
+
+/* Store a new record into a map object.
+   `map' specifies the map 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.
+   If a record with the same key exists in the map, this function has no effect. */
+bool tcmapputkeep(TCMAP *map, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Store a new string record into a map object.
+   `map' specifies the map object.
+   `kstr' specifies the string of the key.
+   `vstr' specifies the string of the value.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the map, this function has no effect. */
+bool tcmapputkeep2(TCMAP *map, const char *kstr, const char *vstr);
+
+
+/* Concatenate a value at the end of the value of the existing record in a map object.
+   `map' specifies the map 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 there is no corresponding record, a new record is created. */
+void tcmapputcat(TCMAP *map, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Concatenate a string value at the end of the value of the existing record in a map object.
+   `map' specifies the map object.
+   `kstr' specifies the string of the key.
+   `vstr' specifies the string of the value.
+   If there is no corresponding record, a new record is created. */
+void tcmapputcat2(TCMAP *map, const char *kstr, const char *vstr);
+
+
+/* Remove a record of a map object.
+   `map' specifies the map 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.  False is returned when no record corresponds to
+   the specified key. */
+bool tcmapout(TCMAP *map, const void *kbuf, int ksiz);
+
+
+/* Remove a string record of a map object.
+   `map' specifies the map object.
+   `kstr' specifies the string of the key.
+   If successful, the return value is true.  False is returned when no record corresponds to
+   the specified key. */
+bool tcmapout2(TCMAP *map, const char *kstr);
+
+
+/* Retrieve a record in a map object.
+   `map' specifies the map 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.  `NULL' is returned when no record corresponds.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string. */
+const void *tcmapget(const TCMAP *map, const void *kbuf, int ksiz, int *sp);
+
+
+/* Retrieve a string record in a map object.
+   `map' specifies the map object.
+   `kstr' specifies the string of the key.
+   If successful, the return value is the string of the value of the corresponding record.
+   `NULL' is returned when no record corresponds. */
+const char *tcmapget2(const TCMAP *map, const char *kstr);
+
+
+/* Move a record to the edge of a map object.
+   `map' specifies the map object.
+   `kbuf' specifies the pointer to the region of a key.
+   `ksiz' specifies the size of the region of the key.
+   `head' specifies the destination which is the head if it is true or the tail if else.
+   If successful, the return value is true.  False is returned when no record corresponds to
+   the specified key. */
+bool tcmapmove(TCMAP *map, const void *kbuf, int ksiz, bool head);
+
+
+/* Move a string record to the edge of a map object.
+   `map' specifies the map object.
+   `kstr' specifies the string of a key.
+   `head' specifies the destination which is the head if it is true or the tail if else.
+   If successful, the return value is true.  False is returned when no record corresponds to
+   the specified key. */
+bool tcmapmove2(TCMAP *map, const char *kstr, bool head);
+
+
+/* Initialize the iterator of a map object.
+   `map' specifies the map object.
+   The iterator is used in order to access the key of every record stored in the map object. */
+void tcmapiterinit(TCMAP *map);
+
+
+/* Get the next key of the iterator of a map object.
+   `map' specifies the map 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'.  `NULL' is returned when no record can be fetched from the iterator.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.
+   The order of iteration is assured to be the same as the stored order. */
+const void *tcmapiternext(TCMAP *map, int *sp);
+
+
+/* Get the next key string of the iterator of a map object.
+   `map' specifies the map object.
+   If successful, the return value is the pointer to the region of the next key, else, it is
+   `NULL'.  `NULL' is returned when no record can be fetched from the iterator.
+   The order of iteration is assured to be the same as the stored order. */
+const char *tcmapiternext2(TCMAP *map);
+
+
+/* Get the number of records stored in a map object.
+   `map' specifies the map object.
+   The return value is the number of the records stored in the map object. */
+uint64_t tcmaprnum(const TCMAP *map);
+
+
+/* Get the total size of memory used in a map object.
+   `map' specifies the map object.
+   The return value is the total size of memory used in a map object. */
+uint64_t tcmapmsiz(const TCMAP *map);
+
+
+/* Create a list object containing all keys in a map object.
+   `map' specifies the map object.
+   The return value is the new list object containing all keys in the map object.
+   Because the object of the return value is created with the function `tclistnew', it should
+   be deleted with the function `tclistdel' when it is no longer in use. */
+TCLIST *tcmapkeys(const TCMAP *map);
+
+
+/* Create a list object containing all values in a map object.
+   `map' specifies the map object.
+   The return value is the new list object containing all values in the map object.
+   Because the object of the return value is created with the function `tclistnew', it should
+   be deleted with the function `tclistdel' when it is no longer in use. */
+TCLIST *tcmapvals(const TCMAP *map);
+
+
+/* Add an integer to a record in a map object.
+   `map' specifies the map 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.
+   The return value is the summation value.
+   If the corresponding record exists, the value is treated as an integer and is added to.  If no
+   record corresponds, a new record of the additional value is stored. */
+int tcmapaddint(TCMAP *map, const void *kbuf, int ksiz, int num);
+
+
+/* Add a real number to a record in a map object.
+   `map' specifies the map 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.
+   The return value is the summation value.
+   If the corresponding record exists, the value is treated as a real number and is added to.  If
+   no record corresponds, a new record of the additional value is stored. */
+double tcmapadddouble(TCMAP *map, const void *kbuf, int ksiz, double num);
+
+
+/* Clear a map object.
+   `map' specifies the map object.
+   All records are removed. */
+void tcmapclear(TCMAP *map);
+
+
+/* Remove front records of a map object.
+   `map' specifies the map object.
+   `num' specifies the number of records to be removed. */
+void tcmapcutfront(TCMAP *map, int num);
+
+
+/* Serialize a map object into a byte array.
+   `map' specifies the map object.
+   `sp' specifies the pointer to the variable into which the size of the region of the return
+   value is assigned.
+   The return value is the pointer to the region of the result serial region.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+void *tcmapdump(const TCMAP *map, int *sp);
+
+
+/* Create a map object from a serialized byte array.
+   `ptr' specifies the pointer to the region of serialized byte array.
+   `size' specifies the size of the region.
+   The return value is a new map object.
+   Because the object of the return value is created with the function `tcmapnew', it should be
+   deleted with the function `tcmapdel' when it is no longer in use. */
+TCMAP *tcmapload(const void *ptr, int size);
+
+
+
+/*************************************************************************************************
+ * hash map (for experts)
+ *************************************************************************************************/
+
+
+/* Store a record and make it semivolatile in a map object.
+   `map' specifies the map 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 a record with the same key exists in the map, it is overwritten.  The record is moved to
+   the tail. */
+void tcmapput3(TCMAP *map, const void *kbuf, int ksiz, const char *vbuf, int vsiz);
+
+
+/* Store a record of the value of two regions into a map object.
+   `map' specifies the map object.
+   `kbuf' specifies the pointer to the region of the key.
+   `ksiz' specifies the size of the region of the key.
+   `fvbuf' specifies the pointer to the former region of the value.
+   `fvsiz' specifies the size of the former region of the value.
+   `lvbuf' specifies the pointer to the latter region of the value.
+   `lvsiz' specifies the size of the latter region of the value.
+   If a record with the same key exists in the map, it is overwritten. */
+void tcmapput4(TCMAP *map, const void *kbuf, int ksiz,
+               const void *fvbuf, int fvsiz, const void *lvbuf, int lvsiz);
+
+
+/* Concatenate a value at the existing record and make it semivolatile in a map object.
+   `map' specifies the map 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 there is no corresponding record, a new record is created. */
+void tcmapputcat3(TCMAP *map, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Store a record into a map object with a duplication handler.
+   `map' specifies the map 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.  `NULL' means that record addition is
+   ommited if there is no corresponding record.
+   `vsiz' specifies the size of the region of the value.
+   `proc' specifies the pointer to the callback function to process duplication.  It receives
+   four parameters.  The first parameter is the pointer to the region of the value.  The second
+   parameter is the size of the region of the value.  The third parameter is the pointer to the
+   variable into which the size of the region of the return value is assigned.  The fourth
+   parameter is the pointer to the optional opaque object.  It returns the pointer to the result
+   object allocated with `malloc'.  It is released by the caller.  If it is `NULL', the record is
+   not modified.  If it is `(void *)-1', the record is removed.
+   `op' specifies an arbitrary pointer to be given as a parameter of the callback function.  If
+   it is not needed, `NULL' can be specified.
+   If successful, the return value is true, else, it is false. */
+bool tcmapputproc(TCMAP *map, const void *kbuf, int ksiz, const void *vbuf, int vsiz,
+                  TCPDPROC proc, void *op);
+
+
+/* Retrieve a semivolatile record in a map object.
+   `map' specifies the map 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.  `NULL' is returned when no record corresponds.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  The internal region of the returned
+   record is moved to the tail so that the record will survive for a time under LRU cache
+   algorithm removing records from the head. */
+const void *tcmapget3(TCMAP *map, const void *kbuf, int ksiz, int *sp);
+
+
+/* Retrieve a string record in a map object with specifying the default value string.
+   `map' specifies the map object.
+   `kstr' specifies the string of the key.
+   `dstr' specifies the string of the default value.
+   The return value is the string of the value of the corresponding record or the default value
+   string. */
+const char *tcmapget4(TCMAP *map, const char *kstr, const char *dstr);
+
+
+/* Initialize the iterator of a map object at the record corresponding a key.
+   `map' specifies the map object.
+   `kbuf' specifies the pointer to the region of the key.
+   `ksiz' specifies the size of the region of the key.
+   If there is no record corresponding the condition, the iterator is not modified. */
+void tcmapiterinit2(TCMAP *map, const void *kbuf, int ksiz);
+
+
+/* Initialize the iterator of a map object at the record corresponding a key string.
+   `map' specifies the map object.
+   `kstr' specifies the string of the key.
+   If there is no record corresponding the condition, the iterator is not modified. */
+void tcmapiterinit3(TCMAP *map, const char *kstr);
+
+
+/* Get the value bound to the key fetched from the iterator of a map object.
+   `kbuf' specifies the pointer to the region of the iteration key.
+   `sp' specifies the pointer to the variable into which the size of the region of the return
+   value is assigned.
+   The return value is the pointer to the region of the value of the corresponding record.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string. */
+const void *tcmapiterval(const void *kbuf, int *sp);
+
+
+/* Get the value string bound to the key fetched from the iterator of a map object.
+   `kstr' specifies the string of the iteration key.
+   The return value is the pointer to the region of the value of the corresponding record. */
+const char *tcmapiterval2(const char *kstr);
+
+
+/* Create an array of strings of all keys in a map object.
+   `map' specifies the map object.
+   `np' specifies the pointer to a variable into which the number of elements of the return value
+   is assigned.
+   The return value is the pointer to the array of all string keys in the map object.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call if when is no longer in use.  Note that elements of the array
+   point to the inner objects, whose life duration is synchronous with the map object. */
+const char **tcmapkeys2(const TCMAP *map, int *np);
+
+
+/* Create an array of strings of all values in a map object.
+   `map' specifies the map object.
+   `np' specifies the pointer to a variable into which the number of elements of the return value
+   is assigned.
+   The return value is the pointer to the array of all string values in the map object.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call if when is no longer in use.  Note that elements of the array
+   point to the inner objects, whose life duration is synchronous with the map object. */
+const char **tcmapvals2(const TCMAP *map, int *np);
+
+
+/* Extract a map record from a serialized byte array.
+   `ptr' specifies the pointer to the region of serialized byte array.
+   `size' specifies the size of the region.
+   `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.  `NULL' is returned when no record corresponds.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string. */
+void *tcmaploadone(const void *ptr, int size, const void *kbuf, int ksiz, int *sp);
+
+
+/* Perform formatted output into a map object.
+   `map' specifies the map object.
+   `kstr' specifies the string of the key.
+   `format' specifies the printf-like format string.  The conversion character `%' can be used
+   with such flag characters as `s', `d', `o', `u', `x', `X', `c', `e', `E', `f', `g', `G', `@',
+   `?', `b', and `%'.  `@' works as with `s' but escapes meta characters of XML.  `?' works as
+   with `s' but escapes meta characters of URL.  `b' converts an integer to the string as binary
+   numbers.  The other conversion character work as with each original.
+   The other arguments are used according to the format string. */
+void tcmapprintf(TCMAP *map, const char *kstr, const char *format, ...);
+
+
+
+/*************************************************************************************************
+ * ordered tree
+ *************************************************************************************************/
+
+
+typedef struct _TCTREEREC {              /* type of structure for an element of a tree */
+  int32_t ksiz;                          /* size of the region of the key */
+  int32_t vsiz;                          /* size of the region of the value */
+  struct _TCTREEREC *left;               /* pointer to the left child */
+  struct _TCTREEREC *right;              /* pointer to the right child */
+} TCTREEREC;
+
+typedef struct {                         /* type of structure for a tree */
+  TCTREEREC *root;                       /* pointer to the root element */
+  TCTREEREC *cur;                        /* pointer to the current element */
+  uint64_t rnum;                         /* number of records */
+  uint64_t msiz;                         /* total size of records */
+  TCCMP cmp;                             /* pointer to the comparison function */
+  void *cmpop;                           /* opaque object for the comparison function */
+} TCTREE;
+
+
+/* Create a tree object.
+   The return value is the new tree object. */
+TCTREE *tctreenew(void);
+
+
+/* Create a tree object with specifying the custom comparison function.
+   `cmp' specifies the pointer to the custom comparison function.  It receives five parameters.
+   The first parameter is the pointer to the region of one key.  The second parameter is the size
+   of the region of one key.  The third parameter is the pointer to the region of the other key.
+   The fourth parameter is the size of the region of the other key.  The fifth parameter is the
+   pointer to the optional opaque object.  It returns positive if the former is big, negative if
+   the latter is big, 0 if both are equivalent.
+   `cmpop' specifies an arbitrary pointer to be given as a parameter of the comparison function.
+   If it is not needed, `NULL' can be specified.
+   The return value is the new tree object.
+   The default comparison function compares keys of two records by lexical order.  The functions
+   `tccmplexical' (dafault), `tccmpdecimal', `tccmpint32', and `tccmpint64' are built-in. */
+TCTREE *tctreenew2(TCCMP cmp, void *cmpop);
+
+
+/* Copy a tree object.
+   `tree' specifies the tree object.
+   The return value is the new tree object equivalent to the specified object. */
+TCTREE *tctreedup(const TCTREE *tree);
+
+
+/* Delete a tree object.
+   `tree' specifies the tree object.
+   Note that the deleted object and its derivatives can not be used anymore. */
+void tctreedel(TCTREE *tree);
+
+
+/* Store a record into 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.
+   `vbuf' specifies the pointer to the region of the value.
+   `vsiz' specifies the size of the region of the value.
+   If a record with the same key exists in the tree, it is overwritten. */
+void tctreeput(TCTREE *tree, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Store a string record into a tree object.
+   `tree' specifies the tree object.
+   `kstr' specifies the string of the key.
+   `vstr' specifies the string of the value.
+   If a record with the same key exists in the tree, it is overwritten. */
+void tctreeput2(TCTREE *tree, const char *kstr, const char *vstr);
+
+
+/* Store a new record into 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.
+   `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.
+   If a record with the same key exists in the tree, this function has no effect. */
+bool tctreeputkeep(TCTREE *tree, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Store a new string record into a tree object.
+   `tree' specifies the tree object.
+   `kstr' specifies the string of the key.
+   `vstr' specifies the string of the value.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the tree, this function has no effect. */
+bool tctreeputkeep2(TCTREE *tree, const char *kstr, const char *vstr);
+
+
+/* Concatenate a value at the end of the value of the existing record in 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.
+   `vbuf' specifies the pointer to the region of the value.
+   `vsiz' specifies the size of the region of the value.
+   If there is no corresponding record, a new record is created. */
+void tctreeputcat(TCTREE *tree, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Concatenate a string value at the end of the value of the existing record in a tree object.
+   `tree' specifies the tree object.
+   `kstr' specifies the string of the key.
+   `vstr' specifies the string of the value.
+   If there is no corresponding record, a new record is created. */
+void tctreeputcat2(TCTREE *tree, const char *kstr, const char *vstr);
+
+
+/* Store a record into a tree object with a duplication handler.
+   `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.
+   `vbuf' specifies the pointer to the region of the value.  `NULL' means that record addition is
+   ommited if there is no corresponding record.
+   `vsiz' specifies the size of the region of the value.
+   `proc' specifies the pointer to the callback function to process duplication.  It receives
+   four parameters.  The first parameter is the pointer to the region of the value.  The second
+   parameter is the size of the region of the value.  The third parameter is the pointer to the
+   variable into which the size of the region of the return value is assigned.  The fourth
+   parameter is the pointer to the optional opaque object.  It returns the pointer to the result
+   object allocated with `malloc'.  It is released by the caller.  If it is `NULL', the record is
+   not modified.  If it is `(void *)-1', the record is removed.
+   `op' specifies an arbitrary pointer to be given as a parameter of the callback function.  If
+   it is not needed, `NULL' can be specified.
+   If successful, the return value is true, else, it is false. */
+bool tctreeputproc(TCTREE *tree, const void *kbuf, int ksiz, const void *vbuf, int vsiz,
+                   TCPDPROC proc, void *op);
+
+
+/* Remove a record 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.
+   If successful, the return value is true.  False is returned when no record corresponds to
+   the specified key. */
+bool tctreeout(TCTREE *tree, const void *kbuf, int ksiz);
+
+
+/* Remove a string record of a tree object.
+   `tree' specifies the tree object.
+   `kstr' specifies the string of the key.
+   If successful, the return value is true.  False is returned when no record corresponds to
+   the specified key. */
+bool tctreeout2(TCTREE *tree, const char *kstr);
+
+
+/* Retrieve a record in 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.
+   `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.  `NULL' is returned when no record corresponds.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string. */
+const void *tctreeget(TCTREE *tree, const void *kbuf, int ksiz, int *sp);
+
+
+/* Retrieve a string record in a tree object.
+   `tree' specifies the tree object.
+   `kstr' specifies the string of the key.
+   If successful, the return value is the string of the value of the corresponding record.
+   `NULL' is returned when no record corresponds. */
+const char *tctreeget2(TCTREE *tree, const char *kstr);
+
+
+/* Initialize the iterator of a tree object.
+   `tree' specifies the tree object.
+   The iterator is used in order to access the key of every record stored in the tree object. */
+void tctreeiterinit(TCTREE *tree);
+
+
+/* Get the next key of the iterator of a tree object.
+   `tree' specifies the tree 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'.  `NULL' is returned when no record can be fetched from the iterator.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.
+   The order of iteration is assured to be ascending of the keys. */
+const void *tctreeiternext(TCTREE *tree, int *sp);
+
+
+/* Get the next key string of the iterator of a tree object.
+   `tree' specifies the tree object.
+   If successful, the return value is the pointer to the region of the next key, else, it is
+   `NULL'.  `NULL' is returned when no record can be fetched from the iterator.
+   The order of iteration is assured to be ascending of the keys. */
+const char *tctreeiternext2(TCTREE *tree);
+
+
+/* Get the number of records stored in a tree object.
+   `tree' specifies the tree object.
+   The return value is the number of the records stored in the tree object. */
+uint64_t tctreernum(const TCTREE *tree);
+
+
+/* Get the total size of memory used in a tree object.
+   `tree' specifies the tree object.
+   The return value is the total size of memory used in a tree object. */
+uint64_t tctreemsiz(const TCTREE *tree);
+
+
+/* Create a list object containing all keys in a tree object.
+   `tree' specifies the tree object.
+   The return value is the new list object containing all keys in the tree object.
+   Because the object of the return value is created with the function `tclistnew', it should
+   be deleted with the function `tclistdel' when it is no longer in use. */
+TCLIST *tctreekeys(const TCTREE *tree);
+
+
+/* Create a list object containing all values in a tree object.
+   `tree' specifies the tree object.
+   The return value is the new list object containing all values in the tree object.
+   Because the object of the return value is created with the function `tclistnew', it should
+   be deleted with the function `tclistdel' when it is no longer in use. */
+TCLIST *tctreevals(const TCTREE *tree);
+
+
+/* Add an integer to a record in 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.
+   `num' specifies the additional value.
+   The return value is the summation value.
+   If the corresponding record exists, the value is treated as an integer and is added to.  If no
+   record corresponds, a new record of the additional value is stored. */
+int tctreeaddint(TCTREE *tree, const void *kbuf, int ksiz, int num);
+
+
+/* Add a real number to a record in 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.
+   `num' specifies the additional value.
+   The return value is the summation value.
+   If the corresponding record exists, the value is treated as a real number and is added to.  If
+   no record corresponds, a new record of the additional value is stored. */
+double tctreeadddouble(TCTREE *tree, const void *kbuf, int ksiz, double num);
+
+
+/* Clear a tree object.
+   `tree' specifies the tree object.
+   All records are removed. */
+void tctreeclear(TCTREE *tree);
+
+
+/* Remove fringe records of a tree object.
+   `tree' specifies the tree object.
+   `num' specifies the number of records to be removed. */
+void tctreecutfringe(TCTREE *tree, int num);
+
+
+/* Serialize a tree object into a byte array.
+   `tree' specifies the tree object.
+   `sp' specifies the pointer to the variable into which the size of the region of the return
+   value is assigned.
+   The return value is the pointer to the region of the result serial region.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+void *tctreedump(const TCTREE *tree, int *sp);
+
+
+/* Create a tree object from a serialized byte array.
+   `ptr' specifies the pointer to the region of serialized byte array.
+   `size' specifies the size of the region.
+   `cmp' specifies the pointer to the custom comparison function.
+   `cmpop' specifies an arbitrary pointer to be given as a parameter of the comparison function.
+   If it is not needed, `NULL' can be specified.
+   The return value is a new tree object.
+   Because the object of the return value is created with the function `tctreenew', it should be
+   deleted with the function `tctreedel' when it is no longer in use. */
+TCTREE *tctreeload(const void *ptr, int size, TCCMP cmp, void *cmpop);
+
+
+
+/*************************************************************************************************
+ * ordered tree (for experts)
+ *************************************************************************************************/
+
+
+/* Store a record into a tree object without balancing nodes.
+   `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.
+   `vbuf' specifies the pointer to the region of the value.
+   `vsiz' specifies the size of the region of the value.
+   If a record with the same key exists in the tree, it is overwritten.  The structure of the
+   tree is not modifed by this function. */
+void tctreeput3(TCTREE *tree, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Store a new record into a tree object without balancing nodes.
+   `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.
+   `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.
+   If a record with the same key exists in the tree, this function has no effect.  The structure
+   of the tree is not modifed by this function. */
+bool tctreeputkeep3(TCTREE *tree, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Concatenate a value at the existing record in a tree object without balancing nodes.
+   `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.
+   `vbuf' specifies the pointer to the region of the value.
+   `vsiz' specifies the size of the region of the value.
+   If there is no corresponding record, a new record is created.  The structure of the tree is
+   not modifed by this function. */
+void tctreeputcat3(TCTREE *tree, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Retrieve a record in a tree object without balancing nodes.
+   `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.
+   `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.  `NULL' is returned when no record corresponds.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  The structure of the tree is not
+   modifed by this function. */
+const void *tctreeget3(const TCTREE *tree, const void *kbuf, int ksiz, int *sp);
+
+
+/* Retrieve a string record in a tree object with specifying the default value string.
+   `tree' specifies the tree object.
+   `kstr' specifies the string of the key.
+   `dstr' specifies the string of the default value.
+   The return value is the string of the value of the corresponding record or the default value
+   string. */
+const char *tctreeget4(TCTREE *tree, const char *kstr, const char *dstr);
+
+
+/* Initialize the iterator of a tree object in front of records corresponding a key.
+   `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 iterator is set to the first record corresponding the key or the next substitute if
+   completely matching record does not exist. */
+void tctreeiterinit2(TCTREE *tree, const void *kbuf, int ksiz);
+
+
+/* Initialize the iterator of a tree object in front of records corresponding a key string.
+   `tree' specifies the tree object.
+   `kstr' specifies the string of the key.
+   The iterator is set to the first record corresponding the key or the next substitute if
+   completely matching record does not exist. */
+void tctreeiterinit3(TCTREE *tree, const char *kstr);
+
+
+/* Get the value bound to the key fetched from the iterator of a tree object.
+   `kbuf' specifies the pointer to the region of the iteration key.
+   `sp' specifies the pointer to the variable into which the size of the region of the return
+   value is assigned.
+   The return value is the pointer to the region of the value of the corresponding record.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string. */
+const void *tctreeiterval(const void *kbuf, int *sp);
+
+
+/* Get the value string bound to the key fetched from the iterator of a tree object.
+   `kstr' specifies the string of the iteration key.
+   The return value is the pointer to the region of the value of the corresponding record. */
+const char *tctreeiterval2(const char *kstr);
+
+
+/* Create an array of strings of all keys in a tree object.
+   `tree' specifies the tree object.
+   `np' specifies the pointer to a variable into which the number of elements of the return value
+   is assigned.
+   The return value is the pointer to the array of all string keys in the tree object.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call if when is no longer in use.  Note that elements of the array
+   point to the inner objects, whose life duration is synchronous with the tree object. */
+const char **tctreekeys2(const TCTREE *tree, int *np);
+
+
+/* Create an array of strings of all values in a tree object.
+   `tree' specifies the tree object.
+   `np' specifies the pointer to a variable into which the number of elements of the return value
+   is assigned.
+   The return value is the pointer to the array of all string values in the tree object.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call if when is no longer in use.  Note that elements of the array
+   point to the inner objects, whose life duration is synchronous with the tree object. */
+const char **tctreevals2(const TCTREE *tree, int *np);
+
+
+/* Extract a tree record from a serialized byte array.
+   `ptr' specifies the pointer to the region of serialized byte array.
+   `size' specifies the size of the region.
+   `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.  `NULL' is returned when no record corresponds.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string. */
+void *tctreeloadone(const void *ptr, int size, const void *kbuf, int ksiz, int *sp);
+
+
+/* Perform formatted output into a tree object.
+   `map' specifies the tree object.
+   `kstr' specifies the string of the key.
+   `format' specifies the printf-like format string.  The conversion character `%' can be used
+   with such flag characters as `s', `d', `o', `u', `x', `X', `c', `e', `E', `f', `g', `G', `@',
+   `?', `b', and `%'.  `@' works as with `s' but escapes meta characters of XML.  `?' works as
+   with `s' but escapes meta characters of URL.  `b' converts an integer to the string as binary
+   numbers.  The other conversion character work as with each original.
+   The other arguments are used according to the format string. */
+void tctreeprintf(TCTREE *tree, const char *kstr, const char *format, ...);
+
+
+
+/*************************************************************************************************
+ * on-memory hash database
+ *************************************************************************************************/
+
+
+typedef struct {                         /* type of structure for a on-memory hash database */
+  void **mmtxs;                          /* mutexes for method */
+  void *imtx;                            /* mutex for iterator */
+  TCMAP **maps;                          /* internal map objects */
+  int iter;                              /* index of maps for the iterator */
+} TCMDB;
+
+
+/* Create an on-memory hash database object.
+   The return value is the new on-memory hash database object.
+   The object can be shared by plural threads because of the internal mutex. */
+TCMDB *tcmdbnew(void);
+
+
+/* Create an on-memory hash database object with specifying the number of the buckets.
+   `bnum' specifies the number of the buckets.
+   The return value is the new on-memory hash database object.
+   The object can be shared by plural threads because of the internal mutex. */
+TCMDB *tcmdbnew2(uint32_t bnum);
+
+
+/* Delete an on-memory hash database object.
+   `mdb' specifies the on-memory hash database object. */
+void tcmdbdel(TCMDB *mdb);
+
+
+/* Store a record into an on-memory hash database object.
+   `mdb' specifies the on-memory 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.
+   If a record with the same key exists in the database, it is overwritten. */
+void tcmdbput(TCMDB *mdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Store a string record into an on-memory hash database object.
+   `mdb' specifies the on-memory hash database object.
+   `kstr' specifies the string of the key.
+   `vstr' specifies the string of the value.
+   If a record with the same key exists in the database, it is overwritten. */
+void tcmdbput2(TCMDB *mdb, const char *kstr, const char *vstr);
+
+
+/* Store a new record into an on-memory hash database object.
+   `mdb' specifies the on-memory 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.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the database, this function has no effect. */
+bool tcmdbputkeep(TCMDB *mdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Store a new string record into an on-memory hash database object.
+   `mdb' specifies the on-memory hash database object.
+   `kstr' specifies the string of the key.
+   `vstr' specifies the string of the value.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the database, this function has no effect. */
+bool tcmdbputkeep2(TCMDB *mdb, const char *kstr, const char *vstr);
+
+
+/* Concatenate a value at the end of the existing record in an on-memory hash database.
+   `mdb' specifies the on-memory 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.
+   If there is no corresponding record, a new record is created. */
+void tcmdbputcat(TCMDB *mdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Concatenate a string at the end of the existing record in an on-memory hash database.
+   `mdb' specifies the on-memory hash database object.
+   `kstr' specifies the string of the key.
+   `vstr' specifies the string of the value.
+   If there is no corresponding record, a new record is created. */
+void tcmdbputcat2(TCMDB *mdb, const char *kstr, const char *vstr);
+
+
+/* Remove a record of an on-memory hash database object.
+   `mdb' specifies the on-memory 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.  False is returned when no record corresponds to
+   the specified key. */
+bool tcmdbout(TCMDB *mdb, const void *kbuf, int ksiz);
+
+
+/* Remove a string record of an on-memory hash database object.
+   `mdb' specifies the on-memory hash database object.
+   `kstr' specifies the string of the key.
+   If successful, the return value is true.  False is returned when no record corresponds to
+   the specified key. */
+bool tcmdbout2(TCMDB *mdb, const char *kstr);
+
+
+/* Retrieve a record in an on-memory hash database object.
+   `mdb' specifies the on-memory 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.
+   If successful, the return value is the pointer to the region of the value of the
+   corresponding record.  `NULL' is returned when no record corresponds.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when
+   it is no longer in use. */
+void *tcmdbget(TCMDB *mdb, const void *kbuf, int ksiz, int *sp);
+
+
+/* Retrieve a string record in an on-memory hash database object.
+   `mdb' specifies the on-memory hash database object.
+   `kstr' specifies the string of the key.
+   If successful, the return value is the string of the value of the corresponding record.
+   `NULL' is returned when no record corresponds.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+char *tcmdbget2(TCMDB *mdb, const char *kstr);
+
+
+/* Get the size of the value of a record in an on-memory hash database object.
+   `mdb' specifies the on-memory 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 the size of the value of the corresponding record, else,
+   it is -1. */
+int tcmdbvsiz(TCMDB *mdb, const void *kbuf, int ksiz);
+
+
+/* Get the size of the value of a string record in an on-memory hash database object.
+   `mdb' specifies the on-memory hash database object.
+   `kstr' specifies the string of the key.
+   If successful, the return value is the size of the value of the corresponding record, else,
+   it is -1. */
+int tcmdbvsiz2(TCMDB *mdb, const char *kstr);
+
+
+/* Initialize the iterator of an on-memory hash database object.
+   `mdb' specifies the on-memory hash database object.
+   The iterator is used in order to access the key of every record stored in the on-memory
+   database. */
+void tcmdbiterinit(TCMDB *mdb);
+
+
+/* Get the next key of the iterator of an on-memory hash database object.
+   `mdb' specifies the on-memory 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'.  `NULL' is returned when no record can be fetched from the iterator.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when
+   it is no longer in use.  The order of iteration is assured to be the same as the stored
+   order. */
+void *tcmdbiternext(TCMDB *mdb, int *sp);
+
+
+/* Get the next key string of the iterator of an on-memory hash database object.
+   `mdb' specifies the on-memory hash database object.
+   If successful, the return value is the pointer to the region of the next key, else, it is
+   `NULL'.  `NULL' is returned when no record can be fetched from the iterator.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use.  The order of iteration is assured
+   to be the same as the stored order. */
+char *tcmdbiternext2(TCMDB *mdb);
+
+
+/* Get forward matching keys in an on-memory hash database object.
+   `mdb' specifies the on-memory hash 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.  This function does never fail.
+   It returns an empty list even if no key corresponds.
+   Because the object of the return value is created with the function `tclistnew', it should be
+   deleted with the function `tclistdel' when it is no longer in use.  Note that this function
+   may be very slow because every key in the database is scanned. */
+TCLIST *tcmdbfwmkeys(TCMDB *mdb, const void *pbuf, int psiz, int max);
+
+
+/* Get forward matching string keys in an on-memory hash database object.
+   `mdb' specifies the on-memory hash database object.
+   `pstr' specifies the string 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.  This function does never fail.
+   It returns an empty list even if no key corresponds.
+   Because the object of the return value is created with the function `tclistnew', it should be
+   deleted with the function `tclistdel' when it is no longer in use.  Note that this function
+   may be very slow because every key in the database is scanned. */
+TCLIST *tcmdbfwmkeys2(TCMDB *mdb, const char *pstr, int max);
+
+
+/* Get the number of records stored in an on-memory hash database object.
+   `mdb' specifies the on-memory hash database object.
+   The return value is the number of the records stored in the database. */
+uint64_t tcmdbrnum(TCMDB *mdb);
+
+
+/* Get the total size of memory used in an on-memory hash database object.
+   `mdb' specifies the on-memory hash database object.
+   The return value is the total size of memory used in the database. */
+uint64_t tcmdbmsiz(TCMDB *mdb);
+
+
+/* Add an integer to a record in an on-memory hash database object.
+   `mdb' specifies the on-memory hash 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.
+   The return value is the summation value.
+   If the corresponding record exists, the value is treated as an integer and is added to.  If no
+   record corresponds, a new record of the additional value is stored. */
+int tcmdbaddint(TCMDB *mdb, const void *kbuf, int ksiz, int num);
+
+
+/* Add a real number to a record in an on-memory hash database object.
+   `mdb' specifies the on-memory hash 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.
+   The return value is the summation value.
+   If the corresponding record exists, the value is treated as a real number and is added to.  If
+   no record corresponds, a new record of the additional value is stored. */
+double tcmdbadddouble(TCMDB *mdb, const void *kbuf, int ksiz, double num);
+
+
+/* Clear an on-memory hash database object.
+   `mdb' specifies the on-memory hash database object.
+   All records are removed. */
+void tcmdbvanish(TCMDB *mdb);
+
+
+/* Remove front records of an on-memory hash database object.
+   `mdb' specifies the on-memory hash database object.
+   `num' specifies the number of records to be removed. */
+void tcmdbcutfront(TCMDB *mdb, int num);
+
+
+
+/*************************************************************************************************
+ * on-memory hash database (for experts)
+ *************************************************************************************************/
+
+
+/* Store a record and make it semivolatile in an on-memory hash database object.
+   `mdb' specifies the on-memory 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.
+   If a record with the same key exists in the map, it is overwritten.  The record is moved to
+   the tail. */
+void tcmdbput3(TCMDB *mdb, const void *kbuf, int ksiz, const char *vbuf, int vsiz);
+
+
+/* Store a record of the value of two regions into an on-memory hash database object.
+   `mdb' specifies the on-memory hash database object.
+   `kbuf' specifies the pointer to the region of the key.
+   `ksiz' specifies the size of the region of the key.
+   `fvbuf' specifies the pointer to the former region of the value.
+   `fvsiz' specifies the size of the former region of the value.
+   `lvbuf' specifies the pointer to the latter region of the value.
+   `lvsiz' specifies the size of the latter region of the value.
+   If a record with the same key exists in the database, it is overwritten. */
+void tcmdbput4(TCMDB *mdb, const void *kbuf, int ksiz,
+               const void *fvbuf, int fvsiz, const void *lvbuf, int lvsiz);
+
+
+/* Concatenate a value and make it semivolatile in on-memory hash database object.
+   `mdb' specifies the on-memory 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.
+   If there is no corresponding record, a new record is created. */
+void tcmdbputcat3(TCMDB *mdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Store a record into a on-memory hash database object with a duplication handler.
+   `mdb' specifies the on-memory 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.  `NULL' means that record addition is
+   ommited if there is no corresponding record.
+   `vsiz' specifies the size of the region of the value.
+   `proc' specifies the pointer to the callback function to process duplication.  It receives
+   four parameters.  The first parameter is the pointer to the region of the value.  The second
+   parameter is the size of the region of the value.  The third parameter is the pointer to the
+   variable into which the size of the region of the return value is assigned.  The fourth
+   parameter is the pointer to the optional opaque object.  It returns the pointer to the result
+   object allocated with `malloc'.  It is released by the caller.  If it is `NULL', the record is
+   not modified.  If it is `(void *)-1', the record is removed.
+   `op' specifies an arbitrary pointer to be given as a parameter of the callback function.  If
+   it is not needed, `NULL' can be specified.
+   If successful, the return value is true, else, it is false. */
+bool tcmdbputproc(TCMDB *mdb, const void *kbuf, int ksiz, const void *vbuf, int vsiz,
+                  TCPDPROC proc, void *op);
+
+
+/* Retrieve a record and move it astern in an on-memory hash database object.
+   `mdb' specifies the on-memory 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.
+   If successful, the return value is the pointer to the region of the value of the
+   corresponding record.  `NULL' is returned when no record corresponds.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return value
+   is allocated with the `malloc' call, it should be released with the `free' call when it is no
+   longer in use.  The internal region of the returned record is moved to the tail so that the
+   record will survive for a time under LRU cache algorithm removing records from the head. */
+void *tcmdbget3(TCMDB *mdb, const void *kbuf, int ksiz, int *sp);
+
+
+/* Initialize the iterator of an on-memory map database object in front of a key.
+   `mdb' specifies the on-memory map database object.
+   `kbuf' specifies the pointer to the region of the key.
+   `ksiz' specifies the size of the region of the key.
+   If there is no record corresponding the condition, the iterator is not modified. */
+void tcmdbiterinit2(TCMDB *mdb, const void *kbuf, int ksiz);
+
+
+/* Initialize the iterator of an on-memory map database object in front of a key string.
+   `mdb' specifies the on-memory map database object.
+   `kstr' specifies the string of the key.
+   If there is no record corresponding the condition, the iterator is not modified. */
+void tcmdbiterinit3(TCMDB *mdb, const char *kstr);
+
+
+/* Process each record atomically of an on-memory hash database object.
+   `iter' specifies the pointer to the iterator function called for each record.  It receives
+   five parameters.  The first parameter is the pointer to the region of the key.  The second
+   parameter is the size of the region of the key.  The third parameter is the pointer to the
+   region of the value.  The fourth parameter is the size of the region of the value.  The fifth
+   parameter is the pointer to the optional opaque object.  It returns true to continue iteration
+   or false to stop iteration.
+   `op' specifies an arbitrary pointer to be given as a parameter of the iterator function.  If
+   it is not needed, `NULL' can be specified. */
+void tcmdbforeach(TCMDB *mdb, TCITER iter, void *op);
+
+
+
+/*************************************************************************************************
+ * on-memory tree database
+ *************************************************************************************************/
+
+
+typedef struct {                         /* type of structure for a on-memory tree database */
+  void *mmtx;                            /* mutex for method */
+  TCTREE *tree;                          /* internal tree object */
+} TCNDB;
+
+
+/* Create an on-memory tree database object.
+   The return value is the new on-memory tree database object.
+   The object can be shared by plural threads because of the internal mutex. */
+TCNDB *tcndbnew(void);
+
+
+/* Create an on-memory tree database object with specifying the custom comparison function.
+   `cmp' specifies the pointer to the custom comparison function.
+   `cmpop' specifies an arbitrary pointer to be given as a parameter of the comparison function.
+   If it is not needed, `NULL' can be specified.
+   The return value is the new on-memory tree database object.
+   The default comparison function compares keys of two records by lexical order.  The functions
+   `tccmplexical' (dafault), `tccmpdecimal', `tccmpint32', and `tccmpint64' are built-in.  The
+   object can be shared by plural threads because of the internal mutex. */
+TCNDB *tcndbnew2(TCCMP cmp, void *cmpop);
+
+
+/* Delete an on-memory tree database object.
+   `ndb' specifies the on-memory tree database object. */
+void tcndbdel(TCNDB *ndb);
+
+
+/* Store a record into an on-memory tree database object.
+   `ndb' specifies the on-memory 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.
+   If a record with the same key exists in the database, it is overwritten. */
+void tcndbput(TCNDB *ndb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Store a string record into an on-memory tree database object.
+   `ndb' specifies the on-memory tree database object.
+   `kstr' specifies the string of the key.
+   `vstr' specifies the string of the value.
+   If a record with the same key exists in the database, it is overwritten. */
+void tcndbput2(TCNDB *ndb, const char *kstr, const char *vstr);
+
+
+/* Store a new record into an on-memory tree database object.
+   `ndb' specifies the on-memory 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.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the database, this function has no effect. */
+bool tcndbputkeep(TCNDB *ndb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Store a new string record into an on-memory tree database object.
+   `ndb' specifies the on-memory tree database object.
+   `kstr' specifies the string of the key.
+   `vstr' specifies the string of the value.
+   If successful, the return value is true, else, it is false.
+   If a record with the same key exists in the database, this function has no effect. */
+bool tcndbputkeep2(TCNDB *ndb, const char *kstr, const char *vstr);
+
+
+/* Concatenate a value at the end of the existing record in an on-memory tree database.
+   `ndb' specifies the on-memory 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.
+   If there is no corresponding record, a new record is created. */
+void tcndbputcat(TCNDB *ndb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Concatenate a string at the end of the existing record in an on-memory tree database.
+   `ndb' specifies the on-memory tree database object.
+   `kstr' specifies the string of the key.
+   `vstr' specifies the string of the value.
+   If there is no corresponding record, a new record is created. */
+void tcndbputcat2(TCNDB *ndb, const char *kstr, const char *vstr);
+
+
+/* Remove a record of an on-memory tree database object.
+   `ndb' specifies the on-memory 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.  False is returned when no record corresponds to
+   the specified key. */
+bool tcndbout(TCNDB *ndb, const void *kbuf, int ksiz);
+
+
+/* Remove a string record of an on-memory tree database object.
+   `ndb' specifies the on-memory tree database object.
+   `kstr' specifies the string of the key.
+   If successful, the return value is true.  False is returned when no record corresponds to
+   the specified key. */
+bool tcndbout2(TCNDB *ndb, const char *kstr);
+
+
+/* Retrieve a record in an on-memory tree database object.
+   `ndb' specifies the on-memory 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.  `NULL' is returned when no record corresponds.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when
+   it is no longer in use. */
+void *tcndbget(TCNDB *ndb, const void *kbuf, int ksiz, int *sp);
+
+
+/* Retrieve a string record in an on-memory tree database object.
+   `ndb' specifies the on-memory tree database object.
+   `kstr' specifies the string of the key.
+   If successful, the return value is the string of the value of the corresponding record.
+   `NULL' is returned when no record corresponds.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+char *tcndbget2(TCNDB *ndb, const char *kstr);
+
+
+/* Get the size of the value of a record in an on-memory tree database object.
+   `ndb' specifies the on-memory 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 size of the value of the corresponding record, else,
+   it is -1. */
+int tcndbvsiz(TCNDB *ndb, const void *kbuf, int ksiz);
+
+
+/* Get the size of the value of a string record in an on-memory tree database object.
+   `ndb' specifies the on-memory tree database object.
+   `kstr' specifies the string of the key.
+   If successful, the return value is the size of the value of the corresponding record, else,
+   it is -1. */
+int tcndbvsiz2(TCNDB *ndb, const char *kstr);
+
+
+/* Initialize the iterator of an on-memory tree database object.
+   `ndb' specifies the on-memory tree database object.
+   The iterator is used in order to access the key of every record stored in the on-memory
+   database. */
+void tcndbiterinit(TCNDB *ndb);
+
+
+/* Get the next key of the iterator of an on-memory tree database object.
+   `ndb' specifies the on-memory tree 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'.  `NULL' is returned when no record can be fetched from the iterator.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when
+   it is no longer in use.  The order of iteration is assured to be the same as the stored
+   order. */
+void *tcndbiternext(TCNDB *ndb, int *sp);
+
+
+/* Get the next key string of the iterator of an on-memory tree database object.
+   `ndb' specifies the on-memory tree database object.
+   If successful, the return value is the pointer to the region of the next key, else, it is
+   `NULL'.  `NULL' is returned when no record can be fetched from the iterator.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use.  The order of iteration is assured
+   to be the same as the stored order. */
+char *tcndbiternext2(TCNDB *ndb);
+
+
+/* Get forward matching keys in an on-memory tree database object.
+   `ndb' specifies the on-memory 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.  If it is negative, no limit is
+   specified.
+   The return value is a list object of the corresponding keys.  This function does never fail.
+   It returns an empty list even if no key corresponds.
+   Because the object of the return value is created with the function `tclistnew', it should be
+   deleted with the function `tclistdel' when it is no longer in use. */
+TCLIST *tcndbfwmkeys(TCNDB *ndb, const void *pbuf, int psiz, int max);
+
+
+/* Get forward matching string keys in an on-memory tree database object.
+   `ndb' specifies the on-memory tree database object.
+   `pstr' specifies the string 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.  This function does never fail.
+   It returns an empty list even if no key corresponds.
+   Because the object of the return value is created with the function `tclistnew', it should be
+   deleted with the function `tclistdel' when it is no longer in use. */
+TCLIST *tcndbfwmkeys2(TCNDB *ndb, const char *pstr, int max);
+
+
+/* Get the number of records stored in an on-memory tree database object.
+   `ndb' specifies the on-memory tree database object.
+   The return value is the number of the records stored in the database. */
+uint64_t tcndbrnum(TCNDB *ndb);
+
+
+/* Get the total size of memory used in an on-memory tree database object.
+   `ndb' specifies the on-memory tree database object.
+   The return value is the total size of memory used in the database. */
+uint64_t tcndbmsiz(TCNDB *ndb);
+
+
+/* Add an integer to a record in an on-memory tree database object.
+   `ndb' specifies the on-memory tree 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.
+   The return value is the summation value.
+   If the corresponding record exists, the value is treated as an integer and is added to.  If no
+   record corresponds, a new record of the additional value is stored. */
+int tcndbaddint(TCNDB *ndb, const void *kbuf, int ksiz, int num);
+
+
+/* Add a real number to a record in an on-memory tree database object.
+   `ndb' specifies the on-memory tree 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.
+   The return value is the summation value.
+   If the corresponding record exists, the value is treated as a real number and is added to.  If
+   no record corresponds, a new record of the additional value is stored. */
+double tcndbadddouble(TCNDB *ndb, const void *kbuf, int ksiz, double num);
+
+
+/* Clear an on-memory tree database object.
+   `ndb' specifies the on-memory tree database object.
+   All records are removed. */
+void tcndbvanish(TCNDB *ndb);
+
+
+/* Remove fringe records of an on-memory tree database object.
+   `ndb' specifies the on-memory tree database object.
+   `num' specifies the number of records to be removed. */
+void tcndbcutfringe(TCNDB *ndb, int num);
+
+
+
+/*************************************************************************************************
+ * ordered tree (for experts)
+ *************************************************************************************************/
+
+
+/* Store a record into a on-memory tree database without balancing nodes.
+   `ndb' specifies the on-memory tree database.
+   `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 a record with the same key exists in the database, it is overwritten.  The structure of the
+   tree is not modifed by this function. */
+void tcndbput3(TCNDB *ndb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Store a new record into a on-memory tree database object without balancing nodes.
+   `ndb' specifies the on-memory tree database.
+   `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.
+   If a record with the same key exists in the database, this function has no effect.  The
+   structure of the tree is not modifed by this function. */
+bool tcndbputkeep3(TCNDB *ndb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Concatenate a value in a on-memory tree database without balancing nodes.
+   `ndb' specifies the on-memory tree database.
+   `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 there is no corresponding record, a new record is created.  The structure of the tree is
+   not modifed by this function. */
+void tcndbputcat3(TCNDB *ndb, const void *kbuf, int ksiz, const void *vbuf, int vsiz);
+
+
+/* Store a record into a on-memory tree database object with a duplication handler.
+   `ndb' specifies the on-memory tree database.
+   `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.  `NULL' means that record addition is
+   ommited if there is no corresponding record.
+   `vsiz' specifies the size of the region of the value.
+   `proc' specifies the pointer to the callback function to process duplication.  It receives
+   four parameters.  The first parameter is the pointer to the region of the value.  The second
+   parameter is the size of the region of the value.  The third parameter is the pointer to the
+   variable into which the size of the region of the return value is assigned.  The fourth
+   parameter is the pointer to the optional opaque object.  It returns the pointer to the result
+   object allocated with `malloc'.  It is released by the caller.  If it is `NULL', the record is
+   not modified.  If it is `(void *)-1', the record is removed.
+   `op' specifies an arbitrary pointer to be given as a parameter of the callback function.  If
+   it is not needed, `NULL' can be specified.
+   If successful, the return value is true, else, it is false. */
+bool tcndbputproc(TCNDB *ndb, const void *kbuf, int ksiz, const void *vbuf, int vsiz,
+                  TCPDPROC proc, void *op);
+
+
+/* Retrieve a record in an on-memory tree database object without balancing nodes.
+   `ndb' specifies the on-memory 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.  `NULL' is returned when no record corresponds.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return value
+   is allocated with the `malloc' call, it should be released with the `free' call when it is no
+   longer in use.  The structure of the tree is not modifed by this function. */
+void *tcndbget3(TCNDB *ndb, const void *kbuf, int ksiz, int *sp);
+
+
+/* Initialize the iterator of an on-memory tree database object in front of a key.
+   `ndb' specifies the on-memory tree database object.
+   `kbuf' specifies the pointer to the region of the key.
+   `ksiz' specifies the size of the region of the key.
+   The iterator is set to the first record corresponding the key or the next substitute if
+   completely matching record does not exist. */
+void tcndbiterinit2(TCNDB *ndb, const void *kbuf, int ksiz);
+
+
+/* Initialize the iterator of an on-memory tree database object in front of a key string.
+   `ndb' specifies the on-memory tree database object.
+   `kstr' specifies the string of the key.
+   The iterator is set to the first record corresponding the key or the next substitute if
+   completely matching record does not exist. */
+void tcndbiterinit3(TCNDB *ndb, const char *kstr);
+
+
+/* Process each record atomically of an on-memory tree database object.
+   `iter' specifies the pointer to the iterator function called for each record.  It receives
+   five parameters.  The first parameter is the pointer to the region of the key.  The second
+   parameter is the size of the region of the key.  The third parameter is the pointer to the
+   region of the value.  The fourth parameter is the size of the region of the value.  The fifth
+   parameter is the pointer to the optional opaque object.  It returns true to continue iteration
+   or false to stop iteration.
+   `op' specifies an arbitrary pointer to be given as a parameter of the iterator function.  If
+   it is not needed, `NULL' can be specified. */
+void tcndbforeach(TCNDB *ndb, TCITER iter, void *op);
+
+
+
+/*************************************************************************************************
+ * memory pool
+ *************************************************************************************************/
+
+
+typedef struct {                         /* type of an element of memory pool */
+  void *ptr;                             /* pointer */
+  void (*del)(void *);                   /* deleting function */
+} TCMPELEM;
+
+typedef struct {                         /* type of structure for a memory pool object */
+  void *mutex;                           /* mutex for operations */
+  TCMPELEM *elems;                       /* array of elements */
+  int anum;                              /* number of the elements of the array */
+  int num;                               /* number of used elements */
+} TCMPOOL;
+
+
+/* Create a memory pool object.
+   The return value is the new memory pool object. */
+TCMPOOL *tcmpoolnew(void);
+
+
+/* Delete a memory pool object.
+   `mpool' specifies the memory pool object.
+   Note that the deleted object and its derivatives can not be used anymore. */
+void tcmpooldel(TCMPOOL *mpool);
+
+
+/* Relegate an arbitrary object to a memory pool object.
+   `mpool' specifies the memory pool object.
+   `ptr' specifies the pointer to the object to be relegated.  If it is `NULL', this function has
+   no effect.
+   `del' specifies the pointer to the function to delete the object.
+   The return value is the pointer to the given object.
+   This function assures that the specified object is deleted when the memory pool object is
+   deleted. */
+void *tcmpoolpush(TCMPOOL *mpool, void *ptr, void (*del)(void *));
+
+
+/* Relegate an allocated region to a memory pool object.
+   `mpool' specifies the memory pool object.
+   `ptr' specifies the pointer to the region to be relegated.  If it is `NULL', this function has
+   no effect.
+   The return value is the pointer to the given object.
+   This function assures that the specified region is released when the memory pool object is
+   deleted. */
+void *tcmpoolpushptr(TCMPOOL *mpool, void *ptr);
+
+
+/* Relegate an extensible string object to a memory pool object.
+   `mpool' specifies the memory pool object.
+   `xstr' specifies the extensible string object.  If it is `NULL', this function has no effect.
+   The return value is the pointer to the given object.
+   This function assures that the specified object is deleted when the memory pool object is
+   deleted. */
+TCXSTR *tcmpoolpushxstr(TCMPOOL *mpool, TCXSTR *xstr);
+
+
+/* Relegate a list object to a memory pool object.
+   `mpool' specifies the memory pool object.
+   `list' specifies the list object.  If it is `NULL', this function has no effect.
+   The return value is the pointer to the given object.
+   This function assures that the specified object is deleted when the memory pool object is
+   deleted. */
+TCLIST *tcmpoolpushlist(TCMPOOL *mpool, TCLIST *list);
+
+
+/* Relegate a map object to a memory pool object.
+   `mpool' specifies the memory pool object.
+   `map' specifies the map object.  If it is `NULL', this function has no effect.
+   The return value is the pointer to the given object.
+   This function assures that the specified object is deleted when the memory pool object is
+   deleted. */
+TCMAP *tcmpoolpushmap(TCMPOOL *mpool, TCMAP *map);
+
+
+/* Relegate a tree object to a memory pool object.
+   `mpool' specifies the memory pool object.
+   `tree' specifies the tree object.  If it is `NULL', this function has no effect.
+   The return value is the pointer to the given object.
+   This function assures that the specified object is deleted when the memory pool object is
+   deleted. */
+TCTREE *tcmpoolpushtree(TCMPOOL *mpool, TCTREE *tree);
+
+
+/* Allocate a region relegated to a memory pool object.
+   `mpool' specifies the memory pool object.
+   The return value is the pointer to the allocated region under the memory pool. */
+void *tcmpoolmalloc(TCMPOOL *mpool, size_t size);
+
+
+/* Create an extensible string object relegated to a memory pool object.
+   The return value is the new extensible string object under the memory pool. */
+TCXSTR *tcmpoolxstrnew(TCMPOOL *mpool);
+
+
+/* Create a list object relegated to a memory pool object.
+   The return value is the new list object under the memory pool. */
+TCLIST *tcmpoollistnew(TCMPOOL *mpool);
+
+
+/* Create a map object relegated to a memory pool object.
+   The return value is the new map object under the memory pool. */
+TCMAP *tcmpoolmapnew(TCMPOOL *mpool);
+
+
+/* Create a tree object relegated to a memory pool object.
+   The return value is the new tree object under the memory pool. */
+TCTREE *tcmpooltreenew(TCMPOOL *mpool);
+
+
+/* Remove the most recently installed cleanup handler of a memory pool object.
+   `mpool' specifies the memory pool object.
+   `exe' specifies whether to execute the destructor of the removed handler. */
+void tcmpoolpop(TCMPOOL *mpool, bool exe);
+
+
+/* Remove all cleanup handler of a memory pool object.
+   `mpool' specifies the memory pool object.
+   `exe' specifies whether to execute the destructors of the removed handlers. */
+void tcmpoolclear(TCMPOOL *mpool, bool exe);
+
+
+/* Get the global memory pool object.
+   The return value is the global memory pool object.
+   The global memory pool object is a singleton and assured to be deleted when the porcess is
+   terminating normally. */
+TCMPOOL *tcmpoolglobal(void);
+
+
+
+/*************************************************************************************************
+ * miscellaneous utilities
+ *************************************************************************************************/
+
+
+/* Get the larger value of two integers.
+   `a' specifies an integer.
+   `b' specifies the other integer.
+   The return value is the larger value of the two. */
+long tclmax(long a, long b);
+
+
+/* Get the lesser value of two integers.
+   `a' specifies an integer.
+   `b' specifies the other integer.
+   The return value is the lesser value of the two. */
+long tclmin(long a, long b);
+
+
+/* Get a random number as long integer based on uniform distribution.
+   The return value is the random number between 0 and `ULONG_MAX'.
+   This function uses the random number source device and generates a real random number if
+   possible. */
+unsigned long tclrand(void);
+
+
+/* Get a random number as double decimal based on uniform distribution.
+   The return value is the random number equal to or greater than 0, and less than 1.0.
+   This function uses the random number source device and generates a real random number if
+   possible. */
+double tcdrand(void);
+
+
+/* Get a random number as double decimal based on normal distribution.
+   `avg' specifies the average.
+   `sd' specifies the standard deviation.
+   The return value is the random number.
+   This function uses the random number source device and generates a real random number if
+   possible. */
+double tcdrandnd(double avg, double sd);
+
+
+/* Compare two strings with case insensitive evaluation.
+   `astr' specifies a string.
+   `bstr' specifies of the other string.
+   The return value is positive if the former is big, negative if the latter is big, 0 if both
+   are equivalent. */
+int tcstricmp(const char *astr, const char *bstr);
+
+
+/* Check whether a string begins with a key.
+   `str' specifies the target string.
+   `key' specifies the forward matching key string.
+   The return value is true if the target string begins with the key, else, it is false. */
+bool tcstrfwm(const char *str, const char *key);
+
+
+/* Check whether a string begins with a key with case insensitive evaluation.
+   `str' specifies the target string.
+   `key' specifies the forward matching key string.
+   The return value is true if the target string begins with the key, else, it is false. */
+bool tcstrifwm(const char *str, const char *key);
+
+
+/* Check whether a string ends with a key.
+   `str' specifies the target string.
+   `key' specifies the backward matching key string.
+   The return value is true if the target string ends with the key, else, it is false. */
+bool tcstrbwm(const char *str, const char *key);
+
+
+/* Check whether a string ends with a key with case insensitive evaluation.
+   `str' specifies the target string.
+   `key' specifies the backward matching key string.
+   The return value is true if the target string ends with the key, else, it is false. */
+bool tcstribwm(const char *str, const char *key);
+
+
+/* Calculate the edit distance of two strings.
+   `astr' specifies a string.
+   `bstr' specifies of the other string.
+   The return value is the edit distance which is known as the Levenshtein distance.  The cost is
+   calculated by byte. */
+int tcstrdist(const char *astr, const char *bstr);
+
+
+/* Calculate the edit distance of two UTF-8 strings.
+   `astr' specifies a string.
+   `bstr' specifies of the other string.
+   The return value is the edit distance which is known as the Levenshtein distance.  The cost is
+   calculated by Unicode character. */
+int tcstrdistutf(const char *astr, const char *bstr);
+
+
+/* Convert the letters of a string into upper case.
+   `str' specifies the string to be converted.
+   The return value is the string itself. */
+char *tcstrtoupper(char *str);
+
+
+/* Convert the letters of a string into lower case.
+   `str' specifies the string to be converted.
+   The return value is the string itself. */
+char *tcstrtolower(char *str);
+
+
+/* Cut space characters at head or tail of a string.
+   `str' specifies the string to be converted.
+   The return value is the string itself. */
+char *tcstrtrim(char *str);
+
+
+/* Squeeze space characters in a string and trim it.
+   `str' specifies the string to be converted.
+   The return value is the string itself. */
+char *tcstrsqzspc(char *str);
+
+
+/* Substitute characters in a string.
+   `str' specifies the string to be converted.
+   `rstr' specifies the string containing characters to be replaced.
+   `sstr' specifies the string containing characters to be substituted.
+   If the substitute string is shorter then the replacement string, corresponding characters are
+   removed. */
+char *tcstrsubchr(char *str, const char *rstr, const char *sstr);
+
+
+/* Count the number of characters in a string of UTF-8.
+   `str' specifies the string of UTF-8.
+   The return value is the number of characters in the string. */
+int tcstrcntutf(const char *str);
+
+
+/* Cut a string of UTF-8 at the specified number of characters.
+   `str' specifies the string of UTF-8.
+   `num' specifies the number of characters to be kept.
+   The return value is the string itself. */
+char *tcstrcututf(char *str, int num);
+
+
+/* Convert a UTF-8 string into a UCS-2 array.
+   `str' specifies the UTF-8 string.
+   `ary' specifies the pointer to the region into which the result UCS-2 codes are written.  The
+   size of the buffer should be sufficient.
+   `np' specifies the pointer to a variable into which the number of elements of the result array
+   is assigned. */
+void tcstrutftoucs(const char *str, uint16_t *ary, int *np);
+
+
+/* Convert a UCS-2 array into a UTF-8 string.
+   `ary' specifies the array of UCS-2 codes.
+   `num' specifies the number of the array.
+   `str' specifies the pointer to the region into which the result UTF-8 string is written.  The
+   size of the buffer should be sufficient.
+   The return value is the length of the result string. */
+int tcstrucstoutf(const uint16_t *ary, int num, char *str);
+
+
+/* Create a list object by splitting a string.
+   `str' specifies the source string.
+   `delim' specifies a string containing delimiting characters.
+   The return value is a list object of the split elements.
+   If two delimiters are successive, it is assumed that an empty element is between the two.
+   Because the object of the return value is created with the function `tclistnew', it should be
+   deleted with the function `tclistdel' when it is no longer in use. */
+TCLIST *tcstrsplit(const char *str, const char *delims);
+
+
+/* Create a string by joining all elements of a list object.
+   `list' specifies a list object.
+   `delim' specifies a delimiting character.
+   The return value is the result string.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+char *tcstrjoin(const TCLIST *list, char delim);
+
+
+/* Convert a string to an integer.
+   `str' specifies the string.
+   The return value is the integer.  If the string does not contain numeric expression, 0 is
+   returned.
+   This function is equivalent to `atoll' except that it does not depend on the locale. */
+int64_t tcatoi(const char *str);
+
+
+/* Convert a string with a metric prefix to an integer.
+   `str' specifies the string, which can be trailed by a binary metric prefix.  "K", "M", "G",
+   "T", "P", and "E" are supported.  They are case-insensitive.
+   The return value is the integer.  If the string does not contain numeric expression, 0 is
+   returned.  If the integer overflows the domain, `INT64_MAX' or `INT64_MIN' is returned
+   according to the sign. */
+int64_t tcatoix(const char *str);
+
+
+/* Convert a string to a real number.
+   `str' specifies the string.
+   The return value is the real number.  If the string does not contain numeric expression, 0.0
+   is returned.
+   This function is equivalent to `atof' except that it does not depend on the locale. */
+double tcatof(const char *str);
+
+
+/* Check whether a string matches a regular expression.
+   `str' specifies the target string.
+   `regex' specifies the regular expression string.  If it begins with `*', the trailing
+   substring is used as a case-insensitive regular expression.
+   The return value is true if matching is success, else, it is false. */
+bool tcregexmatch(const char *str, const char *regex);
+
+
+/* Replace each substring matching a regular expression string.
+   `str' specifies the target string.
+   `regex' specifies the regular expression string for substrings.  If it begins with `*', the
+   trailing substring is used as a case-insensitive regular expression.
+   `alt' specifies the alternative string with which each substrings is replaced.  Each `&' in
+   the string is replaced with the matched substring.  Each `\' in the string escapes the
+   following character.  Special escapes "\1" through "\9" referring to the corresponding
+   matching sub-expressions in the regular expression string are supported.
+   The return value is a new converted string.  Even if the regular expression is invalid, a copy
+   of the original string is returned.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+char *tcregexreplace(const char *str, const char *regex, const char *alt);
+
+
+/* Get the MD5 hash value of a serial object.
+   `ptr' specifies the pointer to the region.
+   `size' specifies the size of the region.
+   `buf' specifies the pointer to the region into which the result string is written.  The size
+   of the buffer should be equal to or more than 48 bytes. */
+void tcmd5hash(const void *ptr, int size, char *buf);
+
+
+/* Cipher or decipher a serial object with the Arcfour stream cipher.
+   `ptr' specifies the pointer to the region.
+   `size' specifies the size of the region.
+   `kbuf' specifies the pointer to the region of the cipher key.
+   `ksiz' specifies the size of the region of the cipher key.
+   `obuf' specifies the pointer to the region into which the result data is written.  The size
+   of the buffer should be equal to or more than the input region. */
+void tcarccipher(const void *ptr, int size, const void *kbuf, int ksiz, void *obuf);
+
+
+/* Get the time of day in seconds.
+   The return value is the time of day in seconds.  The accuracy is in microseconds. */
+double tctime(void);
+
+
+/* Get time in milleconds since Epoch. */
+unsigned long tcmstime(void);
+
+
+/* Get the Gregorian calendar of a time.
+   `t' specifies the source time in seconds from the epoch.  If it is `INT64_MAX', the current
+   time is specified.
+   `jl' specifies the jet lag of a location in seconds.  If it is `INT_MAX', the local jet lag is
+   specified.
+   `yearp' specifies the pointer to a variable to which the year is assigned.  If it is `NULL',
+   it is not used.
+   `monp' specifies the pointer to a variable to which the month is assigned.  If it is `NULL',
+   it is not used.  1 means January and 12 means December.
+   `dayp' specifies the pointer to a variable to which the day of the month is assigned.  If it
+   is `NULL', it is not used.
+   `hourp' specifies the pointer to a variable to which the hours is assigned.  If it is `NULL',
+   it is not used.
+   `minp' specifies the pointer to a variable to which the minutes is assigned.  If it is `NULL',
+   it is not used.
+   `secp' specifies the pointer to a variable to which the seconds is assigned.  If it is `NULL',
+   it is not used. */
+void tccalendar(int64_t t, int jl, int *yearp, int *monp, int *dayp,
+                int *hourp, int *minp, int *secp);
+
+
+/* Format a date as a string in W3CDTF.
+   `t' specifies the source time in seconds from the epoch.  If it is `INT64_MAX', the current
+   time is specified.
+   `jl' specifies the jet lag of a location in seconds.  If it is `INT_MAX', the local jet lag is
+   specified.
+   `buf' specifies the pointer to the region into which the result string is written.  The size
+   of the buffer should be equal to or more than 48 bytes.
+   W3CDTF represents a date as "YYYY-MM-DDThh:mm:ddTZD". */
+void tcdatestrwww(int64_t t, int jl, char *buf);
+
+
+/* Format a date as a string in RFC 1123 format.
+   `t' specifies the source time in seconds from the epoch.  If it is `INT64_MAX', the current
+   time is specified.
+   `jl' specifies the jet lag of a location in seconds.  If it is `INT_MAX', the local jet lag is
+   specified.
+   `buf' specifies the pointer to the region into which the result string is written.  The size
+   of the buffer should be equal to or more than 48 bytes.
+   RFC 1123 format represents a date as "Wdy, DD-Mon-YYYY hh:mm:dd TZD". */
+void tcdatestrhttp(int64_t t, int jl, char *buf);
+
+
+/* Get the time value of a date string.
+   `str' specifies the date string in decimal, hexadecimal, W3CDTF, or RFC 822 (1123).  Decimal
+   can be trailed by "s" for in seconds, "m" for in minutes, "h" for in hours, and "d" for in
+   days.
+   The return value is the time value of the date or `INT64_MIN' if the format is invalid. */
+int64_t tcstrmktime(const char *str);
+
+
+/* Get the jet lag of the local time.
+   The return value is the jet lag of the local time in seconds. */
+int tcjetlag(void);
+
+
+/* Get the day of week of a date.
+   `year' specifies the year of a date.
+   `mon' specifies the month of the date.
+   `day' specifies the day of the date.
+   The return value is the day of week of the date.  0 means Sunday and 6 means Saturday. */
+int tcdayofweek(int year, int mon, int day);
+
+
+
+/*************************************************************************************************
+ * miscellaneous utilities (for experts)
+ *************************************************************************************************/
+
+
+enum {                                   /* enumeration for UCS normalization */
+  TCUNSPACE = 1 << 0,                    /* white space normalization */
+  TCUNLOWER = 1 << 1,                    /* lower case normalization */
+  TCUNNOACC = 1 << 2,                    /* strip accent marks */
+  TCUNWIDTH = 1 << 3                     /* half-width normalization */
+};
+
+enum {                                   /* enumeration for KWIC generator */
+  TCKWMUTAB = 1 << 0,                    /* mark up by tabs */
+  TCKWMUCTRL = 1 << 1,                   /* mark up by control characters */
+  TCKWMUBRCT = 1 << 2,                   /* mark up by brackets */
+  TCKWNOOVER = 1 << 24,                  /* no overlap */
+  TCKWPULEAD = 1 << 25                   /* pick up the lead string */
+};
+
+typedef struct {                         /* type of structure for a consistent hashing node */
+  uint32_t seq;                          /* sequential number */
+  uint32_t hash;                         /* hash value */
+} TCCHIDXNODE;
+
+typedef struct {                         /* type of structure for a consistent hashing object */
+  TCCHIDXNODE *nodes;                    /* node array */
+  int nnum;                              /* number of the node array */
+} TCCHIDX;
+
+
+/* Check whether a string is numeric completely or not.
+   `str' specifies the string to be checked.
+   The return value is true if the string is numeric, else, it is false. */
+bool tcstrisnum(const char *str);
+
+
+/* Convert a hexadecimal string to an integer.
+   `str' specifies the string.
+   The return value is the integer.  If the string does not contain numeric expression, 0 is
+   returned. */
+int64_t tcatoih(const char *str);
+
+
+/* Skip space characters at head of a string.
+   `str' specifies the string.
+   The return value is the pointer to the first non-space character. */
+const char *tcstrskipspc(const char *str);
+
+
+/* Normalize a UTF-8 string.
+   `str' specifies the string of UTF-8.
+   `opts' specifies options by bitwise-or: `TCUNSPACE' specifies that white space characters are
+   normalized into the ASCII space and they are squeezed into one, `TCUNLOWER' specifies that
+   alphabetical characters are normalized into lower cases, `TCUNNOACC' specifies that
+   alphabetical characters with accent marks are normalized without accent marks, `TCUNWIDTH'
+   specifies that full-width characters are normalized into half-width characters.
+   The return value is the string itself. */
+char *tcstrutfnorm(char *str, int opts);
+
+
+/* Normalize a UCS-2 array.
+   `ary' specifies the array of UCS-2 codes.
+   `num' specifies the number of elements of the array.
+   `opts' specifies options by bitwise-or: `TCUNSPACE' specifies that white space characters are
+   normalized into the ASCII space and they are squeezed into one, `TCUNLOWER' specifies that
+   alphabetical characters are normalized into lower cases, `TCUNNOACC' specifies that
+   alphabetical characters with accent marks are normalized without accent marks, `TCUNWIDTH'
+   specifies that full-width characters are normalized into half-width characters.
+   The return value is the number of elements of the result array. */
+int tcstrucsnorm(uint16_t *ary, int num, int opts);
+
+
+/* Generate a keyword-in-context string from a text and keywords.
+   `str' specifies the text string of UTF-8.
+   `words' specifies a list object of the keyword strings.
+   `width' specifies the width of strings picked up around each keyword.
+   `opts' specifies options by bitwise-or: `TCKWMUTAB' specifies that each keyword is marked up
+   between two tab characters, `TCKWMUCTRL' specifies that each keyword is marked up by the STX
+   (0x02) code and the ETX (0x03) code, `TCKWMUBRCT' specifies that each keyword is marked up by
+   the two square brackets, `TCKWNOOVER' specifies that each context does not overlap,
+   `TCKWPULEAD' specifies that the lead string is picked up forcibly.
+   The return value is the list object whose elements are strings around keywords.
+   Because the object of the return value is created with the function `tclistnew', it should
+   be deleted with the function `tclistdel' when it is no longer in use. */
+TCLIST *tcstrkwic(const char *str, const TCLIST *words, int width, int opts);
+
+
+/* Tokenize a text separating by white space characters.
+   `str' specifies the string.
+   The return value is the list object whose elements are extracted tokens.
+   Because the object of the return value is created with the function `tclistnew', it should
+   be deleted with the function `tclistdel' when it is no longer in use. */
+TCLIST *tcstrtokenize(const char *str);
+
+
+/* Create a list object by splitting a region by zero code.
+   `ptr' specifies the pointer to the region.
+   `size' specifies the size of the region.
+   The return value is a list object of the split elements.
+   If two delimiters are successive, it is assumed that an empty element is between the two.
+   Because the object of the return value is created with the function `tclistnew', it should be
+   deleted with the function `tclistdel' when it is no longer in use. */
+TCLIST *tcstrsplit2(const void *ptr, int size);
+
+
+/* Create a map object by splitting a string.
+   `str' specifies the source string where the key and the value of each record are situated one
+   after the other.
+   `delim' specifies a string containing delimiting characters.
+   The return value is a map object of the split records.
+   Because the object of the return value is created with the function `tcmapnew', it should be
+   deleted with the function `tcmapdel' when it is no longer in use. */
+TCMAP *tcstrsplit3(const char *str, const char *delims);
+
+
+/* Create a map object by splitting a region by zero code.
+   `ptr' specifies the pointer to the region where the key and the value of each record are
+   situated one after the other.
+   `size' specifies the size of the region.
+   The return value is a map object of the split records.
+   Because the object of the return value is created with the function `tcmapnew', it should be
+   deleted with the function `tcmapdel' when it is no longer in use. */
+TCMAP *tcstrsplit4(const void *ptr, int size);
+
+
+/* Create a region separated by zero code by joining all elements of a list object.
+   `list' specifies a list object.
+   The return value is the result region.
+   `sp' specifies the pointer to the variable into which the size of the region of the return
+   value is assigned.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+void *tcstrjoin2(const TCLIST *list, int *sp);
+
+
+/* Create a string by joining all records of a map object.
+   `map' specifies a map object.
+   `delim' specifies a delimiting character.
+   The return value is the result string where the key and the value of each record are situated
+   one after the other.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+char *tcstrjoin3(const TCMAP *map, char delim);
+
+
+/* Create a region separated by zero code by joining all records of a map object.
+   `list' specifies a list object.
+   The return value is the result region, where the key and the value of each record are
+   situated one after the other.
+   `sp' specifies the pointer to the variable into which the size of the region of the return
+   value is assigned.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+void *tcstrjoin4(const TCMAP *map, int *sp);
+
+
+/* Sort top records of an array.
+   `base' spacifies the pointer to an array.
+   `nmemb' specifies the number of elements of the array.
+   `size' specifies the size of each element.
+   `top' specifies the number of top records.
+   `compar' specifies the pointer to comparing function.  The two arguments specify the pointers
+   of elements.  The comparing function should returns positive if the former is big, negative
+   if the latter is big, 0 if both are equal. */
+void tctopsort(void *base, size_t nmemb, size_t size, size_t top,
+               int(*compar)(const void *, const void *));
+
+
+/* Suspend execution of the current thread.
+   `sec' specifies the interval of the suspension in seconds.
+   If successful, the return value is true, else, it is false. */
+bool tcsleep(double sec);
+
+
+/* Get the current system information.
+   The return value is a map object of the current system information or `NULL' on failure.
+   The key "utime" indicates the user time of the CPU.  The key "stime" indicates the system time
+   of the CPU.  The key "size" indicates the process size in bytes.  The "rss" indicates the
+   resident set size in bytes.  "total" indicates the total size of the real memory.  "free"
+   indicates the free size of the real memory.  "cached" indicates the cached size of the real
+   memory.
+   Because the object of the return value is created with the function `tcmapnew', it should be
+   deleted with the function `tcmapdel' when it is no longer in use. */
+TCMAP *tcsysinfo(void);
+
+
+/* Create a consistent hashing object.
+   `range' specifies the number of nodes.  It should be more than 0.  The range of hash values is
+   from 0 to less than the specified number.
+   The return value is the new consistent hashing object.
+   Consistent hashing is useful because the addition or removal of one node does not
+   significantly change the mapping of keys to nodes. */
+TCCHIDX *tcchidxnew(int range);
+
+
+/* Delete a consistent hashing object.
+   `chidx' specifies the consistent hashing object. */
+void tcchidxdel(TCCHIDX *chidx);
+
+
+/* Get the consistent hashing value of a record.
+   `chidx' specifies the consistent hashing object.
+   `ptr' specifies the pointer to the region of the record.
+   `size' specifies the size of the region.
+   The return value is the hash value of the record. */
+int tcchidxhash(TCCHIDX *chidx, const void *ptr, int size);
+
+
+
+/*************************************************************************************************
+ * filesystem utilities
+ *************************************************************************************************/
+
+
+/* Get the canonicalized absolute path of a file.
+   `path' specifies the path of the file.
+   The return value is the canonicalized absolute path of a file, or `NULL' if the path is
+   invalid.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+char *tcrealpath(const char *path);
+
+
+/* Get the status information of a file.
+   `path' specifies the path of the file.
+   `isdirp' specifies the pointer to a variable into which whether the file is a directory is
+   assigned.  If it is `NULL', it is ignored.
+   `sizep' specifies the pointer to a variable into which the size of the file is assigned.  If
+   it is `NULL', it is ignored.
+   `ntimep' specifies the pointer to a variable into which the size of the file is assigned.  If
+   it is `NULL', it is ignored.
+   If successful, the return value is true, else, it is false. */
+bool tcstatfile(const char *path, bool *isdirp, int64_t *sizep, int64_t *mtimep);
+
+
+/* Read whole data of a file.
+   `path' specifies the path of the file.  If it is `NULL', the standard input is specified.
+   `limit' specifies the limiting size of reading data.  If it is not more than 0, the limitation
+   is not specified.
+   `sp' specifies the pointer to the variable into which the size of the region of the return
+   value is assigned.  If it is `NULL', it is not used.
+   The return value is the pointer to the allocated region of the read data, or `NULL' if the
+   file could not be opened.
+   Because an additional zero code is appended at the end of the region of the return value, the
+   return value can be treated as a character string.  Because the region of the return value is
+   allocated with the `malloc' call, it should be released with the `free' call when when is no
+   longer in use.  */
+void *tcreadfile(const char *path, int limit, int *sp);
+
+
+/* Read every line of a file.
+   `path' specifies the path of the file.  If it is `NULL', the standard input is specified.
+   The return value is a list object of every lines if successful, else it is `NULL'.
+   Line separators are cut out.  Because the object of the return value is created with the
+   function `tclistnew', it should be deleted with the function `tclistdel' when it is no longer
+   in use. */
+TCLIST *tcreadfilelines(const char *path);
+
+
+/* Write data into a file.
+   `path' specifies the path of the file.  If it is `NULL', the standard output is specified.
+   `ptr' specifies the pointer to the data region.
+   `size' specifies the size of the region.
+   If successful, the return value is true, else, it is false. */
+bool tcwritefile(const char *path, const void *ptr, int size);
+
+
+/* Copy a file.
+   `src' specifies the path of the source file.
+   `dest' specifies the path of the destination file.
+   The return value is true if successful, else, it is false.
+   If the destination file exists, it is overwritten. */
+bool tccopyfile(const char *src, const char *dest);
+
+
+/* Read names of files in a directory.
+   `path' specifies the path of the directory.
+   The return value is a list object of names if successful, else it is `NULL'.
+   Links to the directory itself and to the parent directory are ignored.
+   Because the object of the return value is created with the function `tclistnew', it should
+   be deleted with the function `tclistdel' when it is no longer in use. */
+TCLIST *tcreaddir(const char *path);
+
+
+/* Expand a pattern into a list of matched paths.
+   `pattern' specifies the matching pattern.
+   The return value is a list object of matched paths.  If no path is matched, an empty list is
+   returned.
+   Because the object of the return value is created with the function `tclistnew', it should
+   be deleted with the function `tclistdel' when it is no longer in use. */
+TCLIST *tcglobpat(const char *pattern);
+
+
+/* Remove a file or a directory and its sub ones recursively.
+   `path' specifies the path of the link.
+   If successful, the return value is true, else, it is false.  False is returned when the link
+   does not exist or the permission is denied. */
+bool tcremovelink(const char *path);
+
+
+/* Write data into a file.
+   `fd' specifies the file descriptor.
+   `buf' specifies the buffer to be written.
+   `size' specifies the size of the buffer.
+   The return value is true if successful, else, it is false. */
+bool tcwrite(int fd, const void *buf, size_t size);
+
+
+/* Read data from a file.
+   `fd' specifies the file descriptor.
+   `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. */
+bool tcread(int fd, void *buf, size_t size);
+
+
+/* Lock a file.
+   `fd' specifies the file descriptor.
+   `ex' specifies whether an exclusive lock or a shared lock is performed.
+   `nb' specifies whether to request with non-blocking.
+   The return value is true if successful, else, it is false. */
+bool tclock(int fd, bool ex, bool nb);
+
+
+/* Unlock a file.
+   `fd' specifies the file descriptor.
+   The return value is true if successful, else, it is false. */
+bool tcunlock(int fd);
+
+
+/* Execute a shell command.
+   `args' specifies an array of the command name and its arguments.
+   `anum' specifies the number of elements of the array.
+   The return value is the exit code of the command or `INT_MAX' on failure.
+   The command name and the arguments are quoted and meta characters are escaped. */
+int tcsystem(const char **args, int anum);
+
+
+
+/*************************************************************************************************
+ * encoding utilities
+ *************************************************************************************************/
+
+
+/* Encode a serial object with URL encoding.
+   `ptr' specifies the pointer to the region.
+   `size' specifies the size of the region.
+   The return value is the result string.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call if when is no longer in use. */
+char *tcurlencode(const char *ptr, int size);
+
+
+/* Decode a string encoded with URL encoding.
+   `str' specifies the encoded string.
+   `sp' specifies the pointer to a variable into which the size of the region of the return
+   value is assigned.
+   The return value is the pointer to the region of the result.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when
+   it is no longer in use. */
+char *tcurldecode(const char *str, int *sp);
+
+
+/* Break up a URL into elements.
+   `str' specifies the URL string.
+   The return value is the map object whose keys are the name of elements.  The key "self"
+   specifies the URL itself.  The key "scheme" indicates the scheme.  The key "host" indicates
+   the host of the server.  The key "port" indicates the port number of the server.  The key
+   "authority" indicates the authority information.  The key "path" indicates the path of the
+   resource.  The key "file" indicates the file name without the directory section.  The key
+   "query" indicates the query string.  The key "fragment" indicates the fragment string.
+   Supported schema are HTTP, HTTPS, FTP, and FILE.  Absolute URL and relative URL are supported.
+   Because the object of the return value is created with the function `tcmapnew', it should be
+   deleted with the function `tcmapdel' when it is no longer in use. */
+TCMAP *tcurlbreak(const char *str);
+
+
+/* Resolve a relative URL with an absolute URL.
+   `base' specifies the absolute URL of the base location.
+   `target' specifies the URL to be resolved.
+   The return value is the resolved URL.  If the target URL is relative, a new URL of relative
+   location from the base location is returned.  Else, a copy of the target URL is returned.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+char *tcurlresolve(const char *base, const char *target);
+
+
+/* Encode a serial object with Base64 encoding.
+   `ptr' specifies the pointer to the region.
+   `size' specifies the size of the region.
+   The return value is the result string.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call if when is no longer in use. */
+char *tcbaseencode(const char *ptr, int size);
+
+
+/* Decode a string encoded with Base64 encoding.
+   `str' specifies the encoded string.
+   `sp' specifies the pointer to a variable into which the size of the region of the return
+   value is assigned.
+   The return value is the pointer to the region of the result.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when
+   it is no longer in use. */
+char *tcbasedecode(const char *str, int *sp);
+
+
+/* Encode a serial object with Quoted-printable encoding.
+   `ptr' specifies the pointer to the region.
+   `size' specifies the size of the region.
+   The return value is the result string.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call if when is no longer in use. */
+char *tcquoteencode(const char *ptr, int size);
+
+
+/* Decode a string encoded with Quoted-printable encoding.
+   `str' specifies the encoded string.
+   `sp' specifies the pointer to a variable into which the size of the region of the return
+   value is assigned.
+   The return value is the pointer to the region of the result.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when
+   it is no longer in use. */
+char *tcquotedecode(const char *str, int *sp);
+
+
+/* Encode a string with MIME encoding.
+   `str' specifies the string.
+   `encname' specifies the string of the name of the character encoding.
+   `base' specifies whether to use Base64 encoding.  If it is false, Quoted-printable is used.
+   The return value is the result string.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+char *tcmimeencode(const char *str, const char *encname, bool base);
+
+
+/* Decode a string encoded with MIME encoding.
+   `str' specifies the encoded string.
+   `enp' specifies the pointer to the region into which the name of encoding is written.  If it
+   is `NULL', it is not used.  The size of the buffer should be equal to or more than 32 bytes.
+   The return value is the result string.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+char *tcmimedecode(const char *str, char *enp);
+
+
+/* Split a string of MIME into headers and the body.
+   `ptr' specifies the pointer to the region of MIME data.
+   `size' specifies the size of the region.
+   `headers' specifies a map object to store headers.  If it is `NULL', it is not used.  Each key
+   of the map is an uncapitalized header name.
+   `sp' specifies the pointer to the variable into which the size of the region of the return
+   value is assigned.
+   The return value is the pointer to the region of the body data.
+   If the content type is defined, the header map has the key "TYPE" specifying the type.  If the
+   character encoding is defined, the key "CHARSET" indicates the encoding name.  If the boundary
+   string of multipart is defined, the key "BOUNDARY" indicates the string.  If the content
+   disposition is defined, the key "DISPOSITION" indicates the direction.  If the file name is
+   defined, the key "FILENAME" indicates the name.  If the attribute name is defined, the key
+   "NAME" indicates the name.  Because the region of the return value is allocated with the
+   `malloc' call, it should be released with the `free' call when it is no longer in use. */
+char *tcmimebreak(const char *ptr, int size, TCMAP *headers, int *sp);
+
+
+/* Split multipart data of MIME into its parts.
+   `ptr' specifies the pointer to the region of multipart data of MIME.
+   `size' specifies the size of the region.
+   `boundary' specifies the boundary string.
+   The return value is a list object.  Each element of the list is the data of a part.
+   Because the object of the return value is created with the function `tclistnew', it should be
+   deleted with the function `tclistdel' when it is no longer in use. */
+TCLIST *tcmimeparts(const char *ptr, int size, const char *boundary);
+
+
+/* Encode a serial object with hexadecimal encoding.
+   `ptr' specifies the pointer to the region.
+   `size' specifies the size of the region.
+   The return value is the result string.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call if when is no longer in use. */
+char *tchexencode(const char *ptr, int size);
+
+
+/* Decode a string encoded with hexadecimal encoding.
+   `str' specifies the encoded string.
+   `sp' specifies the pointer to a variable into which the size of the region of the return
+   value is assigned.
+   The return value is the pointer to the region of the result.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when
+   it is no longer in use. */
+char *tchexdecode(const char *str, int *sp);
+
+
+/* Compress a serial object with Packbits encoding.
+   `ptr' specifies the pointer to the region.
+   `size' specifies the size of the region.
+   `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 result object, else, it is `NULL'.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+char *tcpackencode(const char *ptr, int size, int *sp);
+
+
+/* Decompress a serial object compressed with Packbits encoding.
+   `ptr' specifies the pointer to the region.
+   `size' specifies the size of the region.
+   `sp' specifies the pointer to a variable into which the size of the region of the return
+   value is assigned.
+   If successful, the return value is the pointer to the result object, else, it is `NULL'.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when it
+   is no longer in use. */
+char *tcpackdecode(const char *ptr, int size, int *sp);
+
+
+/* Compress a serial object with TCBS encoding.
+   `ptr' specifies the pointer to the region.
+   `size' specifies the size of the region.
+   `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 result object, else, it is `NULL'.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+char *tcbsencode(const char *ptr, int size, int *sp);
+
+
+/* Decompress a serial object compressed with TCBS encoding.
+   `ptr' specifies the pointer to the region.
+   `size' specifies the size of the region.
+   `sp' specifies the pointer to a variable into which the size of the region of the return
+   value is assigned.
+   If successful, the return value is the pointer to the result object, else, it is `NULL'.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when it
+   is no longer in use. */
+char *tcbsdecode(const char *ptr, int size, int *sp);
+
+
+/* Compress a serial object with Deflate encoding.
+   `ptr' specifies the pointer to the region.
+   `size' specifies the size of the region.
+   `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 result object, else, it is `NULL'.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+char *tcdeflate(const char *ptr, int size, int *sp);
+
+
+/* Decompress a serial object compressed with Deflate encoding.
+   `ptr' specifies the pointer to the region.
+   `size' specifies the size of the region.
+   `sp' specifies the pointer to a variable into which the size of the region of the return
+   value is assigned.
+   If successful, the return value is the pointer to the result object, else, it is `NULL'.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when it
+   is no longer in use. */
+char *tcinflate(const char *ptr, int size, int *sp);
+
+
+/* Compress a serial object with GZIP encoding.
+   `ptr' specifies the pointer to the region.
+   `size' specifies the size of the region.
+   `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 result object, else, it is `NULL'.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+char *tcgzipencode(const char *ptr, int size, int *sp);
+
+
+/* Decompress a serial object compressed with GZIP encoding.
+   `ptr' specifies the pointer to the region.
+   `size' specifies the size of the region.
+   `sp' specifies the pointer to a variable into which the size of the region of the return
+   value is assigned.
+   If successful, the return value is the pointer to the result object, else, it is `NULL'.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when it
+   is no longer in use. */
+char *tcgzipdecode(const char *ptr, int size, int *sp);
+
+
+/* Get the CRC32 checksum of a serial object.
+   `ptr' specifies the pointer to the region.
+   `size' specifies the size of the region.
+   The return value is the CRC32 checksum of the object. */
+unsigned int tcgetcrc(const char *ptr, int size);
+
+
+/* Compress a serial object with BZIP2 encoding.
+   `ptr' specifies the pointer to the region.
+   `size' specifies the size of the region.
+   `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 result object, else, it is `NULL'.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+char *tcbzipencode(const char *ptr, int size, int *sp);
+
+
+/* Decompress a serial object compressed with BZIP2 encoding.
+   `ptr' specifies the pointer to the region.
+   `size' specifies the size of the region.
+   `sp' specifies the pointer to a variable into which the size of the region of the return
+   value is assigned.
+   If successful, the return value is the pointer to the result object, else, it is `NULL'.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when it
+   is no longer in use. */
+char *tcbzipdecode(const char *ptr, int size, int *sp);
+
+
+/* Encode an array of nonnegative integers with BER encoding.
+   `ary' specifies the pointer to the array of nonnegative integers.
+   `anum' specifies the size of the array.
+   `sp' specifies the pointer to a variable into which the size of the region of the return
+   value is assigned.
+   The return value is the pointer to the region of the result.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call if when is no longer in use. */
+char *tcberencode(const unsigned int *ary, int anum, int *sp);
+
+
+/* Decode a serial object encoded with BER encoding.
+   `ptr' specifies the pointer to the region.
+   `size' specifies the size of the region.
+   `np' specifies the pointer to a variable into which the number of elements of the return value
+   is assigned.
+   The return value is the pointer to the array of the result.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call if when is no longer in use. */
+unsigned int *tcberdecode(const char *ptr, int size, int *np);
+
+
+/* Escape meta characters in a string with the entity references of XML.
+   `str' specifies the string.
+   The return value is the pointer to the escaped string.
+   This function escapes only `&', `<', `>', and `"'.  Because the region of the return value
+   is allocated with the `malloc' call, it should be released with the `free' call when it is no
+   longer in use. */
+char *tcxmlescape(const char *str);
+
+
+/* Unescape entity references in a string of XML.
+   `str' specifies the string.
+   The return value is the unescaped string.
+   This function restores only `&amp;', `&lt;', `&gt;', and `&quot;'.  Because the region of the
+   return value is allocated with the `malloc' call, it should be released with the `free' call
+   when it is no longer in use. */
+char *tcxmlunescape(const char *str);
+
+
+
+/*************************************************************************************************
+ * encoding utilities (for experts)
+ *************************************************************************************************/
+
+
+/* Encode a map object into a string in the x-www-form-urlencoded format.
+   `params' specifies a map object of parameters.
+   The return value is the result string.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+char *tcwwwformencode(const TCMAP *params);
+
+
+/* Decode a query string in the x-www-form-urlencoded format.
+   `str' specifies the query string.
+   `params' specifies a map object into which the result parameters are stored. */
+void tcwwwformdecode(const char *str, TCMAP *params);
+
+
+/* Decode a data region in the x-www-form-urlencoded or multipart-form-data format.
+   `ptr' specifies the pointer to the data region.
+   `size' specifies the size of the data region.
+   `type' specifies the value of the content-type header.  If it is `NULL', the type is specified
+   as x-www-form-urlencoded.
+   `params' specifies a map object into which the result parameters are stored. */
+void tcwwwformdecode2(const void *ptr, int size, const char *type, TCMAP *params);
+
+
+/* Split an XML string into tags and text sections.
+   `str' specifies the string.
+   The return value is the list object whose elements are strings of tags or text sections.
+   Because the object of the return value is created with the function `tclistnew', it should
+   be deleted with the function `tclistdel' when it is no longer in use.  Because this function
+   does not check validation, it can handle also HTML and SGML. */
+TCLIST *tcxmlbreak(const char *str);
+
+
+/* Get the map of attributes of an XML tag.
+   `str' specifies the pointer to the region of a tag string.
+   The return value is the map object containing attribute names and their values which are
+   unescaped.  You can get the name of the tag with the key of an empty string.
+   Because the object of the return value is created with the function `tcmapnew', it should
+   be deleted with the function `tcmapdel' when it is no longer in use. */
+TCMAP *tcxmlattrs(const char *str);
+
+
+/* Escape meta characters in a string with backslash escaping of the C language.
+   `str' specifies the string.
+   The return value is the pointer to the escaped string.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call if when is no longer in use. */
+char *tccstrescape(const char *str);
+
+
+/* Unescape a string escaped by backslash escaping of the C language.
+   `str' specifies the string.
+   The return value is the unescaped string.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call if when is no longer in use. */
+char *tccstrunescape(const char *str);
+
+
+/* Escape meta characters in a string with backslash escaping of JSON.
+   `str' specifies the string.
+   The return value is the pointer to the escaped string.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call if when is no longer in use. */
+char *tcjsonescape(const char *str);
+
+
+/* Unescape a string escaped by backslash escaping of JSON.
+   `str' specifies the string.
+   The return value is the unescaped string.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call if when is no longer in use. */
+char *tcjsonunescape(const char *str);
+
+
+
+/*************************************************************************************************
+ * template serializer
+ *************************************************************************************************/
+
+
+typedef struct {                         /* type of structure for a template */
+  TCLIST *elems;                         /* elements separated by the separators */
+  char *begsep;                          /* beginning separator */
+  char *endsep;                          /* ending separator */
+  TCMAP *conf;                           /* configuration variables */
+} TCTMPL;
+
+
+/* Create a template object.
+   The return value is the new template object. */
+TCTMPL *tctmplnew(void);
+
+
+/* Delete a template object.
+   `tmpl' specifies the template object. */
+void tctmpldel(TCTMPL *tmpl);
+
+
+/* Set the separator strings of a template object.
+   `tmpl' specifies the template object.
+   `begsep' specifies the beginning separator string.  By default, it is "[%".
+   `endsep' specifies the ending separator string.  By default, it is "%]". */
+void tctmplsetsep(TCTMPL *tmpl, const char *begsep, const char *endsep);
+
+
+/* Load a template string into a template object.
+   `tmpl' specifies the template object.
+   `str' specifies the template string.  Directives between "[%" and "%]" can be included in the
+   template string.  If the variable name is specified in the directive, it is expanded as the
+   value of the variable.  "." is used in order to access a record of a hash variable.  For
+   example, "[% foo.bar.baz %]" is expanded as the value of the record whose key is "baz" in the
+   hash variable of the record whose key is "bar" in the hash variable whose name is "foo".
+   Moreover, control flow directives are also supported.  "[% IF ... %]", "[% ELSE %]", and
+   "[% END %]" are conditional directives.  "[% FOREACH ... %]" and "[% END %]" are iterator
+   directives for a list object.  "[% SET ... %]" is a session variable setting directive.
+   "[% CONF ... %]" is a configuration directive.  If the ending separator of a directive is
+   leaded by "\", the next linefeed character is ignored.  Variable expansion directive needs the
+   parameter for the variable name.  The optional parameter "DEF" trailed by a string specifies
+   the default value.  The optional parameter "ENC" trailed by a string specifies the encoding
+   format.  "URL" for the URL escape encoding, "XML" for the XML escape encoding, "CSTR" for
+   C-string escape encoding, and "JSON" for JSON escape encoding are supported.  The conditional
+   directive needs the parameter for the variable name.  If the variable exists, the block to the
+   correspondent ending directive is evaluated, else, the block is ignored.  The optional
+   parameter "EQ" trailed by a string specifies the string full matching test.  The optional
+   parameter "INC" trailed by a string specifies the string including matching test.  The
+   optional parameter "PRT" indicates the printable test.  The optional parameter "RX" trailed by
+   a string specifies the regular expression matching test.  The optional parameter "NOT" inverts
+   the logical determination.  The iterator directive needs the parameter for the variable name
+   of a list object.  The block to the correspondent ending directive is evaluated for each
+   element of the list.  The optional parameter specifies the local variable name of each
+   element.  The session variable setting directive needs the parameters for the variable name
+   and its value.  The configuration directive needs the parameters for the variable name and
+   its value. */
+void tctmplload(TCTMPL *tmpl, const char *str);
+
+
+/* Load a template string from a file into a template object.
+   `tmpl' specifies the template object.
+   `path' specifies the input file.
+   If successful, the return value is true, else, it is false. */
+bool tctmplload2(TCTMPL *tmpl, const char *path);
+
+
+/* Serialize the template string of a template object.
+   `tmpl' specifies the template object.
+   `vars' specifies the variables to be applied into the template.
+   The return value is the dumped template string.
+   Because the region of the return value is allocated with the `malloc' call, it should be
+   released with the `free' call when it is no longer in use. */
+char *tctmpldump(TCTMPL *tmpl, const TCMAP *vars);
+
+
+/* Get the value of a configuration variable of a template object.
+   `tmpl' specifies the template object.
+   `name' specifies the name of the configuration variable.
+   The return value is the string value of the configuration variable or `NULL' if it is not
+   defined. */
+const char *tctmplconf(TCTMPL *tmpl, const char *name);
+
+
+/* Store a list object into a list object with the type information.
+   `list' specifies the container list object.
+   `obj' specifies the list object to be stored. */
+void tclistpushlist(TCLIST *list, const TCLIST *obj);
+
+
+/* Store a map object into a list object with the type information.
+   `list' specifies the container list object.
+   `obj' specifies the map object to be stored. */
+void tclistpushmap(TCLIST *list, const TCMAP *obj);
+
+
+/* Store a list object into a map object with the type information.
+   `map' specifies the container map object.
+   `kstr' specifies the string of the key.
+   `obj' specifies the list object to be stored. */
+void tcmapputlist(TCMAP *map, const char *kstr, const TCLIST *obj);
+
+
+/* Store a map object into a map object with the type information.
+   `map' specifies the container map object.
+   `kstr' specifies the string of the key.
+   `obj' specifies the map object to be stored. */
+void tcmapputmap(TCMAP *map, const char *kstr, const TCMAP *obj);
+
+
+
+/*************************************************************************************************
+ * pointer list
+ *************************************************************************************************/
+
+
+typedef struct {                         /* type of structure for a pointer list */
+  void **array;                          /* array of pointers */
+  int anum;                              /* number of the elements of the array */
+  int start;                             /* start index of used elements */
+  int num;                               /* number of used elements */
+} TCPTRLIST;
+
+
+/* Create a pointer list object.
+   The return value is the new pointer list object. */
+TCPTRLIST *tcptrlistnew(void);
+
+
+/* Create a pointer list object with expecting the number of elements.
+   `anum' specifies the number of elements expected to be stored in the list.
+   The return value is the new pointer list object. */
+TCPTRLIST *tcptrlistnew2(int anum);
+
+
+/* Copy a pointer list object.
+   `ptrlist' specifies the pointer list object.
+   The return value is the new pointer list object equivalent to the specified object. */
+TCPTRLIST *tcptrlistdup(const TCPTRLIST *ptrlist);
+
+
+/* Delete a pointer list object.
+   `ptrlist' specifies the pointer list object.
+   Note that the deleted object and its derivatives can not be used anymore. */
+void tcptrlistdel(TCPTRLIST *ptrlist);
+
+
+/* Get the number of elements of a pointer list object.
+   `ptrlist' specifies the pointer list object.
+   The return value is the number of elements of the list. */
+int tcptrlistnum(const TCPTRLIST *ptrlist);
+
+
+/* Get the pointer to the region of an element of a pointer list object.
+   `ptrlist' specifies the pointer list object.
+   `index' specifies the index of the element.
+   The return value is the pointer to the region of the value.
+   If `index' is equal to or more than the number of elements, the return value is `NULL'. */
+void *tcptrlistval(const TCPTRLIST *ptrlist, int index);
+
+
+/* Add an element at the end of a pointer list object.
+   `ptrlist' specifies the pointer list object.
+   `ptr' specifies the pointer to the region of the new element. */
+void tcptrlistpush(TCPTRLIST *ptrlist, void *ptr);
+
+
+/* Remove an element of the end of a pointer list object.
+   `ptrlist' specifies the pointer list object.
+   The return value is the pointer to the region of the removed element.
+   If the list is empty, the return value is `NULL'. */
+void *tcptrlistpop(TCPTRLIST *ptrlist);
+
+
+/* Add an element at the top of a pointer list object.
+   `ptrlist' specifies the pointer list object.
+   `ptr' specifies the pointer to the region of the new element. */
+void tcptrlistunshift(TCPTRLIST *ptrlist, void *ptr);
+
+
+/* Remove an element of the top of a pointer list object.
+   `ptrlist' specifies the pointer list object.
+   The return value is the pointer to the region of the removed element.
+   If the list is empty, the return value is `NULL'. */
+void *tcptrlistshift(TCPTRLIST *ptrlist);
+
+
+/* Add an element at the specified location of a pointer list object.
+   `ptrlist' specifies the pointer list object.
+   `index' specifies the index of the new element.
+   `ptr' specifies the pointer to the region of the new element.
+   If `index' is equal to or more than the number of elements, this function has no effect. */
+void tcptrlistinsert(TCPTRLIST *ptrlist, int index, void *ptr);
+
+
+/* Remove an element at the specified location of a pointer list object.
+   `ptrlist' specifies the pointer list object.
+   `index' specifies the index of the element to be removed.
+   The return value is the pointer to the region of the removed element.
+   If `index' is equal to or more than the number of elements, no element is removed and the
+   return value is `NULL'. */
+void *tcptrlistremove(TCPTRLIST *ptrlist, int index);
+
+
+/* Overwrite an element at the specified location of a pointer list object.
+   `ptrlist' specifies the pointer list object.
+   `index' specifies the index of the element to be overwritten.
+   `ptr' specifies the pointer to the region of the new content.
+   If `index' is equal to or more than the number of elements, this function has no effect. */
+void tcptrlistover(TCPTRLIST *ptrlist, int index, void *ptr);
+
+
+/* Clear a pointer list object.
+   `ptrlist' specifies the pointer list object.
+   All elements are removed. */
+void tcptrlistclear(TCPTRLIST *ptrlist);
+
+
+
+/*************************************************************************************************
+ * bit operation utilities
+ *************************************************************************************************/
+
+
+typedef struct {                         /* type of structure for a bit stream object */
+  uint8_t *sp;                           /* start pointer */
+  uint8_t *cp;                           /* current pointer */
+  int idx;                               /* bit index */
+  int size;                              /* size of used region */
+} TCBITSTRM;
+
+typedef unsigned char TCBITMAP;          /* type of a bit map object */
+
+
+/* Create a bitmap object. */
+#define TCBITMAPNEW(TC_num) \
+  tccalloc(((TC_num) >> 3) + 1, 1);
+
+
+/* Delete a bitmap object */
+#define TCBITMAPDEL(TC_bitmap) \
+  do { \
+    tcfree((TC_bitmap)); \
+  } while(false);
+
+
+/* Turn on a field of a bitmap object. */
+#define TCBITMAPON(TC_bitmap, TC_idx) \
+  do { \
+    (TC_bitmap)[(TC_idx)>>3] |= 0x1 << ((TC_idx) & 0x7); \
+  } while(false);
+
+
+/* Turn off a field of a bitmap object. */
+#define TCBITMAPOFF(TC_bitmap, TC_idx) \
+  do { \
+    (TC_bitmap)[(TC_idx)>>3] &= ~(0x1 << ((TC_idx) & 0x7)); \
+  } while(false);
+
+
+/* Check a field of a bitmap object. */
+#define TCBITMAPCHECK(TC_bitmap, TC_idx) \
+  ((TC_bitmap)[(TC_idx)>>3] & 0x1 << ((TC_idx) & 0x7))
+
+
+/* Initialize a bit stream object as writer. */
+#define TCBITSTRMINITW(TC_bitstrm, TC_ptr) \
+  do { \
+    (TC_bitstrm).sp = (uint8_t *)(TC_ptr); \
+    (TC_bitstrm).cp = (TC_bitstrm).sp; \
+    *(TC_bitstrm).cp = 0; \
+    (TC_bitstrm).idx = 3; \
+    (TC_bitstrm).size = 1; \
+  } while(false);
+
+
+/* Concatenate a bit to a bit stream object. */
+#define TCBITSTRMCAT(TC_bitstrm, sign) \
+  do { \
+    if((TC_bitstrm).idx >= 8){ \
+      *(++(TC_bitstrm).cp) = 0; \
+      (TC_bitstrm).idx = 0; \
+      (TC_bitstrm).size++; \
+    } \
+    *(TC_bitstrm).cp |= (sign << (TC_bitstrm).idx); \
+    (TC_bitstrm).idx++; \
+  } while(false);
+
+
+/* Set the end mark to a bit stream object. */
+#define TCBITSTRMSETEND(TC_bitstrm) \
+  do { \
+    if((TC_bitstrm).idx >= 8){ \
+      *(++(TC_bitstrm).cp) = 0; \
+      (TC_bitstrm).idx = 0; \
+      (TC_bitstrm).size++; \
+    } \
+    *(TC_bitstrm).sp |= (TC_bitstrm).idx & 7; \
+  } while(false);
+
+
+/* Get the size of the used region of a bit stream object. */
+#define TCBITSTRMSIZE(TC_bitstrm) \
+  ((TC_bitstrm).size)
+
+
+/* Initialize a bit stream object as reader. */
+#define TCBITSTRMINITR(TC_bitstrm, TC_ptr, TC_size) \
+  do { \
+    (TC_bitstrm).sp = (uint8_t *)(TC_ptr); \
+    (TC_bitstrm).cp = (TC_bitstrm).sp; \
+    (TC_bitstrm).idx = 3; \
+    (TC_bitstrm).size = (TC_size); \
+  } while(false);
+
+
+/* Read a bit from a bit stream object. */
+#define TCBITSTRMREAD(TC_bitstrm, TC_sign) \
+  do { \
+    if((TC_bitstrm).idx >= 8){ \
+      (TC_bitstrm).cp++; \
+      (TC_bitstrm).idx = 0; \
+    } \
+    (TC_sign) = (*((TC_bitstrm).cp) & (1 << (TC_bitstrm).idx)) > 0; \
+    (TC_bitstrm).idx++; \
+  } while(false);
+
+
+/* Get the number of bits of a bit stream object. */
+#define TCBITSTRMNUM(TC_bitstrm) \
+  ((((TC_bitstrm).size - 1) << 3) + (*(TC_bitstrm).sp & 7) - 3)
+
+
+
+/*************************************************************************************************
+ * features for experts
+ *************************************************************************************************/
+
+
+#include <stdio.h>
+
+#define _TC_VERSION    "1.0.0"
+#define _TC_LIBVER     911
+#define _TC_FORMATVER  "1.0"
+
+enum {                                   /* enumeration for error codes */
+  TCESUCCESS,                            /* success */
+  TCETHREAD,                             /* threading error */
+  TCEINVALID,                            /* invalid operation */
+  TCENOFILE,                             /* file not found */
+  TCENOPERM,                             /* no permission */
+  TCEMETA,                               /* invalid meta data */
+  TCERHEAD,                              /* invalid record header */
+  TCEOPEN,                               /* open error */
+  TCECLOSE,                              /* close error */
+  TCETRUNC,                              /* trunc error */
+  TCESYNC,                               /* sync error */
+  TCESTAT,                               /* stat error */
+  TCESEEK,                               /* seek error */
+  TCEREAD,                               /* read error */
+  TCEWRITE,                              /* write error */
+  TCEMMAP,                               /* mmap error */
+  TCELOCK,                               /* lock error */
+  TCEUNLINK,                             /* unlink error */
+  TCERENAME,                             /* rename error */
+  TCEMKDIR,                              /* mkdir error */
+  TCERMDIR,                              /* rmdir error */
+  TCEKEEP,                               /* existing record */
+  TCENOREC,                              /* no record found */
+  TCEMISC = 9999                         /* miscellaneous error */
+};
+
+enum {                                   /* enumeration for database type */
+  TCDBTHASH,                             /* hash table */
+  TCDBTBTREE,                            /* B+ tree */
+  TCDBTFIXED,                            /* fixed-length */
+  TCDBTTABLE                             /* table */
+};
+
+
+/* Get the message string corresponding to an error code.
+   `ecode' specifies the error code.
+   The return value is the message string of the error code. */
+const char *tcerrmsg(int ecode);
+
+
+/* Show error message on the standard error output and exit.
+   `message' specifies an error message.
+   This function does not return. */
+void *tcmyfatal(const char *message);
+
+
+/* Allocate a large nullified region.
+   `size' specifies the size of the region.
+   The return value is the pointer to the allocated nullified region.
+   This function handles failure of memory allocation implicitly.  The region of the return value
+   should be released with the function `tczerounmap' when it is no longer in use. */
+void *tczeromap(uint64_t size);
+
+
+/* Free a large nullfied region.
+   `ptr' specifies the pointer to the region. */
+void tczerounmap(void *ptr);
+
+
+/* Lock the global mutex object.
+   If successful, the return value is true, else, it is false. */
+bool tcglobalmutexlock(void);
+
+
+/* Lock the global mutex object by shared locking.
+   If successful, the return value is true, else, it is false. */
+bool tcglobalmutexlockshared(void);
+
+
+/* Unlock the global mutex object.
+   If successful, the return value is true, else, it is false. */
+bool tcglobalmutexunlock(void);
+
+
+/* Lock the absolute path of a file.
+   `path' specifies the path of the file.
+   If successful, the return value is true, else, it is false. */
+bool tcpathlock(const char *path);
+
+
+/* Unock the absolute path of a file.
+   `path' specifies the path of the file.
+   If successful, the return value is true, else, it is false. */
+bool tcpathunlock(const char *path);
+
+
+/* Convert an integer to the string as binary numbers.
+   `num' specifies the integer.
+   `buf' specifies the pointer to the region into which the result string is written.  The size
+   of the buffer should be equal to or more than 65 bytes.
+   `col' specifies the number of columns.  If it is not more than 0, it depends on the integer.
+   `fc' specifies the filling character.
+   The return value is the length of the result string. */
+int tcnumtostrbin(uint64_t num, char *buf, int col, int fc);
+
+
+/* Compare two keys by lexical order.
+   `aptr' specifies the pointer to the region of one key.
+   `asiz' specifies the size of the region of one key.
+   `bptr' specifies the pointer to the region of the other key.
+   `bsiz' specifies the size of the region of the other key.
+   `op' is ignored.
+   The return value is positive if the former is big, negative if the latter is big, 0 if both
+   are equivalent. */
+int tccmplexical(const char *aptr, int asiz, const char *bptr, int bsiz, void *op);
+
+
+/* Compare two keys as decimal strings of real numbers.
+   `aptr' specifies the pointer to the region of one key.
+   `asiz' specifies the size of the region of one key.
+   `bptr' specifies the pointer to the region of the other key.
+   `bsiz' specifies the size of the region of the other key.
+   `op' is ignored.
+   The return value is positive if the former is big, negative if the latter is big, 0 if both
+   are equivalent. */
+int tccmpdecimal(const char *aptr, int asiz, const char *bptr, int bsiz, void *op);
+
+
+/* Compare two keys as 32-bit integers in the native byte order.
+   `aptr' specifies the pointer to the region of one key.
+   `asiz' specifies the size of the region of one key.
+   `bptr' specifies the pointer to the region of the other key.
+   `bsiz' specifies the size of the region of the other key.
+   `op' is ignored.
+   The return value is positive if the former is big, negative if the latter is big, 0 if both
+   are equivalent. */
+int tccmpint32(const char *aptr, int asiz, const char *bptr, int bsiz, void *op);
+
+
+/* Compare two keys as 64-bit integers in the native byte order.
+   `aptr' specifies the pointer to the region of one key.
+   `asiz' specifies the size of the region of one key.
+   `bptr' specifies the pointer to the region of the other key.
+   `bsiz' specifies the size of the region of the other key.
+   `op' is ignored.
+   The return value is positive if the former is big, negative if the latter is big, 0 if both
+   are equivalent. */
+int tccmpint64(const char *aptr, int asiz, const char *bptr, int bsiz, void *op);
+
+
+/* Encode a serial object with BWT encoding.
+   `ptr' specifies the pointer to the region.
+   `size' specifies the size of the region.
+   `idxp' specifies the pointer to the variable into which the index of the original string in
+   the rotation array is assigned.
+   The return value is the pointer to the result object.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when it
+   is no longer in use. */
+char *tcbwtencode(const char *ptr, int size, int *idxp);
+
+
+/* Decode a serial object encoded with BWT encoding.
+   `ptr' specifies the pointer to the region.
+   `size' specifies the size of the region.
+   `idx' specifies the index of the original string in the rotation array is assigned.
+   The return value is the pointer to the result object.
+   Because an additional zero code is appended at the end of the region of the return value,
+   the return value can be treated as a character string.  Because the region of the return
+   value is allocated with the `malloc' call, it should be released with the `free' call when it
+   is no longer in use. */
+char *tcbwtdecode(const char *ptr, int size, int idx);
+
+
+/* Get the binary logarithm of an integer.
+   `num' specifies an integer.
+   The return value is the binary logarithm. */
+long tclog2l(long num);
+
+
+/* Get the binary logarithm of a real number.
+   `num' specifies a real number.
+   The return value is the binary logarithm. */
+double tclog2d(double num);
+
+
+/* Get the aligned offset of a file offset.
+   `off' specifies the file offset.
+   The return value is the aligned offset. */
+uint64_t tcpagealign(uint64_t off);
+
+
+/* Print debug information with a formatted string as with `printf'. */
+#if __STDC_VERSION__ >= 199901L
+#define TCDPRINTF(...) \
+  do { \
+    fprintf(stderr, "%s:%d:%s: ", __FILE__, __LINE__, __func__); \
+    fprintf(stderr, __VA_ARGS__); \
+    fprintf(stderr, "\n"); \
+  } while(false);
+#else
+#define TCDPRINTF(TC_str) \
+  do { \
+    fprintf(stderr, "%s:%d:%s: %s\n", __FILE__, __LINE__, __func__, TC_str); \
+  } while(false);
+#endif
+
+
+/* Print hexadecimal pattern of a binary region. */
+#define TCPRINTHEX(TC_ptr, TC_size) \
+  do { \
+    for(int TC_i = 0; TC_i < (TC_size); TC_i++){ \
+      if(TC_i > 0) putchar(' '); \
+      printf("%02X", ((unsigned char *)(TC_ptr))[TC_i]); \
+    } \
+    putchar('\n'); \
+  } while(false);
+
+
+/* Print an extensible string object. */
+#define TCPRINTXSTR(TC_xstr) \
+  do { \
+    fwrite(tcxstrptr((TC_xstr)), tcxstrsize((TC_xstr)), 1, stdout); \
+    putchar('\n'); \
+  } while(false);
+
+
+/* Print all elements of a list object. */
+#define TCPRINTLIST(TC_list) \
+  do { \
+    for(int TC_i = 0; TC_i < tclistnum((TC_list)); TC_i++){ \
+      int TC_size; \
+      const char *TC_ptr = tclistval((TC_list), TC_i, &TC_size); \
+      printf("%p\t", (void *)(TC_list)); \
+      fwrite(TC_ptr, TC_size, 1, stdout); \
+      putchar('\n'); \
+    } \
+    putchar('\n'); \
+  } while(false);
+
+
+/* Print all records of a list object. */
+#define TCPRINTMAP(TC_map) \
+  do { \
+    TCLIST *TC_keys = tcmapkeys((TC_map)); \
+    for(int TC_i = 0; TC_i < tclistnum(TC_keys); TC_i++){ \
+      int TC_ksiz; \
+      const char *TC_kbuf = tclistval(TC_keys, TC_i, &TC_ksiz); \
+      int TC_vsiz; \
+      const char *TC_vbuf = tcmapget((TC_map), TC_kbuf, TC_ksiz, &TC_vsiz); \
+      printf("%p\t", (void *)(TC_map)); \
+      fwrite(TC_kbuf, TC_ksiz, 1, stdout); \
+      putchar('\t'); \
+      fwrite(TC_vbuf, TC_vsiz, 1, stdout); \
+      putchar('\n'); \
+    } \
+    putchar('\n'); \
+    tclistdel(TC_keys); \
+  } while(false);
+
+
+/* Alias of `tcmalloc'. */
+#if defined(_MYFASTEST)
+#define TCMALLOC(TC_res, TC_size) \
+  do { \
+    (TC_res) = MYMALLOC(TC_size); \
+  } while(false)
+#else
+#define TCMALLOC(TC_res, TC_size) \
+  do { \
+    if(!((TC_res) = MYMALLOC(TC_size))) tcmyfatal("out of memory"); \
+  } while(false)
+#endif
+
+
+/* Alias of `tccalloc'. */
+#if defined(_MYFASTEST)
+#define TCCALLOC(TC_res, TC_nmemb, TC_size) \
+  do { \
+    (TC_res) = MYCALLOC((TC_nmemb), (TC_size)); \
+  } while(false)
+#else
+#define TCCALLOC(TC_res, TC_nmemb, TC_size) \
+  do { \
+    if(!((TC_res) = MYCALLOC((TC_nmemb), (TC_size)))) tcmyfatal("out of memory"); \
+  } while(false)
+#endif
+
+
+/* Alias of `tcrealloc'. */
+#if defined(_MYFASTEST)
+#define TCREALLOC(TC_res, TC_ptr, TC_size) \
+  do { \
+    (TC_res) = MYREALLOC((TC_ptr), (TC_size)); \
+  } while(false)
+#else
+#define TCREALLOC(TC_res, TC_ptr, TC_size) \
+  do { \
+    if(!((TC_res) = MYREALLOC((TC_ptr), (TC_size)))) tcmyfatal("out of memory"); \
+  } while(false)
+#endif
+
+
+/* Alias of `tcmemdup'. */
+#define TCMEMDUP(TC_res, TC_ptr, TC_size) \
+  do { \
+    TCMALLOC((TC_res), (TC_size) + 1); \
+    memcpy((TC_res), (TC_ptr), (TC_size)); \
+    (TC_res)[TC_size] = '\0'; \
+  } while(false)
+
+
+/* Alias of `tcfree'. */
+#define TCFREE(TC_ptr) \
+  do { \
+    MYFREE(TC_ptr); \
+  } while(false)
+
+
+/* Get the alignment of a variable type. */
+#define TCALIGNOF(TC_a) \
+  ((int)offsetof(struct { int8_t TC_top; TC_a TC_bot; }, TC_bot))
+
+
+/* Get the size of padding bytes for pointer alignment. */
+typedef union { int32_t i; int64_t l; double d; void *p; TCCMP f; } tcgeneric_t;
+#define TCALIGNPAD(TC_hsiz) \
+  (((TC_hsiz | ~-TCALIGNOF(tcgeneric_t)) + 1) - TC_hsiz)
+
+
+/* Alias of `tcxstrcat'. */
+#define TCXSTRCAT(TC_xstr, TC_ptr, TC_size) \
+  do { \
+    int TC_mysize = (TC_size); \
+    int TC_nsize = (TC_xstr)->size + TC_mysize + 1; \
+    if((TC_xstr)->asize < TC_nsize){ \
+      while((TC_xstr)->asize < TC_nsize){ \
+        (TC_xstr)->asize *= 2; \
+        if((TC_xstr)->asize < TC_nsize) (TC_xstr)->asize = TC_nsize; \
+      } \
+      TCREALLOC((TC_xstr)->ptr, (TC_xstr)->ptr, (TC_xstr)->asize); \
+    } \
+    memcpy((TC_xstr)->ptr + (TC_xstr)->size, (TC_ptr), TC_mysize); \
+    (TC_xstr)->size += TC_mysize; \
+    (TC_xstr)->ptr[(TC_xstr)->size] = '\0'; \
+  } while(false)
+
+
+/* Alias of `tcxstrptr'. */
+#define TCXSTRPTR(TC_xstr) \
+  ((TC_xstr)->ptr)
+
+
+/* Alias of `tcxstrsize'. */
+#define TCXSTRSIZE(TC_xstr) \
+  ((TC_xstr)->size)
+
+
+/* Alias of `tclistnum'. */
+#define TCLISTNUM(TC_list) \
+  ((TC_list)->num)
+
+
+/* Alias of `tclistval' but not checking size. */
+#define TCLISTVAL(TC_ptr, TC_list, TC_index, TC_size) \
+  do { \
+    (TC_ptr) = (TC_list)->array[(TC_index)+(TC_list)->start].ptr; \
+    (TC_size) = (TC_list)->array[(TC_index)+(TC_list)->start].size; \
+  } while(false)
+
+
+/* Alias of `tclistval' but not checking size and not using the third parameter. */
+#define TCLISTVALPTR(TC_list, TC_index) \
+  ((void *)((TC_list)->array[(TC_index)+(TC_list)->start].ptr))
+
+
+/* Alias of `tclistval' but not checking size and returning the size of the value. */
+#define TCLISTVALSIZ(TC_list, TC_index) \
+  ((TC_list)->array[(TC_index)+(TC_list)->start].size)
+
+
+/* Alias of `tclistpush'. */
+#define TCLISTPUSH(TC_list, TC_ptr, TC_size) \
+  do { \
+    int TC_mysize = (TC_size); \
+    int TC_index = (TC_list)->start + (TC_list)->num; \
+    if(TC_index >= (TC_list)->anum){ \
+      (TC_list)->anum += (TC_list)->num + 1; \
+      TCREALLOC((TC_list)->array, (TC_list)->array, \
+                (TC_list)->anum * sizeof((TC_list)->array[0])); \
+    } \
+    TCLISTDATUM *array = (TC_list)->array; \
+    TCMALLOC(array[TC_index].ptr, TC_mysize + 1);     \
+    memcpy(array[TC_index].ptr, (TC_ptr), TC_mysize); \
+    array[TC_index].ptr[TC_mysize] = '\0'; \
+    array[TC_index].size = TC_mysize; \
+    (TC_list)->num++; \
+  } while(false)
+
+
+/* Alias of `tclistinsert'. */
+#define TCLISTINSERT(TC_list, TC_index, TC_ptr, TC_size) \
+  do { \
+    int TC_myindex = (TC_index); \
+    TC_myindex += (TC_list)->start; \
+    if((TC_list)->start + (TC_list)->num >= (TC_list)->anum){ \
+      (TC_list)->anum += (TC_list)->num + 1; \
+      TCREALLOC((TC_list)->array, (TC_list)->array, \
+                (TC_list)->anum * sizeof((TC_list)->array[0])); \
+    } \
+    memmove((TC_list)->array + TC_myindex + 1, (TC_list)->array + TC_myindex, \
+            sizeof((TC_list)->array[0]) * ((TC_list)->start + (TC_list)->num - TC_myindex)); \
+    TCMALLOC((TC_list)->array[TC_myindex].ptr, (TC_size) + 1); \
+    memcpy((TC_list)->array[TC_myindex].ptr, (TC_ptr), (TC_size)); \
+    (TC_list)->array[TC_myindex].ptr[(TC_size)] = '\0'; \
+    (TC_list)->array[TC_myindex].size = (TC_size); \
+    (TC_list)->num++; \
+  } while(false)
+
+
+/* Truncate a list object. */
+#define TCLISTTRUNC(TC_list, TC_num) \
+  do { \
+    while((TC_list)->num > (TC_num)){ \
+      TCFREE((TC_list)->array[--(TC_list)->num].ptr); \
+    } \
+  } while(false)
+
+
+/* Alias of `tcmaprnum'. */
+#define TCMAPRNUM(TC_map) \
+  ((TC_map)->rnum)
+
+
+/* Alias of `tcptrlistnum'. */
+#define TCPTRLISTNUM(TC_ptrlist) \
+  ((TC_ptrlist)->num)
+
+
+/* Alias of `tcptrlistval'. */
+#define TCPTRLISTVAL(TC_ptrlist, TC_index) \
+  ((void *)((TC_ptrlist)->array[(TC_index)+(TC_ptrlist)->start]))
+
+
+/* Alias of `tcptrlistpush'. */
+#define TCPTRLISTPUSH(TC_ptrlist, TC_ptr) \
+  do { \
+    int TC_index = (TC_ptrlist)->start + (TC_ptrlist)->num; \
+    if(TC_index >= (TC_ptrlist)->anum){ \
+      (TC_ptrlist)->anum += (TC_ptrlist)->num + 1; \
+      TCREALLOC((TC_ptrlist)->array, (TC_ptrlist)->array, \
+                (TC_ptrlist)->anum * sizeof((TC_ptrlist)->array[0])); \
+    } \
+    (TC_ptrlist)->array[TC_index] = (TC_ptr); \
+    (TC_ptrlist)->num++; \
+  } while(false)
+
+
+/* Alias of `tcptrlistinsert'. */
+#define TCPTRLISTINSERT(TC_ptrlist, TC_index, TC_ptr) \
+  do { \
+    int TC_myindex = (TC_index); \
+    TC_myindex += (TC_ptrlist)->start; \
+    if((TC_ptrlist)->start + (TC_ptrlist)->num >= (TC_ptrlist)->anum){ \
+      (TC_ptrlist)->anum += (TC_ptrlist)->num + 1; \
+      TCREALLOC((TC_ptrlist)->array, (TC_ptrlist)->array, \
+                (TC_ptrlist)->anum * sizeof((TC_ptrlist)->array[0])); \
+    } \
+    memmove((TC_ptrlist)->array + TC_myindex + 1, (TC_ptrlist)->array + TC_myindex, \
+            sizeof((TC_ptrlist)->array[0]) * ((TC_ptrlist)->start + \
+                                              (TC_ptrlist)->num - TC_myindex)); \
+    (TC_ptrlist)->array[TC_myindex] = (TC_ptr); \
+    (TC_ptrlist)->num++; \
+  } while(false)
+
+
+/* Truncate a pointer list object. */
+#define TCPTRLISTTRUNC(TC_ptrlist, TC_num) \
+  do { \
+    (TC_ptrlist)->num = (TC_num); \
+  } while(false)
+
+
+/* tricks for backward compatibility */
+#define BDBCMP            TCCMP
+#define tcbdbrange3       tcbdbfwmkeys2
+#define tcbdbcmplexical   tccmplexical
+#define tcbdbcmpdecimal   tccmpdecimal
+#define tcbdbcmpint32     tccmpint32
+#define tcbdbcmpint64     tccmpint64
+#define tctdbqryprocout   tctdbqrysearchout
+#define tctdbqrysetmax(TC_tdb, TC_max) \
+  tctdbqrysetlimit((TC_tdb), (TC_max), 0)
+
+
+
+__TCUTIL_CLINKAGEEND
+#endif                                   /* duplication check */
+
+
+/* END OF FILE */
diff --git a/tcejdb/testejdb/Makefile b/tcejdb/testejdb/Makefile
new file mode 100644 (file)
index 0000000..671fb9f
--- /dev/null
@@ -0,0 +1,46 @@
+
+CC=gcc
+CPPFLAGS = -I. -I.. -DDEBUG -D_DEBUG -D_GNU_SOURCE=1 -D_REENTRANT -D__EXTENSIONS__
+CFLAGS = -std=c99 -Wall -fPIC -pedantic -fsigned-char -g -O0
+LDFLAGS = -L. -L..
+CMDLDFLAGS =
+LIBS = -lbz2 -lz -lrt -lpthread -lm -lc -lcunit
+RUNENV = LD_LIBRARY_PATH=.:..
+
+TESTS = t1 t2 t3
+
+check : all check-t1 check-t2 check-t3;
+
+check-valgrind :
+       rm -rf *.vlog
+       make RUNCMD="valgrind --tool=memcheck --leak-check=full --log-file=%p.vlog" check
+       grep ERROR *.vlog | grep -v ' 0 errors' ; true
+       grep 'at exit' *.vlog | grep -v ' 0 bytes' ; true
+
+check-t1 :
+       $(RUNENV) $(RUNCMD) ./t1
+
+check-t2 :
+       $(RUNENV) $(RUNCMD) ./t2
+
+check-t3 :
+       $(RUNENV) $(RUNCMD) ./t3
+
+
+all : $(TESTS)
+
+clean :
+       rm -rf *.o $(TESTS) db* *.vlog
+
+t1 : t1.o
+       $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) -ltcejdb $(CMDLDFLAGS) $(LIBS)
+
+t2 : t2.o
+       $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) -ltcejdb $(CMDLDFLAGS) $(LIBS)
+
+t3 : t3.o
+       $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) -ltcejdb $(CMDLDFLAGS) $(LIBS)
+
+
+.PHONY : all clean check run-t1 run-t2 run-t3
+
diff --git a/tcejdb/testejdb/t1.c b/tcejdb/testejdb/t1.c
new file mode 100644 (file)
index 0000000..43c34eb
--- /dev/null
@@ -0,0 +1,294 @@
+/*
+ * File:   ejdbtest1.c
+ * Author: Adamansky Anton <anton@adamansky.com>
+ *
+ * Created on Sep 18, 2012, 10:42:19 PM
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "CUnit/Basic.h"
+#include <assert.h>
+#include "ejdb_private.h"
+
+/*
+ * CUnit Test Suite
+ */
+
+static EJDB* jb;
+
+int init_suite(void) {
+    jb = ejdbnew();
+    if (!ejdbopen(jb, "dbt1", JDBOWRITER | JDBOCREAT | JDBOTRUNC)) {
+        return 1;
+    }
+    return 0;
+}
+
+int clean_suite(void) {
+    ejdbrmcoll(jb, "contacts", true);
+    ejdbclose(jb);
+    ejdbdel(jb);
+    return 0;
+}
+
+void testSaveLoad() {
+    CU_ASSERT_PTR_NOT_NULL_FATAL(jb);
+    bson_oid_t oid;
+    EJCOLL *ccoll = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL(ccoll);
+
+    //Save record
+    bson a1;
+    bson_init(&a1);
+
+    bson_append_string(&a1, "name", "Петров Петр");
+    bson_append_string(&a1, "phone", "333-222-333");
+    bson_append_int(&a1, "age", 33);
+    bson_append_long(&a1, "longage", 0xFFFFFFFFFF01LL);
+    bson_append_double(&a1, "doubleage", 0.333333);
+    bson_finish(&a1);
+    ejdbsavebson(ccoll, &a1, &oid);
+    bson_destroy(&a1);
+
+
+    bson *lbson = ejdbloadbson(ccoll, &oid);
+    CU_ASSERT_PTR_NOT_NULL(lbson);
+    bson_iterator it1;
+    bson_iterator_init(&it1, lbson);
+
+    int btype = bson_iterator_next(&it1);
+    CU_ASSERT(btype == BSON_OID);
+    btype = bson_iterator_next(&it1);
+    CU_ASSERT(btype == BSON_STRING);
+    CU_ASSERT(!strcmp("name", bson_iterator_key(&it1)));
+    CU_ASSERT(!strcmp("Петров Петр", bson_iterator_string(&it1)));
+    btype = bson_iterator_next(&it1);
+    CU_ASSERT(btype == BSON_STRING);
+    CU_ASSERT(!strcmp("phone", bson_iterator_key(&it1)));
+    CU_ASSERT(!strcmp("333-222-333", bson_iterator_string(&it1)));
+    btype = bson_iterator_next(&it1);
+    CU_ASSERT(btype == BSON_INT);
+    CU_ASSERT(!strcmp("age", bson_iterator_key(&it1)));
+    CU_ASSERT(33 == bson_iterator_int(&it1));
+    btype = bson_iterator_next(&it1);
+    CU_ASSERT(btype == BSON_LONG);
+    CU_ASSERT(!strcmp("longage", bson_iterator_key(&it1)));
+    CU_ASSERT(0xFFFFFFFFFF01LL == bson_iterator_long(&it1));
+    btype = bson_iterator_next(&it1);
+    CU_ASSERT(btype == BSON_DOUBLE);
+    CU_ASSERT(!strcmp("doubleage", bson_iterator_key(&it1)));
+    CU_ASSERT_DOUBLE_EQUAL(bson_iterator_double(&it1), 0.3, 0.1);
+    btype = bson_iterator_next(&it1);
+    CU_ASSERT(btype == BSON_EOO);
+    bson_del(lbson);
+}
+
+void testBuildQuery1() {
+    CU_ASSERT_PTR_NOT_NULL_FATAL(jb);
+    /*
+     Query = {
+        "name" : Петров Петр,
+        "age"  : 33,
+        "family" : {
+            "wife" : {
+                "name"  : "Jeniffer",
+                "age"   : {"$gt" : 25},
+                "phone" : "444-111"
+            },
+            "children" : [
+                {
+                    "name" : "Dasha",
+                    "age" : {"$in" : [1, 4, 10]}
+                }
+            ]
+         }
+     */
+    bson q1;
+    bson_init_as_query(&q1);
+    bson_append_string(&q1, "name", "Петров Петр");
+    bson_append_int(&q1, "age", 33);
+
+    bson q1family_wife;
+    bson_init_as_query(&q1family_wife);
+    bson_append_string(&q1family_wife, "name", "Jeniffer");
+    bson_append_start_object(&q1family_wife, "age");
+    bson_append_int(&q1family_wife, "$gt", 25);
+    bson_append_finish_object(&q1family_wife);
+
+    bson_append_string(&q1family_wife, "phone", "444-111");
+    bson_finish(&q1family_wife);
+
+    bson q1family_child;
+    bson_init_as_query(&q1family_child);
+    bson_append_string(&q1family_child, "name", "Dasha");
+
+    //"age" : {"$in" : [1, 4, 10]}
+    bson q1family_child_age_IN;
+    bson_init_as_query(&q1family_child_age_IN);
+    bson_append_start_array(&q1family_child_age_IN, "$in");
+    bson_append_int(&q1family_child_age_IN, "0", 1);
+    bson_append_int(&q1family_child_age_IN, "1", 4);
+    bson_append_int(&q1family_child_age_IN, "2", 10);
+    bson_append_finish_array(&q1family_child_age_IN);
+    bson_finish(&q1family_child_age_IN);
+    bson_append_bson(&q1family_child, "age", &q1family_child_age_IN);
+    bson_finish(&q1family_child);
+
+    bson q1family;
+    bson_init_as_query(&q1family);
+    bson_append_bson(&q1family, "wife", &q1family_wife);
+    bson_append_start_array(&q1family, "children");
+    bson_append_bson(&q1family, "0", &q1family_child);
+    bson_append_finish_array(&q1family);
+    bson_finish(&q1family);
+
+    bson_append_bson(&q1, "family", &q1family);
+    bson_finish(&q1);
+
+    CU_ASSERT_FALSE_FATAL(q1.err);
+    CU_ASSERT_FALSE_FATAL(q1family.err);
+    CU_ASSERT_FALSE_FATAL(q1family_wife.err);
+    CU_ASSERT_FALSE_FATAL(q1family_child.err);
+    CU_ASSERT_FALSE_FATAL(q1family_child_age_IN.err);
+
+    EJQ *ejq = ejdbcreatequery(jb, &q1, NULL, 0, NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(ejq);
+
+    bson_destroy(&q1);
+    bson_destroy(&q1family);
+    bson_destroy(&q1family_wife);
+    bson_destroy(&q1family_child);
+    bson_destroy(&q1family_child_age_IN);
+
+    CU_ASSERT_PTR_NOT_NULL_FATAL(ejq->qobjmap);
+    TCMAP *qmap = ejq->qobjmap;
+
+    CU_ASSERT_EQUAL(qmap->rnum, 7); //
+    int r = qmap->rnum;
+    int sz = 0;
+
+    tcmapiterinit(qmap);
+    const char* key = tcmapiternext2(qmap);
+    --r;
+    CU_ASSERT_STRING_EQUAL(key, "name");
+    const EJQF *qf = tcmapget(qmap, key, strlen(key), &sz);
+    CU_ASSERT_PTR_NOT_NULL(qf);
+    CU_ASSERT_EQUAL(sz, sizeof (*qf));
+    CU_ASSERT_STRING_EQUAL(qf->expr, "Петров Петр");
+    CU_ASSERT_EQUAL(qf->tcop, TDBQCSTREQ);
+
+    key = tcmapiternext2(qmap);
+    --r;
+    CU_ASSERT_STRING_EQUAL(key, "age");
+    qf = tcmapget(qmap, key, strlen(key), &sz);
+    CU_ASSERT_PTR_NOT_NULL(qf);
+    CU_ASSERT_EQUAL(sz, sizeof (*qf));
+    CU_ASSERT_STRING_EQUAL(qf->expr, "33");
+    CU_ASSERT_EQUAL(qf->tcop, TDBQCNUMEQ);
+
+    key = tcmapiternext2(qmap);
+    --r;
+    CU_ASSERT_STRING_EQUAL(key, "family.wife.name");
+    qf = tcmapget(qmap, key, strlen(key), &sz);
+    CU_ASSERT_PTR_NOT_NULL(qf);
+    CU_ASSERT_EQUAL(sz, sizeof (*qf));
+    CU_ASSERT_STRING_EQUAL(qf->expr, "Jeniffer");
+    CU_ASSERT_EQUAL(qf->tcop, TDBQCSTREQ);
+
+    key = tcmapiternext2(qmap);
+    --r;
+    CU_ASSERT_STRING_EQUAL(key, "family.wife.age");
+    qf = tcmapget(qmap, key, strlen(key), &sz);
+    CU_ASSERT_PTR_NOT_NULL(qf);
+    CU_ASSERT_EQUAL(sz, sizeof (*qf));
+    CU_ASSERT_STRING_EQUAL(qf->expr, "25");
+    CU_ASSERT_EQUAL(qf->tcop, TDBQCNUMGT);
+
+
+    key = tcmapiternext2(qmap);
+    --r;
+    CU_ASSERT_STRING_EQUAL(key, "family.wife.phone");
+    qf = tcmapget(qmap, key, strlen(key), &sz);
+    CU_ASSERT_PTR_NOT_NULL(qf);
+    CU_ASSERT_EQUAL(sz, sizeof (*qf));
+    CU_ASSERT_STRING_EQUAL(qf->expr, "444-111");
+    CU_ASSERT_EQUAL(qf->tcop, TDBQCSTREQ);
+
+    key = tcmapiternext2(qmap);
+    --r;
+    CU_ASSERT_STRING_EQUAL(key, "family.children.*0.name");
+    qf = tcmapget(qmap, key, strlen(key), &sz);
+    CU_ASSERT_PTR_NOT_NULL(qf);
+    CU_ASSERT_EQUAL(sz, sizeof (*qf));
+    CU_ASSERT_STRING_EQUAL(qf->expr, "Dasha");
+    CU_ASSERT_EQUAL(qf->tcop, TDBQCSTREQ);
+
+    key = tcmapiternext2(qmap);
+    --r;
+    CU_ASSERT_STRING_EQUAL(key, "family.children.*0.age");
+    qf = tcmapget(qmap, key, strlen(key), &sz);
+    CU_ASSERT_PTR_NOT_NULL(qf);
+    CU_ASSERT_EQUAL(sz, sizeof (*qf));
+    CU_ASSERT_EQUAL(qf->ftype, BSON_ARRAY);
+    TCLIST *al = tclistload(qf->expr, qf->exprsz);
+    char* als = tcstrjoin(al, ',');
+    CU_ASSERT_STRING_EQUAL(als, "1,4,10");
+    TCFREE(als);
+    tclistdel(al);
+    CU_ASSERT_EQUAL(qf->tcop, TDBQCNUMOREQ);
+
+    CU_ASSERT_EQUAL(r, 0);
+
+
+    ejdbquerydel(ejq);
+}
+
+void testDBOptions() {
+    EJCOLLOPTS opts;
+    opts.cachedrecords = 10000;
+    opts.compressed = true;
+    opts.large = true;
+    opts.records = 110000;
+    EJCOLL *coll = ejdbcreatecoll(jb, "optscoll", &opts);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(coll);
+    TCHDB *hdb = coll->tdb->hdb;
+    CU_ASSERT_TRUE(hdb->bnum >= (opts.records * 2 + 1));
+    CU_ASSERT_EQUAL(hdb->rcnum, opts.cachedrecords);
+    CU_ASSERT_TRUE(hdb->opts & HDBTDEFLATE);
+    CU_ASSERT_TRUE(hdb->opts & HDBTLARGE);
+    CU_ASSERT_TRUE(ejdbrmcoll(jb, "optscoll", true));
+}
+
+int main() {
+    setlocale(LC_ALL, "en_US.UTF-8");
+    CU_pSuite pSuite = NULL;
+
+    /* Initialize the CUnit test registry */
+    if (CUE_SUCCESS != CU_initialize_registry())
+        return CU_get_error();
+
+    /* Add a suite to the registry */
+    pSuite = CU_add_suite("t1", init_suite, clean_suite);
+    if (NULL == pSuite) {
+        CU_cleanup_registry();
+        return CU_get_error();
+    }
+
+    /* Add the tests to the suite */
+    if ((NULL == CU_add_test(pSuite, "testSaveLoad", testSaveLoad)) ||
+            (NULL == CU_add_test(pSuite, "testBuildQuery1", testBuildQuery1)) ||
+            (NULL == CU_add_test(pSuite, "testDBOptions", testDBOptions))
+
+            ) {
+        CU_cleanup_registry();
+        return CU_get_error();
+    }
+
+    /* Run all tests using the CUnit Basic interface */
+    CU_basic_set_mode(CU_BRM_VERBOSE);
+    CU_basic_run_tests();
+    int ret = CU_get_error() || CU_get_number_of_failures();
+    CU_cleanup_registry();
+    return ret;
+}
diff --git a/tcejdb/testejdb/t2.c b/tcejdb/testejdb/t2.c
new file mode 100644 (file)
index 0000000..dba4488
--- /dev/null
@@ -0,0 +1,2311 @@
+/*
+ * File:   newcunittest.c
+ * Author: Adamansky Anton <anton@adamansky.com>
+ *
+ * Created on Oct 1, 2012, 3:13:44 PM
+ */
+
+#include "bson.h"
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "CUnit/Basic.h"
+#include <assert.h>
+#include "ejdb_private.h"
+#include <locale.h>
+
+/*
+ * CUnit Test Suite
+ */
+
+static EJDB* jb;
+
+int init_suite(void) {
+    jb = ejdbnew();
+    if (!ejdbopen(jb, "dbt2", JDBOWRITER | JDBOCREAT | JDBOTRUNC)) {
+        return 1;
+    }
+    return 0;
+}
+
+int clean_suite(void) {
+    ejdbrmcoll(jb, "contacts", true);
+    ejdbclose(jb);
+    ejdbdel(jb);
+    return 0;
+}
+
+void testAddData() {
+    CU_ASSERT_PTR_NOT_NULL_FATAL(jb);
+    bson_oid_t oid;
+    EJCOLL *ccoll = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL(ccoll);
+
+    //Record 1
+    bson a1;
+
+    bson_init(&a1);
+    bson_append_string(&a1, "name", "Антонов");
+    bson_append_string(&a1, "phone", "333-222-333");
+    bson_append_int(&a1, "age", 33);
+    bson_append_long(&a1, "longscore", 0xFFFFFFFFFF01LL);
+    bson_append_double(&a1, "dblscore", 0.333333);
+    bson_append_start_object(&a1, "address");
+    bson_append_string(&a1, "city", "Novosibirsk");
+    bson_append_string(&a1, "country", "Russian Federation");
+    bson_append_string(&a1, "zip", "630090");
+    bson_append_string(&a1, "street", "Pirogova");
+    bson_append_int(&a1, "room", 334);
+    bson_append_finish_object(&a1);
+    CU_ASSERT_FALSE_FATAL(a1.err);
+    bson_finish(&a1);
+    ejdbsavebson(ccoll, &a1, &oid);
+    bson_destroy(&a1);
+
+    //Record 2
+    bson_init(&a1);
+    bson_append_string(&a1, "name", "Адаманский");
+    bson_append_string(&a1, "phone", "444-123-333");
+    bson_append_long(&a1, "longscore", 0xFFFFFFFFFF02LL);
+    bson_append_double(&a1, "dblscore", 0.93);
+    bson_append_start_object(&a1, "address");
+    bson_append_string(&a1, "city", "Novosibirsk");
+    bson_append_string(&a1, "country", "Russian Federation");
+    bson_append_string(&a1, "zip", "630090");
+    bson_append_string(&a1, "street", "Pirogova");
+    bson_append_int(&a1, "room", 335);
+    bson_append_finish_object(&a1);
+    bson_append_start_array(&a1, "labels");
+    bson_append_string(&a1, "0", "red");
+    bson_append_string(&a1, "1", "green");
+    bson_append_string(&a1, "2", "with gap, label");
+    bson_append_finish_array(&a1);
+    bson_append_start_array(&a1, "drinks");
+    bson_append_int(&a1, "0", 4);
+    bson_append_long(&a1, "1", 556667);
+    bson_append_double(&a1, "2", 77676.22);
+    bson_append_finish_array(&a1);
+
+    bson_finish(&a1);
+    CU_ASSERT_FALSE_FATAL(a1.err);
+    ejdbsavebson(ccoll, &a1, &oid);
+    bson_destroy(&a1);
+
+    //Record 3
+    bson_init(&a1);
+    bson_append_string(&a1, "name", "Ivanov");
+    bson_append_long(&a1, "longscore", 66);
+    bson_append_double(&a1, "dblscore", 1.0);
+    bson_append_start_object(&a1, "address");
+    bson_append_string(&a1, "city", "Petropavlovsk");
+    bson_append_string(&a1, "country", "Russian Federation");
+    bson_append_string(&a1, "zip", "683042");
+    bson_append_string(&a1, "street", "Dalnaya");
+    bson_append_finish_object(&a1);
+    bson_append_start_array(&a1, "drinks");
+    bson_append_int(&a1, "0", 41);
+    bson_append_long(&a1, "1", 222334);
+    bson_append_double(&a1, "2", 77676.22);
+    bson_append_finish_array(&a1);
+    bson_finish(&a1);
+    CU_ASSERT_FALSE_FATAL(a1.err);
+
+    CU_ASSERT_TRUE(ejdbsavebson(ccoll, &a1, &oid));
+    bson_destroy(&a1);
+}
+
+void testSetIndex1() {
+    EJCOLL *ccoll = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(ccoll);
+    CU_ASSERT_TRUE(ejdbsetindex(ccoll, "ab.c.d", JDIDXSTR));
+    CU_ASSERT_TRUE(ejdbsetindex(ccoll, "ab.c.d", JDIDXSTR | JDIDXNUM));
+    CU_ASSERT_TRUE(ejdbsetindex(ccoll, "ab.c.d", JDIDXDROPALL));
+    CU_ASSERT_TRUE(ejdbsetindex(ccoll, "address.zip", JDIDXSTR));
+    CU_ASSERT_TRUE(ejdbsetindex(ccoll, "name", JDIDXSTR));
+
+    //Insert new record with active index
+    //Record 4
+    bson a1;
+    bson_oid_t oid;
+    bson_init(&a1);
+    bson_append_string(&a1, "name", "John Travolta");
+    bson_append_start_object(&a1, "address");
+    bson_append_string(&a1, "country", "USA");
+    bson_append_string(&a1, "zip", "4499995");
+    bson_append_finish_object(&a1);
+    bson_finish(&a1);
+    CU_ASSERT_FALSE_FATAL(a1.err);
+    CU_ASSERT_TRUE(ejdbsavebson(ccoll, &a1, &oid));
+    bson_destroy(&a1);
+
+
+    //Update record 4 with active index
+    //Record 4
+    bson_init(&a1);
+    bson_append_oid(&a1, "_id", &oid);
+    bson_append_string(&a1, "name", "John Travolta2");
+    bson_append_start_object(&a1, "address");
+    bson_append_string(&a1, "country", "USA");
+    bson_append_string(&a1, "zip", "4499996");
+    bson_append_finish_object(&a1);
+    bson_finish(&a1);
+    CU_ASSERT_FALSE_FATAL(a1.err);
+
+    CU_ASSERT_TRUE(ejdbsavebson(ccoll, &a1, &oid));
+    CU_ASSERT_TRUE(ejdbrmbson(ccoll, &oid));
+    bson_destroy(&a1);
+
+    //Save Travolta again
+    bson_init(&a1);
+    bson_append_oid(&a1, "_id", &oid);
+    bson_append_string(&a1, "name", "John Travolta");
+    bson_append_start_object(&a1, "address");
+    bson_append_string(&a1, "country", "USA");
+    bson_append_string(&a1, "zip", "4499996");
+    bson_append_string(&a1, "street", "Beverly Hills");
+    bson_append_finish_object(&a1);
+    bson_append_start_array(&a1, "labels");
+    bson_append_string(&a1, "0", "yellow");
+    bson_append_string(&a1, "1", "red");
+    bson_append_string(&a1, "2", "black");
+    bson_append_finish_array(&a1);
+    bson_finish(&a1);
+    CU_ASSERT_FALSE_FATAL(a1.err);
+    CU_ASSERT_TRUE(ejdbsavebson(ccoll, &a1, &oid));
+    bson_destroy(&a1);
+}
+
+void testQuery1() {
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_append_string(&bsq1, "address.zip", "630090");
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson bshints;
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "name", 1); //ASC order on name
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+
+    //for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+    //    void *bsdata = TCLISTVALPTR(q1res, i);
+    //    bson_print_raw(stderr, bsdata, 0);
+    //}
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'saddress.zip'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX TCOP: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: TRUE"));
+    CU_ASSERT_PTR_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("Адаманский", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_string("630090", TCLISTVALPTR(q1res, i), "address.zip"));
+        } else if (i == 1) {
+            CU_ASSERT_FALSE(bson_compare_string("Антонов", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_string("630090", TCLISTVALPTR(q1res, i), "address.zip"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    ejdbquerydel(q1);
+    tcxstrdel(log);
+}
+
+void testQuery2() {
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_append_string(&bsq1, "address.zip", "630090");
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson bshints;
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "name", -1); //DESC order on name
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'saddress.zip'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX TCOP: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: TRUE"));
+    CU_ASSERT_PTR_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("Антонов", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_string("630090", TCLISTVALPTR(q1res, i), "address.zip"));
+        } else if (i == 1) {
+            CU_ASSERT_FALSE(bson_compare_string("Адаманский", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_string("630090", TCLISTVALPTR(q1res, i), "address.zip"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    ejdbquerydel(q1);
+    tcxstrdel(log);
+}
+
+void testQuery3() {
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+    CU_ASSERT_TRUE(ejdbsetindex(contacts, "address.zip", JDIDXDROPALL));
+
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_append_string(&bsq1, "address.zip", "630090");
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson bshints;
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "name", 1); //ASC order on name
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'sname'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX TCOP: 20"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: FALSE"));
+    CU_ASSERT_PTR_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("Адаманский", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_string("630090", TCLISTVALPTR(q1res, i), "address.zip"));
+        } else if (i == 1) {
+            CU_ASSERT_FALSE(bson_compare_string("Антонов", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_string("630090", TCLISTVALPTR(q1res, i), "address.zip"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    ejdbquerydel(q1);
+    tcxstrdel(log);
+}
+
+void testQuery4() {
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+    CU_ASSERT_TRUE(ejdbsetindex(contacts, "name", JDIDXDROPALL));
+
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_append_string(&bsq1, "address.zip", "630090");
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson bshints;
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "name", 1); //ASC order on name
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("Адаманский", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_string("630090", TCLISTVALPTR(q1res, i), "address.zip"));
+        } else if (i == 1) {
+            CU_ASSERT_FALSE(bson_compare_string("Антонов", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_string("630090", TCLISTVALPTR(q1res, i), "address.zip"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    ejdbquerydel(q1);
+    tcxstrdel(log);
+}
+
+void testQuery5() {
+
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_append_string(&bsq1, "labels", "red");
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    bson_destroy(&bsq1);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+}
+
+void testQuery6() {
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+    CU_ASSERT_TRUE(ejdbsetindex(contacts, "labels", JDIDXARR));
+
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_append_string(&bsq1, "labels", "red");
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson bshints;
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "name", 1); //ASC order on name
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'alabels'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX TCOP: 5"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "token occurrence: \"red\" 2"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("John Travolta", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_string("4499996", TCLISTVALPTR(q1res, i), "address.zip"));
+        } else if (i == 1) {
+            CU_ASSERT_FALSE(bson_compare_string("Адаманский", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_string("630090", TCLISTVALPTR(q1res, i), "address.zip"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+}
+
+void testQuery7() {
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_append_string(&bsq1, "labels", "with gap, label");
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson bshints;
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "name", 1); //ASC order on name
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'alabels'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX TCOP: 5"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "token occurrence: \"with gap, label\" 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 1"));
+    CU_ASSERT_EQUAL(count, 1);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 1);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("Адаманский", TCLISTVALPTR(q1res, i), "name"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+}
+
+void testQuery8() {
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+
+    //"labels" : {"$in" : ["yellow", "green"]}
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_append_start_object(&bsq1, "labels");
+    bson_append_start_array(&bsq1, "$in");
+    bson_append_string(&bsq1, "0", "green");
+    bson_append_string(&bsq1, "1", "yellow");
+    bson_append_finish_array(&bsq1);
+    bson_append_finish_object(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson bshints;
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "name", 1); //ASC order on name
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'alabels'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX TCOP: 5"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "token occurrence: \"green\" 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "token occurrence: \"yellow\" 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("John Travolta", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_string("yellow", TCLISTVALPTR(q1res, i), "labels.0"));
+        } else if (i == 1) {
+            CU_ASSERT_FALSE(bson_compare_string("Адаманский", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_string("green", TCLISTVALPTR(q1res, i), "labels.1"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+}
+
+void testQuery9() {
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+    CU_ASSERT_TRUE(ejdbsetindex(contacts, "labels", JDIDXDROPALL));
+
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_append_string(&bsq1, "labels", "red");
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson bshints;
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "name", 1); //ASC order on name
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: TRUE"));
+    CU_ASSERT_PTR_NULL(strstr(TCXSTRPTR(log), "MAIN IDX TCOP"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("John Travolta", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_string("4499996", TCLISTVALPTR(q1res, i), "address.zip"));
+        } else if (i == 1) {
+            CU_ASSERT_FALSE(bson_compare_string("Адаманский", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_string("630090", TCLISTVALPTR(q1res, i), "address.zip"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+}
+
+void testQuery10() {
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+    CU_ASSERT_TRUE(ejdbsetindex(contacts, "address.street", JDIDXSTR));
+
+    //"address.street" : {"$in" : ["Pirogova", "Beverly Hills"]}
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_append_start_object(&bsq1, "address.street");
+    bson_append_start_array(&bsq1, "$in");
+    bson_append_string(&bsq1, "0", "Pirogova");
+    bson_append_string(&bsq1, "1", "Beverly Hills");
+    bson_append_finish_array(&bsq1);
+    bson_append_finish_object(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson bshints;
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "name", 1); //ASC order on name
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'saddress.street'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX TCOP: 6"));
+    CU_ASSERT_PTR_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 3"));
+    CU_ASSERT_EQUAL(count, 3);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 3);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("John Travolta", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_string("Beverly Hills", TCLISTVALPTR(q1res, i), "address.street"));
+        } else if (i == 1) {
+            CU_ASSERT_FALSE(bson_compare_string("Адаманский", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_string("Pirogova", TCLISTVALPTR(q1res, i), "address.street"));
+        } else if (i == 2) {
+            CU_ASSERT_FALSE(bson_compare_string("Антонов", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_string("Pirogova", TCLISTVALPTR(q1res, i), "address.street"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+}
+
+void testQuery11() {
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+    CU_ASSERT_TRUE(ejdbsetindex(contacts, "address.street", JDIDXDROPALL));
+
+    //"address.street" : {"$in" : ["Pirogova", "Beverly Hills"]}
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_append_start_object(&bsq1, "address.street");
+    bson_append_start_array(&bsq1, "$in");
+    bson_append_string(&bsq1, "0", "Pirogova");
+    bson_append_string(&bsq1, "1", "Beverly Hills");
+    bson_append_finish_array(&bsq1);
+    bson_append_finish_object(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson bshints;
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "name", 1); //ASC order on name
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: TRUE"));
+    CU_ASSERT_PTR_NULL(strstr(TCXSTRPTR(log), "MAIN IDX TCOP"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 3"));
+    CU_ASSERT_EQUAL(count, 3);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 3);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("John Travolta", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_string("Beverly Hills", TCLISTVALPTR(q1res, i), "address.street"));
+        } else if (i == 1) {
+            CU_ASSERT_FALSE(bson_compare_string("Адаманский", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_string("Pirogova", TCLISTVALPTR(q1res, i), "address.street"));
+        } else if (i == 2) {
+            CU_ASSERT_FALSE(bson_compare_string("Антонов", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_string("Pirogova", TCLISTVALPTR(q1res, i), "address.street"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+}
+
+void testQuery12() {
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+
+    //"labels" : {"$in" : ["yellow", "green"]}
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_append_start_object(&bsq1, "labels");
+    bson_append_start_array(&bsq1, "$in");
+    bson_append_string(&bsq1, "0", "green");
+    bson_append_string(&bsq1, "1", "yellow");
+    bson_append_finish_array(&bsq1);
+    bson_append_finish_object(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson bshints;
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "name", 1); //ASC order on name
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: TRUE"));
+    CU_ASSERT_PTR_NULL(strstr(TCXSTRPTR(log), "MAIN IDX TCOP"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("John Travolta", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_string("yellow", TCLISTVALPTR(q1res, i), "labels.0"));
+        } else if (i == 1) {
+            CU_ASSERT_FALSE(bson_compare_string("Адаманский", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_string("green", TCLISTVALPTR(q1res, i), "labels.1"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+}
+
+void testQuery13() {
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+
+    //"drinks" : {"$in" : [4, 77676.22]}
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_append_start_object(&bsq1, "drinks");
+    bson_append_start_array(&bsq1, "$in");
+    bson_append_int(&bsq1, "0", 4);
+    bson_append_double(&bsq1, "1", 77676.22);
+    bson_append_finish_array(&bsq1);
+    bson_append_finish_object(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson bshints;
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "name", 1); //ASC order on name
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: TRUE"));
+    CU_ASSERT_PTR_NULL(strstr(TCXSTRPTR(log), "MAIN IDX TCOP"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("Ivanov", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_long(41, TCLISTVALPTR(q1res, i), "drinks.0"));
+            CU_ASSERT_FALSE(bson_compare_long(222334, TCLISTVALPTR(q1res, i), "drinks.1"));
+            CU_ASSERT_FALSE(bson_compare_double(77676.22, TCLISTVALPTR(q1res, i), "drinks.2"));
+        } else if (i == 1) {
+            CU_ASSERT_FALSE(bson_compare_string("Адаманский", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_long(4, TCLISTVALPTR(q1res, i), "drinks.0"));
+            CU_ASSERT_FALSE(bson_compare_long(556667, TCLISTVALPTR(q1res, i), "drinks.1"));
+            CU_ASSERT_FALSE(bson_compare_double(77676.22, TCLISTVALPTR(q1res, i), "drinks.2"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+}
+
+void testQuery14() {
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+    CU_ASSERT_TRUE(ejdbsetindex(contacts, "drinks", JDIDXARR));
+
+    //"drinks" : {"$in" : [4, 77676.22]}
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_append_start_object(&bsq1, "drinks");
+    bson_append_start_array(&bsq1, "$in");
+    bson_append_int(&bsq1, "0", 4);
+    bson_append_double(&bsq1, "1", 77676.22);
+    bson_append_finish_array(&bsq1);
+    bson_append_finish_object(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson bshints;
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "name", 1); //ASC order on name
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'adrinks'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX TCOP: 21"));
+    CU_ASSERT_PTR_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "token occurrence: \"4\" 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "token occurrence: \"77676.220000\" 2"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+}
+
+void testQuery15() {
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+    CU_ASSERT_TRUE(ejdbsetindex(contacts, "dblscore", JDIDXNUM));
+
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_append_double(&bsq1, "dblscore", 0.333333);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'ndblscore'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX TCOP: 8"));
+    CU_ASSERT_PTR_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 1"));
+    CU_ASSERT_EQUAL(count, 1);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 1);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("Антонов", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_double(0.333333, TCLISTVALPTR(q1res, i), "dblscore"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+    bson_destroy(&bsq1);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+}
+
+void testQuery16() {
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+    CU_ASSERT_TRUE(ejdbsetindex(contacts, "dblscore", JDIDXDROPALL));
+
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_append_double(&bsq1, "dblscore", 0.333333);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 1"));
+    CU_ASSERT_EQUAL(count, 1);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 1);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("Антонов", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_double(0.333333, TCLISTVALPTR(q1res, i), "dblscore"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+    bson_destroy(&bsq1);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+}
+
+void testQuery17() {
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+    CU_ASSERT_TRUE(ejdbsetindex(contacts, "dblscore", JDIDXNUM));
+
+    //"dblscore" : {"$bt" : [0.95, 0.3]}
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_append_start_object(&bsq1, "dblscore");
+    bson_append_start_array(&bsq1, "$bt");
+    bson_append_double(&bsq1, "0", 0.95);
+    bson_append_double(&bsq1, "1", 0.333333);
+    bson_append_finish_array(&bsq1);
+    bson_append_finish_object(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+
+    bson bshints;
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "dblscore", -1); //DESC order on name
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'ndblscore'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: FALSE"));
+    CU_ASSERT_PTR_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX TCOP: 13"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("Адаманский", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_double(0.93, TCLISTVALPTR(q1res, i), "dblscore"));
+        } else if (i == 1) {
+            CU_ASSERT_FALSE(bson_compare_string("Антонов", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_double(0.333333, TCLISTVALPTR(q1res, i), "dblscore"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+    //Second query
+    tcxstrclear(log);
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    ejdbquerydel(q1);
+
+    CU_ASSERT_TRUE(ejdbsetindex(contacts, "dblscore", JDIDXDROPALL));
+
+    bson_init_as_query(&bsq1);
+    bson_append_start_object(&bsq1, "dblscore");
+    bson_append_start_array(&bsq1, "$bt");
+    bson_append_double(&bsq1, "0", 0.95);
+    bson_append_double(&bsq1, "1", 0.333333);
+    bson_append_finish_array(&bsq1);
+    bson_append_finish_object(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "dblscore", 1); //ASC order on name
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    count = 0;
+    q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("Антонов", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_double(0.333333, TCLISTVALPTR(q1res, i), "dblscore"));
+        } else if (i == 1) {
+            CU_ASSERT_FALSE(bson_compare_string("Адаманский", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_double(0.93, TCLISTVALPTR(q1res, i), "dblscore"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+}
+
+void testQuery18() {
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+    CU_ASSERT_TRUE(ejdbsetindex(contacts, "name", JDIDXARR));
+
+    //{"name" : {$strand : ["Travolta", "John"]}}
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_append_start_object(&bsq1, "name");
+    bson_append_start_array(&bsq1, "$strand");
+    bson_append_string(&bsq1, "0", "Travolta");
+    bson_append_string(&bsq1, "1", "John");
+    bson_append_finish_array(&bsq1);
+    bson_append_finish_object(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson bshints;
+    bson_init_as_query(&bshints);
+    /*bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "dblscore", 1); //ASC order on name
+    bson_append_finish_object(&bshints);*/
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'aname'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: FALSE"));
+    CU_ASSERT_PTR_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX TCOP: 4"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "token occurrence: \"John\" 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "token occurrence: \"Travolta\" 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 1"));
+    CU_ASSERT_EQUAL(count, 1);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 1);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("John Travolta", TCLISTVALPTR(q1res, i), "name"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+    //Second query
+    tcxstrclear(log);
+    tclistdel(q1res);
+    CU_ASSERT_TRUE(ejdbsetindex(contacts, "name", JDIDXDROPALL));
+
+    count = 0;
+    q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 1"));
+    CU_ASSERT_EQUAL(count, 1);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 1);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("John Travolta", TCLISTVALPTR(q1res, i), "name"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+}
+
+void testQuery19() {
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+    CU_ASSERT_TRUE(ejdbsetindex(contacts, "name", JDIDXARR));
+
+    //{"name" : {$stror : ["Travolta", "Антонов", "John"]}}
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_append_start_object(&bsq1, "name");
+    bson_append_start_array(&bsq1, "$stror");
+    bson_append_string(&bsq1, "0", "Travolta");
+    bson_append_string(&bsq1, "1", "Антонов");
+    bson_append_string(&bsq1, "2", "John");
+    bson_append_finish_array(&bsq1);
+    bson_append_finish_object(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson bshints;
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "name", -1); //DESC order on name
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'aname'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX TCOP: 5"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "token occurrence: \"John\" 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "token occurrence: \"Travolta\" 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "token occurrence: \"Антонов\" 1"));
+    CU_ASSERT_PTR_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("Антонов", TCLISTVALPTR(q1res, i), "name"));
+        } else if (i == 1) {
+            CU_ASSERT_FALSE(bson_compare_string("John Travolta", TCLISTVALPTR(q1res, i), "name"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+    //No-index query
+    tcxstrclear(log);
+    tclistdel(q1res);
+    CU_ASSERT_TRUE(ejdbsetindex(contacts, "name", JDIDXDROPALL));
+
+    count = 0;
+    q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("Антонов", TCLISTVALPTR(q1res, i), "name"));
+        } else if (i == 1) {
+            CU_ASSERT_FALSE(bson_compare_string("John Travolta", TCLISTVALPTR(q1res, i), "name"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+}
+
+void testQuery20() {
+    //dblscore
+    //{'dblscore' : {'$gte' : 0.93}}
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+    CU_ASSERT_TRUE(ejdbsetindex(contacts, "dblscore", JDIDXNUM));
+
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_append_start_object(&bsq1, "dblscore");
+    bson_append_double(&bsq1, "$gte", 0.93);
+    bson_append_finish_object(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson bshints;
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "dblscore", 1); //ASC order on dblscore
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'ndblscore'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX TCOP: 10"));
+    CU_ASSERT_PTR_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("Адаманский", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_double(0.93, TCLISTVALPTR(q1res, i), "dblscore"));
+        } else if (i == 1) {
+            CU_ASSERT_FALSE(bson_compare_string("Ivanov", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_double(1.0, TCLISTVALPTR(q1res, i), "dblscore"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+
+    //GT
+
+    bson_init_as_query(&bsq1);
+    bson_append_start_object(&bsq1, "dblscore");
+    bson_append_double(&bsq1, "$gt", 0.93);
+    bson_append_finish_object(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "dblscore", 1); //ASC order on dblscore
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    count = 0;
+    log = tcxstrnew();
+    q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'ndblscore'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX TCOP: 9"));
+    CU_ASSERT_PTR_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 1"));
+    CU_ASSERT_EQUAL(count, 1);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 1);
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+
+    //NOINDEX
+    CU_ASSERT_TRUE(ejdbsetindex(contacts, "dblscore", JDIDXDROPALL));
+
+    //NOINDEX GTE
+    bson_init_as_query(&bsq1);
+    bson_append_start_object(&bsq1, "dblscore");
+    bson_append_double(&bsq1, "$gte", 0.93);
+    bson_append_finish_object(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "dblscore", -1); //DESC order on dblscore
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    count = 0;
+    log = tcxstrnew();
+    q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 1) {
+            CU_ASSERT_FALSE(bson_compare_string("Адаманский", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_double(0.93, TCLISTVALPTR(q1res, i), "dblscore"));
+        } else if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("Ivanov", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_double(1.0, TCLISTVALPTR(q1res, i), "dblscore"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+}
+
+void testQuery21() {
+    //{'dblscore' : {'lte' : 0.93}}
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+    CU_ASSERT_TRUE(ejdbsetindex(contacts, "dblscore", JDIDXNUM));
+
+    //LTE
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_append_start_object(&bsq1, "dblscore");
+    bson_append_double(&bsq1, "$lte", 0.93);
+    bson_append_finish_object(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson bshints;
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "dblscore", -1); //DESC order on dblscore
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'ndblscore'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX TCOP: 12"));
+    CU_ASSERT_PTR_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("Адаманский", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_double(0.93, TCLISTVALPTR(q1res, i), "dblscore"));
+        } else if (i == 1) {
+            CU_ASSERT_FALSE(bson_compare_string("Антонов", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_double(0.333333, TCLISTVALPTR(q1res, i), "dblscore"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+
+    //LT
+    //{'dblscore' : {'$lt' : 0.93}}
+    bson_init_as_query(&bsq1);
+    bson_append_start_object(&bsq1, "dblscore");
+    bson_append_double(&bsq1, "$lt", 0.93);
+    bson_append_finish_object(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "dblscore", -1); //DESC order on dblscore
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    count = 0;
+    log = tcxstrnew();
+    q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'ndblscore'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX TCOP: 11"));
+    CU_ASSERT_PTR_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 1"));
+    CU_ASSERT_EQUAL(count, 1);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 1);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("Антонов", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_double(0.333333, TCLISTVALPTR(q1res, i), "dblscore"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+
+    CU_ASSERT_TRUE(ejdbsetindex(contacts, "dblscore", JDIDXDROPALL));
+
+    //NOINDEX GTE
+    bson_init_as_query(&bsq1);
+    bson_append_start_object(&bsq1, "dblscore");
+    bson_append_double(&bsq1, "$lte", 0.93);
+    bson_append_finish_object(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "dblscore", -1); //DESC order on dblscore
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    count = 0;
+    log = tcxstrnew();
+    q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("Адаманский", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_double(0.93, TCLISTVALPTR(q1res, i), "dblscore"));
+        } else if (i == 1) {
+            CU_ASSERT_FALSE(bson_compare_string("Антонов", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_double(0.333333, TCLISTVALPTR(q1res, i), "dblscore"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+}
+
+void testQuery22() {
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+    CU_ASSERT_TRUE(ejdbsetindex(contacts, "address.country", JDIDXSTR));
+
+    //{"address.country" : {$begin : "Ru"}}
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_append_start_object(&bsq1, "address.country");
+    bson_append_string(&bsq1, "$begin", "Ru");
+    bson_append_finish_object(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson bshints;
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "dblscore", -1); //DESC order on dblscore
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'saddress.country'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: TRUE"));
+    CU_ASSERT_PTR_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX TCOP: 2"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 3"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: TRUE"));
+    CU_ASSERT_EQUAL(count, 3);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 3);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("Ivanov", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_double(1.0, TCLISTVALPTR(q1res, i), "dblscore"));
+            CU_ASSERT_FALSE(bson_compare_string("Russian Federation", TCLISTVALPTR(q1res, i), "address.country"));
+        } else if (i == 1) {
+            CU_ASSERT_FALSE(bson_compare_string("Адаманский", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_double(0.93, TCLISTVALPTR(q1res, i), "dblscore"));
+            CU_ASSERT_FALSE(bson_compare_string("Russian Federation", TCLISTVALPTR(q1res, i), "address.country"));
+        } else if (i == 2) {
+            CU_ASSERT_FALSE(bson_compare_string("Антонов", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_double(0.333333, TCLISTVALPTR(q1res, i), "dblscore"));
+            CU_ASSERT_FALSE(bson_compare_string("Russian Federation", TCLISTVALPTR(q1res, i), "address.country"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+
+    CU_ASSERT_TRUE(ejdbsetindex(contacts, "address.country", JDIDXDROPALL));
+
+    bson_init_as_query(&bsq1);
+    bson_append_start_object(&bsq1, "address.country");
+    bson_append_string(&bsq1, "$begin", "R");
+    bson_append_finish_object(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "dblscore", -1); //DESC order on dblscore
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    count = 0;
+    log = tcxstrnew();
+    q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 3"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: TRUE"));
+    CU_ASSERT_EQUAL(count, 3);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 3);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("Ivanov", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_double(1.0, TCLISTVALPTR(q1res, i), "dblscore"));
+            CU_ASSERT_FALSE(bson_compare_string("Russian Federation", TCLISTVALPTR(q1res, i), "address.country"));
+        } else if (i == 1) {
+            CU_ASSERT_FALSE(bson_compare_string("Адаманский", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_double(0.93, TCLISTVALPTR(q1res, i), "dblscore"));
+            CU_ASSERT_FALSE(bson_compare_string("Russian Federation", TCLISTVALPTR(q1res, i), "address.country"));
+        } else if (i == 2) {
+            CU_ASSERT_FALSE(bson_compare_string("Антонов", TCLISTVALPTR(q1res, i), "name"));
+            CU_ASSERT_FALSE(bson_compare_double(0.333333, TCLISTVALPTR(q1res, i), "dblscore"));
+            CU_ASSERT_FALSE(bson_compare_string("Russian Federation", TCLISTVALPTR(q1res, i), "address.country"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+}
+
+void testQuery23() {
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_append_regex(&bsq1, "name", "(IvaNov$|АНтоноВ$)", "i");
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson bshints;
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "name", -1); //DESC order on dblscore
+    bson_append_finish_object(&bshints);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: TRUE"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == 0) {
+            CU_ASSERT_FALSE(bson_compare_string("Антонов", TCLISTVALPTR(q1res, i), "name"));
+        } else if (i == 1) {
+            CU_ASSERT_FALSE(bson_compare_string("Ivanov", TCLISTVALPTR(q1res, i), "name"));
+        } else {
+            CU_ASSERT_TRUE(false);
+        }
+    }
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+}
+
+void testQuery24() {
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson bshints;
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "name", -1); //DESC order on dblscore
+    bson_append_finish_object(&bshints);
+    bson_append_long(&bshints, "$skip", 1);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAX: 4294967295"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "SKIP: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 3"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: TRUE"));
+    CU_ASSERT_EQUAL(count, 3);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 3);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == TCLISTNUM(q1res) - 1) {
+            CU_ASSERT_FALSE(bson_compare_string("Ivanov", TCLISTVALPTR(q1res, i), "name"));
+        }
+    }
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+
+    bson_init_as_query(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson_init_as_query(&bshints);
+    bson_append_start_object(&bshints, "$orderby");
+    bson_append_int(&bshints, "name", -1); //DESC order on dblscore
+    bson_append_finish_object(&bshints);
+    bson_append_long(&bshints, "$skip", 1);
+    bson_append_long(&bshints, "$max", 2);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+    q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    count = 0;
+    log = tcxstrnew();
+    q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAX: 2"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "SKIP: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: TRUE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS COUNT: 2"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: TRUE"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        if (i == TCLISTNUM(q1res) - 1) {
+            CU_ASSERT_FALSE(bson_compare_string("John Travolta", TCLISTVALPTR(q1res, i), "name"));
+        }
+    }
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+
+    //No order specified
+    bson_init_as_query(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson_init_as_query(&bshints);
+    bson_append_long(&bshints, "$skip", 1);
+    bson_append_long(&bshints, "$max", 2);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+    q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    count = 0;
+    log = tcxstrnew();
+    q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAX: 2"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "SKIP: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS COUNT: 2"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: FALSE"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+
+    bson_init_as_query(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson_init_as_query(&bshints);
+    bson_append_long(&bshints, "$skip", 4);
+    bson_append_long(&bshints, "$max", 2);
+    bson_finish(&bshints);
+    CU_ASSERT_FALSE_FATAL(bshints.err);
+    q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, &bshints);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    count = 0;
+    log = tcxstrnew();
+    q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAX: 2"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "SKIP: 4"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS COUNT: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: FALSE"));
+    CU_ASSERT_EQUAL(count, 0);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 0);
+
+    bson_destroy(&bsq1);
+    bson_destroy(&bshints);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+}
+
+void testQuery25() { //$or
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    bson obs[2];
+    bson_init_as_query(&obs[0]);
+    bson_append_string(&obs[0], "name", "Ivanov");
+    bson_finish(&obs[0]);
+    CU_ASSERT_FALSE_FATAL(obs[0].err);
+
+    bson_init_as_query(&obs[1]);
+    bson_append_string(&obs[1], "name", "Антонов");
+    bson_finish(&obs[1]);
+    CU_ASSERT_FALSE_FATAL(obs[1].err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, obs, 2, NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAX: 4294967295"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "SKIP: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "$OR QUERIES: 2"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS COUNT: 2"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: FALSE"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        CU_ASSERT_TRUE(
+                !bson_compare_string("Ivanov", TCLISTVALPTR(q1res, i), "name") ||
+                !bson_compare_string("Антонов", TCLISTVALPTR(q1res, i), "name"));
+
+    }
+    bson_destroy(&bsq1);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+
+    for (int i = 0; i < 2; ++i) {
+        bson_destroy(&obs[i]);
+    }
+}
+
+void testQuery26() { //$not $nin
+    EJCOLL *contacts = ejdbcreatecoll(jb, "contacts", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(contacts);
+
+    //{'address.city' : {$not : 'Novosibirsk'}}
+    bson bsq1;
+    bson_init_as_query(&bsq1);
+    bson_append_start_object(&bsq1, "address.city");
+    bson_append_string(&bsq1, "$not", "Novosibirsk");
+    bson_append_finish_object(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    EJQ *q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    uint32_t count = 0;
+    TCXSTR *log = tcxstrnew();
+    TCLIST *q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAX: 4294967295"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "SKIP: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "$OR QUERIES: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS COUNT: 2"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: FALSE"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        CU_ASSERT_TRUE(bson_compare_string("Novosibirsk", TCLISTVALPTR(q1res, i), "address.city"));
+    }
+
+    bson_destroy(&bsq1);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+
+    //Double negation {'address.city' : {$not : {'$not' : 'Novosibirsk'}}}
+    bson_init_as_query(&bsq1);
+    bson_append_start_object(&bsq1, "address.city");
+    bson_append_start_object(&bsq1, "$not");
+    bson_append_string(&bsq1, "$not", "Novosibirsk");
+    bson_append_finish_object(&bsq1);
+    bson_append_finish_object(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    count = 0;
+    log = tcxstrnew();
+    q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAX: 4294967295"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "SKIP: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "$OR QUERIES: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS COUNT: 2"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: FALSE"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        CU_ASSERT_TRUE(!bson_compare_string("Novosibirsk", TCLISTVALPTR(q1res, i), "address.city"));
+    }
+
+    bson_destroy(&bsq1);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+
+    //"name" : {"$nin" : ["John Travolta", "Ivanov"]}
+    bson_init_as_query(&bsq1);
+    bson_append_start_object(&bsq1, "name");
+    bson_append_start_array(&bsq1, "$nin");
+    bson_append_string(&bsq1, "0", "John Travolta");
+    bson_append_string(&bsq1, "1", "Ivanov");
+    bson_append_finish_array(&bsq1);
+    bson_append_finish_object(&bsq1);
+    bson_finish(&bsq1);
+    CU_ASSERT_FALSE_FATAL(bsq1.err);
+
+    q1 = ejdbcreatequery(jb, &bsq1, NULL, 0, NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(q1);
+
+    count = 0;
+    log = tcxstrnew();
+    q1res = ejdbqrysearch(contacts, q1, &count, 0, log);
+    //fprintf(stderr, "%s", TCXSTRPTR(log));
+
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAX: 4294967295"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "SKIP: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "COUNT ONLY: NO"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "MAIN IDX: 'NONE'"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ORDER FIELDS: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "ACTIVE CONDITIONS: 1"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "$OR QUERIES: 0"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FETCH ALL: FALSE"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RUN FULLSCAN"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS SIZE: 2"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "RS COUNT: 2"));
+    CU_ASSERT_PTR_NOT_NULL(strstr(TCXSTRPTR(log), "FINAL SORTING: FALSE"));
+    CU_ASSERT_EQUAL(count, 2);
+    CU_ASSERT_TRUE(TCLISTNUM(q1res) == 2);
+
+    for (int i = 0; i < TCLISTNUM(q1res); ++i) {
+        CU_ASSERT_TRUE(bson_compare_string("John Travolta", TCLISTVALPTR(q1res, i), "name"));
+        CU_ASSERT_TRUE(bson_compare_string("Ivanov", TCLISTVALPTR(q1res, i), "name"));
+    }
+
+    bson_destroy(&bsq1);
+    tclistdel(q1res);
+    tcxstrdel(log);
+    ejdbquerydel(q1);
+}
+
+int main() {
+    setlocale(LC_ALL, "en_US.UTF-8");
+    CU_pSuite pSuite = NULL;
+
+    /* Initialize the CUnit test registry */
+    if (CUE_SUCCESS != CU_initialize_registry())
+        return CU_get_error();
+
+    /* Add a suite to the registry */
+    pSuite = CU_add_suite("t2", init_suite, clean_suite);
+    if (NULL == pSuite) {
+        CU_cleanup_registry();
+        return CU_get_error();
+    }
+
+    /* Add the tests to the suite */
+    if ((NULL == CU_add_test(pSuite, "testAddData", testAddData)) ||
+            (NULL == CU_add_test(pSuite, "testSetIndex1", testSetIndex1)) ||
+            (NULL == CU_add_test(pSuite, "testQuery1", testQuery1)) ||
+            (NULL == CU_add_test(pSuite, "testQuery2", testQuery2)) ||
+            (NULL == CU_add_test(pSuite, "testQuery3", testQuery3)) ||
+            (NULL == CU_add_test(pSuite, "testQuery4", testQuery4)) ||
+            (NULL == CU_add_test(pSuite, "testQuery5", testQuery5)) ||
+            (NULL == CU_add_test(pSuite, "testQuery6", testQuery6)) ||
+            (NULL == CU_add_test(pSuite, "testQuery7", testQuery7)) ||
+            (NULL == CU_add_test(pSuite, "testQuery8", testQuery8)) ||
+            (NULL == CU_add_test(pSuite, "testQuery9", testQuery9)) ||
+            (NULL == CU_add_test(pSuite, "testQuery10", testQuery10)) ||
+            (NULL == CU_add_test(pSuite, "testQuery11", testQuery11)) ||
+            (NULL == CU_add_test(pSuite, "testQuery12", testQuery12)) ||
+            (NULL == CU_add_test(pSuite, "testQuery13", testQuery13)) ||
+            (NULL == CU_add_test(pSuite, "testQuery14", testQuery14)) ||
+            (NULL == CU_add_test(pSuite, "testQuery15", testQuery15)) ||
+            (NULL == CU_add_test(pSuite, "testQuery16", testQuery16)) ||
+            (NULL == CU_add_test(pSuite, "testQuery17", testQuery17)) ||
+            (NULL == CU_add_test(pSuite, "testQuery18", testQuery18)) ||
+            (NULL == CU_add_test(pSuite, "testQuery19", testQuery19)) ||
+            (NULL == CU_add_test(pSuite, "testQuery20", testQuery20)) ||
+            (NULL == CU_add_test(pSuite, "testQuery21", testQuery21)) ||
+            (NULL == CU_add_test(pSuite, "testQuery22", testQuery22)) ||
+            (NULL == CU_add_test(pSuite, "testQuery23", testQuery23)) ||
+            (NULL == CU_add_test(pSuite, "testQuery24", testQuery24)) ||
+            (NULL == CU_add_test(pSuite, "testQuery25", testQuery25)) ||
+            (NULL == CU_add_test(pSuite, "testQuery26", testQuery26))
+            ) {
+        CU_cleanup_registry();
+        return CU_get_error();
+    }
+
+    /* Run all tests using the CUnit Basic interface */
+    CU_basic_set_mode(CU_BRM_VERBOSE);
+    CU_basic_run_tests();
+    int ret = CU_get_error() || CU_get_number_of_failures();
+    CU_cleanup_registry();
+    return ret;
+}
diff --git a/tcejdb/testejdb/t3.c b/tcejdb/testejdb/t3.c
new file mode 100644 (file)
index 0000000..d426add
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ * File:   t3.c
+ * Author: Adamansky Anton <anton@adamansky.com>
+ *
+ * Created on Oct 26, 2012, 12:12:45 PM
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <assert.h>
+#include "ejdb_private.h"
+#include <locale.h>
+
+#include "CUnit/Basic.h"
+
+/*
+ * CUnit Test Suite
+ */
+
+static EJDB* jb;
+const int RS = 100000;
+const int QRS = 100;
+static bson* recs;
+
+int init_suite(void) {
+    assert(QRS < RS);
+    jb = ejdbnew();
+    if (!ejdbopen(jb, "dbt3", JDBOWRITER | JDBOCREAT | JDBOTRUNC)) {
+        return 1;
+    }
+    srandom(tcmstime());
+    recs = malloc(RS * sizeof (bson));
+    if (!recs) {
+        return 1;
+    }
+    for (int i = 0; i < RS; ++i) {
+        bson bs;
+        bson_init(&bs);
+        bson_append_long(&bs, "ts", tcmstime());
+        char str[128];
+        int len = 0;
+        do {
+            len = random() % 128;
+        } while (len <= 0);
+        str[0] = 'A' + (i % 26);
+        for (int j = 1; j < len; ++j) {
+            str[j] = 'a' + random() % 26;
+        }
+        str[len] = '\0';
+        bson_append_string(&bs, "rstring", str);
+        bson_finish(&bs);
+        recs[i] = bs;
+    }
+
+    return 0;
+}
+
+int clean_suite(void) {
+    ejdbclose(jb);
+    ejdbdel(jb);
+    for (int i = 0; i < RS; ++i) {
+        bson_destroy(&recs[i]);
+    }
+    free(recs);
+    return 0;
+}
+
+void testPerf1() {
+    CU_ASSERT_PTR_NOT_NULL_FATAL(jb);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(recs);
+    EJCOLL *coll = ejdbcreatecoll(jb, "pcoll1", NULL);
+    CU_ASSERT_PTR_NOT_NULL_FATAL(coll);
+    unsigned long st = tcmstime();
+    for (int i = 0; i < RS; ++i) {
+        bson_oid_t oid;
+        ejdbsavebson(coll, recs + i, &oid);
+    }
+    ejdbsyncoll(coll);
+    fprintf(stderr, "\ntestPerf1(): SAVE BSONS: %d, TIME %lu ms\n", RS, tcmstime() - st);
+
+    st = tcmstime();
+    uint32_t acount = 0;
+    int i;
+    for (i = 0; i < QRS; ++i) {
+        int idx = random() % QRS;
+        bson *bs = recs + idx;
+        assert(bs);
+        EJQ *q = ejdbcreatequery(jb, bs, NULL, 0, NULL);
+        assert(q);
+        uint32_t count;
+        ejdbqrysearch(coll, q, &count, EJQRYCOUNT, NULL);
+        assert(count);
+        if (count != 1) {
+            fprintf(stderr, "CNT=%u\n", count);
+        }
+        acount += count;
+        ejdbquerydel(q);
+    }
+    CU_ASSERT_TRUE(i <= acount);
+    fprintf(stderr, "testPerf1(): %u QUERIES, TIME: %lu ms, PER QUERY TIME: %lu ms\n",
+            i, tcmstime() - st, (unsigned long) ((tcmstime() - st) / QRS));
+
+    st = tcmstime();
+    CU_ASSERT_TRUE(ejdbsetindex(coll, "rstring", JDIDXSTR));
+    fprintf(stderr, "testPerf1(): SET INDEX 'rstring' TIME: %lu ms\n", tcmstime() - st);
+
+    st = tcmstime();
+    acount = 0;
+    for (i = 0; i < QRS; ++i) {
+        int idx = random() % QRS;
+        bson *bs = recs + idx;
+        assert(bs);
+        EJQ *q = ejdbcreatequery(jb, bs, NULL, 0, NULL);
+        assert(q);
+        uint32_t count;
+        ejdbqrysearch(coll, q, &count, EJQRYCOUNT, NULL);
+        assert(count);
+        acount += count;
+        ejdbquerydel(q);
+    }
+    CU_ASSERT_TRUE(i <= acount);
+    fprintf(stderr, "testPerf1(): %u QUERIES WITH 'rstring' INDEX, TIME: %lu ms, PER QUERY TIME: %lu ms\n",
+            i, tcmstime() - st, (unsigned long) ((tcmstime() - st) / QRS));
+
+    ejdbrmcoll(jb, coll->cname, true);
+}
+
+int main() {
+    setlocale(LC_ALL, "en_US.UTF-8");
+    CU_pSuite pSuite = NULL;
+
+    /* Initialize the CUnit test registry */
+    if (CUE_SUCCESS != CU_initialize_registry())
+        return CU_get_error();
+
+    /* Add a suite to the registry */
+    pSuite = CU_add_suite("t3", init_suite, clean_suite);
+    if (NULL == pSuite) {
+        CU_cleanup_registry();
+        return CU_get_error();
+    }
+
+    /* Add the tests to the suite */
+    if (
+            (NULL == CU_add_test(pSuite, "testPerf1", testPerf1))
+
+            ) {
+        CU_cleanup_registry();
+        return CU_get_error();
+    }
+
+    /* Run all tests using the CUnit Basic interface */
+    CU_basic_set_mode(CU_BRM_VERBOSE);
+    CU_basic_run_tests();
+    int ret = CU_get_error() || CU_get_number_of_failures();
+    CU_cleanup_registry();
+    return ret;
+}
diff --git a/tcejdb/timsort-impl.h b/tcejdb/timsort-impl.h
new file mode 100644 (file)
index 0000000..4500fe7
--- /dev/null
@@ -0,0 +1,798 @@
+/*
+ * Copyright (C) 2011 Patrick O. Perry
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+static void NAME(binarySort) (void *a, size_t hi, size_t start,
+        comparator compare, void *opaque, size_t width);
+static size_t NAME(countRunAndMakeAscending) (void *a, size_t hi,
+        comparator compare,
+        void *opaque,
+        size_t width);
+static void NAME(reverseRange) (void *a, size_t hi, size_t width);
+static int NAME(mergeCollapse) (struct timsort * ts, size_t width);
+static int NAME(mergeForceCollapse) (struct timsort * ts, size_t width);
+static int NAME(mergeAt) (struct timsort * ts, size_t i, size_t width);
+static size_t NAME(gallopLeft) (void *key, void *base, size_t len,
+        size_t hint, comparator compare, void *opaque, size_t width);
+static size_t NAME(gallopRight) (void *key, void *base, size_t len,
+        size_t hint, comparator compare, void *opaque, size_t width);
+static int NAME(mergeLo) (struct timsort * ts, void *base1, size_t len1,
+        void *base2, size_t len2, size_t width);
+static int NAME(mergeHi) (struct timsort * ts, void *base1, size_t len1,
+        void *base2, size_t len2, size_t width);
+
+static int NAME(timsort) (void *a, size_t nel, size_t width,
+        int (*c) (const void *, const void *, void *opaque), void *opaque) {
+    assert(a || !nel || !width);
+    assert(c);
+
+    int err = SUCCESS;
+
+    if (nel < 2 || !width)
+        return err; // Arrays of size 0 and 1 are always sorted
+
+    // If array is small, do a "mini-TimSort" with no merges
+    if (nel < MIN_MERGE) {
+        size_t initRunLen =
+                CALL(countRunAndMakeAscending) (a, nel, c, opaque, width);
+        CALL(binarySort) (a, nel, initRunLen, c, opaque, width);
+        return err;
+    }
+
+    /**
+     * March over the array once, left to right, finding natural runs,
+     * extending short natural runs to minRun elements, and merging runs
+     * to maintain stack invariant.
+     */
+    struct timsort ts;
+    if ((err = timsort_init(&ts, a, nel, c, opaque, width)))
+        return err;
+
+    size_t minRun = minRunLength(nel);
+    do {
+        // Identify next run
+        size_t runLen =
+                CALL(countRunAndMakeAscending) (a, nel, c, opaque, width);
+
+        // If run is short, extend to min(minRun, nel)
+        if (runLen < minRun) {
+            size_t force = nel <= minRun ? nel : minRun;
+            CALL(binarySort) (a, force, runLen, c, opaque, width);
+            runLen = force;
+        }
+        // Push run onto pending-run stack, and maybe merge
+        pushRun(&ts, a, runLen);
+        if ((err = CALL(mergeCollapse) (&ts, width)))
+            goto out;
+
+        // Advance to find next run
+        a = ELEM(a, runLen);
+        nel -= runLen;
+    } while (nel != 0);
+
+    // Merge all remaining runs to complete sort
+    if ((err = CALL(mergeForceCollapse) (&ts, width)))
+        goto out;
+
+    assert(ts.stackSize == 1);
+out:
+    timsort_deinit(&ts);
+    return err;
+}
+
+/**
+ * Sorts the specified portion of the specified array using a binary
+ * insertion sort.  This is the best method for sorting small numbers
+ * of elements.  It requires O(n log n) compares, but O(n^2) data
+ * movement (worst case).
+ *
+ * If the initial part of the specified range is already sorted,
+ * this method can take advantage of it: the method assumes that the
+ * elements from index {@code lo}, inclusive, to {@code start},
+ * exclusive are already sorted.
+ *
+ * @param a the array in which a range is to be sorted
+ * @param hi the index after the last element in the range to be sorted
+ * @param start the index of the first element in the range that is
+ *        not already known to be sorted ({@code lo <= start <= hi})
+ * @param c comparator to used for the sort
+ */
+static void NAME(binarySort) (void *a, size_t hi, size_t start,
+        comparator compare, void *opaque, size_t width) {
+    assert(start <= hi);
+
+    DEFINE_TEMP(pivot);
+
+    if (start == 0)
+        start++;
+
+    char *startp = ELEM(a, start);
+
+    for (; start < hi; start++, startp = INCPTR(startp)) {
+
+        // Set left (and right) to the index where a[start] (pivot) belongs
+        char *leftp = a;
+        size_t right = start;
+
+        /*
+         * Invariants:
+         *   pivot >= all in [0, left).
+         *   pivot <  all in [right, start).
+         */
+        while (0 < right) {
+            size_t mid = right >> 1;
+            void *midp = ELEM(leftp, mid);
+            if (compare(startp, midp, opaque) < 0) {
+                right = mid;
+            } else {
+                leftp = INCPTR(midp);
+                right -= (mid + 1);
+            }
+        }
+        assert(0 == right);
+
+        /*
+         * The invariants still hold: pivot >= all in [lo, left) and
+         * pivot < all in [left, start), so pivot belongs at left.  Note
+         * that if there are elements equal to pivot, left points to the
+         * first slot after them -- that's why this sort is stable.
+         * Slide elements over to make room to make room for pivot.
+         */
+        size_t n = startp - leftp; // The number of bytes to move
+
+        ASSIGN(pivot, startp);
+        memmove(INCPTR(leftp), leftp, n);
+
+        // a[left] = pivot;
+        ASSIGN(leftp, pivot);
+    }
+}
+
+/**
+ * Returns the length of the run beginning at the specified position in
+ * the specified array and reverses the run if it is descending (ensuring
+ * that the run will always be ascending when the method returns).
+ *
+ * A run is the longest ascending sequence with:
+ *
+ *    a[0] <= a[1] <= a[2] <= ...
+ *
+ * or the longest descending sequence with:
+ *
+ *    a[0] >  a[1] >  a[2] >  ...
+ *
+ * For its intended use in a stable mergesort, the strictness of the
+ * definition of "descending" is needed so that the call can safely
+ * reverse a descending sequence without violating stability.
+ *
+ * @param a the array in which a run is to be counted and possibly reversed
+ * @param hi index after the last element that may be contained in the run.
+ *        It is required that {@code 0 < hi}.
+ * @param compare the comparator to used for the sort
+ * @return  the length of the run beginning at the specified position in
+ *          the specified array
+ */
+static size_t NAME(countRunAndMakeAscending) (void *a, size_t hi,
+        comparator compare, void *opaque, size_t width) {
+    assert(0 < hi);
+    size_t runHi = 1;
+    if (runHi == hi)
+        return 1;
+
+    char *cur = INCPTR(a);
+    char *next = INCPTR(cur);
+    runHi++;
+
+    // Find end of run, and reverse range if descending
+    if (compare(cur, a, opaque) < 0) { // Descending
+        while (runHi < hi && compare(next, cur, opaque) < 0) {
+            runHi++;
+            cur = next;
+            next = INCPTR(next);
+        }
+        CALL(reverseRange) (a, runHi, width);
+    } else { // Ascending
+        while (runHi < hi && compare(next, cur, opaque) >= 0) {
+            runHi++;
+            cur = next;
+            next = INCPTR(next);
+        }
+    }
+
+    return runHi;
+}
+
+/**
+ * Reverse the specified range of the specified array.
+ *
+ * @param a the array in which a range is to be reversed
+ * @param hi the index after the last element in the range to be reversed
+ */
+static void NAME(reverseRange) (void *a, size_t hi, size_t width) {
+    assert(hi > 0);
+
+    DEFINE_TEMP(t);
+
+    char *front = a;
+    char *back = ELEM(a, hi - 1);
+
+    while (front < back) {
+        ASSIGN(t, front);
+        ASSIGN(front, back);
+        ASSIGN(back, t);
+        front = INCPTR(front);
+        back = DECPTR(back);
+    }
+}
+
+/**
+ * Examines the stack of runs waiting to be merged and merges adjacent runs
+ * until the stack invariants are reestablished:
+ *
+ *     1. runLen[i - 3] > runLen[i - 2] + runLen[i - 1]
+ *     2. runLen[i - 2] > runLen[i - 1]
+ *
+ * This method is called each time a new run is pushed onto the stack,
+ * so the invariants are guaranteed to hold for i < stackSize upon
+ * entry to the method.
+ */
+static int NAME(mergeCollapse) (struct timsort * ts, size_t width) {
+    int err = SUCCESS;
+
+    while (ts->stackSize > 1) {
+        size_t n = ts->stackSize - 2;
+        if (n > 0
+                && ts->run[n - 1].len <=
+                ts->run[n].len + ts->run[n + 1].len) {
+            if (ts->run[n - 1].len < ts->run[n + 1].len)
+                n--;
+            err = CALL(mergeAt) (ts, n, width);
+            if (err)
+                break;
+        } else if (ts->run[n].len <= ts->run[n + 1].len) {
+            err = CALL(mergeAt) (ts, n, width);
+            if (err)
+                break;
+        } else {
+            break; // Invariant is established
+        }
+    }
+
+    return err;
+}
+
+/**
+ * Merges all runs on the stack until only one remains.  This method is
+ * called once, to complete the sort.
+ */
+static int NAME(mergeForceCollapse) (struct timsort * ts, size_t width) {
+    int err = SUCCESS;
+
+    while (ts->stackSize > 1) {
+        size_t n = ts->stackSize - 2;
+        if (n > 0 && ts->run[n - 1].len < ts->run[n + 1].len)
+            n--;
+        err = CALL(mergeAt) (ts, n, width);
+        if (err)
+            break;
+    }
+
+    return err;
+}
+
+/**
+ * Merges the two runs at stack indices i and i+1.  Run i must be
+ * the penultimate or antepenultimate run on the stack.  In other words,
+ * i must be equal to stackSize-2 or stackSize-3.
+ *
+ * @param i stack index of the first of the two runs to merge
+ */
+static int NAME(mergeAt) (struct timsort * ts, size_t i, size_t width) {
+    assert(ts->stackSize >= 2);
+    assert(i == ts->stackSize - 2 || i == ts->stackSize - 3);
+
+    void *base1 = ts->run[i].base;
+    size_t len1 = ts->run[i].len;
+    void *base2 = ts->run[i + 1].base;
+    size_t len2 = ts->run[i + 1].len;
+    assert(len1 > 0 && len2 > 0);
+    assert(ELEM(base1, len1) == base2);
+
+    /*
+     * Record the length of the combined runs; if i is the 3rd-last
+     * run now, also slide over the last run (which isn't involved
+     * in this merge).  The current run (i+1) goes away in any case.
+     */
+    ts->run[i].len = len1 + len2;
+    if (i == ts->stackSize - 3) {
+        ts->run[i + 1] = ts->run[i + 2];
+    }
+    ts->stackSize--;
+
+    /*
+     * Find where the first element of run2 goes in run1. Prior elements
+     * in run1 can be ignored (because they're already in place).
+     */
+    size_t k = CALL(gallopRight) (base2, base1, len1, 0, ts->c, ts->opaque, width);
+    base1 = ELEM(base1, k);
+    len1 -= k;
+    if (len1 == 0)
+        return SUCCESS;
+
+    /*
+     * Find where the last element of run1 goes in run2. Subsequent elements
+     * in run2 can be ignored (because they're already in place).
+     */
+    len2 =
+            CALL(gallopLeft) (ELEM(base1, len1 - 1), base2, len2, len2 - 1,
+            ts->c, ts->opaque, width);
+    if (len2 == 0)
+        return SUCCESS;
+
+    // Merge remaining runs, using tmp array with min(len1, len2) elements
+    if (len1 <= len2)
+        return CALL(mergeLo) (ts, base1, len1, base2, len2, width);
+    else
+        return CALL(mergeHi) (ts, base1, len1, base2, len2, width);
+}
+
+/**
+ * Locates the position at which to insert the specified key into the
+ * specified sorted range; if the range contains an element equal to key,
+ * returns the index of the leftmost equal element.
+ *
+ * @param key the key whose insertion point to search for
+ * @param base the array in which to search
+ * @param len the length of the range; must be > 0
+ * @param hint the index at which to begin the search, 0 <= hint < n.
+ *     The closer hint is to the result, the faster this method will run.
+ * @param c the comparator used to order the range, and to search
+ * @return the int k,  0 <= k <= n such that a[b + k - 1] < key <= a[b + k],
+ *    pretending that a[b - 1] is minus infinity and a[b + n] is infinity.
+ *    In other words, key belongs at index b + k; or in other words,
+ *    the first k elements of a should precede key, and the last n - k
+ *    should follow it.
+ */
+static size_t NAME(gallopLeft) (void *key, void *base, size_t len,
+        size_t hint, comparator compare,
+        void *opaque,
+        size_t width) {
+    assert(len > 0 && hint < len);
+    char *hintp = ELEM(base, hint);
+    size_t lastOfs = 0;
+    size_t ofs = 1;
+
+    if (compare(key, hintp, opaque) > 0) {
+        // Gallop right until a[hint+lastOfs] < key <= a[hint+ofs]
+        size_t maxOfs = len - hint;
+        while (ofs < maxOfs
+                && compare(key, ELEM(hintp, ofs), opaque) > 0) {
+            lastOfs = ofs;
+            ofs = (ofs << 1) + 1; // eventually this becomes SIZE_MAX
+        }
+        if (ofs > maxOfs)
+            ofs = maxOfs;
+
+        // Make offsets relative to base
+        lastOfs += hint + 1; // POP: we add 1 here so lastOfs stays non-negative
+        ofs += hint;
+    } else { // key <= a[hint]
+        // Gallop left until a[hint-ofs] < key <= a[hint-lastOfs]
+        const size_t maxOfs = hint + 1;
+        while (ofs < maxOfs
+                && compare(key, ELEM(hintp, -ofs), opaque) <= 0) {
+            lastOfs = ofs;
+            ofs = (ofs << 1) + 1; // no need to check for overflow
+        }
+        if (ofs > maxOfs)
+            ofs = maxOfs;
+
+        // Make offsets relative to base
+        size_t tmp = lastOfs;
+        lastOfs = hint + 1 - ofs; // POP: we add 1 here so lastOfs stays non-negative
+        ofs = hint - tmp;
+    }
+    assert(lastOfs <= ofs && ofs <= len);
+
+    /*
+     * Now a[lastOfs-1] < key <= a[ofs], so key belongs somewhere
+     * to the right of lastOfs but no farther right than ofs.  Do a binary
+     * search, with invariant a[lastOfs - 1] < key <= a[ofs].
+     */
+    // lastOfs++; POP: we added 1 above to keep lastOfs non-negative
+    while (lastOfs < ofs) {
+        //size_t m = lastOfs + ((ofs - lastOfs) >> 1);
+        // http://stackoverflow.com/questions/4844165/safe-integer-middle-value-formula
+        size_t m = (lastOfs & ofs) + ((lastOfs ^ ofs) >> 1);
+
+        if (compare(key, ELEM(base, m), opaque) > 0)
+            lastOfs = m + 1; // a[m] < key
+        else
+            ofs = m; // key <= a[m]
+    }
+    assert(lastOfs == ofs); // so a[ofs - 1] < key <= a[ofs]
+    return ofs;
+}
+
+/**
+ * Like gallopLeft, except that if the range contains an element equal to
+ * key, gallopRight returns the index after the rightmost equal element.
+ *
+ * @param key the key whose insertion point to search for
+ * @param base the array in which to search
+ * @param len the length of the range; must be > 0
+ * @param hint the index at which to begin the search, 0 <= hint < n.
+ *     The closer hint is to the result, the faster this method will run.
+ * @param c the comparator used to order the range, and to search
+ * @return the int k,  0 <= k <= n such that a[b + k - 1] <= key < a[b + k]
+ */
+static size_t NAME(gallopRight) (void *key, void *base, size_t len,
+        size_t hint, comparator compare,
+        void *opaque,
+        size_t width) {
+    assert(len > 0 && hint < len);
+
+    char *hintp = ELEM(base, hint);
+    size_t ofs = 1;
+    size_t lastOfs = 0;
+
+    if (compare(key, hintp, opaque) < 0) {
+        // Gallop left until a[hint - ofs] <= key < a[hint - lastOfs]
+        size_t maxOfs = hint + 1;
+        while (ofs < maxOfs
+                && compare(key, ELEM(hintp, -ofs), opaque) < 0) {
+            lastOfs = ofs;
+            ofs = (ofs << 1) + 1; // no need to check for overflow
+        }
+        if (ofs > maxOfs)
+            ofs = maxOfs;
+
+        // Make offsets relative to base
+        size_t tmp = lastOfs;
+        lastOfs = hint + 1 - ofs;
+        ofs = hint - tmp;
+    } else { // a[hint] <= key
+        // Gallop right until a[hint + lastOfs] <= key < a[hint + ofs]
+        size_t maxOfs = len - hint;
+        while (ofs < maxOfs
+                && compare(key, ELEM(hintp, ofs), opaque) >= 0) {
+            lastOfs = ofs;
+            ofs = (ofs << 1) + 1; // no need to check for overflow
+        }
+        if (ofs > maxOfs)
+            ofs = maxOfs;
+
+        // Make offsets relative to base
+        lastOfs += hint + 1;
+        ofs += hint;
+    }
+    assert(lastOfs <= ofs && ofs <= len);
+
+    /*
+     * Now a[lastOfs - 1] <= key < a[ofs], so key belongs somewhere to
+     * the right of lastOfs but no farther right than ofs.  Do a binary
+     * search, with invariant a[lastOfs - 1] <= key < a[ofs].
+     */
+    while (lastOfs < ofs) {
+        // size_t m = lastOfs + ((ofs - lastOfs) >> 1);
+        size_t m = (lastOfs & ofs) + ((lastOfs ^ ofs) >> 1);
+
+        if (compare(key, ELEM(base, m), opaque) < 0)
+            ofs = m; // key < a[m]
+        else
+            lastOfs = m + 1; // a[m] <= key
+    }
+    assert(lastOfs == ofs); // so a[ofs - 1] <= key < a[ofs]
+    return ofs;
+}
+
+/**
+ * Merges two adjacent runs in place, in a stable fashion.  The first
+ * element of the first run must be greater than the first element of the
+ * second run (a[base1] > a[base2]), and the last element of the first run
+ * (a[base1 + len1-1]) must be greater than all elements of the second run.
+ *
+ * For performance, this method should be called only when len1 <= len2;
+ * its twin, mergeHi should be called if len1 >= len2.  (Either method
+ * may be called if len1 == len2.)
+ *
+ * @param base1 first element in first run to be merged
+ * @param len1  length of first run to be merged (must be > 0)
+ * @param base2 first element in second run to be merged
+ *        (must be aBase + aLen)
+ * @param len2  length of second run to be merged (must be > 0)
+ */
+static int NAME(mergeLo) (struct timsort * ts, void *base1, size_t len1,
+        void *base2, size_t len2, size_t width) {
+    assert(len1 > 0 && len2 > 0 && ELEM(base1, len1) == base2);
+
+    // Copy first run into temp array
+    void *tmp = ensureCapacity(ts, len1, width);
+    if (!tmp)
+        return FAILURE;
+
+    // System.arraycopy(a, base1, tmp, 0, len1);
+    memcpy(tmp, base1, LEN(len1));
+
+    char *cursor1 = tmp; // Indexes into tmp array
+    char *cursor2 = base2; // Indexes int a
+    char *dest = base1; // Indexes int a
+
+    // Move first element of second run and deal with degenerate cases
+    // a[dest++] = a[cursor2++];
+    ASSIGN(dest, cursor2);
+    dest = INCPTR(dest);
+    cursor2 = INCPTR(cursor2);
+
+    if (--len2 == 0) {
+        memcpy(dest, cursor1, LEN(len1));
+        return SUCCESS;
+    }
+    if (len1 == 1) {
+        memcpy(dest, cursor2, LEN(len2));
+
+        // a[dest + len2] = tmp[cursor1]; // Last elt of run 1 to end of merge
+        ASSIGN(ELEM(dest, len2), cursor1);
+        return SUCCESS;
+    }
+
+    comparator compare = ts->c; // Use local variable for performance
+    size_t minGallop = ts->minGallop; //  "    "       "     "      "
+
+    while (1) {
+        size_t count1 = 0; // Number of times in a row that first run won
+        size_t count2 = 0; // Number of times in a row that second run won
+
+        /*
+         * Do the straightforward thing until (if ever) one run starts
+         * winning consistently.
+         */
+        do {
+            assert(len1 > 1 && len2 > 0);
+            if (compare(cursor2, cursor1, ts->opaque) < 0) {
+                ASSIGN(dest, cursor2);
+                dest = INCPTR(dest);
+                cursor2 = INCPTR(cursor2);
+                count2++;
+                count1 = 0;
+                if (--len2 == 0)
+                    goto outer;
+                if (count2 >= minGallop)
+                    break;
+            } else {
+                ASSIGN(dest, cursor1);
+                dest = INCPTR(dest);
+                cursor1 = INCPTR(cursor1);
+                count1++;
+                count2 = 0;
+                if (--len1 == 1)
+                    goto outer;
+                if (count1 >= minGallop)
+                    break;
+            }
+        } while (1); // (count1 | count2) < minGallop);
+
+        /*
+         * One run is winning so consistently that galloping may be a
+         * huge win. So try that, and continue galloping until (if ever)
+         * neither run appears to be winning consistently anymore.
+         */
+        do {
+            assert(len1 > 1 && len2 > 0);
+            count1 =
+                    CALL(gallopRight) (cursor2, cursor1, len1, 0,
+                    compare, ts->opaque, width);
+            if (count1 != 0) {
+                memcpy(dest, cursor1, LEN(count1));
+                dest = ELEM(dest, count1);
+                cursor1 = ELEM(cursor1, count1);
+                len1 -= count1;
+                if (len1 <= 1) // len1 == 1 || len1 == 0
+                    goto outer;
+            }
+            ASSIGN(dest, cursor2);
+            dest = INCPTR(dest);
+            cursor2 = INCPTR(cursor2);
+            if (--len2 == 0)
+                goto outer;
+
+            count2 =
+                    CALL(gallopLeft) (cursor1, cursor2, len2, 0,
+                    compare, ts->opaque, width);
+            if (count2 != 0) {
+                memcpy(dest, cursor2, LEN(count2));
+                dest = ELEM(dest, count2);
+                cursor2 = ELEM(cursor2, count2);
+                len2 -= count2;
+                if (len2 == 0)
+                    goto outer;
+            }
+            ASSIGN(dest, cursor1);
+            dest = INCPTR(dest);
+            cursor1 = INCPTR(cursor1);
+            if (--len1 == 1)
+                goto outer;
+            if (minGallop > 0)
+                minGallop--;
+        } while (count1 >= MIN_GALLOP || count2 >= MIN_GALLOP);
+        minGallop += 2; // Penalize for leaving gallop mode
+    } // End of "outer" loop
+outer:
+    ts->minGallop = minGallop < 1 ? 1 : minGallop; // Write back to field
+
+    if (len1 == 1) {
+        assert(len2 > 0);
+        memcpy(dest, cursor2, LEN(len2));
+        ASSIGN(ELEM(dest, len2), cursor1); //  Last elt of run 1 to end of merge
+
+    } else if (len1 == 0) {
+        errno = EINVAL; // Comparison method violates its general contract
+        return FAILURE;
+    } else {
+        assert(len2 == 0);
+        assert(len1 > 1);
+        memcpy(dest, cursor1, LEN(len1));
+    }
+    return SUCCESS;
+}
+
+/**
+ * Like mergeLo, except that this method should be called only if
+ * len1 >= len2; mergeLo should be called if len1 <= len2.  (Either method
+ * may be called if len1 == len2.)
+ *
+ * @param base1 first element in first run to be merged
+ * @param len1  length of first run to be merged (must be > 0)
+ * @param base2 first element in second run to be merged
+ *        (must be aBase + aLen)
+ * @param len2  length of second run to be merged (must be > 0)
+ */
+static int NAME(mergeHi) (struct timsort *ts, void *base1, size_t len1,
+        void *base2, size_t len2, size_t width) {
+    assert(len1 > 0 && len2 > 0 && ELEM(base1, len1) == base2);
+
+    // Copy second run into temp array
+    void *tmp = ensureCapacity(ts, len2, width);
+    if (!tmp)
+        return FAILURE;
+
+    memcpy(tmp, base2, LEN(len2));
+
+    char *cursor1 = ELEM(base1, len1 - 1); // Indexes into a
+    char *cursor2 = ELEM(tmp, len2 - 1); // Indexes into tmp array
+    char *dest = ELEM(base2, len2 - 1); // Indexes into a
+
+    // Move last element of first run and deal with degenerate cases
+    // a[dest--] = a[cursor1--];
+    ASSIGN(dest, cursor1);
+    dest = DECPTR(dest);
+    cursor1 = DECPTR(cursor1);
+    if (--len1 == 0) {
+        memcpy(ELEM(dest, -(len2 - 1)), tmp, LEN(len2));
+        return SUCCESS;
+    }
+    if (len2 == 1) {
+        dest = ELEM(dest, -len1);
+        cursor1 = ELEM(cursor1, -len1);
+        memcpy(ELEM(dest, 1), ELEM(cursor1, 1), LEN(len1));
+        // a[dest] = tmp[cursor2];
+        ASSIGN(dest, cursor2);
+        return SUCCESS;
+    }
+
+    comparator compare = ts->c; // Use local variable for performance
+    size_t minGallop = ts->minGallop; //  "    "       "     "      "
+
+    while (1) {
+        size_t count1 = 0; // Number of times in a row that first run won
+        size_t count2 = 0; // Number of times in a row that second run won
+
+        /*
+         * Do the straightforward thing until (if ever) one run
+         * appears to win consistently.
+         */
+        do {
+            assert(len1 > 0 && len2 > 1);
+            if (compare(cursor2, cursor1, ts->opaque) < 0) {
+                ASSIGN(dest, cursor1);
+                dest = DECPTR(dest);
+                cursor1 = DECPTR(cursor1);
+                count1++;
+                count2 = 0;
+                if (--len1 == 0)
+                    goto outer;
+            } else {
+                ASSIGN(dest, cursor2);
+                dest = DECPTR(dest);
+                cursor2 = DECPTR(cursor2);
+                count2++;
+                count1 = 0;
+                if (--len2 == 1)
+                    goto outer;
+            }
+        } while ((count1 | count2) < minGallop);
+
+        /*
+         * One run is winning so consistently that galloping may be a
+         * huge win. So try that, and continue galloping until (if ever)
+         * neither run appears to be winning consistently anymore.
+         */
+        do {
+            assert(len1 > 0 && len2 > 1);
+            count1 =
+                    len1 - CALL(gallopRight) (cursor2, base1,
+                    len1, len1 - 1, compare, ts->opaque,
+                    width);
+            if (count1 != 0) {
+                dest = ELEM(dest, -count1);
+                cursor1 = ELEM(cursor1, -count1);
+                len1 -= count1;
+                memcpy(INCPTR(dest), INCPTR(cursor1),
+                        LEN(count1));
+                if (len1 == 0)
+                    goto outer;
+            }
+            ASSIGN(dest, cursor2);
+            dest = DECPTR(dest);
+            cursor2 = DECPTR(cursor2);
+            if (--len2 == 1)
+                goto outer;
+
+            count2 =
+                    len2 - CALL(gallopLeft) (cursor1, tmp, len2,
+                    len2 - 1, compare,
+                    ts->opaque,
+                    width);
+            if (count2 != 0) {
+                dest = ELEM(dest, -count2);
+                cursor2 = ELEM(cursor2, -count2);
+                len2 -= count2;
+                memcpy(INCPTR(dest),
+                        INCPTR(cursor2), LEN(count2));
+                if (len2 <= 1) // len2 == 1 || len2 == 0
+                    goto outer;
+            }
+            ASSIGN(dest, cursor1);
+            dest = DECPTR(dest);
+            cursor1 = DECPTR(cursor1);
+            if (--len1 == 0)
+                goto outer;
+            if (minGallop > 0)
+                minGallop--;
+        } while (count1 >= MIN_GALLOP || count2 >= MIN_GALLOP);
+        minGallop += 2; // Penalize for leaving gallop mode
+    } // End of "outer" loop
+outer:
+    ts->minGallop = minGallop < 1 ? 1 : minGallop; // Write back to field
+
+    if (len2 == 1) {
+        assert(len1 > 0);
+        dest = ELEM(dest, -len1);
+        cursor1 = ELEM(cursor1, -len1);
+        memcpy(INCPTR(dest), INCPTR(cursor1), LEN(len1));
+        // a[dest] = tmp[cursor2];  // Move first elt of run2 to front of merge
+        ASSIGN(dest, cursor2);
+    } else if (len2 == 0) {
+        errno = EINVAL; // Comparison method violates its general contract
+        return FAILURE;
+    } else {
+        assert(len1 == 0);
+        assert(len2 > 0);
+        memcpy(ELEM(dest, -(len2 - 1)), tmp, LEN(len2));
+    }
+
+    return SUCCESS;
+}
\ No newline at end of file
diff --git a/tcejdb/timsort.c b/tcejdb/timsort.c
new file mode 100644 (file)
index 0000000..5240399
--- /dev/null
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2011 Patrick O. Perry
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>                // assert
+#include <errno.h>                // EINVAL
+#include <stddef.h>                // size_t, NULL
+#include <stdlib.h>                // malloc, free
+#include <string.h>                // memcpy, memmove
+#include "ejdbutl.h"
+
+/**
+ * This is the minimum sized sequence that will be merged.  Shorter
+ * sequences will be lengthened by calling binarySort.  If the entire
+ * array is less than this length, no merges will be performed.
+ *
+ * This constant should be a power of two.  It was 64 in Tim Peter's C
+ * implementation, but 32 was empirically determined to work better in
+ * [Android's Java] implementation.  In the unlikely event that you set
+ * this constant to be a number that's not a power of two, you'll need
+ * to change the {@link #minRunLength} computation.
+ *
+ * If you decrease this constant, you must change the stackLen
+ * computation in the TimSort constructor, or you risk an
+ * ArrayOutOfBounds exception.  See listsort.txt for a discussion
+ * of the minimum stack length required as a function of the length
+ * of the array being sorted and the minimum merge sequence length.
+ */
+#define MIN_MERGE 32
+
+/**
+ * When we get into galloping mode, we stay there until both runs win less
+ * often than MIN_GALLOP consecutive times.
+ */
+#define MIN_GALLOP 7
+
+/**
+ * Maximum initial size of tmp array, which is used for merging.  The array
+ * can grow to accommodate demand.
+ *
+ * Unlike Tim's original C version, we do not allocate this much storage
+ * when sorting smaller arrays.  This change was required for performance.
+ */
+#define INITIAL_TMP_STORAGE_LENGTH 256
+
+/**
+ * Maximum stack size.  This depends on MIN_MERGE and sizeof(size_t).
+ */
+#define MAX_STACK 85
+
+/**
+ * Define MALLOC_STACK if you want to allocate the run stack on the heap.
+ * Otherwise, 2* MAX_STACK * sizeof(size_t) ~ 1.3K gets reserved on the
+ * call stack.
+ */
+/* #undef MALLOC_STACK */
+
+#define DEFINE_TEMP(temp) char temp[WIDTH]
+#define ASSIGN(x, y) memcpy(x, y, WIDTH)
+#define INCPTR(x) ((void *)((char *)(x) + WIDTH))
+#define DECPTR(x) ((void *)((char *)(x) - WIDTH))
+#define ELEM(a,i) ((char *)(a) + (i) * WIDTH)
+#define LEN(n) ((n) * WIDTH)
+
+#define MIN(a,b) ((a) <= (b) ? (a) : (b))
+#define SUCCESS 0
+#define FAILURE (-1)
+
+#define CONCAT(x, y) x ## _ ## y
+#define MAKE_STR(x, y) CONCAT(x,y)
+#define NAME(x) MAKE_STR(x, WIDTH)
+#define CALL(x) NAME(x)
+
+typedef int (*comparator) (const void *x, const void *y, void *opaque);
+
+struct timsort_run {
+    void *base;
+    size_t len;
+};
+
+struct timsort {
+    /**
+     * The array being sorted.
+     */
+    void *a;
+    size_t a_length;
+
+    /**
+     * The comparator for this sort.
+     */
+    int (*c) (const void *x, const void *y, void *opaque);
+
+    void *opaque;
+
+    /**
+     * This controls when we get *into* galloping mode.  It is initialized
+     * to MIN_GALLOP.  The mergeLo and mergeHi methods nudge it higher for
+     * random data, and lower for highly structured data.
+     */
+    size_t minGallop;
+
+    /**
+     * Temp storage for merges.
+     */
+    void *tmp;
+    size_t tmp_length;
+
+    /**
+     * A stack of pending runs yet to be merged.  Run i starts at
+     * address base[i] and extends for len[i] elements.  It's always
+     * true (so long as the indices are in bounds) that:
+     *
+     *     runBase[i] + runLen[i] == runBase[i + 1]
+     *
+     * so we could cut the storage for this, but it's a minor amount,
+     * and keeping all the info explicit simplifies the code.
+     */
+    size_t stackSize; // Number of pending runs on stack
+    size_t stackLen; // maximum stack size
+#ifdef MALLOC_STACK
+    struct timsort_run *run;
+#else
+    struct timsort_run run[MAX_STACK];
+#endif
+};
+
+static int timsort_init(struct timsort *ts, void *a, size_t len,
+        int (*c) (const void *, const void *, void *opaque),
+        void *opaque,
+        size_t width);
+static void timsort_deinit(struct timsort *ts);
+static size_t minRunLength(size_t n);
+static void pushRun(struct timsort *ts, void *runBase, size_t runLen);
+static void *ensureCapacity(struct timsort *ts, size_t minCapacity,
+        size_t width);
+
+/**
+ * Creates a TimSort instance to maintain the state of an ongoing sort.
+ *
+ * @param a the array to be sorted
+ * @param nel the length of the array
+ * @param c the comparator to determine the order of the sort
+ * @param width the element width
+ */
+static int timsort_init(struct timsort *ts, void *a, size_t len,
+        int (*c) (const void *, const void *, void *opaque),
+        void *opaque,
+        size_t width) {
+    assert(ts);
+    assert(a || !len);
+    assert(c);
+
+    ts->minGallop = MIN_GALLOP;
+    ts->stackSize = 0;
+
+    ts->a = a;
+    ts->a_length = len;
+    ts->c = c;
+    ts->opaque = opaque;
+
+    // Allocate temp storage (which may be increased later if necessary)
+    ts->tmp_length = (len < 2 * INITIAL_TMP_STORAGE_LENGTH ?
+            len >> 1 : INITIAL_TMP_STORAGE_LENGTH);
+    ts->tmp = malloc(ts->tmp_length * width);
+
+    /*
+     * Allocate runs-to-be-merged stack (which cannot be expanded).  The
+     * stack length requirements are described in listsort.txt.  The C
+     * version always uses the same stack length (85), but this was
+     * measured to be too expensive when sorting "mid-sized" arrays (e.g.,
+     * 100 elements) in Java.  Therefore, we use smaller (but sufficiently
+     * large) stack lengths for smaller arrays.  The "magic numbers" in the
+     * computation below must be changed if MIN_MERGE is decreased.  See
+     * the MIN_MERGE declaration above for more information.
+     */
+
+    /* POP:
+     * In listsort.txt, Tim argues that the run lengths form a decreasing
+     * sequence, and each run length is greater than the previous two.
+     * Thus, lower bounds on the minimum runLen numbers on the stack are:
+     *
+     *   [      1           = b[1]
+     *   ,      minRun      = b[2]
+     *   ,  1 * minRun +  2 = b[3]
+     *   ,  2 * minRun +  3 = b[4]
+     *   ,  3 * minRun +  6 = b[5]
+     *   , ...
+     *   ],
+     *
+     * Moreover, minRun >= MIN_MERGE / 2.  Also, note that the sum of the
+     * run lenghts is less than or equal to the length of the array.
+     *
+     * Let s be the stack length and n be the array length.  If s >= 2, then n >= b[1] + b[2].
+     * More generally, if s >= m, then n >= b[1] + b[2] + ... + b[m] = B[m].  Conversely, if
+     * n < B[m], then s < m.
+     *
+     * In Haskell, we can compute the bin sizes using the fibonacci numbers
+     *
+     *     fibs = 1:1:(zipWith (+) fibs (tail fibs))
+     *
+     *     cumSums a = case a of { [] -> [] ; (x:xs) -> x:(map (x+) (cumSums xs)) }
+     *
+     *     fibSums = cumSums fibs
+     *
+     *     binSizes minRun = ([ 1, minRun, minRun + 2 ]
+     *                        ++ [ (1 + minRun) * (fibs !! (i+2))
+     *                             + fibSums !! (i+1) - fibs !! i | i <- [0..] ])
+     *
+     *     arraySizes minRun = cumSums (binSizes minRun)
+     *
+     * We these funcitons, we can compute a table with minRun = MIN_MERGE / 2 = 16:
+     *
+     *     m          B[m]
+     *   ---------------------------
+     *      1                    17
+     *      2                    35
+     *      3                    70
+     *      4                   124
+     *      5                   214
+     *      6                   359
+     *     11                  4220
+     *     17                 76210 # > 2^16 - 1
+     *     40            4885703256 # > 2^32 - 1
+     *     86  20061275507500957239 # > 2^64 - 1
+     *
+     * If len < B[m], then stackLen < m:
+     */
+#ifdef MALLOC_STACK
+    ts->stackLen = (len < 359 ? 5
+            : len < 4220 ? 10
+            : len < 76210 ? 16 : len < 4885703256ULL ? 39 : 85);
+
+    /* Note that this is slightly more liberal than in the Java
+     * implementation.  The discrepancy might be because the Java
+     * implementation uses a less accurate lower bound.
+     */
+    //stackLen = (len < 120 ? 5 : len < 1542 ? 10 : len < 119151 ? 19 : 40);
+
+    ts->run = malloc(ts->stackLen * sizeof (ts->run[0]));
+#else
+    ts->stackLen = MAX_STACK;
+#endif
+
+    if (ts->tmp && ts->run) {
+        return SUCCESS;
+    } else {
+        timsort_deinit(ts);
+        return FAILURE;
+    }
+}
+
+static void timsort_deinit(struct timsort *ts) {
+    free(ts->tmp);
+#ifdef MALLOC_STACK
+    free(ts->run);
+#endif
+}
+
+/**
+ * Returns the minimum acceptable run length for an array of the specified
+ * length. Natural runs shorter than this will be extended with
+ * {@link #binarySort}.
+ *
+ * Roughly speaking, the computation is:
+ *
+ *  If n < MIN_MERGE, return n (it's too small to bother with fancy stuff).
+ *  Else if n is an exact power of 2, return MIN_MERGE/2.
+ *  Else return an int k, MIN_MERGE/2 <= k <= MIN_MERGE, such that n/k
+ *   is close to, but strictly less than, an exact power of 2.
+ *
+ * For the rationale, see listsort.txt.
+ *
+ * @param n the length of the array to be sorted
+ * @return the length of the minimum run to be merged
+ */
+static size_t minRunLength(size_t n) {
+    size_t r = 0; // Becomes 1 if any 1 bits are shifted off
+    while (n >= MIN_MERGE) {
+        r |= (n & 1);
+        n >>= 1;
+    }
+    return n + r;
+}
+
+/**
+ * Pushes the specified run onto the pending-run stack.
+ *
+ * @param runBase index of the first element in the run
+ * @param runLen  the number of elements in the run
+ */
+static void pushRun(struct timsort *ts, void *runBase, size_t runLen) {
+    assert(ts->stackSize < ts->stackLen);
+
+    ts->run[ts->stackSize++] = (struct timsort_run){
+        runBase, runLen
+    };
+}
+
+/**
+ * Ensures that the external array tmp has at least the specified
+ * number of elements, increasing its size if necessary.  The size
+ * increases exponentially to ensure amortized linear time complexity.
+ *
+ * @param minCapacity the minimum required capacity of the tmp array
+ * @return tmp, whether or not it grew
+ */
+static void *ensureCapacity(struct timsort *ts, size_t minCapacity,
+        size_t width) {
+    if (ts->tmp_length < minCapacity) {
+        // Compute smallest power of 2 > minCapacity
+        size_t newSize = minCapacity;
+        newSize |= newSize >> 1;
+        newSize |= newSize >> 2;
+        newSize |= newSize >> 4;
+        newSize |= newSize >> 8;
+        newSize |= newSize >> 16;
+        if (sizeof (newSize) > 4)
+            newSize |= newSize >> 32;
+
+        newSize++;
+        newSize = MIN(newSize, ts->a_length >> 1);
+        if (newSize == 0) { // (overflow) Not bloody likely!
+            newSize = minCapacity;
+        }
+
+        free(ts->tmp);
+        ts->tmp_length = newSize;
+        ts->tmp = malloc(ts->tmp_length * width);
+    }
+
+    return ts->tmp;
+}
+
+#define WIDTH 4
+#include "timsort-impl.h"
+#undef WIDTH
+
+#define WIDTH 8
+#include "timsort-impl.h"
+#undef WIDTH
+
+#define WIDTH 16
+#include "timsort-impl.h"
+#undef WIDTH
+
+#define WIDTH width
+#include "timsort-impl.h"
+#undef WIDTH
+
+/**
+ * @param a the array to be sorted
+ * @param nel the length of the array
+ * @param c the comparator to determine the order of the sort
+ * @param width the element width
+ * @param opaque data for the comparator function
+ * @param opaque data for the comparator function
+ */
+int ejdbtimsort(void *a, size_t nel, size_t width,
+        int (*c) (const void*, const void*, void*), void *opaque) {
+    switch (width) {
+        case 4:
+            return timsort_4(a, nel, width, c, opaque);
+        case 8:
+            return timsort_8(a, nel, width, c, opaque);
+        case 16:
+            return timsort_16(a, nel, width, c, opaque);
+        default:
+            return timsort_width(a, nel, width, c, opaque);
+    }
+}
+
+typedef struct {
+    int (*cmp)(const TCLISTDATUM*, const TCLISTDATUM*, void *opaque);
+    void *tcopaque;
+} tclistdata;
+
+static inline int tclistcmp(const void* a, const void* b, void* o) {
+    tclistdata* op = o;
+    assert(op && op->cmp);
+    return op->cmp(a, b, op->tcopaque);
+}
+
+int ejdbtimsortlist(TCLIST *list,
+        int (*compar) (const TCLISTDATUM*, const TCLISTDATUM*, void *opaque), void *opaque) {
+    tclistdata op;
+    op.cmp = compar;
+    op.tcopaque = opaque;
+    return ejdbtimsort(list->array + list->start, list->num, sizeof (TCLISTDATUM), tclistcmp, &op);
+}
diff --git a/tcejdb/tokyocabinet.idl b/tcejdb/tokyocabinet.idl
new file mode 100644 (file)
index 0000000..dc80529
--- /dev/null
@@ -0,0 +1,336 @@
+/*************************************************************************************************
+ * IDL for bindings of scripting languages
+ *                                                               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.
+ *************************************************************************************************/
+
+
+/**
+ * namespace of Tokyo Cabinet
+ */
+module tokyocabinet {
+  //----------------------------------------------------------------
+  // list of strings (substituted for by the native mechanism)
+  //----------------------------------------------------------------
+  interface List {
+    string get(in long index);
+  };
+  //----------------------------------------------------------------
+  // map of strings (substituted for by the native mechanism)
+  //----------------------------------------------------------------
+  interface Map {
+    string get(in string key);
+  };
+  //----------------------------------------------------------------
+  // the error codes
+  //----------------------------------------------------------------
+  interface ECODE {
+    const long ESUCCESS = 0;
+    const long ETHREAD = 1;
+    const long EINVALID = 2;
+    const long ENOFILE = 3;
+    const long ENOPERM = 4;
+    const long EMETA = 5;
+    const long ERHEAD = 6;
+    const long EOPEN = 7;
+    const long ECLOSE = 8;
+    const long ETRUNC = 9;
+    const long ESYNC = 10;
+    const long ESTAT = 11;
+    const long ESEEK = 12;
+    const long EREAD = 13;
+    const long EWRITE = 14;
+    const long EMMAP = 15;
+    const long ELOCK = 16;
+    const long EUNLINK = 17;
+    const long ERENAME = 18;
+    const long EMKDIR = 19;
+    const long ERMDIR = 20;
+    const long EKEEP = 21;
+    const long ENOREC = 22;
+    const long EMISC = 9999;
+    long ecode();
+    string errmsg(in long ecode);
+  };
+  //----------------------------------------------------------------
+  // the hash database API
+  //----------------------------------------------------------------
+  interface HDB :ECODE {
+    const long TLARGE = 1 << 0;
+    const long TDEFLATE = 1 << 1;
+    const long TBZIP = 1 << 2;
+    const long TTCBS = 1 << 3;
+    const long OREADER = 1 << 0;
+    const long OWRITER = 1 << 1;
+    const long OCREAT = 1 << 2;
+    const long OTRUNC = 1 << 3;
+    const long ONOLCK = 1 << 4;
+    const long OLCKNB = 1 << 5;
+    const long OTSYNC = 1 << 6;
+    boolean tune(in long long bnum, in long apow, in long fpow, in long opts);
+    boolean setcache(in long rcnum);
+    boolean setxmsiz(in long long xmsiz);
+    boolean setdfunit(in long dfunit);
+    boolean open(in string path, in long omode);
+    boolean close();
+    boolean put(in string key, in string value);
+    boolean putkeep(in string key, in string value);
+    boolean putcat(in string key, in string value);
+    boolean putasync(in string key, in string value);
+    boolean out(in string key);
+    string get(in string key);
+    long vsiz(in string key);
+    boolean iterinit();
+    string iternext();
+    List fwmkeys(in string prefix, in long max);
+    long addint(in string key, in long num);
+    double adddouble(in string key, in double num);
+    boolean sync();
+    boolean optimize(in long long bnum, in long apow, in long fpow, in long opts);
+    boolean vanish();
+    boolean copy(in string path);
+    boolean tranbegin();
+    boolean trancommit();
+    boolean tranabort();
+    string path();
+    long long rnum();
+    long long fsiz();
+  };
+  //----------------------------------------------------------------
+  // the B+ tree database API
+  //----------------------------------------------------------------
+  interface BDB :ECODE {
+    const long TLARGE = 1 << 0;
+    const long TDEFLATE = 1 << 1;
+    const long TBZIP = 1 << 2;
+    const long TTCBS = 1 << 3;
+    const long OREADER = 1 << 0;
+    const long OWRITER = 1 << 1;
+    const long OCREAT = 1 << 2;
+    const long OTRUNC = 1 << 3;
+    const long ONOLCK = 1 << 4;
+    const long OLCKNB = 1 << 5;
+    const long OTSYNC = 1 << 6;
+    boolean tune(in long lmemb, in long nmemb,
+                 in long long bnum, in long apow, in long fpow, in long opts);
+    boolean setcache(in long lcnum, in long ncnum);
+    boolean setxmsiz(in long long xmsiz);
+    boolean setdfunit(in long dfunit);
+    boolean open(in string path, in long omode);
+    boolean close();
+    boolean put(in string key, in string value);
+    boolean putkeep(in string key, in string value);
+    boolean putcat(in string key, in string value);
+    boolean putdup(in string key, in string value);
+    boolean putlist(in string key, in List values);
+    boolean out(in string key);
+    boolean outlist(in string key);
+    string get(in string key);
+    List getlist(in string key);
+    long vnum(in string key);
+    long vsiz(in string key);
+    List range(in string bkey, in boolean binc, in string ekey, in boolean einc, in long max);
+    List fwmkeys(in string prefix, in long max);
+    long addint(in string key, in long num);
+    double adddouble(in string key, in double num);
+    boolean sync();
+    boolean optimize(in long lmemb, in long nmemb,
+                     in long long bnum, in long apow, in long fpow, in long opts);
+    boolean vanish();
+    boolean copy(in string path);
+    boolean tranbegin();
+    boolean trancommit();
+    boolean tranabort();
+    string path();
+    long long rnum();
+    long long fsiz();
+  };
+  //----------------------------------------------------------------
+  // the B+ tree cursor API
+  //----------------------------------------------------------------
+  interface BDBCUR {
+    const long CPCURRENT = 0;
+    const long CPBEFORE = 1;
+    const long CPAFTER = 2;
+    boolean first();
+    boolean last();
+    boolean jump(in string key);
+    boolean prev();
+    boolean next();
+    boolean put(in string value, in long cpmode);
+    boolean out();
+    string key();
+    string val();
+  };
+  //----------------------------------------------------------------
+  // the fixed-length database API
+  //----------------------------------------------------------------
+  interface FDB :ECODE {
+    const long OREADER = 1 << 0;
+    const long OWRITER = 1 << 1;
+    const long OCREAT = 1 << 2;
+    const long OTRUNC = 1 << 3;
+    const long ONOLCK = 1 << 4;
+    const long OLCKNB = 1 << 5;
+    const long OTSYNC = 1 << 6;
+    boolean tune(in long width, in long long limsiz);
+    boolean open(in string path, in long omode);
+    boolean close();
+    boolean put(in string key, in string value);
+    boolean putkeep(in string key, in string value);
+    boolean putcat(in string key, in string value);
+    boolean out(in string key);
+    string get(in string key);
+    long vsiz(in string key);
+    boolean iterinit();
+    string iternext();
+    List range(in string interval, in long max);
+    long addint(in string key, in long num);
+    double adddouble(in string key, in double num);
+    boolean sync();
+    boolean optimize(in long width, in long long limsiz);
+    boolean vanish();
+    boolean copy(in string path);
+    boolean tranbegin();
+    boolean trancommit();
+    boolean tranabort();
+    string path();
+    long long rnum();
+    long long fsiz();
+  };
+  //----------------------------------------------------------------
+  // the table database API
+  //----------------------------------------------------------------
+  interface TDB :ECODE {
+    const long TLARGE = 1 << 0;
+    const long TDEFLATE = 1 << 1;
+    const long TBZIP = 1 << 2;
+    const long TTCBS = 1 << 3;
+    const long OREADER = 1 << 0;
+    const long OWRITER = 1 << 1;
+    const long OCREAT = 1 << 2;
+    const long OTRUNC = 1 << 3;
+    const long ONOLCK = 1 << 4;
+    const long OLCKNB = 1 << 5;
+    const long OTSYNC = 1 << 6;
+    const long ITLEXICAL = 0;
+    const long ITDECIMAL = 1;
+    const long ITTOKEN = 2;
+    const long ITQGRAM = 3;
+    const long ITOPT = 9998;
+    const long ITVOID = 9999;
+    const long ITKEEP = 1 << 24;
+    boolean tune(in long long bnum, in long apow, in long fpow, in long opts);
+    boolean setcache(in long rcnum, in long lcnum, in long ncnum);
+    boolean setxmsiz(in long long xmsiz);
+    boolean setdfunit(in long dfunit);
+    boolean open(in string path, in long omode);
+    boolean close();
+    boolean put(in string pkey, in Map cols);
+    boolean putkeep(in string pkey, in Map cols);
+    boolean putcat(in string pkey, in Map cols);
+    boolean out(in string pkey);
+    Map get(in string pkey);
+    long vsiz(in string pkey);
+    boolean iterinit();
+    string iternext();
+    List fwmkeys(in string prefix, in long max);
+    long addint(in string pkey, in long num);
+    double adddouble(in string pkey, in double num);
+    boolean sync();
+    boolean optimize(in long long bnum, in long apow, in long fpow, in long opts);
+    boolean vanish();
+    boolean copy(in string path);
+    boolean tranbegin();
+    boolean trancommit();
+    boolean tranabort();
+    string path();
+    long long rnum();
+    long long fsiz();
+    boolean setindex(in string name, in long type);
+    long long genuid();
+  };
+  //----------------------------------------------------------------
+  // the table query API
+  //----------------------------------------------------------------
+  interface TDBQRY {
+    const long QCSTREQ = 0;
+    const long QCSTRINC = 1;
+    const long QCSTRBW = 2;
+    const long QCSTREW = 3;
+    const long QCSTRAND = 4;
+    const long QCSTROR = 5;
+    const long QCSTROREQ = 6;
+    const long QCSTRRX = 7;
+    const long QCNUMEQ = 8;
+    const long QCNUMGT = 9;
+    const long QCNUMGE = 10;
+    const long QCNUMLT = 11;
+    const long QCNUMLE = 12;
+    const long QCNUMBT = 13;
+    const long QCNUMOREQ = 14;
+    const long QCFTSPH = 15;
+    const long QCFTSAND = 16;
+    const long QCFTSOR = 17;
+    const long QCFTSEX = 18;
+    const long QCNEGATE = 1 << 24;
+    const long QCNOIDX = 1 << 25;
+    const long QOSTRASC = 0;
+    const long QOSTRDESC = 1;
+    const long QONUMASC = 2;
+    const long QONUMDESC = 3;
+    const long MSUNION = 0;
+    const long MSISECT = 1;
+    const long MSDIFF = 2;
+    void addcond(in string name, in long op, in string expr);
+    void setorder(in string name, in long type);
+    void setlimit(in long max, in long skip);
+    List search();
+    boolean searchout();
+    string hint();
+    typedef sequence<TDBQRY> QueryList;
+    List metasearch(in QueryList qrys, in long type);
+  };
+  //----------------------------------------------------------------
+  // the abstract database API
+  //----------------------------------------------------------------
+  interface ADB {
+    boolean open(in string name);
+    boolean close();
+    boolean put(in string key, in string value);
+    boolean putkeep(in string key, in string value);
+    boolean putcat(in string key, in string value);
+    boolean out(in string key);
+    string get(in string key);
+    long vsiz(in string key);
+    boolean iterinit();
+    string iternext();
+    List fwmkeys(in string prefix, in long max);
+    long addint(in string key, in long num);
+    double adddouble(in string key, in double num);
+    boolean sync();
+    boolean optimize(in string params);
+    boolean vanish();
+    boolean copy(in string path);
+    boolean tranbegin();
+    boolean trancommit();
+    boolean tranabort();
+    string path();
+    long long rnum();
+    long long size();
+    List misc(in string name, in List args);
+  };
+};
+
+
+
+/* END OF FILE */