source@mandoc.bsd.lv
 help / color / mirror / Atom feed
* mdocml: Remove the dependency on SQLite without loss of functionality.
@ 2016-07-19 21:32 schwarze
  0 siblings, 0 replies; only message in thread
From: schwarze @ 2016-07-19 21:32 UTC (permalink / raw)
  To: source

Log Message:
-----------
Remove the dependency on SQLite without loss of functionality.
Stop supporting systems that don't have mmap(3).
Drop the obsolete names_check() now that we deleted MLINKS.

Modified Files:
--------------
    mdocml:
        INSTALL
        Makefile
        Makefile.depend
        TODO
        configure
        configure.local.example
        main.c
        mandocdb.c
        mansearch.c
        mansearch.h
        read.c

Added Files:
-----------
    mdocml:
        dba.c
        dba.h
        dba_array.c
        dba_array.h
        dba_read.c
        dba_write.c
        dba_write.h
        dbm.c
        dbm.h
        dbm_map.c
        dbm_map.h

Removed Files:
-------------
    mdocml:
        compat_sqlite3_errstr.c
        mansearch_const.c
        test-mmap.c
        test-sqlite3.c
        test-sqlite3_errstr.c

Revision Data
-------------
Index: INSTALL
===================================================================
RCS file: /home/cvs/mdocml/mdocml/INSTALL,v
retrieving revision 1.15
retrieving revision 1.16
diff -LINSTALL -LINSTALL -u -p -r1.15 -r1.16
--- INSTALL
+++ INSTALL
@@ -66,7 +66,7 @@ Otherwise, if your system uses man.conf(
 a "manpath" line for each directory tree, and the order of these
 lines meets your wishes.
 
-7. If you compiled with database support, run the command "sudo
+7. Run the command "sudo
 makewhatis" to build mandoc.db(5) databases in all the directory
 trees configured in step 6.  Whenever installing new manual pages,
 re-run makewhatis(8) to update the databases, or apropos(1) will
@@ -84,20 +84,9 @@ manual page source.
 
 Understanding mandoc dependencies
 ---------------------------------
-The mandoc(1), man(1), and demandoc(1) utilities only depend
-on the zlib library for decompressing gzipped manual pages,
-but makewhatis(8) and apropos(1) depend on the following
-additional software:
+The following libraries are required:
 
-1. The SQLite database system, see <http://sqlite.org/>.
-The recommended version of SQLite is 3.8.4.3 or newer.  The mandoc
-toolset is known to work with version 3.7.5 or newer.  Versions
-older than 3.8.3 may not achieve full performance due to the
-missing SQLITE_DETERMINISTIC optimization flag.  Versions older
-than 3.8.0 may not show full error information if opening a database
-fails due to the missing sqlite3_errstr() API.  Both are very minor
-problems, apropos(1) is fully usable with SQLite 3.7.5.  Versions
-older than 3.7.5 may or may not work, they have not been tested.
+1. zlib for decompressing gzipped manual pages.
 
 2. The fts(3) directory traversion functions.
 If your system does not have them, the bundled compatibility version
--- compat_sqlite3_errstr.c
+++ /dev/null
@@ -1,16 +0,0 @@
-#include "config.h"
-
-#if HAVE_SQLITE3_ERRSTR
-
-int dummy;
-
-#else
-
-const char *
-sqlite3_errstr(int rc)
-{
-
-	return rc ? "unknown error" : "not an error";
-}
-
-#endif
Index: mandocdb.c
===================================================================
RCS file: /home/cvs/mdocml/mdocml/mandocdb.c,v
retrieving revision 1.220
retrieving revision 1.221
diff -Lmandocdb.c -Lmandocdb.c -u -p -r1.220 -r1.221
--- mandocdb.c
+++ mandocdb.c
@@ -37,6 +37,7 @@
 #if HAVE_SANDBOX_INIT
 #include <sandbox.h>
 #endif
+#include <stdarg.h>
 #include <stddef.h>
 #include <stdio.h>
 #include <stdint.h>
@@ -44,8 +45,6 @@
 #include <string.h>
 #include <unistd.h>
 
-#include <sqlite3.h>
-
 #include "mandoc_aux.h"
 #include "mandoc_ohash.h"
 #include "mandoc.h"
@@ -54,29 +53,11 @@
 #include "man.h"
 #include "manconf.h"
 #include "mansearch.h"
+#include "dba_array.h"
+#include "dba.h"
 
-extern int mansearch_keymax;
 extern const char *const mansearch_keynames[];
 
-#define	SQL_EXEC(_v) \
-	if (SQLITE_OK != sqlite3_exec(db, (_v), NULL, NULL, NULL)) \
-		say("", "%s: %s", (_v), sqlite3_errmsg(db))
-#define	SQL_BIND_TEXT(_s, _i, _v) \
-	if (SQLITE_OK != sqlite3_bind_text \
-		((_s), (_i)++, (_v), -1, SQLITE_STATIC)) \
-		say(mlink->file, "%s", sqlite3_errmsg(db))
-#define	SQL_BIND_INT(_s, _i, _v) \
-	if (SQLITE_OK != sqlite3_bind_int \
-		((_s), (_i)++, (_v))) \
-		say(mlink->file, "%s", sqlite3_errmsg(db))
-#define	SQL_BIND_INT64(_s, _i, _v) \
-	if (SQLITE_OK != sqlite3_bind_int64 \
-		((_s), (_i)++, (_v))) \
-		say(mlink->file, "%s", sqlite3_errmsg(db))
-#define SQL_STEP(_s) \
-	if (SQLITE_DONE != sqlite3_step((_s))) \
-		say(mlink->file, "%s", sqlite3_errmsg(db))
-
 enum	op {
 	OP_DEFAULT = 0, /* new dbs from dir list or default config */
 	OP_CONFFILE, /* new databases from custom config file */
@@ -98,14 +79,14 @@ struct	inodev {
 
 struct	mpage {
 	struct inodev	 inodev;  /* used for hashing routine */
-	int64_t		 pageid;  /* pageid in mpages SQL table */
+	struct dba_array *dba;
 	char		*sec;     /* section from file content */
 	char		*arch;    /* architecture from file content */
 	char		*title;   /* title from file content */
 	char		*desc;    /* description from file content */
 	struct mlink	*mlinks;  /* singly linked list */
-	int		 form;    /* format from file content */
 	int		 name_head_done;
+	enum form	 form;    /* format from file content */
 };
 
 struct	mlink {
@@ -116,19 +97,9 @@ struct	mlink {
 	char		*fsec;    /* section from file name suffix */
 	struct mlink	*next;    /* singly linked list */
 	struct mpage	*mpage;   /* parent */
-	int		 dform;   /* format from directory */
-	int		 fform;   /* format from file name suffix */
 	int		 gzip;	  /* filename has a .gz suffix */
-};
-
-enum	stmt {
-	STMT_DELETE_PAGE = 0,	/* delete mpage */
-	STMT_INSERT_PAGE,	/* insert mpage */
-	STMT_INSERT_LINK,	/* insert mlink */
-	STMT_INSERT_NAME,	/* insert name */
-	STMT_SELECT_NAME,	/* retrieve existing name flags */
-	STMT_INSERT_KEY,	/* insert parsed key */
-	STMT__MAX
+	enum form	 dform;   /* format from directory */
+	enum form	 fform;   /* format from file name suffix */
 };
 
 typedef	int (*mdoc_fp)(struct mpage *, const struct roff_meta *,
@@ -142,20 +113,17 @@ struct	mdoc_handler {
 
 int		 mandocdb(int, char *[]);
 
-static	void	 dbclose(int);
-static	void	 dbadd(struct mpage *);
+static	void	 dbadd(struct dba *, struct mpage *);
 static	void	 dbadd_mlink(const struct mlink *mlink);
-static	void	 dbadd_mlink_name(const struct mlink *mlink);
-static	int	 dbopen(int);
-static	void	 dbprune(void);
+static	void	 dbprune(struct dba *);
+static	void	 dbwrite(struct dba *);
 static	void	 filescan(const char *);
 static	void	 mlink_add(struct mlink *, const struct stat *);
 static	void	 mlink_check(struct mpage *, struct mlink *);
 static	void	 mlink_free(struct mlink *);
 static	void	 mlinks_undupe(struct mpage *);
 static	void	 mpages_free(void);
-static	void	 mpages_merge(struct mparse *);
-static	void	 names_check(void);
+static	void	 mpages_merge(struct dba *, struct mparse *);
 static	void	 parse_cat(struct mpage *, int);
 static	void	 parse_man(struct mpage *, const struct roff_meta *,
 			const struct roff_node *);
@@ -191,7 +159,6 @@ static	int	 set_basedir(const char *, in
 static	int	 treescan(void);
 static	size_t	 utf8(unsigned int, char [7]);
 
-static	char		 tempfilename[32];
 static	int		 nodb; /* no database changes */
 static	int		 mparse_options; /* abort the parse early */
 static	int		 use_all; /* use all found files */
@@ -205,8 +172,6 @@ static	struct ohash	 mpages; /* table of
 static	struct ohash	 mlinks; /* table of directory entries */
 static	struct ohash	 names; /* table of all names */
 static	struct ohash	 strings; /* table of all strings */
-static	sqlite3		*db = NULL; /* current database */
-static	sqlite3_stmt	*stmts[STMT__MAX]; /* current statements */
 static	uint64_t	 name_mask;
 
 static	const struct mdoc_handler mdocs[MDOC_MAX] = {
@@ -341,6 +306,7 @@ mandocdb(int argc, char *argv[])
 {
 	struct manconf	  conf;
 	struct mparse	 *mp;
+	struct dba	 *dba;
 	const char	 *path_arg, *progname;
 	size_t		  j, sz;
 	int		  ch, i;
@@ -360,7 +326,6 @@ mandocdb(int argc, char *argv[])
 #endif
 
 	memset(&conf, 0, sizeof(conf));
-	memset(stmts, 0, STMT__MAX * sizeof(sqlite3_stmt *));
 
 	/*
 	 * We accept a few different invocations.
@@ -461,7 +426,7 @@ mandocdb(int argc, char *argv[])
 		if (OP_TEST != op && 0 == set_basedir(path_arg, 1))
 			goto out;
 
-		if (dbopen(1)) {
+		if ((dba = dba_read(MANDOC_DB)) != NULL) {
 			/*
 			 * The existing database is usable.  Process
 			 * all files specified on the command-line.
@@ -479,7 +444,7 @@ mandocdb(int argc, char *argv[])
 			for (i = 0; i < argc; i++)
 				filescan(argv[i]);
 			if (OP_TEST != op)
-				dbprune();
+				dbprune(dba);
 		} else {
 			/*
 			 * Database missing or corrupt.
@@ -489,12 +454,13 @@ mandocdb(int argc, char *argv[])
 			op = OP_DEFAULT;
 			if (0 == treescan())
 				goto out;
-			if (0 == dbopen(0))
-				goto out;
+			dba = dba_new(128);
 		}
 		if (OP_DELETE != op)
-			mpages_merge(mp);
-		dbclose(OP_DEFAULT == op ? 0 : 1);
+			mpages_merge(dba, mp);
+		if (nodb == 0)
+			dbwrite(dba);
+		dba_free(dba);
 	} else {
 		/*
 		 * If we have arguments, use them as our manpaths.
@@ -539,14 +505,11 @@ mandocdb(int argc, char *argv[])
 				continue;
 			if (0 == treescan())
 				continue;
-			if (0 == dbopen(0))
-				continue;
-
-			mpages_merge(mp);
-			if (warnings && !nodb &&
-			    ! (MPARSE_QUICK & mparse_options))
-				names_check();
-			dbclose(0);
+			dba = dba_new(128);
+			mpages_merge(dba, mp);
+			if (nodb == 0)
+				dbwrite(dba);
+			dba_free(dba);
 
 			if (j + 1 < conf.manpath.sz) {
 				mpages_free();
@@ -596,7 +559,8 @@ treescan(void)
 	FTS		*f;
 	FTSENT		*ff;
 	struct mlink	*mlink;
-	int		 dform, gzip;
+	int		 gzip;
+	enum form	 dform;
 	char		*dsec, *arch, *fsec, *cp;
 	const char	*path;
 	const char	*argv[2];
@@ -970,6 +934,7 @@ mlink_add(struct mlink *mlink, const str
 		mpage = mandoc_calloc(1, sizeof(struct mpage));
 		mpage->inodev.st_ino = inodev.st_ino;
 		mpage->inodev.st_dev = inodev.st_dev;
+		mpage->form = FORM_NONE;
 		ohash_insert(&mpages, slot, mpage);
 	} else
 		mlink->next = mpage->mlinks;
@@ -1118,7 +1083,7 @@ mlink_check(struct mpage *mpage, struct 
  * and filename to determine whether the file is parsable or not.
  */
 static void
-mpages_merge(struct mparse *mp)
+mpages_merge(struct dba *dba, struct mparse *mp)
 {
 	char			 any[] = "any";
 	struct mpage		*mpage, *mpage_dest;
@@ -1129,9 +1094,6 @@ mpages_merge(struct mparse *mp)
 	int			 fd;
 	unsigned int		 pslot;
 
-	if ( ! nodb)
-		SQL_EXEC("BEGIN TRANSACTION");
-
 	mpage = ohash_first(&mpages, &pslot);
 	while (mpage != NULL) {
 		mlinks_undupe(mpage);
@@ -1188,8 +1150,8 @@ mpages_merge(struct mparse *mp)
 					 * to the target.
 					 */
 
-					if (mpage_dest->pageid)
-						dbadd_mlink_name(mlink);
+					if (mpage_dest->dba != NULL)
+						dbadd_mlink(mlink);
 
 					if (mlink->next == NULL)
 						break;
@@ -1254,7 +1216,7 @@ mpages_merge(struct mparse *mp)
 			     mlink = mlink->next)
 				mlink_check(mpage, mlink);
 
-		dbadd(mpage);
+		dbadd(dba, mpage);
 		mlink = mpage->mlinks;
 
 nextpage:
@@ -1262,44 +1224,6 @@ nextpage:
 		ohash_delete(&names);
 		mpage = ohash_next(&mpages, &pslot);
 	}
-
-	if (0 == nodb)
-		SQL_EXEC("END TRANSACTION");
-}
-
-static void
-names_check(void)
-{
-	sqlite3_stmt	*stmt;
-	const char	*name, *sec, *arch, *key;
-
-	sqlite3_prepare_v2(db,
-	  "SELECT name, sec, arch, key FROM ("
-	    "SELECT name AS key, pageid FROM names "
-	    "WHERE bits & ? AND NOT EXISTS ("
-	      "SELECT pageid FROM mlinks "
-	      "WHERE mlinks.pageid == names.pageid "
-	      "AND mlinks.name == names.name"
-	    ")"
-	  ") JOIN ("
-	    "SELECT sec, arch, name, pageid FROM mlinks "
-	    "GROUP BY pageid"
-	  ") USING (pageid);",
-	  -1, &stmt, NULL);
-
-	if (sqlite3_bind_int64(stmt, 1, NAME_TITLE) != SQLITE_OK)
-		say("", "%s", sqlite3_errmsg(db));
-
-	while (sqlite3_step(stmt) == SQLITE_ROW) {
-		name = (const char *)sqlite3_column_text(stmt, 0);
-		sec  = (const char *)sqlite3_column_text(stmt, 1);
-		arch = (const char *)sqlite3_column_text(stmt, 2);
-		key  = (const char *)sqlite3_column_text(stmt, 3);
-		say("", "%s(%s%s%s) lacks mlink \"%s\"", name, sec,
-		    '\0' == *arch ? "" : "/",
-		    '\0' == *arch ? "" : arch, key);
-	}
-	sqlite3_finalize(stmt);
 }
 
 static void
@@ -1823,7 +1747,7 @@ putkeys(const struct mpage *mpage, char 
 	} else {
 		htab = &strings;
 		if (debug > 1)
-		    for (i = 0; i < mansearch_keymax; i++)
+		    for (i = 0; i < KEY_MAX; i++)
 			if ((uint64_t)1 << i & v)
 			    say(mpage->mlinks->file,
 				"Adding key %s=%*s",
@@ -2029,53 +1953,23 @@ render_string(char **public, size_t *psz
 static void
 dbadd_mlink(const struct mlink *mlink)
 {
-	size_t		 i;
-
-	i = 1;
-	SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mlink->dsec);
-	SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mlink->arch);
-	SQL_BIND_TEXT(stmts[STMT_INSERT_LINK], i, mlink->name);
-	SQL_BIND_INT64(stmts[STMT_INSERT_LINK], i, mlink->mpage->pageid);
-	SQL_STEP(stmts[STMT_INSERT_LINK]);
-	sqlite3_reset(stmts[STMT_INSERT_LINK]);
-}
-
-static void
-dbadd_mlink_name(const struct mlink *mlink)
-{
-	uint64_t	 bits;
-	size_t		 i;
-
-	dbadd_mlink(mlink);
-
-	i = 1;
-	SQL_BIND_INT64(stmts[STMT_SELECT_NAME], i, mlink->mpage->pageid);
-	bits = NAME_FILE & NAME_MASK;
-	if (sqlite3_step(stmts[STMT_SELECT_NAME]) == SQLITE_ROW) {
-		bits |= sqlite3_column_int64(stmts[STMT_SELECT_NAME], 0);
-		sqlite3_reset(stmts[STMT_SELECT_NAME]);
-	}
-
-	i = 1;
-	SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, bits);
-	SQL_BIND_TEXT(stmts[STMT_INSERT_NAME], i, mlink->name);
-	SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, mlink->mpage->pageid);
-	SQL_STEP(stmts[STMT_INSERT_NAME]);
-	sqlite3_reset(stmts[STMT_INSERT_NAME]);
+	dba_page_alias(mlink->mpage->dba, mlink->name, NAME_FILE);
+	dba_page_add(mlink->mpage->dba, DBP_SECT, mlink->dsec);
+	dba_page_add(mlink->mpage->dba, DBP_ARCH, mlink->arch);
+	dba_page_add(mlink->mpage->dba, DBP_FILE, mlink->file);
 }
 
 /*
  * Flush the current page's terms (and their bits) into the database.
- * Wrap the entire set of additions in a transaction to make sqlite be a
- * little faster.
  * Also, handle escape sequences at the last possible moment.
  */
 static void
-dbadd(struct mpage *mpage)
+dbadd(struct dba *dba, struct mpage *mpage)
 {
 	struct mlink	*mlink;
 	struct str	*key;
 	char		*cp;
+	uint64_t	 mask;
 	size_t		 i;
 	unsigned int	 slot;
 	int		 mustfree;
@@ -2120,111 +2014,87 @@ dbadd(struct mpage *mpage)
 	cp = mpage->desc;
 	i = strlen(cp);
 	mustfree = render_string(&cp, &i);
-	i = 1;
-	SQL_BIND_TEXT(stmts[STMT_INSERT_PAGE], i, cp);
-	SQL_BIND_INT(stmts[STMT_INSERT_PAGE], i, mpage->form);
-	SQL_STEP(stmts[STMT_INSERT_PAGE]);
-	mpage->pageid = sqlite3_last_insert_rowid(db);
-	sqlite3_reset(stmts[STMT_INSERT_PAGE]);
+	mpage->dba = dba_page_new(dba->pages, mlink->name,
+	    mlink->dsec, mlink->arch, cp, mlink->file, mpage->form);
 	if (mustfree)
 		free(cp);
 
-	while (NULL != mlink) {
+	while ((mlink = mlink->next) != NULL)
 		dbadd_mlink(mlink);
-		mlink = mlink->next;
-	}
-	mlink = mpage->mlinks;
 
 	for (key = ohash_first(&names, &slot); NULL != key;
 	     key = ohash_next(&names, &slot)) {
 		assert(key->mpage == mpage);
-		i = 1;
-		SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, key->mask);
-		SQL_BIND_TEXT(stmts[STMT_INSERT_NAME], i, key->key);
-		SQL_BIND_INT64(stmts[STMT_INSERT_NAME], i, mpage->pageid);
-		SQL_STEP(stmts[STMT_INSERT_NAME]);
-		sqlite3_reset(stmts[STMT_INSERT_NAME]);
+		dba_page_alias(mpage->dba, key->key, key->mask);
 		free(key);
 	}
 	for (key = ohash_first(&strings, &slot); NULL != key;
 	     key = ohash_next(&strings, &slot)) {
 		assert(key->mpage == mpage);
-		i = 1;
-		SQL_BIND_INT64(stmts[STMT_INSERT_KEY], i, key->mask);
-		SQL_BIND_TEXT(stmts[STMT_INSERT_KEY], i, key->key);
-		SQL_BIND_INT64(stmts[STMT_INSERT_KEY], i, mpage->pageid);
-		SQL_STEP(stmts[STMT_INSERT_KEY]);
-		sqlite3_reset(stmts[STMT_INSERT_KEY]);
+		i = 0;
+		for (mask = TYPE_Xr; mask <= TYPE_Lb; mask *= 2) {
+			if (key->mask & mask)
+				dba_macro_add(dba->macros, i,
+				    key->key, mpage->dba);
+			i++;
+		}
 		free(key);
 	}
 }
 
 static void
-dbprune(void)
+dbprune(struct dba *dba)
 {
-	struct mpage	*mpage;
-	struct mlink	*mlink;
-	size_t		 i;
-	unsigned int	 slot;
-
-	if (0 == nodb)
-		SQL_EXEC("BEGIN TRANSACTION");
+	struct dba_array	*page, *files;
+	char			*file;
 
-	for (mpage = ohash_first(&mpages, &slot); NULL != mpage;
-	     mpage = ohash_next(&mpages, &slot)) {
-		mlink = mpage->mlinks;
-		if (debug)
-			say(mlink->file, "Deleting from database");
-		if (nodb)
-			continue;
-		for ( ; NULL != mlink; mlink = mlink->next) {
-			i = 1;
-			SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE],
-			    i, mlink->dsec);
-			SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE],
-			    i, mlink->arch);
-			SQL_BIND_TEXT(stmts[STMT_DELETE_PAGE],
-			    i, mlink->name);
-			SQL_STEP(stmts[STMT_DELETE_PAGE]);
-			sqlite3_reset(stmts[STMT_DELETE_PAGE]);
+	dba_array_FOREACH(dba->pages, page) {
+		files = dba_array_get(page, DBP_FILE);
+		dba_array_FOREACH(files, file) {
+			if (*file < ' ')
+				file++;
+			if (ohash_find(&mlinks, ohash_qlookup(&mlinks,
+			    file)) != NULL) {
+				if (debug)
+					say(file, "Deleting from database");
+				dba_array_del(dba->pages);
+				break;
+			}
 		}
 	}
-
-	if (0 == nodb)
-		SQL_EXEC("END TRANSACTION");
 }
 
 /*
- * Close an existing database and its prepared statements.
- * If "real" is not set, rename the temporary file into the real one.
+ * Write the database from memory to disk.
  */
 static void
-dbclose(int real)
+dbwrite(struct dba *dba)
 {
-	size_t		 i;
+	char		 tfn[32];
 	int		 status;
 	pid_t		 child;
 
-	if (nodb)
+	if (dba_write(MANDOC_DB "~", dba) != -1) {
+		if (rename(MANDOC_DB "~", MANDOC_DB) == -1) {
+			exitcode = (int)MANDOCLEVEL_SYSERR;
+			say(MANDOC_DB, "&rename");
+			unlink(MANDOC_DB "~");
+		}
 		return;
-
-	for (i = 0; i < STMT__MAX; i++) {
-		sqlite3_finalize(stmts[i]);
-		stmts[i] = NULL;
 	}
 
-	sqlite3_close(db);
-	db = NULL;
-
-	if (real)
+	(void)strlcpy(tfn, "/tmp/mandocdb.XXXXXXXX", sizeof(tfn));
+	if (mkdtemp(tfn) == NULL) {
+		exitcode = (int)MANDOCLEVEL_SYSERR;
+		say("", "&%s", tfn);
 		return;
+	}
 
-	if ('\0' == *tempfilename) {
-		if (-1 == rename(MANDOC_DB "~", MANDOC_DB)) {
-			exitcode = (int)MANDOCLEVEL_SYSERR;
-			say(MANDOC_DB, "&rename");
-		}
-		return;
+	(void)strlcat(tfn, "/" MANDOC_DB, sizeof(tfn));
+	if (dba_write(tfn, dba) == -1) {
+		exitcode = (int)MANDOCLEVEL_SYSERR;
+		say(tfn, "&dba_write");
+		goto out;
 	}
 
 	switch (child = fork()) {
@@ -2233,14 +2103,13 @@ dbclose(int real)
 		say("", "&fork cmp");
 		return;
 	case 0:
-		execlp("cmp", "cmp", "-s",
-		    tempfilename, MANDOC_DB, (char *)NULL);
+		execlp("cmp", "cmp", "-s", tfn, MANDOC_DB, (char *)NULL);
 		say("", "&exec cmp");
 		exit(0);
 	default:
 		break;
 	}
-	if (-1 == waitpid(child, &status, 0)) {
+	if (waitpid(child, &status, 0) == -1) {
 		exitcode = (int)MANDOCLEVEL_SYSERR;
 		say("", "&wait cmp");
 	} else if (WIFSIGNALED(status)) {
@@ -2252,173 +2121,27 @@ dbclose(int real)
 		    "Data changed, but cannot replace database");
 	}
 
-	*strrchr(tempfilename, '/') = '\0';
+out:
+	*strrchr(tfn, '/') = '\0';
 	switch (child = fork()) {
 	case -1:
 		exitcode = (int)MANDOCLEVEL_SYSERR;
 		say("", "&fork rm");
 		return;
 	case 0:
-		execlp("rm", "rm", "-rf", tempfilename, (char *)NULL);
+		execlp("rm", "rm", "-rf", tfn, (char *)NULL);
 		say("", "&exec rm");
 		exit((int)MANDOCLEVEL_SYSERR);
 	default:
 		break;
 	}
-	if (-1 == waitpid(child, &status, 0)) {
+	if (waitpid(child, &status, 0) == -1) {
 		exitcode = (int)MANDOCLEVEL_SYSERR;
 		say("", "&wait rm");
 	} else if (WIFSIGNALED(status) || WEXITSTATUS(status)) {
 		exitcode = (int)MANDOCLEVEL_SYSERR;
-		say("", "%s: Cannot remove temporary directory",
-		    tempfilename);
-	}
-}
-
-/*
- * This is straightforward stuff.
- * Open a database connection to a "temporary" database, then open a set
- * of prepared statements we'll use over and over again.
- * If "real" is set, we use the existing database; if not, we truncate a
- * temporary one.
- * Must be matched by dbclose().
- */
-static int
-dbopen(int real)
-{
-	const char	*sql;
-	int		 rc, ofl;
-
-	if (nodb)
-		return 1;
-
-	*tempfilename = '\0';
-	ofl = SQLITE_OPEN_READWRITE;
-
-	if (real) {
-		rc = sqlite3_open_v2(MANDOC_DB, &db, ofl, NULL);
-		if (SQLITE_OK != rc) {
-			exitcode = (int)MANDOCLEVEL_SYSERR;
-			if (SQLITE_CANTOPEN != rc)
-				say(MANDOC_DB, "%s", sqlite3_errstr(rc));
-			return 0;
-		}
-		goto prepare_statements;
-	}
-
-	ofl |= SQLITE_OPEN_CREATE | SQLITE_OPEN_EXCLUSIVE;
-
-	remove(MANDOC_DB "~");
-	rc = sqlite3_open_v2(MANDOC_DB "~", &db, ofl, NULL);
-	if (SQLITE_OK == rc)
-		goto create_tables;
-	if (MPARSE_QUICK & mparse_options) {
-		exitcode = (int)MANDOCLEVEL_SYSERR;
-		say(MANDOC_DB "~", "%s", sqlite3_errstr(rc));
-		return 0;
-	}
-
-	(void)strlcpy(tempfilename, "/tmp/mandocdb.XXXXXX",
-	    sizeof(tempfilename));
-	if (NULL == mkdtemp(tempfilename)) {
-		exitcode = (int)MANDOCLEVEL_SYSERR;
-		say("", "&%s", tempfilename);
-		return 0;
-	}
-	(void)strlcat(tempfilename, "/" MANDOC_DB,
-	    sizeof(tempfilename));
-	rc = sqlite3_open_v2(tempfilename, &db, ofl, NULL);
-	if (SQLITE_OK != rc) {
-		exitcode = (int)MANDOCLEVEL_SYSERR;
-		say("", "%s: %s", tempfilename, sqlite3_errstr(rc));
-		return 0;
-	}
-
-create_tables:
-	sql = "CREATE TABLE \"mpages\" (\n"
-	      " \"desc\" TEXT NOT NULL,\n"
-	      " \"form\" INTEGER NOT NULL,\n"
-	      " \"pageid\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL\n"
-	      ");\n"
-	      "\n"
-	      "CREATE TABLE \"mlinks\" (\n"
-	      " \"sec\" TEXT NOT NULL,\n"
-	      " \"arch\" TEXT NOT NULL,\n"
-	      " \"name\" TEXT NOT NULL,\n"
-	      " \"pageid\" INTEGER NOT NULL REFERENCES mpages(pageid) "
-		"ON DELETE CASCADE\n"
-	      ");\n"
-	      "CREATE INDEX mlinks_pageid_idx ON mlinks (pageid);\n"
-	      "\n"
-	      "CREATE TABLE \"names\" (\n"
-	      " \"bits\" INTEGER NOT NULL,\n"
-	      " \"name\" TEXT NOT NULL,\n"
-	      " \"pageid\" INTEGER NOT NULL REFERENCES mpages(pageid) "
-		"ON DELETE CASCADE,\n"
-	      " UNIQUE (\"name\", \"pageid\") ON CONFLICT REPLACE\n"
-	      ");\n"
-	      "\n"
-	      "CREATE TABLE \"keys\" (\n"
-	      " \"bits\" INTEGER NOT NULL,\n"
-	      " \"key\" TEXT NOT NULL,\n"
-	      " \"pageid\" INTEGER NOT NULL REFERENCES mpages(pageid) "
-		"ON DELETE CASCADE\n"
-	      ");\n"
-	      "CREATE INDEX keys_pageid_idx ON keys (pageid);\n";
-
-	if (SQLITE_OK != sqlite3_exec(db, sql, NULL, NULL, NULL)) {
-		exitcode = (int)MANDOCLEVEL_SYSERR;
-		say(MANDOC_DB, "%s", sqlite3_errmsg(db));
-		sqlite3_close(db);
-		return 0;
-	}
-
-prepare_statements:
-	if (SQLITE_OK != sqlite3_exec(db,
-	    "PRAGMA foreign_keys = ON", NULL, NULL, NULL)) {
-		exitcode = (int)MANDOCLEVEL_SYSERR;
-		say(MANDOC_DB, "PRAGMA foreign_keys: %s",
-		    sqlite3_errmsg(db));
-		sqlite3_close(db);
-		return 0;
-	}
-
-	sql = "DELETE FROM mpages WHERE pageid IN "
-		"(SELECT pageid FROM mlinks WHERE "
-		"sec=? AND arch=? AND name=?)";
-	sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_DELETE_PAGE], NULL);
-	sql = "INSERT INTO mpages "
-		"(desc,form) VALUES (?,?)";
-	sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_PAGE], NULL);
-	sql = "INSERT INTO mlinks "
-		"(sec,arch,name,pageid) VALUES (?,?,?,?)";
-	sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_LINK], NULL);
-	sql = "SELECT bits FROM names where pageid = ?";
-	sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_SELECT_NAME], NULL);
-	sql = "INSERT INTO names "
-		"(bits,name,pageid) VALUES (?,?,?)";
-	sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_NAME], NULL);
-	sql = "INSERT INTO keys "
-		"(bits,key,pageid) VALUES (?,?,?)";
-	sqlite3_prepare_v2(db, sql, -1, &stmts[STMT_INSERT_KEY], NULL);
-
-#ifndef __APPLE__
-	/*
-	 * When opening a new database, we can turn off
-	 * synchronous mode for much better performance.
-	 */
-
-	if (real && SQLITE_OK != sqlite3_exec(db,
-	    "PRAGMA synchronous = OFF", NULL, NULL, NULL)) {
-		exitcode = (int)MANDOCLEVEL_SYSERR;
-		say(MANDOC_DB, "PRAGMA synchronous: %s",
-		    sqlite3_errmsg(db));
-		sqlite3_close(db);
-		return 0;
+		say("", "%s: Cannot remove temporary directory", tfn);
 	}
-#endif
-
-	return 1;
 }
 
 static int
--- mansearch_const.c
+++ /dev/null
@@ -1,33 +0,0 @@
-/*	$Id: mansearch_const.c,v 1.7 2014/12/01 08:05:52 schwarze Exp $ */
-/*
- * Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org>
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-#include "config.h"
-
-#include <sys/types.h>
-
-#include <stdint.h>
-
-#include "mansearch.h"
-
-const int mansearch_keymax = 40;
-
-const char *const mansearch_keynames[40] = {
-	"arch", "sec",	"Xr",	"Ar",	"Fa",	"Fl",	"Dv",	"Fn",
-	"Ic",	"Pa",	"Cm",	"Li",	"Em",	"Cd",	"Va",	"Ft",
-	"Tn",	"Er",	"Ev",	"Sy",	"Sh",	"In",	"Ss",	"Ox",
-	"An",	"Mt",	"St",	"Bx",	"At",	"Nx",	"Fx",	"Lk",
-	"Ms",	"Bsx",	"Dx",	"Rs",	"Vt",	"Lb",	"Nm",	"Nd"
-};
--- /dev/null
+++ dba.c
@@ -0,0 +1,418 @@
+/*	$Id: dba.c,v 1.1 2016/07/19 21:31:55 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Allocation-based version of the mandoc database, for read-write access.
+ * The interface is defined in "dba.h".
+ */
+#include <sys/types.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mandoc_aux.h"
+#include "mansearch.h"
+#include "dba_write.h"
+#include "dba_array.h"
+#include "dba.h"
+
+static void	*prepend(const char *, char);
+static void	 dba_pages_write(struct dba_array *);
+static int	 compare_names(const void *, const void *);
+static void	 dba_macros_write(struct dba_array *);
+static void	 dba_macro_write(struct dba_array *);
+
+
+/*** top-level functions **********************************************/
+
+struct dba *
+dba_new(int32_t npages)
+{
+	struct dba	*dba;
+	int32_t		 im;
+
+	dba = mandoc_malloc(sizeof(*dba));
+	dba->pages = dba_array_new(npages, DBA_GROW);
+	dba->macros = dba_array_new(MACRO_MAX, 0);
+	for (im = 0; im < MACRO_MAX; im++)
+		dba_array_set(dba->macros, im, dba_array_new(128, DBA_GROW));
+	return dba;
+}
+
+void
+dba_free(struct dba *dba)
+{
+	struct dba_array	*page, *macro, *entry;
+
+	dba_array_FOREACH(dba->macros, macro) {
+		dba_array_undel(macro);
+		dba_array_FOREACH(macro, entry) {
+			free(dba_array_get(entry, 0));
+			dba_array_free(dba_array_get(entry, 1));
+			dba_array_free(entry);
+		}
+		dba_array_free(macro);
+	}
+	dba_array_free(dba->macros);
+
+	dba_array_undel(dba->pages);
+	dba_array_FOREACH(dba->pages, page) {
+		dba_array_free(dba_array_get(page, DBP_NAME));
+		dba_array_free(dba_array_get(page, DBP_SECT));
+		dba_array_free(dba_array_get(page, DBP_ARCH));
+		free(dba_array_get(page, DBP_DESC));
+		dba_array_free(dba_array_get(page, DBP_FILE));
+		dba_array_free(page);
+	}
+	dba_array_free(dba->pages);
+
+	free(dba);
+}
+
+/*
+ * Write the complete mandoc database to disk; the format is:
+ * - One integer each for magic and version.
+ * - One pointer each to the macros table and to the final magic.
+ * - The pages table.
+ * - The macros table.
+ * - And at the very end, the magic integer again.
+ */
+int
+dba_write(const char *fname, struct dba *dba)
+{
+	int	 save_errno;
+	int32_t	 pos_end, pos_macros, pos_macros_ptr;
+
+	if (dba_open(fname) == -1)
+		return -1;
+	dba_int_write(MANDOCDB_MAGIC);
+	dba_int_write(MANDOCDB_VERSION);
+	pos_macros_ptr = dba_skip(1, 2);
+	dba_pages_write(dba->pages);
+	pos_macros = dba_tell();
+	dba_macros_write(dba->macros);
+	pos_end = dba_tell();
+	dba_int_write(MANDOCDB_MAGIC);
+	dba_seek(pos_macros_ptr);
+	dba_int_write(pos_macros);
+	dba_int_write(pos_end);
+	if (dba_close() == -1) {
+		save_errno = errno;
+		unlink(fname);
+		errno = save_errno;
+		return -1;
+	}
+	return 0;
+}
+
+
+/*** functions for handling pages *************************************/
+
+/*
+ * Create a new page and append it to the pages table.
+ */
+struct dba_array *
+dba_page_new(struct dba_array *pages, const char *name, const char *sect,
+    const char *arch, const char *desc, const char *file, enum form form)
+{
+	struct dba_array *page, *entry;
+
+	page = dba_array_new(DBP_MAX, 0);
+	entry = dba_array_new(1, DBA_STR | DBA_GROW);
+	dba_array_add(entry, prepend(name, NAME_FILE & NAME_MASK));
+	dba_array_add(page, entry);
+	entry = dba_array_new(1, DBA_STR | DBA_GROW);
+	dba_array_add(entry, (void *)sect);
+	dba_array_add(page, entry);
+	if (arch != NULL && *arch != '\0') {
+		entry = dba_array_new(1, DBA_STR | DBA_GROW);
+		dba_array_add(entry, (void *)arch);
+	} else
+		entry = NULL;
+	dba_array_add(page, entry);
+	dba_array_add(page, mandoc_strdup(desc));
+	entry = dba_array_new(1, DBA_STR | DBA_GROW);
+	dba_array_add(entry, prepend(file, form));
+	dba_array_add(page, entry);
+	dba_array_add(pages, page);
+	return page;
+}
+
+/*
+ * Add a section, architecture, or file name to an existing page.
+ * Passing the NULL pointer for the architecture makes the page MI.
+ * In that case, any earlier or later architectures are ignored.
+ */
+void
+dba_page_add(struct dba_array *page, int32_t ie, const char *str)
+{
+	struct dba_array	*entries;
+	char			*entry;
+
+	entries = dba_array_get(page, ie);
+	if (ie == DBP_ARCH) {
+		if (entries == NULL)
+			return;
+		if (str == NULL) {
+			dba_array_free(entries);
+			dba_array_set(page, DBP_ARCH, NULL);
+			return;
+		}
+	}
+	if (*str == '\0')
+		return;
+	dba_array_FOREACH(entries, entry)
+		if (strcmp(entry, str) == 0)
+			return;
+	dba_array_add(entries, (void *)str);
+}
+
+/*
+ * Add an additional name to an existing page.
+ */
+void
+dba_page_alias(struct dba_array *page, const char *name, uint64_t mask)
+{
+	struct dba_array	*entries;
+	char			*entry;
+	char			 maskbyte;
+
+	if (*name == '\0')
+		return;
+	maskbyte = mask & NAME_MASK;
+	entries = dba_array_get(page, DBP_NAME);
+	dba_array_FOREACH(entries, entry) {
+		if (strcmp(entry + 1, name) == 0) {
+			*entry |= maskbyte;
+			return;
+		}
+	}
+	dba_array_add(entries, prepend(name, maskbyte));
+}
+
+/*
+ * Return a pointer to a temporary copy of instr with inbyte prepended.
+ */
+static void *
+prepend(const char *instr, char inbyte)
+{
+	static char	*outstr = NULL;
+	static size_t	 outlen = 0;
+	size_t		 newlen;
+
+	newlen = strlen(instr) + 1;
+	if (newlen > outlen) {
+		outstr = mandoc_realloc(outstr, newlen + 1);
+		outlen = newlen;
+	}
+	*outstr = inbyte;
+	memcpy(outstr + 1, instr, newlen);
+	return outstr;
+}
+
+/*
+ * Write the pages table to disk; the format is:
+ * - One integer containing the number of pages.
+ * - For each page, five pointers to the names, sections,
+ *   architectures, description, and file names of the page.
+ *   MI pages write 0 instead of the architecture pointer.
+ * - One list each for names, sections, architectures, descriptions and
+ *   file names.  The description for each page ends with a NUL byte.
+ *   For all the other lists, each string ends with a NUL byte,
+ *   and the last string for a page ends with two NUL bytes.
+ * - To assure alignment of following integers,
+ *   the end is padded with NUL bytes up to a multiple of four bytes.
+ */
+static void
+dba_pages_write(struct dba_array *pages)
+{
+	struct dba_array	*page, *names;
+	void			*entry;
+	int32_t			 pos_pages, pos_end;
+
+	pos_pages = dba_array_writelen(pages, 5);
+	dba_array_FOREACH(pages, page) {
+		dba_array_setpos(page, DBP_NAME, dba_tell());
+		names = dba_array_get(page, DBP_NAME);
+		dba_array_sort(names, compare_names);
+		dba_array_writelst(names);
+	}
+	dba_array_FOREACH(pages, page) {
+		dba_array_setpos(page, DBP_SECT, dba_tell());
+		dba_array_writelst(dba_array_get(page, DBP_SECT));
+	}
+	dba_array_FOREACH(pages, page) {
+		if ((entry = dba_array_get(page, DBP_ARCH)) != NULL) {
+			dba_array_setpos(page, DBP_ARCH, dba_tell());
+			dba_array_writelst(entry);
+		} else
+			dba_array_setpos(page, DBP_ARCH, 0);
+	}
+	dba_array_FOREACH(pages, page) {
+		dba_array_setpos(page, DBP_DESC, dba_tell());
+		dba_str_write(dba_array_get(page, DBP_DESC));
+	}
+	dba_array_FOREACH(pages, page) {
+		dba_array_setpos(page, DBP_FILE, dba_tell());
+		dba_array_writelst(dba_array_get(page, DBP_FILE));
+	}
+	pos_end = dba_align();
+	dba_seek(pos_pages);
+	dba_array_FOREACH(pages, page)
+		dba_array_writepos(page);
+	dba_seek(pos_end);
+}
+
+static int
+compare_names(const void *vp1, const void *vp2)
+{
+	const char	*cp1, *cp2;
+	int		 diff;
+
+	cp1 = *(char **)vp1;
+	cp2 = *(char **)vp2;
+	return (diff = *cp2 - *cp1) ? diff :
+	    strcasecmp(cp1 + 1, cp2 + 1);
+}
+
+
+/*** functions for handling macros ************************************/
+
+/*
+ * Create a new macro entry and append it to one of the macro tables.
+ */
+void
+dba_macro_new(struct dba *dba, int32_t im, const char *value,
+    const int32_t *pp)
+{
+	struct dba_array	*entry, *pages;
+	const int32_t		*ip;
+	int32_t			 np;
+
+	np = 0;
+	for (ip = pp; *ip; ip++)
+		np++;
+	pages = dba_array_new(np, DBA_GROW);
+	for (ip = pp; *ip; ip++)
+		dba_array_add(pages, dba_array_get(dba->pages,
+		    be32toh(*ip) / 5 / sizeof(*ip) - 1));
+
+	entry = dba_array_new(2, 0);
+	dba_array_add(entry, mandoc_strdup(value));
+	dba_array_add(entry, pages);
+
+	dba_array_add(dba_array_get(dba->macros, im), entry);
+}
+
+/*
+ * Look up a macro entry by value and add a reference to a new page to it.
+ * If the value does not yet exist, create a new macro entry
+ * and add it to the macro table in question.
+ */
+void
+dba_macro_add(struct dba_array *macros, int32_t im, const char *value,
+    struct dba_array *page)
+{
+	struct dba_array	*macro, *entry, *pages;
+
+	if (*value == '\0')
+		return;
+	macro = dba_array_get(macros, im);
+	dba_array_FOREACH(macro, entry)
+		if (strcmp(value, dba_array_get(entry, 0)) == 0)
+			break;
+	if (entry == NULL) {
+		entry = dba_array_new(2, 0);
+		dba_array_add(entry, mandoc_strdup(value));
+		pages = dba_array_new(1, DBA_GROW);
+		dba_array_add(entry, pages);
+		dba_array_add(macro, entry);
+	} else
+		pages = dba_array_get(entry, 1);
+	dba_array_add(pages, page);
+}
+
+/*
+ * Write the macros table to disk; the format is:
+ * - The number of macro tables (actually, MACRO_MAX).
+ * - That number of pointers to the individual macro tables.
+ * - The individual macro tables.
+ */
+static void
+dba_macros_write(struct dba_array *macros)
+{
+	struct dba_array	*macro;
+	int32_t			 im, pos_macros, pos_end;
+
+	pos_macros = dba_array_writelen(macros, 1);
+	im = 0;
+	dba_array_FOREACH(macros, macro) {
+		dba_array_setpos(macros, im++, dba_tell());
+		dba_macro_write(macro);
+	}
+	pos_end = dba_tell();
+	dba_seek(pos_macros);
+	dba_array_writepos(macros);
+	dba_seek(pos_end);
+}
+
+/*
+ * Write one individual macro table to disk; the format is:
+ * - The number of entries in the table.
+ * - For each entry, two pointers, the first one to the value
+ *   and the second one to the list of pages.
+ * - A list of values, each ending in a NUL byte.
+ * - To assure alignment of following integers,
+ *   padding with NUL bytes up to a multiple of four bytes.
+ * - A list of pointers to pages, each list ending in a 0 integer.
+ */
+static void
+dba_macro_write(struct dba_array *macro)
+{
+	struct dba_array	*entry, *pages, *page;
+	int			 empty;
+	int32_t			 addr, pos_macro, pos_end;
+
+	dba_array_FOREACH(macro, entry) {
+		pages = dba_array_get(entry, 1);
+		empty = 1;
+		dba_array_FOREACH(pages, page)
+			if (dba_array_getpos(page))
+				empty = 0;
+		if (empty)
+			dba_array_del(macro);
+	}
+	pos_macro = dba_array_writelen(macro, 2);
+	dba_array_FOREACH(macro, entry) {
+		dba_array_setpos(entry, 0, dba_tell());
+		dba_str_write(dba_array_get(entry, 0));
+	}
+	dba_align();
+	dba_array_FOREACH(macro, entry) {
+		dba_array_setpos(entry, 1, dba_tell());
+		pages = dba_array_get(entry, 1);
+		dba_array_FOREACH(pages, page)
+			if ((addr = dba_array_getpos(page)))
+				dba_int_write(addr);
+		dba_int_write(0);
+	}
+	pos_end = dba_tell();
+	dba_seek(pos_macro);
+	dba_array_FOREACH(macro, entry)
+		dba_array_writepos(entry);
+	dba_seek(pos_end);
+}
--- /dev/null
+++ dbm.c
@@ -0,0 +1,452 @@
+/*	$Id: dbm.c,v 1.1 2016/07/19 21:31:55 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Map-based version of the mandoc database, for read-only access.
+ * The interface is defined in "dbm.h".
+ */
+#include <assert.h>
+#include <endian.h>
+#include <err.h>
+#include <errno.h>
+#include <regex.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mansearch.h"
+#include "dbm_map.h"
+#include "dbm.h"
+
+struct macro {
+	int32_t	value;
+	int32_t	pages;
+};
+
+struct page {
+	int32_t	name;
+	int32_t	sect;
+	int32_t	arch;
+	int32_t	desc;
+	int32_t	file;
+};
+
+enum iter {
+	ITER_NONE = 0,
+	ITER_NAME,
+	ITER_SECT,
+	ITER_ARCH,
+	ITER_DESC,
+	ITER_MACRO
+};
+
+static struct macro	*macros[MACRO_MAX];
+static int32_t		 nvals[MACRO_MAX];
+static struct page	*pages;
+static int32_t		 npages;
+static enum iter	 iteration;
+
+static struct dbm_res	 page_bytitle(enum iter, const struct dbm_match *);
+static struct dbm_res	 page_byarch(const struct dbm_match *);
+static struct dbm_res	 page_bymacro(int32_t, const struct dbm_match *);
+static char		*macro_bypage(int32_t, int32_t);
+
+
+/*** top level functions **********************************************/
+
+/*
+ * Open a disk-based mandoc database for read-only access.
+ * Map the pages and macros[] arrays.
+ * Return 0 on success.  Return -1 and set errno on failure.
+ */
+int
+dbm_open(const char *fname)
+{
+	const int32_t	*mp, *ep;
+	int32_t		 im;
+
+	if (dbm_map(fname) == -1)
+		return -1;
+
+	if ((npages = be32toh(*dbm_getint(4))) < 0) {
+		warnx("dbm_open(%s): Invalid number of pages: %d",
+		    fname, npages);
+		goto fail;
+	}
+	pages = (struct page *)dbm_getint(5);
+
+	if ((mp = dbm_get(*dbm_getint(2))) == NULL) {
+		warnx("dbm_open(%s): Invalid offset of macros array", fname);
+		goto fail;
+	}
+	if (be32toh(*mp) != MACRO_MAX) {
+		warnx("dbm_open(%s): Invalid number of macros: %d",
+		    fname, be32toh(*mp));
+		goto fail;
+	}
+	for (im = 0; im < MACRO_MAX; im++) {
+		if ((ep = dbm_get(*++mp)) == NULL) {
+			warnx("dbm_open(%s): Invalid offset of macro %d",
+			    fname, im);
+			goto fail;
+		}
+		nvals[im] = be32toh(*ep);
+		macros[im] = (struct macro *)++ep;
+	}
+	return 0;
+
+fail:
+	dbm_unmap();
+	errno = EFTYPE;
+	return -1;
+}
+
+void
+dbm_close(void)
+{
+	dbm_unmap();
+}
+
+
+/*** functions for handling pages *************************************/
+
+int32_t
+dbm_page_count(void)
+{
+	return npages;
+}
+
+/*
+ * Give the caller pointers to the data for one manual page.
+ */
+struct dbm_page *
+dbm_page_get(int32_t ip)
+{
+	static struct dbm_page	 res;
+
+	assert(ip >= 0);
+	assert(ip < npages);
+	res.name = dbm_get(pages[ip].name);
+	res.sect = dbm_get(pages[ip].sect);
+	res.arch = pages[ip].arch ? dbm_get(pages[ip].arch) : NULL;
+	res.desc = dbm_get(pages[ip].desc);
+	res.file = dbm_get(pages[ip].file);
+	res.addr = dbm_addr(pages + ip);
+	return &res;
+}
+
+/*
+ * Functions to start filtered iterations over manual pages.
+ */
+void
+dbm_page_byname(const struct dbm_match *match)
+{
+	assert(match != NULL);
+	page_bytitle(ITER_NAME, match);
+}
+
+void
+dbm_page_bysect(const struct dbm_match *match)
+{
+	assert(match != NULL);
+	page_bytitle(ITER_SECT, match);
+}
+
+void
+dbm_page_byarch(const struct dbm_match *match)
+{
+	assert(match != NULL);
+	page_byarch(match);
+}
+
+void
+dbm_page_bydesc(const struct dbm_match *match)
+{
+	assert(match != NULL);
+	page_bytitle(ITER_DESC, match);
+}
+
+void
+dbm_page_bymacro(int32_t im, const struct dbm_match *match)
+{
+	assert(im >= 0);
+	assert(im < MACRO_MAX);
+	assert(match != NULL);
+	page_bymacro(im, match);
+}
+
+/*
+ * Return the number of the next manual page in the current iteration.
+ */
+struct dbm_res
+dbm_page_next(void)
+{
+	struct dbm_res			 res = {-1, 0};
+
+	switch(iteration) {
+	case ITER_NONE:
+		return res;
+	case ITER_ARCH:
+		return page_byarch(NULL);
+	case ITER_MACRO:
+		return page_bymacro(0, NULL);
+	default:
+		return page_bytitle(iteration, NULL);
+	}
+}
+
+/*
+ * Functions implementing the iteration over manual pages.
+ */
+static struct dbm_res
+page_bytitle(enum iter arg_iter, const struct dbm_match *arg_match)
+{
+	static const struct dbm_match	*match;
+	static const char		*cp;	
+	static int32_t			 ip;
+	struct dbm_res			 res = {-1, 0};
+
+	assert(arg_iter == ITER_NAME || arg_iter == ITER_DESC ||
+	    arg_iter == ITER_SECT);
+
+	/* Initialize for a new iteration. */
+
+	if (arg_match != NULL) {
+		iteration = arg_iter;
+		match = arg_match;
+		switch (iteration) {
+		case ITER_NAME:
+			cp = dbm_get(pages[0].name);
+			break;
+		case ITER_SECT:
+			cp = dbm_get(pages[0].sect);
+			break;
+		case ITER_DESC:
+			cp = dbm_get(pages[0].desc);
+			break;
+		default:
+			abort();
+		}
+		ip = 0;
+		return res;
+	}
+
+	/* Search for a name. */
+
+	while (ip < npages) {
+		if (iteration == ITER_NAME)
+			cp++;
+		if (dbm_match(match, cp))
+			break;
+		cp = strchr(cp, '\0') + 1;
+		if (iteration == ITER_DESC)
+			ip++;
+		else if (*cp == '\0') {
+			cp++;
+			ip++;
+		}
+	}
+
+	/* Reached the end without a match. */
+
+	if (ip == npages) {
+		iteration = ITER_NONE;
+		match = NULL;
+		cp = NULL;
+		return res;
+	}
+
+	/* Found a match; save the quality for later retrieval. */
+
+	res.page = ip;
+	res.bits = iteration == ITER_NAME ? cp[-1] : 0;
+
+	/* Skip the remaining names of this page. */
+
+	if (++ip < npages) {
+		do {
+			cp++;
+		} while (cp[-1] != '\0' ||
+		    (iteration != ITER_DESC && cp[-2] != '\0'));
+	}
+	return res;
+}
+
+static struct dbm_res
+page_byarch(const struct dbm_match *arg_match)
+{
+	static const struct dbm_match	*match;
+	struct dbm_res			 res = {-1, 0};
+	static int32_t			 ip;
+	const char			*cp;	
+
+	/* Initialize for a new iteration. */
+
+	if (arg_match != NULL) {
+		iteration = ITER_ARCH;
+		match = arg_match;
+		ip = 0;
+		return res;
+	}
+
+	/* Search for an architecture. */
+
+	for ( ; ip < npages; ip++)
+		if (pages[ip].arch)
+			for (cp = dbm_get(pages[ip].arch);
+			    *cp != '\0';
+			    cp = strchr(cp, '\0') + 1)
+				if (dbm_match(match, cp)) {
+					res.page = ip++;
+					return res;
+				}
+
+	/* Reached the end without a match. */
+
+	iteration = ITER_NONE;
+	match = NULL;
+	return res;
+}
+
+static struct dbm_res
+page_bymacro(int32_t im, const struct dbm_match *match)
+{
+	static const int32_t	*pp;
+	struct dbm_res		 res = {-1, 0};
+	const char		*cp;
+	int32_t			 iv;
+
+	assert(im >= 0);
+	assert(im < MACRO_MAX);
+
+	/* Initialize for a new iteration. */
+
+	if (match != NULL) {
+		iteration = ITER_MACRO;
+		cp = nvals[im] ? dbm_get(macros[im]->value) : NULL;
+		for (iv = 0; iv < nvals[im]; iv++) {
+			if (dbm_match(match, cp))
+				break;
+			cp = strchr(cp, '\0') + 1;
+		}
+		pp = iv == nvals[im] ? NULL : dbm_get(macros[im][iv].pages);
+		return res;
+	}
+	if (iteration != ITER_MACRO)
+		return res;
+
+	/* No more matches. */
+
+	if (pp == NULL || *pp == 0) {
+		iteration = ITER_NONE;
+		pp = NULL;
+		return res;
+	}
+
+	/* Found a match. */
+
+	res.page = (struct page *)dbm_get(*pp++) - pages;
+	return res;
+}
+
+
+/*** functions for handling macros ************************************/
+
+int32_t
+dbm_macro_count(int32_t im)
+{
+	assert(im >= 0);
+	assert(im < MACRO_MAX);
+	return nvals[im];
+}
+
+struct dbm_macro *
+dbm_macro_get(int32_t im, int32_t iv)
+{
+	static struct dbm_macro macro;
+
+	assert(im >= 0);
+	assert(im < MACRO_MAX);
+	assert(iv >= 0);
+	assert(iv < nvals[im]);
+	macro.value = dbm_get(macros[im][iv].value);
+	macro.pp = dbm_get(macros[im][iv].pages);
+	return &macro;
+}
+
+/*
+ * Filtered iteration over macro entries.
+ */
+void
+dbm_macro_bypage(int32_t im, int32_t ip)
+{
+	assert(im >= 0);
+	assert(im < MACRO_MAX);
+	assert(ip != 0);
+	macro_bypage(im, ip);
+}
+
+char *
+dbm_macro_next(void)
+{
+	return macro_bypage(MACRO_MAX, 0);
+}
+
+static char *
+macro_bypage(int32_t arg_im, int32_t arg_ip)
+{
+	static const int32_t	*pp;
+	static int32_t		 im, ip, iv;
+
+	/* Initialize for a new iteration. */
+
+	if (arg_im < MACRO_MAX && arg_ip != 0) {
+		im = arg_im;
+		ip = arg_ip;
+		pp = dbm_get(macros[im]->pages);
+		iv = 0;
+		return NULL;
+	}
+	if (im >= MACRO_MAX)
+		return NULL;
+
+	/* Search for the next value. */
+
+	while (iv < nvals[im]) {
+		if (*pp == ip)
+			break;
+		if (*pp == 0)
+			iv++;
+		pp++;
+	}
+
+	/* Reached the end without a match. */
+
+	if (iv == nvals[im]) {
+		im = MACRO_MAX;
+		ip = 0;
+		pp = NULL;
+		return NULL;
+	}
+
+	/* Found a match; skip the remaining pages of this entry. */
+
+	if (++iv < nvals[im])
+		while (*pp++ != 0)
+			continue;
+
+	return dbm_get(macros[im][iv - 1].value);
+}
--- /dev/null
+++ dba_write.c
@@ -0,0 +1,117 @@
+/*	$Id: dba_write.c,v 1.1 2016/07/19 21:31:55 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Low-level functions for serializing allocation-based data to disk.
+ * The interface is defined in "dba_write.h".
+ */
+#include <assert.h>
+#include <endian.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "dba_write.h"
+
+static FILE	*ofp;
+
+
+int
+dba_open(const char *fname)
+{
+	ofp = fopen(fname, "w");
+	return ofp == NULL ? -1 : 0;
+}
+
+int
+dba_close(void)
+{
+	return fclose(ofp) == EOF ? -1 : 0;
+}
+
+int32_t
+dba_tell(void)
+{
+	long		 pos;
+
+	if ((pos = ftell(ofp)) == -1)
+		err(1, "ftell");
+	if (pos >= INT32_MAX) {
+		errno = EOVERFLOW;
+		err(1, "ftell = %ld", pos);
+	}
+	return pos;
+}
+
+void
+dba_seek(int32_t pos)
+{
+	if (fseek(ofp, pos, SEEK_SET) == -1)
+		err(1, "fseek(%d)", pos);
+}
+
+int32_t
+dba_align(void)
+{
+	int32_t		 pos;
+
+	pos = dba_tell();
+	while (pos & 3) {
+		dba_char_write('\0');
+		pos++;
+	}
+	return pos;
+}
+
+int32_t
+dba_skip(int32_t nmemb, int32_t sz)
+{
+	const int32_t	 out[5] = {0, 0, 0, 0, 0};
+	int32_t		 i, pos;
+
+	assert(sz >= 0);
+	assert(nmemb > 0);
+	assert(nmemb <= 5);
+	pos = dba_tell();
+	for (i = 0; i < sz; i++)
+		if (nmemb - fwrite(&out, sizeof(out[0]), nmemb, ofp))
+			err(1, "fwrite");
+	return pos;
+}
+
+void
+dba_char_write(int c)
+{
+	if (putc(c, ofp) == EOF)
+		err(1, "fputc");
+}
+
+void
+dba_str_write(const char *str)
+{
+	if (fputs(str, ofp) == EOF)
+		err(1, "fputs");
+	dba_char_write('\0');
+}
+
+void
+dba_int_write(int32_t i)
+{
+	i = htobe32(i);
+	if (fwrite(&i, sizeof(i), 1, ofp) != 1)
+		err(1, "fwrite");
+}
--- /dev/null
+++ dba.h
@@ -0,0 +1,51 @@
+/*	$Id: dba.h,v 1.1 2016/07/19 21:31:55 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Public interface of the allocation-based version
+ * of the mandoc database, for read-write access.
+ * To be used by dba.c, dba_read.c, and makewhatis(8).
+ */
+
+#define	DBP_NAME	0
+#define	DBP_SECT	1
+#define	DBP_ARCH	2
+#define	DBP_DESC	3
+#define	DBP_FILE	4
+#define	DBP_MAX		5
+
+struct dba_array;
+
+struct dba {
+	struct dba_array	*pages;
+	struct dba_array	*macros;
+};
+
+
+struct dba	*dba_new(int32_t);
+void		 dba_free(struct dba *);
+struct dba	*dba_read(const char *);
+int		 dba_write(const char *, struct dba *);
+
+struct dba_array *dba_page_new(struct dba_array *, const char *,
+			const char *, const char *, const char *,
+			const char *, enum form);
+void		 dba_page_add(struct dba_array *, int32_t, const char *);
+void		 dba_page_alias(struct dba_array *, const char *, uint64_t);
+
+void		 dba_macro_new(struct dba *, int32_t,
+			const char *, const int32_t *);
+void		 dba_macro_add(struct dba_array *, int32_t,
+			const char *, struct dba_array *);
Index: mansearch.c
===================================================================
RCS file: /home/cvs/mdocml/mdocml/mansearch.c,v
retrieving revision 1.65
retrieving revision 1.66
diff -Lmansearch.c -Lmansearch.c -u -p -r1.65 -r1.66
--- mansearch.c
+++ mansearch.c
@@ -1,4 +1,4 @@
-/*	$Id$ */
+/*	$OpenBSD: mansearch.c,v 1.50 2016/07/09 15:23:36 schwarze Exp $ */
 /*
  * Copyright (c) 2012 Kristaps Dzonsons <kristaps@bsd.lv>
  * Copyright (c) 2013, 2014, 2015 Ingo Schwarze <schwarze@openbsd.org>
@@ -15,15 +15,12 @@
  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
-#include "config.h"
 
 #include <sys/mman.h>
 #include <sys/types.h>
 
 #include <assert.h>
-#if HAVE_ERR
 #include <err.h>
-#endif
 #include <errno.h>
 #include <fcntl.h>
 #include <glob.h>
@@ -36,121 +33,50 @@
 #include <string.h>
 #include <unistd.h>
 
-#include <sqlite3.h>
-#ifndef SQLITE_DETERMINISTIC
-#define SQLITE_DETERMINISTIC 0
-#endif
-
 #include "mandoc.h"
 #include "mandoc_aux.h"
 #include "mandoc_ohash.h"
 #include "manconf.h"
 #include "mansearch.h"
-
-extern int mansearch_keymax;
-extern const char *const mansearch_keynames[];
-
-#define	SQL_BIND_TEXT(_db, _s, _i, _v) \
-	do { if (SQLITE_OK != sqlite3_bind_text \
-		((_s), (_i)++, (_v), -1, SQLITE_STATIC)) \
-		errx((int)MANDOCLEVEL_SYSERR, "%s", sqlite3_errmsg((_db))); \
-	} while (0)
-#define	SQL_BIND_INT64(_db, _s, _i, _v) \
-	do { if (SQLITE_OK != sqlite3_bind_int64 \
-		((_s), (_i)++, (_v))) \
-		errx((int)MANDOCLEVEL_SYSERR, "%s", sqlite3_errmsg((_db))); \
-	} while (0)
-#define	SQL_BIND_BLOB(_db, _s, _i, _v) \
-	do { if (SQLITE_OK != sqlite3_bind_blob \
-		((_s), (_i)++, (&_v), sizeof(_v), SQLITE_STATIC)) \
-		errx((int)MANDOCLEVEL_SYSERR, "%s", sqlite3_errmsg((_db))); \
-	} while (0)
+#include "dbm.h"
 
 struct	expr {
-	regex_t		 regexp;  /* compiled regexp, if applicable */
-	const char	*substr;  /* to search for, if applicable */
-	struct expr	*next;    /* next in sequence */
-	uint64_t	 bits;    /* type-mask */
-	int		 equal;   /* equality, not subsring match */
-	int		 open;    /* opening parentheses before */
-	int		 and;	  /* logical AND before */
-	int		 close;   /* closing parentheses after */
+	/* Used for terms: */
+	struct dbm_match match;   /* Match type and expression. */
+	uint64_t	 bits;    /* Type mask. */
+	/* Used for OR and AND groups: */
+	struct expr	*next;    /* Next child in the parent group. */
+	struct expr	*child;   /* First child in this group. */
+	enum { EXPR_TERM, EXPR_OR, EXPR_AND } type;
 };
 
-struct	match {
-	uint64_t	 pageid; /* identifier in database */
-	uint64_t	 bits; /* name type mask */
-	char		*desc; /* manual page description */
-	int		 form; /* bit field: formatted, zipped? */
+const char *const mansearch_keynames[KEY_MAX] = {
+	"arch",	"sec",	"Xr",	"Ar",	"Fa",	"Fl",	"Dv",	"Fn",
+	"Ic",	"Pa",	"Cm",	"Li",	"Em",	"Cd",	"Va",	"Ft",
+	"Tn",	"Er",	"Ev",	"Sy",	"Sh",	"In",	"Ss",	"Ox",
+	"An",	"Mt",	"St",	"Bx",	"At",	"Nx",	"Fx",	"Lk",
+	"Ms",	"Bsx",	"Dx",	"Rs",	"Vt",	"Lb",	"Nm",	"Nd"
 };
 
-static	void		 buildnames(const struct mansearch *,
-				struct manpage *, sqlite3 *,
-				sqlite3_stmt *, uint64_t,
-				const char *, int form);
-static	char		*buildoutput(sqlite3 *, sqlite3_stmt *,
-				 uint64_t, uint64_t);
+
+static	struct ohash	*manmerge(struct expr *, struct ohash *);
+static	struct ohash	*manmerge_term(struct expr *, struct ohash *);
+static	struct ohash	*manmerge_or(struct expr *, struct ohash *);
+static	struct ohash	*manmerge_and(struct expr *, struct ohash *);
+static	char		*buildnames(const struct dbm_page *);
+static	char		*buildoutput(size_t, int32_t);
+static	size_t		 lstlen(const char *);
+static	void		 lstcat(char *, size_t *, const char *);
+static	int		 lstmatch(const char *, const char *);
 static	struct expr	*exprcomp(const struct mansearch *,
-				int, char *[]);
+				int, char *[], int *);
+static	struct expr	*expr_and(const struct mansearch *,
+				int, char *[], int *);
+static	struct expr	*exprterm(const struct mansearch *,
+				int, char *[], int *);
 static	void		 exprfree(struct expr *);
-static	struct expr	*exprterm(const struct mansearch *, char *, int);
 static	int		 manpage_compare(const void *, const void *);
-static	void		 sql_append(char **sql, size_t *sz,
-				const char *newstr, int count);
-static	void		 sql_match(sqlite3_context *context,
-				int argc, sqlite3_value **argv);
-static	void		 sql_regexp(sqlite3_context *context,
-				int argc, sqlite3_value **argv);
-static	char		*sql_statement(const struct expr *);
-
-
-int
-mansearch_setup(int start)
-{
-	static void	*pagecache;
-	int		 c;
-
-#define	PC_PAGESIZE	1280
-#define	PC_NUMPAGES	256
-
-	if (start) {
-		if (NULL != pagecache) {
-			warnx("pagecache already enabled");
-			return (int)MANDOCLEVEL_BADARG;
-		}
-
-		pagecache = mmap(NULL, PC_PAGESIZE * PC_NUMPAGES,
-		    PROT_READ | PROT_WRITE,
-		    MAP_SHARED | MAP_ANON, -1, 0);
-
-		if (MAP_FAILED == pagecache) {
-			warn("mmap");
-			pagecache = NULL;
-			return (int)MANDOCLEVEL_SYSERR;
-		}
-
-		c = sqlite3_config(SQLITE_CONFIG_PAGECACHE,
-		    pagecache, PC_PAGESIZE, PC_NUMPAGES);
-
-		if (SQLITE_OK == c)
-			return (int)MANDOCLEVEL_OK;
-
-		warnx("pagecache: %s", sqlite3_errstr(c));
-
-	} else if (NULL == pagecache) {
-		warnx("pagecache missing");
-		return (int)MANDOCLEVEL_BADARG;
-	}
-
-	if (-1 == munmap(pagecache, PC_PAGESIZE * PC_NUMPAGES)) {
-		warn("munmap");
-		pagecache = NULL;
-		return (int)MANDOCLEVEL_SYSERR;
-	}
 
-	pagecache = NULL;
-	return (int)MANDOCLEVEL_OK;
-}
 
 int
 mansearch(const struct mansearch *search,
@@ -158,21 +84,18 @@ mansearch(const struct mansearch *search
 		int argc, char *argv[],
 		struct manpage **res, size_t *sz)
 {
-	int64_t		 pageid;
-	uint64_t	 outbit, iterbit;
 	char		 buf[PATH_MAX];
-	char		*sql;
+	struct dbm_res	*rp;
+	struct expr	*e;
+	struct dbm_page	*page;
 	struct manpage	*mpage;
-	struct expr	*e, *ep;
-	sqlite3		*db;
-	sqlite3_stmt	*s, *s2;
-	struct match	*mp;
-	struct ohash	 htab;
-	unsigned int	 idx;
-	size_t		 i, j, cur, maxres;
-	int		 c, chdir_status, getcwd_status, indexbit;
+	struct ohash	*htab;
+	size_t		 cur, i, maxres, outkey;
+	unsigned int	 slot;
+	int		 argi, chdir_status, getcwd_status, im;
 
-	if (argc == 0 || (e = exprcomp(search, argc, argv)) == NULL) {
+	argi = 0;
+	if ((e = exprcomp(search, argc, argv, &argi)) == NULL) {
 		*sz = 0;
 		return 0;
 	}
@@ -180,19 +103,14 @@ mansearch(const struct mansearch *search
 	cur = maxres = 0;
 	*res = NULL;
 
-	if (NULL != search->outkey) {
-		outbit = TYPE_Nd;
-		for (indexbit = 0, iterbit = 1;
-		     indexbit < mansearch_keymax;
-		     indexbit++, iterbit <<= 1) {
+	outkey = KEY_Nd;
+	if (search->outkey != NULL)
+		for (im = 0; im < KEY_MAX; im++)
 			if (0 == strcasecmp(search->outkey,
-			    mansearch_keynames[indexbit])) {
-				outbit = iterbit;
+			    mansearch_keynames[im])) {
+				outkey = im;
 				break;
 			}
-		}
-	} else
-		outbit = 0;
 
 	/*
 	 * Remember the original working directory, if possible.
@@ -208,8 +126,6 @@ mansearch(const struct mansearch *search
 	} else
 		getcwd_status = 1;
 
-	sql = sql_statement(e);
-
 	/*
 	 * Loop over the directories (containing databases) for us to
 	 * search.
@@ -235,123 +151,48 @@ mansearch(const struct mansearch *search
 		}
 		chdir_status = 1;
 
-		c = sqlite3_open_v2(MANDOC_DB, &db,
-		    SQLITE_OPEN_READONLY, NULL);
-
-		if (SQLITE_OK != c) {
+		if (dbm_open(MANDOC_DB) == -1) {
 			warn("%s/%s", paths->paths[i], MANDOC_DB);
-			sqlite3_close(db);
 			continue;
 		}
 
-		/*
-		 * Define the SQL functions for substring
-		 * and regular expression matching.
-		 */
-
-		c = sqlite3_create_function(db, "match", 2,
-		    SQLITE_UTF8 | SQLITE_DETERMINISTIC,
-		    NULL, sql_match, NULL, NULL);
-		assert(SQLITE_OK == c);
-		c = sqlite3_create_function(db, "regexp", 2,
-		    SQLITE_UTF8 | SQLITE_DETERMINISTIC,
-		    NULL, sql_regexp, NULL, NULL);
-		assert(SQLITE_OK == c);
-
-		j = 1;
-		c = sqlite3_prepare_v2(db, sql, -1, &s, NULL);
-		if (SQLITE_OK != c)
-			errx((int)MANDOCLEVEL_SYSERR,
-			    "%s", sqlite3_errmsg(db));
-
-		for (ep = e; NULL != ep; ep = ep->next) {
-			if (NULL == ep->substr) {
-				SQL_BIND_BLOB(db, s, j, ep->regexp);
-			} else
-				SQL_BIND_TEXT(db, s, j, ep->substr);
-			if (0 == ((TYPE_Nd | TYPE_Nm) & ep->bits))
-				SQL_BIND_INT64(db, s, j, ep->bits);
+		if ((htab = manmerge(e, NULL)) == NULL) {
+			dbm_close();
+			continue;
 		}
 
-		mandoc_ohash_init(&htab, 4, offsetof(struct match, pageid));
+		for (rp = ohash_first(htab, &slot); rp != NULL;
+		    rp = ohash_next(htab, &slot)) {
+			page = dbm_page_get(rp->page);
 
-		/*
-		 * Hash each entry on its [unique] document identifier.
-		 * This is a uint64_t.
-		 * Instead of using a hash function, simply convert the
-		 * uint64_t to a uint32_t, the hash value's type.
-		 * This gives good performance and preserves the
-		 * distribution of buckets in the table.
-		 */
-		while (SQLITE_ROW == (c = sqlite3_step(s))) {
-			pageid = sqlite3_column_int64(s, 2);
-			idx = ohash_lookup_memory(&htab,
-			    (char *)&pageid, sizeof(uint64_t),
-			    (uint32_t)pageid);
-
-			if (NULL != ohash_find(&htab, idx))
+			if (lstmatch(search->sec, page->sect) == 0 ||
+			    lstmatch(search->arch, page->arch) == 0)
 				continue;
 
-			mp = mandoc_calloc(1, sizeof(struct match));
-			mp->pageid = pageid;
-			mp->form = sqlite3_column_int(s, 1);
-			mp->bits = sqlite3_column_int64(s, 3);
-			if (TYPE_Nd == outbit)
-				mp->desc = mandoc_strdup((const char *)
-				    sqlite3_column_text(s, 0));
-			ohash_insert(&htab, idx, mp);
-		}
-
-		if (SQLITE_DONE != c)
-			warnx("%s", sqlite3_errmsg(db));
-
-		sqlite3_finalize(s);
-
-		c = sqlite3_prepare_v2(db,
-		    "SELECT sec, arch, name, pageid FROM mlinks "
-		    "WHERE pageid=? ORDER BY sec, arch, name",
-		    -1, &s, NULL);
-		if (SQLITE_OK != c)
-			errx((int)MANDOCLEVEL_SYSERR,
-			    "%s", sqlite3_errmsg(db));
-
-		c = sqlite3_prepare_v2(db,
-		    "SELECT bits, key, pageid FROM keys "
-		    "WHERE pageid=? AND bits & ?",
-		    -1, &s2, NULL);
-		if (SQLITE_OK != c)
-			errx((int)MANDOCLEVEL_SYSERR,
-			    "%s", sqlite3_errmsg(db));
-
-		for (mp = ohash_first(&htab, &idx);
-				NULL != mp;
-				mp = ohash_next(&htab, &idx)) {
 			if (cur + 1 > maxres) {
 				maxres += 1024;
 				*res = mandoc_reallocarray(*res,
-				    maxres, sizeof(struct manpage));
+				    maxres, sizeof(**res));
 			}
 			mpage = *res + cur;
+			mandoc_asprintf(&mpage->file, "%s/%s",
+			    paths->paths[i], page->file + 1);
+			mpage->names = buildnames(page);
+			mpage->output = (int)outkey == KEY_Nd ?
+			    mandoc_strdup(page->desc) :
+			    buildoutput(outkey, page->addr);
 			mpage->ipath = i;
-			mpage->bits = mp->bits;
-			mpage->sec = 10;
-			mpage->form = mp->form;
-			buildnames(search, mpage, db, s, mp->pageid,
-			    paths->paths[i], mp->form);
-			if (mpage->names != NULL) {
-				mpage->output = TYPE_Nd & outbit ?
-				    mp->desc : outbit ?
-				    buildoutput(db, s2, mp->pageid, outbit) :
-				    NULL;
-				cur++;
-			}
-			free(mp);
-		}
-
-		sqlite3_finalize(s);
-		sqlite3_finalize(s2);
-		sqlite3_close(db);
-		ohash_delete(&htab);
+			mpage->bits = rp->bits;
+			mpage->sec = *page->sect - '0';
+			if (mpage->sec < 0 || mpage->sec > 9)
+				mpage->sec = 10;
+			mpage->form = *page->file;
+			free(rp);
+			cur++;
+		}
+		ohash_delete(htab);
+		free(htab);
+		dbm_close();
 
 		/*
 		 * In man(1) mode, prefer matches in earlier trees
@@ -365,291 +206,303 @@ mansearch(const struct mansearch *search
 	if (chdir_status && getcwd_status && chdir(buf) == -1)
 		warn("%s", buf);
 	exprfree(e);
-	free(sql);
 	*sz = cur;
 	return 1;
 }
 
-void
-mansearch_free(struct manpage *res, size_t sz)
+/*
+ * Merge the results for the expression tree rooted at e
+ * into the the result list htab.
+ */
+static struct ohash *
+manmerge(struct expr *e, struct ohash *htab)
 {
-	size_t	 i;
-
-	for (i = 0; i < sz; i++) {
-		free(res[i].file);
-		free(res[i].names);
-		free(res[i].output);
+	switch (e->type) {
+	case EXPR_TERM:
+		return manmerge_term(e, htab);
+	case EXPR_OR:
+		return manmerge_or(e->child, htab);
+	case EXPR_AND:
+		return manmerge_and(e->child, htab);
+	default:
+		abort();
 	}
-	free(res);
 }
 
-static int
-manpage_compare(const void *vp1, const void *vp2)
+static struct ohash *
+manmerge_term(struct expr *e, struct ohash *htab)
 {
-	const struct manpage	*mp1, *mp2;
-	int			 diff;
+	struct dbm_res	 res, *rp;
+	uint64_t	 ib;
+	unsigned int	 slot;
+	int		 im;
 
-	mp1 = vp1;
-	mp2 = vp2;
-	return (diff = mp2->bits - mp1->bits) ? diff :
-	    (diff = mp1->sec - mp2->sec) ? diff :
-	    strcasecmp(mp1->names, mp2->names);
-}
+	if (htab == NULL) {
+		htab = mandoc_malloc(sizeof(*htab));
+		mandoc_ohash_init(htab, 4, offsetof(struct dbm_res, page));
+	}
 
-static void
-buildnames(const struct mansearch *search, struct manpage *mpage,
-		sqlite3 *db, sqlite3_stmt *s,
-		uint64_t pageid, const char *path, int form)
-{
-	glob_t		 globinfo;
-	char		*firstname, *newnames, *prevsec, *prevarch;
-	const char	*oldnames, *sep1, *name, *sec, *sep2, *arch, *fsec;
-	size_t		 i;
-	int		 c, globres;
-
-	mpage->file = NULL;
-	mpage->names = NULL;
-	firstname = prevsec = prevarch = NULL;
-	i = 1;
-	SQL_BIND_INT64(db, s, i, pageid);
-	while (SQLITE_ROW == (c = sqlite3_step(s))) {
-
-		/* Decide whether we already have some names. */
-
-		if (NULL == mpage->names) {
-			oldnames = "";
-			sep1 = "";
-		} else {
-			oldnames = mpage->names;
-			sep1 = ", ";
+	for (im = 0, ib = 1; im < KEY_MAX; im++, ib <<= 1) {
+		if ((e->bits & ib) == 0)
+			continue;
+
+		switch (ib) {
+		case TYPE_arch:
+			dbm_page_byarch(&e->match);
+			break;
+		case TYPE_sec:
+			dbm_page_bysect(&e->match);
+			break;
+		case TYPE_Nm:
+			dbm_page_byname(&e->match);
+			break;
+		case TYPE_Nd:
+			dbm_page_bydesc(&e->match);
+			break;
+		default:
+			dbm_page_bymacro(im - 2, &e->match);
+			break;
 		}
 
-		/* Fetch the next name, rejecting sec/arch mismatches. */
+		/*
+		 * When hashing for deduplication, use the unique
+		 * page ID itself instead of a hash function;
+		 * that is quite efficient.
+		 */
 
-		sec = (const char *)sqlite3_column_text(s, 0);
-		if (search->sec != NULL && strcasecmp(sec, search->sec))
-			continue;
-		arch = (const char *)sqlite3_column_text(s, 1);
-		if (search->arch != NULL && *arch != '\0' &&
-		    strcasecmp(arch, search->arch))
-			continue;
-		name = (const char *)sqlite3_column_text(s, 2);
+		for (;;) {
+			res = dbm_page_next();
+			if (res.page == -1)
+				break;
+			slot = ohash_lookup_memory(htab,
+			    (char *)&res, sizeof(res.page), res.page);
+			if ((rp = ohash_find(htab, slot)) != NULL) {
+				rp->bits |= res.bits;
+				continue;
+			}
+			rp = mandoc_malloc(sizeof(*rp));
+			*rp = res;
+			ohash_insert(htab, slot, rp);
+		}
+	}
+	return htab;
+}
 
-		/* Remember the first section found. */
+static struct ohash *
+manmerge_or(struct expr *e, struct ohash *htab)
+{
+	while (e != NULL) {
+		htab = manmerge(e, htab);
+		e = e->next;
+	}
+	return htab;
+}
 
-		if (9 < mpage->sec && '1' <= *sec && '9' >= *sec)
-			mpage->sec = (*sec - '1') + 1;
+static struct ohash *
+manmerge_and(struct expr *e, struct ohash *htab)
+{
+	struct ohash	*hand, *h1, *h2;
+	struct dbm_res	*res;
+	unsigned int	 slot1, slot2;
 
-		/* If the section changed, append the old one. */
+	/* Evaluate the first term of the AND clause. */
 
-		if (NULL != prevsec &&
-		    (strcmp(sec, prevsec) ||
-		     strcmp(arch, prevarch))) {
-			sep2 = '\0' == *prevarch ? "" : "/";
-			mandoc_asprintf(&newnames, "%s(%s%s%s)",
-			    oldnames, prevsec, sep2, prevarch);
-			free(mpage->names);
-			oldnames = mpage->names = newnames;
-			free(prevsec);
-			free(prevarch);
-			prevsec = prevarch = NULL;
-		}
+	hand = manmerge(e, NULL);
 
-		/* Save the new section, to append it later. */
+	while ((e = e->next) != NULL) {
 
-		if (NULL == prevsec) {
-			prevsec = mandoc_strdup(sec);
-			prevarch = mandoc_strdup(arch);
-		}
+		/* Evaluate the next term and prepare for ANDing. */
 
-		/* Append the new name. */
+		h2 = manmerge(e, NULL);
+		if (ohash_entries(h2) < ohash_entries(hand)) {
+			h1 = h2;
+			h2 = hand;
+		} else
+			h1 = hand;
+		hand = mandoc_malloc(sizeof(*hand));
+		mandoc_ohash_init(hand, 4, offsetof(struct dbm_res, page));
 
-		mandoc_asprintf(&newnames, "%s%s%s",
-		    oldnames, sep1, name);
-		free(mpage->names);
-		mpage->names = newnames;
+		/* Keep all pages that are in both result sets. */
 
-		/* Also save the first file name encountered. */
+		for (res = ohash_first(h1, &slot1); res != NULL;
+		    res = ohash_next(h1, &slot1)) {
+			if (ohash_find(h2, ohash_lookup_memory(h2,
+			    (char *)res, sizeof(res->page),
+			    res->page)) == NULL)
+				free(res);
+			else
+				ohash_insert(hand, ohash_lookup_memory(hand,
+				    (char *)res, sizeof(res->page),
+				    res->page), res);
+		}
 
-		if (mpage->file != NULL)
-			continue;
+		/* Discard the merged results. */
 
-		if (form & FORM_SRC) {
-			sep1 = "man";
-			fsec = sec;
-		} else {
-			sep1 = "cat";
-			fsec = "0";
-		}
-		sep2 = *arch == '\0' ? "" : "/";
-		mandoc_asprintf(&mpage->file, "%s/%s%s%s%s/%s.%s",
-		    path, sep1, sec, sep2, arch, name, fsec);
-		if (access(mpage->file, R_OK) != -1)
-			continue;
+		for (res = ohash_first(h2, &slot2); res != NULL;
+		    res = ohash_next(h2, &slot2))
+			free(res);
+		ohash_delete(h2);
+		free(h2);
+		ohash_delete(h1);
+		free(h1);
+	}
+
+	/* Merge the result of the AND into htab. */
 
-		/* Handle unusual file name extensions. */
+	if (htab == NULL)
+		return hand;
 
-		if (firstname == NULL)
-			firstname = mpage->file;
+	for (res = ohash_first(hand, &slot1); res != NULL;
+	    res = ohash_next(hand, &slot1)) {
+		slot2 = ohash_lookup_memory(htab,
+		    (char *)res, sizeof(res->page), res->page);
+		if (ohash_find(htab, slot2) == NULL)
+			ohash_insert(htab, slot2, res);
 		else
-			free(mpage->file);
-		mandoc_asprintf(&mpage->file, "%s/%s%s%s%s/%s.*",
-		    path, sep1, sec, sep2, arch, name);
-		globres = glob(mpage->file, 0, NULL, &globinfo);
-		free(mpage->file);
-		mpage->file = globres ? NULL :
-		    mandoc_strdup(*globinfo.gl_pathv);
-		globfree(&globinfo);
-	}
-	if (c != SQLITE_DONE)
-		warnx("%s", sqlite3_errmsg(db));
-	sqlite3_reset(s);
-
-	/* If none of the files is usable, use the first name. */
-
-	if (mpage->file == NULL)
-		mpage->file = firstname;
-	else if (mpage->file != firstname)
-		free(firstname);
-
-	/* Append one final section to the names. */
-
-	if (prevsec != NULL) {
-		sep2 = *prevarch == '\0' ? "" : "/";
-		mandoc_asprintf(&newnames, "%s(%s%s%s)",
-		    mpage->names, prevsec, sep2, prevarch);
-		free(mpage->names);
-		mpage->names = newnames;
-		free(prevsec);
-		free(prevarch);
+			free(res);
 	}
+
+	/* Discard the merged result. */
+
+	ohash_delete(hand);
+	free(hand);
+	return htab;
 }
 
-static char *
-buildoutput(sqlite3 *db, sqlite3_stmt *s, uint64_t pageid, uint64_t outbit)
+void
+mansearch_free(struct manpage *res, size_t sz)
 {
-	char		*output, *newoutput;
-	const char	*oldoutput, *sep1, *data;
-	size_t		 i;
-	int		 c;
+	size_t	 i;
 
-	output = NULL;
-	i = 1;
-	SQL_BIND_INT64(db, s, i, pageid);
-	SQL_BIND_INT64(db, s, i, outbit);
-	while (SQLITE_ROW == (c = sqlite3_step(s))) {
-		if (NULL == output) {
-			oldoutput = "";
-			sep1 = "";
-		} else {
-			oldoutput = output;
-			sep1 = " # ";
-		}
-		data = (const char *)sqlite3_column_text(s, 1);
-		mandoc_asprintf(&newoutput, "%s%s%s",
-		    oldoutput, sep1, data);
-		free(output);
-		output = newoutput;
+	for (i = 0; i < sz; i++) {
+		free(res[i].file);
+		free(res[i].names);
+		free(res[i].output);
 	}
-	if (SQLITE_DONE != c)
-		warnx("%s", sqlite3_errmsg(db));
-	sqlite3_reset(s);
-	return output;
+	free(res);
 }
 
-/*
- * Implement substring match as an application-defined SQL function.
- * Using the SQL LIKE or GLOB operators instead would be a bad idea
- * because that would require escaping metacharacters in the string
- * being searched for.
- */
-static void
-sql_match(sqlite3_context *context, int argc, sqlite3_value **argv)
+static int
+manpage_compare(const void *vp1, const void *vp2)
+{
+	const struct manpage	*mp1, *mp2;
+	int			 diff;
+
+	mp1 = vp1;
+	mp2 = vp2;
+	return (diff = mp2->bits - mp1->bits) ? diff :
+	    (diff = mp1->sec - mp2->sec) ? diff :
+	    strcasecmp(mp1->names, mp2->names);
+}
+
+static char *
+buildnames(const struct dbm_page *page)
 {
+	char	*buf;
+	size_t	 i, sz;
 
-	assert(2 == argc);
-	sqlite3_result_int(context, NULL != strcasestr(
-	    (const char *)sqlite3_value_text(argv[1]),
-	    (const char *)sqlite3_value_text(argv[0])));
+	sz = lstlen(page->name) + 1 + lstlen(page->sect) +
+	    (page->arch == NULL ? 0 : 1 + lstlen(page->arch)) + 2;
+	buf = mandoc_malloc(sz);
+	i = 0;
+	lstcat(buf, &i, page->name);
+	buf[i++] = '(';
+	lstcat(buf, &i, page->sect);
+	if (page->arch != NULL) {
+		buf[i++] = '/';
+		lstcat(buf, &i, page->arch);
+	}
+	buf[i++] = ')';
+	buf[i++] = '\0';
+	assert(i == sz);
+	return buf;
 }
 
 /*
- * Implement regular expression match
- * as an application-defined SQL function.
+ * Count the buffer space needed to print the NUL-terminated
+ * list of NUL-terminated strings, when printing two separator
+ * characters between strings.
  */
-static void
-sql_regexp(sqlite3_context *context, int argc, sqlite3_value **argv)
+static size_t
+lstlen(const char *cp)
 {
+	size_t	 sz;
 
-	assert(2 == argc);
-	sqlite3_result_int(context, !regexec(
-	    (regex_t *)sqlite3_value_blob(argv[0]),
-	    (const char *)sqlite3_value_text(argv[1]),
-	    0, NULL, 0));
+	for (sz = 0;; sz++) {
+		if (cp[0] == '\0') {
+			if (cp[1] == '\0')
+				break;
+			sz++;
+		} else if (cp[0] < ' ')
+			sz--;
+		cp++;
+	}
+	return sz;
 }
 
+/*
+ * Print the NUL-terminated list of NUL-terminated strings
+ * into the buffer, seperating strings with a comma and a blank.
+ */
 static void
-sql_append(char **sql, size_t *sz, const char *newstr, int count)
+lstcat(char *buf, size_t *i, const char *cp)
 {
-	size_t		 newsz;
+	for (;;) {
+		if (cp[0] == '\0') {
+			if (cp[1] == '\0')
+				break;
+			buf[(*i)++] = ',';
+			buf[(*i)++] = ' ';
+		} else if (cp[0] >= ' ')
+			buf[(*i)++] = cp[0];
+		cp++;
+	}
+}
 
-	newsz = 1 < count ? (size_t)count : strlen(newstr);
-	*sql = mandoc_realloc(*sql, *sz + newsz + 1);
-	if (1 < count)
-		memset(*sql + *sz, *newstr, (size_t)count);
-	else
-		memcpy(*sql + *sz, newstr, newsz);
-	*sz += newsz;
-	(*sql)[*sz] = '\0';
+/*
+ * Return 1 if the string *want occurs in any of the strings
+ * in the NUL-terminated string list *have, or 0 otherwise.
+ * If either argument is NULL or empty, assume no filtering
+ * is desired and return 1.
+ */
+static int
+lstmatch(const char *want, const char *have)
+{
+        if (want == NULL || have == NULL || *have == '\0')
+                return 1;
+        while (*have != '\0') {
+                if (strcasestr(have, want) != NULL)
+                        return 1;
+                have = strchr(have, '\0') + 1;
+        }
+        return 0;
 }
 
 /*
- * Prepare the search SQL statement.
+ * Build a list of values taken by the macro im
+ * in the manual page with big-endian address addr.
  */
 static char *
-sql_statement(const struct expr *e)
+buildoutput(size_t im, int32_t addr)
 {
-	char		*sql;
-	size_t		 sz;
-	int		 needop;
-
-	sql = mandoc_strdup(e->equal ?
-	    "SELECT desc, form, pageid, bits "
-		"FROM mpages NATURAL JOIN names WHERE " :
-	    "SELECT desc, form, pageid, 0 FROM mpages WHERE ");
-	sz = strlen(sql);
-
-	for (needop = 0; NULL != e; e = e->next) {
-		if (e->and)
-			sql_append(&sql, &sz, " AND ", 1);
-		else if (needop)
-			sql_append(&sql, &sz, " OR ", 1);
-		if (e->open)
-			sql_append(&sql, &sz, "(", e->open);
-		sql_append(&sql, &sz,
-		    TYPE_Nd & e->bits
-		    ? (NULL == e->substr
-			? "desc REGEXP ?"
-			: "desc MATCH ?")
-		    : TYPE_Nm == e->bits
-		    ? (NULL == e->substr
-			? "pageid IN (SELECT pageid FROM names "
-			  "WHERE name REGEXP ?)"
-			: e->equal
-			? "name = ? "
-			: "pageid IN (SELECT pageid FROM names "
-			  "WHERE name MATCH ?)")
-		    : (NULL == e->substr
-			? "pageid IN (SELECT pageid FROM keys "
-			  "WHERE key REGEXP ? AND bits & ?)"
-			: "pageid IN (SELECT pageid FROM keys "
-			  "WHERE key MATCH ? AND bits & ?)"), 1);
-		if (e->close)
-			sql_append(&sql, &sz, ")", e->close);
-		needop = 1;
-	}
+	const char	*oldoutput, *sep;
+	char		*output, *newoutput, *value;
 
-	return sql;
+	output = NULL;
+	dbm_macro_bypage(im - 2, addr);
+	while ((value = dbm_macro_next()) != NULL) {
+		if (output == NULL) {
+			oldoutput = "";
+			sep = "";
+		} else {
+			oldoutput = output;
+			sep = " # ";
+		}
+		mandoc_asprintf(&newoutput, "%s%s%s", oldoutput, sep, value);
+		free(output);
+		output = newoutput;
+	}
+	return output;
 }
 
 /*
@@ -658,195 +511,238 @@ sql_statement(const struct expr *e)
  * "(", "foo=bar", etc.).
  */
 static struct expr *
-exprcomp(const struct mansearch *search, int argc, char *argv[])
+exprcomp(const struct mansearch *search, int argc, char *argv[], int *argi)
 {
-	uint64_t	 mask;
-	int		 i, toopen, logic, igncase, toclose;
-	struct expr	*first, *prev, *cur, *next;
-
-	first = cur = NULL;
-	logic = igncase = toopen = toclose = 0;
-
-	for (i = 0; i < argc; i++) {
-		if (0 == strcmp("(", argv[i])) {
-			if (igncase)
-				goto fail;
-			toopen++;
-			toclose++;
-			continue;
-		} else if (0 == strcmp(")", argv[i])) {
-			if (toopen || logic || igncase || NULL == cur)
-				goto fail;
-			cur->close++;
-			if (0 > --toclose)
-				goto fail;
-			continue;
-		} else if (0 == strcmp("-a", argv[i])) {
-			if (toopen || logic || igncase || NULL == cur)
-				goto fail;
-			logic = 1;
+	struct expr	*parent, *child;
+	int		 needterm, nested;
+
+	if ((nested = *argi) == argc)
+		return NULL;
+	needterm = 1;
+	parent = child = NULL;
+	while (*argi < argc) {
+		if (strcmp(")", argv[*argi]) == 0) {
+			if (needterm)
+				warnx("missing term "
+				    "before closing parenthesis");
+			needterm = 0;
+			if (nested)
+				break;
+			warnx("ignoring unmatched right parenthesis");
+			++*argi;
 			continue;
-		} else if (0 == strcmp("-o", argv[i])) {
-			if (toopen || logic || igncase || NULL == cur)
-				goto fail;
-			logic = 2;
+		}
+		if (strcmp("-o", argv[*argi]) == 0) {
+			if (needterm) {
+				if (*argi > 0)
+					warnx("ignoring -o after %s",
+					    argv[*argi - 1]);
+				else
+					warnx("ignoring initial -o");
+			}
+			needterm = 1;
+			++*argi;
 			continue;
-		} else if (0 == strcmp("-i", argv[i])) {
-			if (igncase)
-				goto fail;
-			igncase = 1;
+		}
+		needterm = 0;
+		if (child == NULL) {
+			child = expr_and(search, argc, argv, argi);
 			continue;
 		}
-		next = exprterm(search, argv[i], !igncase);
-		if (NULL == next)
-			goto fail;
-		if (NULL == first)
-			first = next;
-		else
-			cur->next = next;
-		prev = cur = next;
+		if (parent == NULL) {
+			parent = mandoc_calloc(1, sizeof(*parent));
+			parent->type = EXPR_OR;
+			parent->next = NULL;
+			parent->child = child;
+		}
+		child->next = expr_and(search, argc, argv, argi);
+		child = child->next;
+	}
+	if (needterm && *argi)
+		warnx("ignoring trailing %s", argv[*argi - 1]);
+	return parent == NULL ? child : parent;
+}
 
-		/*
-		 * Searching for descriptions must be split out
-		 * because they are stored in the mpages table,
-		 * not in the keys table.
-		 */
+static struct expr *
+expr_and(const struct mansearch *search, int argc, char *argv[], int *argi)
+{
+	struct expr	*parent, *child;
+	int		 needterm;
 
-		for (mask = TYPE_Nm; mask <= TYPE_Nd; mask <<= 1) {
-			if (mask & cur->bits && ~mask & cur->bits) {
-				next = mandoc_calloc(1,
-				    sizeof(struct expr));
-				memcpy(next, cur, sizeof(struct expr));
-				prev->open = 1;
-				cur->bits = mask;
-				cur->next = next;
-				cur = next;
-				cur->bits &= ~mask;
+	needterm = 1;
+	parent = child = NULL;
+	while (*argi < argc) {
+		if (strcmp(")", argv[*argi]) == 0) {
+			if (needterm)
+				warnx("missing term "
+				    "before closing parenthesis");
+			needterm = 0;
+			break;
+		}
+		if (strcmp("-o", argv[*argi]) == 0)
+			break;
+		if (strcmp("-a", argv[*argi]) == 0) {
+			if (needterm) {
+				if (*argi > 0)
+					warnx("ignoring -a after %s",
+					    argv[*argi - 1]);
+				else
+					warnx("ignoring initial -a");
 			}
+			needterm = 1;
+			++*argi;
+			continue;
 		}
-		prev->and = (1 == logic);
-		prev->open += toopen;
-		if (cur != prev)
-			cur->close = 1;
-
-		toopen = logic = igncase = 0;
-	}
-	if ( ! (toopen || logic || igncase || toclose))
-		return first;
-
-fail:
-	if (NULL != first)
-		exprfree(first);
-	return NULL;
+		if (needterm == 0)
+			break;
+		if (child == NULL) {
+			child = exprterm(search, argc, argv, argi);
+			if (child != NULL)
+				needterm = 0;
+			continue;
+		}
+		needterm = 0;
+		if (parent == NULL) {
+			parent = mandoc_calloc(1, sizeof(*parent));
+			parent->type = EXPR_AND;
+			parent->next = NULL;
+			parent->child = child;
+		}
+		child->next = exprterm(search, argc, argv, argi);
+		if (child->next != NULL) {
+			child = child->next;
+			needterm = 0;
+		}
+	}
+	if (needterm && *argi)
+		warnx("ignoring trailing %s", argv[*argi - 1]);
+	return parent == NULL ? child : parent;
 }
 
 static struct expr *
-exprterm(const struct mansearch *search, char *buf, int cs)
+exprterm(const struct mansearch *search, int argc, char *argv[], int *argi)
 {
 	char		 errbuf[BUFSIZ];
 	struct expr	*e;
 	char		*key, *val;
 	uint64_t	 iterbit;
-	int		 i, irc;
+	int		 cs, i, irc;
 
-	if ('\0' == *buf)
-		return NULL;
+	if (strcmp("(", argv[*argi]) == 0) {
+		++*argi;
+		e = exprcomp(search, argc, argv, argi);
+		if (*argi < argc) {
+			assert(strcmp(")", argv[*argi]) == 0);
+			++*argi;
+		} else
+			warnx("unclosed parenthesis");
+		return e;
+	}
 
-	e = mandoc_calloc(1, sizeof(struct expr));
+	e = mandoc_calloc(1, sizeof(*e));
+	e->type = EXPR_TERM;
+	e->bits = 0;
+	e->next = NULL;
+	e->child = NULL;
 
 	if (search->argmode == ARG_NAME) {
 		e->bits = TYPE_Nm;
-		e->substr = buf;
-		e->equal = 1;
+		e->match.type = DBM_EXACT;
+		e->match.str = argv[(*argi)++];
 		return e;
 	}
 
 	/*
 	 * Separate macro keys from search string.
-	 * If needed, request regular expression handling
-	 * by setting e->substr to NULL.
+	 * If needed, request regular expression handling.
 	 */
 
 	if (search->argmode == ARG_WORD) {
 		e->bits = TYPE_Nm;
-		e->substr = NULL;
+		e->match.type = DBM_REGEX;
 #if HAVE_REWB_BSD
-		mandoc_asprintf(&val, "[[:<:]]%s[[:>:]]", buf);
+		mandoc_asprintf(&val, "[[:<:]]%s[[:>:]]", argv[*argi]);
 #elif HAVE_REWB_SYSV
-		mandoc_asprintf(&val, "\\<%s\\>", buf);
+		mandoc_asprintf(&val, "\\<%s\\>", argv[*argi]);
 #else
 		mandoc_asprintf(&val,
-		    "(^|[^a-zA-Z01-9_])%s([^a-zA-Z01-9_]|$)", buf);
+		    "(^|[^a-zA-Z01-9_])%s([^a-zA-Z01-9_]|$)", argv[*argi]);
 #endif
 		cs = 0;
-	} else if ((val = strpbrk(buf, "=~")) == NULL) {
+	} else if ((val = strpbrk(argv[*argi], "=~")) == NULL) {
 		e->bits = TYPE_Nm | TYPE_Nd;
-		e->substr = buf;
+		e->match.type = DBM_SUB;
+		e->match.str = argv[*argi];
 	} else {
-		if (val == buf)
+		if (val == argv[*argi])
 			e->bits = TYPE_Nm | TYPE_Nd;
-		if ('=' == *val)
-			e->substr = val + 1;
+		if (*val == '=') {
+			e->match.type = DBM_SUB;
+			e->match.str = val + 1;
+		} else
+			e->match.type = DBM_REGEX;
 		*val++ = '\0';
-		if (NULL != strstr(buf, "arch"))
+		if (strstr(argv[*argi], "arch") != NULL)
 			cs = 0;
 	}
 
 	/* Compile regular expressions. */
 
-	if (NULL == e->substr) {
-		irc = regcomp(&e->regexp, val,
+	if (e->match.type == DBM_REGEX) {
+		e->match.re = mandoc_malloc(sizeof(*e->match.re));
+		irc = regcomp(e->match.re, val,
 		    REG_EXTENDED | REG_NOSUB | (cs ? 0 : REG_ICASE));
+		if (irc) {
+			regerror(irc, e->match.re, errbuf, sizeof(errbuf));
+			warnx("regcomp /%s/: %s", val, errbuf);
+		}
 		if (search->argmode == ARG_WORD)
 			free(val);
 		if (irc) {
-			regerror(irc, &e->regexp, errbuf, sizeof(errbuf));
-			warnx("regcomp: %s", errbuf);
+			free(e->match.re);
 			free(e);
+			++*argi;
 			return NULL;
 		}
 	}
 
-	if (e->bits)
+	if (e->bits) {
+		++*argi;
 		return e;
+	}
 
 	/*
 	 * Parse out all possible fields.
 	 * If the field doesn't resolve, bail.
 	 */
 
-	while (NULL != (key = strsep(&buf, ","))) {
+	while (NULL != (key = strsep(&argv[*argi], ","))) {
 		if ('\0' == *key)
 			continue;
-		for (i = 0, iterbit = 1;
-		     i < mansearch_keymax;
-		     i++, iterbit <<= 1) {
-			if (0 == strcasecmp(key,
-			    mansearch_keynames[i])) {
+		for (i = 0, iterbit = 1; i < KEY_MAX; i++, iterbit <<= 1) {
+			if (0 == strcasecmp(key, mansearch_keynames[i])) {
 				e->bits |= iterbit;
 				break;
 			}
 		}
-		if (i == mansearch_keymax) {
-			if (strcasecmp(key, "any")) {
-				free(e);
-				return NULL;
-			}
+		if (i == KEY_MAX) {
+			if (strcasecmp(key, "any"))
+				warnx("treating unknown key "
+				    "\"%s\" as \"any\"", key);
 			e->bits |= ~0ULL;
 		}
 	}
 
+	++*argi;
 	return e;
 }
 
 static void
-exprfree(struct expr *p)
+exprfree(struct expr *e)
 {
-	struct expr	*pp;
-
-	while (NULL != p) {
-		pp = p->next;
-		free(p);
-		p = pp;
-	}
+	if (e->next != NULL)
+		exprfree(e->next);
+	if (e->child != NULL)
+		exprfree(e->child);
+	free(e);
 }
Index: configure
===================================================================
RCS file: /home/cvs/mdocml/mdocml/configure,v
retrieving revision 1.43
retrieving revision 1.44
diff -Lconfigure -Lconfigure -u -p -r1.43 -r1.44
--- configure
+++ configure
@@ -40,10 +40,8 @@ CFLAGS="${CFLAGS} -Wno-unused-parameter"
 LDADD=
 LDFLAGS=
 LD_OHASH=
-LD_SQLITE3=
 STATIC="-static"
 
-BUILD_DB=1
 BUILD_CGI=0
 
 HAVE_DIRENT_NAMLEN=
@@ -53,7 +51,6 @@ HAVE_GETLINE=
 HAVE_GETSUBOPT=
 HAVE_ISBLANK=
 HAVE_MKDTEMP=
-HAVE_MMAP=
 HAVE_PLEDGE=
 HAVE_PROGNAME=
 HAVE_REALLOCARRAY=
@@ -70,8 +67,6 @@ HAVE_STRTONUM=
 HAVE_VASPRINTF=
 HAVE_WCHAR=
 
-HAVE_SQLITE3=
-HAVE_SQLITE3_ERRSTR=
 HAVE_OHASH=
 HAVE_MANPATH=
 
@@ -186,7 +181,6 @@ runtest getline		GETLINE		|| true
 runtest getsubopt	GETSUBOPT	|| true
 runtest isblank		ISBLANK		|| true
 runtest mkdtemp		MKDTEMP		|| true
-runtest mmap		MMAP		|| true
 runtest pledge		PLEDGE		|| true
 runtest sandbox_init	SANDBOX_INIT	|| true
 runtest progname	PROGNAME	|| true
@@ -203,44 +197,6 @@ runtest strtonum	STRTONUM	|| true
 runtest vasprintf	VASPRINTF	|| true
 runtest wchar		WCHAR		|| true
 
-# --- sqlite3 ---
-if [ ${BUILD_DB} -eq 0 ]; then
-	echo "BUILD_DB=0 (manual)" 1>&2
-	echo "BUILD_DB=0 (manual)" 1>&3
-	echo 1>&3
-	HAVE_SQLITE3=0
-elif ismanual sqlite3 "${HAVE_SQLITE3}"; then
-	if [ -z "${LD_SQLITE3}" ]; then
-		LD_SQLITE3="-lsqlite3"
-	fi
-elif [ -n "${LD_SQLITE3}" ]; then
-	runtest sqlite3 SQLITE3 "${LD_SQLITE3}" || true
-elif singletest sqlite3 SQLITE3 "-lsqlite3"; then
-	LD_SQLITE3="-lsqlite3"
-elif runtest sqlite3 SQLITE3 \
-		"-I/usr/local/include -L/usr/local/lib -lsqlite3"; then
-	LD_SQLITE3="-L/usr/local/lib -lsqlite3"
-	CFLAGS="${CFLAGS} -I/usr/local/include"
-fi
-if [ ${HAVE_SQLITE3} -eq 0 ]; then
-	LD_SQLITE3=
-	if [ ${BUILD_DB} -gt 0 ]; then
-		echo "BUILD_DB=0 (no sqlite3)" 1>&2
-		echo "BUILD_DB=0 (no sqlite3)" 1>&3
-		echo 1>&3
-		BUILD_DB=0
-	fi
-fi
-
-# --- sqlite3_errstr ---
-if [ ${BUILD_DB} -eq 0 ]; then
-	HAVE_SQLITE3_ERRSTR=1
-elif ismanual sqlite3_errstr "${HAVE_SQLITE3_ERRSTR}"; then
-	:
-else
-	runtest sqlite3_errstr SQLITE3_ERRSTR "${LD_SQLITE3}" || true
-fi
-
 # --- ohash ---
 if ismanual ohash "${HAVE_OHASH}"; then
 	:
@@ -256,7 +212,7 @@ if [ "${HAVE_OHASH}" -eq 0 ]; then
 fi
 
 # --- LDADD ---
-LDADD="${LDADD} ${LD_SQLITE3} ${LD_OHASH} -lz"
+LDADD="${LDADD} ${LD_OHASH} -lz"
 echo "LDADD=\"${LDADD}\"" 1>&2
 echo "LDADD=\"${LDADD}\"" 1>&3
 echo 1>&3
@@ -315,7 +271,6 @@ cat << __HEREDOC__
 #define HAVE_GETSUBOPT ${HAVE_GETSUBOPT}
 #define HAVE_ISBLANK ${HAVE_ISBLANK}
 #define HAVE_MKDTEMP ${HAVE_MKDTEMP}
-#define HAVE_MMAP ${HAVE_MMAP}
 #define HAVE_PLEDGE ${HAVE_PLEDGE}
 #define HAVE_PROGNAME ${HAVE_PROGNAME}
 #define HAVE_REALLOCARRAY ${HAVE_REALLOCARRAY}
@@ -331,8 +286,6 @@ cat << __HEREDOC__
 #define HAVE_STRTONUM ${HAVE_STRTONUM}
 #define HAVE_VASPRINTF ${HAVE_VASPRINTF}
 #define HAVE_WCHAR ${HAVE_WCHAR}
-#define HAVE_SQLITE3 ${HAVE_SQLITE3}
-#define HAVE_SQLITE3_ERRSTR ${HAVE_SQLITE3_ERRSTR}
 #define HAVE_OHASH ${HAVE_OHASH}
 #define HAVE_MANPATH ${HAVE_MANPATH}
 
@@ -371,9 +324,6 @@ fi
 [ ${HAVE_REALLOCARRAY} -eq 0 ] && \
 	echo "extern	void	 *reallocarray(void *, size_t, size_t);"
 
-[ ${BUILD_DB} -gt 0 -a ${HAVE_SQLITE3_ERRSTR} -eq 0 ] &&
-	echo "extern	const char *sqlite3_errstr(int);"
-
 [ ${HAVE_STRCASESTR} -eq 0 ] && \
 	echo "extern	char	 *strcasestr(const char *, const char *);"
 
@@ -413,17 +363,10 @@ exec > Makefile.local
 [ -z "${INSTALL_MAN}"     ] && INSTALL_MAN="${INSTALL} -m 0444"
 [ -z "${INSTALL_DATA}"    ] && INSTALL_DATA="${INSTALL} -m 0444"
 
-if [ ${BUILD_DB} -eq 0 -a ${BUILD_CGI} -gt 0 ]; then
-	echo "BUILD_CGI=0 (no BUILD_DB)" 1>&2
-	echo "BUILD_CGI=0 (no BUILD_DB)" 1>&3
-	BUILD_CGI=0
-fi
-
-BUILD_TARGETS="base-build"
-[ ${BUILD_CGI} -gt 0 ] && BUILD_TARGETS="${BUILD_TARGETS} cgi-build"
-INSTALL_TARGETS="base-install"
-[ ${BUILD_DB}  -gt 0 ] && INSTALL_TARGETS="${INSTALL_TARGETS} db-install"
-[ ${BUILD_CGI} -gt 0 ] && INSTALL_TARGETS="${INSTALL_TARGETS} cgi-install"
+BUILD_TARGETS=
+[ ${BUILD_CGI} -gt 0 ] && BUILD_TARGETS="cgi-build"
+INSTALL_TARGETS=
+[ ${BUILD_CGI} -gt 0 ] && INSTALL_TARGETS="cgi-install"
 
 cat << __HEREDOC__
 BUILD_TARGETS	= ${BUILD_TARGETS}
@@ -459,9 +402,6 @@ INSTALL_LIB	= ${INSTALL_LIB}
 INSTALL_MAN	= ${INSTALL_MAN}
 INSTALL_DATA	= ${INSTALL_DATA}
 __HEREDOC__
-
-[ ${BUILD_DB} -gt 0 ] && \
-	echo "MAIN_OBJS	= \$(BASE_OBJS) \$(DB_OBJS)"
 
 echo "Makefile.local: written" 1>&2
 echo "Makefile.local: written" 1>&3
Index: mansearch.h
===================================================================
RCS file: /home/cvs/mdocml/mdocml/mansearch.h,v
retrieving revision 1.24
retrieving revision 1.25
diff -Lmansearch.h -Lmansearch.h -u -p -r1.24 -r1.25
--- mansearch.h
+++ mansearch.h
@@ -16,7 +16,13 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#define	MANDOC_DB	 "mandoc.db"
+#define	MANDOC_DB	 "mandoc.new.db"
+#define	MANDOCDB_MAGIC	 0x3a7d0cdb
+#define	MANDOCDB_VERSION 0  /* XXX Start counting in production. */
+
+#define	MACRO_MAX	 36
+#define	KEY_Nd		 39
+#define	KEY_MAX		 40
 
 #define	TYPE_arch	 0x0000000000000001ULL
 #define	TYPE_sec	 0x0000000000000002ULL
@@ -66,9 +72,11 @@
 #define	NAME_FILE	 0x0000004000000010ULL
 #define	NAME_MASK	 0x000000000000001fULL
 
-#define	FORM_CAT	 0  /* manual page is preformatted */
-#define	FORM_SRC	 1  /* format is mdoc(7) or man(7) */
-#define	FORM_NONE	 4  /* format is unknown */
+enum	form {
+	FORM_SRC = 1,	/* Format is mdoc(7) or man(7). */
+	FORM_CAT,	/* Manual page is preformatted. */
+	FORM_NONE	/* Format is unknown. */
+};
 
 enum	argmode {
 	ARG_FILE = 0,
@@ -84,7 +92,7 @@ struct	manpage {
 	size_t		 ipath; /* number of the manpath */
 	uint64_t	 bits; /* name type mask */
 	int		 sec; /* section number, 10 means invalid */
-	int		 form; /* 0 == catpage */
+	enum form	 form;
 };
 
 struct	mansearch {
@@ -98,7 +106,6 @@ struct	mansearch {
 
 struct	manpaths;
 
-int	mansearch_setup(int);
 int	mansearch(const struct mansearch *cfg, /* options */
 		const struct manpaths *paths, /* manpaths */
 		int argc, /* size of argv */
Index: Makefile.depend
===================================================================
RCS file: /home/cvs/mdocml/mdocml/Makefile.depend,v
retrieving revision 1.22
retrieving revision 1.23
diff -LMakefile.depend -LMakefile.depend -u -p -r1.22 -r1.23
--- Makefile.depend
+++ Makefile.depend
@@ -1,16 +1,15 @@
 att.o: att.c config.h roff.h mdoc.h libmdoc.h
 cgi.o: cgi.c config.h mandoc_aux.h mandoc.h roff.h mdoc.h man.h main.h manconf.h mansearch.h cgi.h
-chars.o: chars.c config.h mandoc.h mandoc_aux.h mandoc_ohash.h compat_ohash.h libmandoc.h
+chars.o: chars.c config.h mandoc.h mandoc_aux.h mandoc_ohash.h libmandoc.h
 compat_err.o: compat_err.c config.h
-compat_fts.o: compat_fts.c config.h compat_fts.h
+compat_fts.o: compat_fts.c config.h
 compat_getline.o: compat_getline.c config.h
 compat_getsubopt.o: compat_getsubopt.c config.h
 compat_isblank.o: compat_isblank.c config.h
 compat_mkdtemp.o: compat_mkdtemp.c config.h
-compat_ohash.o: compat_ohash.c config.h compat_ohash.h
+compat_ohash.o: compat_ohash.c config.h
 compat_progname.o: compat_progname.c config.h
 compat_reallocarray.o: compat_reallocarray.c config.h
-compat_sqlite3_errstr.o: compat_sqlite3_errstr.c config.h
 compat_strcasestr.o: compat_strcasestr.c config.h
 compat_stringlist.o: compat_stringlist.c config.h compat_stringlist.h
 compat_strlcat.o: compat_strlcat.c config.h
@@ -18,6 +17,12 @@ compat_strlcpy.o: compat_strlcpy.c confi
 compat_strsep.o: compat_strsep.c config.h
 compat_strtonum.o: compat_strtonum.c config.h
 compat_vasprintf.o: compat_vasprintf.c config.h
+dba.o: dba.c mandoc_aux.h mansearch.h dba_write.h dba_array.h dba.h
+dba_array.o: dba_array.c mandoc_aux.h dba_write.h dba_array.h
+dba_read.o: dba_read.c mandoc_aux.h mansearch.h dba_array.h dba.h dbm.h
+dba_write.o: dba_write.c dba_write.h
+dbm.o: dbm.c mansearch.h dbm_map.h dbm.h
+dbm_map.o: dbm_map.c mansearch.h dbm_map.h dbm.h
 demandoc.o: demandoc.c config.h roff.h man.h mdoc.h mandoc.h
 eqn.o: eqn.c config.h mandoc.h mandoc_aux.h libmandoc.h libroff.h
 eqn_html.o: eqn_html.c config.h mandoc.h out.h html.h
@@ -34,11 +39,10 @@ man_validate.o: man_validate.c config.h 
 mandoc.o: mandoc.c config.h mandoc.h mandoc_aux.h libmandoc.h
 mandoc_aux.o: mandoc_aux.c config.h mandoc.h mandoc_aux.h
 mandoc_ohash.o: mandoc_ohash.c mandoc_aux.h mandoc_ohash.h compat_ohash.h
-mandocdb.o: mandocdb.c config.h compat_fts.h mandoc_aux.h mandoc_ohash.h compat_ohash.h mandoc.h roff.h mdoc.h man.h manconf.h mansearch.h
+mandocdb.o: mandocdb.c config.h mandoc_aux.h mandoc_ohash.h mandoc.h roff.h mdoc.h man.h manconf.h mansearch.h dba_array.h dba.h
 manpage.o: manpage.c config.h manconf.h mansearch.h
 manpath.o: manpath.c config.h mandoc_aux.h manconf.h
-mansearch.o: mansearch.c config.h mandoc.h mandoc_aux.h mandoc_ohash.h compat_ohash.h manconf.h mansearch.h
-mansearch_const.o: mansearch_const.c config.h mansearch.h
+mansearch.o: mansearch.c mandoc.h mandoc_aux.h mandoc_ohash.h compat_ohash.h manconf.h mansearch.h dbm.h
 mdoc.o: mdoc.c config.h mandoc_aux.h mandoc.h roff.h mdoc.h libmandoc.h roff_int.h libmdoc.h
 mdoc_argv.o: mdoc_argv.c config.h mandoc_aux.h mandoc.h roff.h mdoc.h libmandoc.h roff_int.h libmdoc.h
 mdoc_hash.o: mdoc_hash.c config.h mandoc.h roff.h mdoc.h libmandoc.h libmdoc.h
@@ -55,7 +59,7 @@ read.o: read.c config.h mandoc_aux.h man
 roff.o: roff.c config.h mandoc.h mandoc_aux.h roff.h libmandoc.h roff_int.h libroff.h predefs.in
 soelim.o: soelim.c config.h compat_stringlist.h
 st.o: st.c config.h roff.h mdoc.h libmdoc.h st.in
-tag.o: tag.c config.h mandoc_aux.h mandoc_ohash.h compat_ohash.h tag.h
+tag.o: tag.c config.h mandoc_aux.h mandoc_ohash.h tag.h
 tbl.o: tbl.c config.h mandoc.h mandoc_aux.h libmandoc.h libroff.h
 tbl_data.o: tbl_data.c config.h mandoc.h mandoc_aux.h libmandoc.h libroff.h
 tbl_html.o: tbl_html.c config.h mandoc.h out.h html.h
Index: read.c
===================================================================
RCS file: /home/cvs/mdocml/mdocml/read.c,v
retrieving revision 1.150
retrieving revision 1.151
diff -Lread.c -Lread.c -u -p -r1.150 -r1.151
--- read.c
+++ read.c
@@ -19,10 +19,8 @@
 #include "config.h"
 
 #include <sys/types.h>
-#if HAVE_MMAP
 #include <sys/mman.h>
 #include <sys/stat.h>
-#endif
 
 #include <assert.h>
 #include <ctype.h>
@@ -598,7 +596,6 @@ read_whole_file(struct mparse *curp, con
 	size_t		 off;
 	ssize_t		 ssz;
 
-#if HAVE_MMAP
 	struct stat	 st;
 
 	if (fstat(fd, &st) == -1)
@@ -622,7 +619,6 @@ read_whole_file(struct mparse *curp, con
 		if (fb->buf != MAP_FAILED)
 			return 1;
 	}
-#endif
 
 	if (curp->gzip) {
 		if ((gz = gzdopen(fd, "rb")) == NULL)
@@ -747,11 +743,9 @@ mparse_readfd(struct mparse *curp, int f
 		    (MPARSE_UTF8 | MPARSE_LATIN1);
 		mparse_parse_buffer(curp, blk, file);
 		curp->filenc = save_filenc;
-#if HAVE_MMAP
 		if (with_mmap)
 			munmap(blk.buf, blk.sz);
 		else
-#endif
 			free(blk.buf);
 	}
 	return curp->file_status;
Index: Makefile
===================================================================
RCS file: /home/cvs/mdocml/mdocml/Makefile,v
retrieving revision 1.488
retrieving revision 1.489
diff -LMakefile -LMakefile -u -p -r1.488 -r1.489
--- Makefile
+++ Makefile
@@ -15,7 +15,7 @@
 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
-VERSION = 1.13.4
+VERSION = 1.14.0
 
 # === LIST OF FILES ====================================================
 
@@ -26,7 +26,6 @@ TESTSRCS	 = test-dirent-namlen.c \
 		   test-getsubopt.c \
 		   test-isblank.c \
 		   test-mkdtemp.c \
-		   test-mmap.c \
 		   test-ohash.c \
 		   test-pledge.c \
 		   test-progname.c \
@@ -34,8 +33,6 @@ TESTSRCS	 = test-dirent-namlen.c \
 		   test-rewb-bsd.c \
 		   test-rewb-sysv.c \
 		   test-sandbox_init.c \
-		   test-sqlite3.c \
-		   test-sqlite3_errstr.c \
 		   test-strcasestr.c \
 		   test-stringlist.c \
 		   test-strlcat.c \
@@ -58,7 +55,6 @@ SRCS		 = att.c \
 		   compat_ohash.c \
 		   compat_progname.c \
 		   compat_reallocarray.c \
-		   compat_sqlite3_errstr.c \
 		   compat_strcasestr.c \
 		   compat_stringlist.c \
 		   compat_strlcat.c \
@@ -66,6 +62,12 @@ SRCS		 = att.c \
 		   compat_strsep.c \
 		   compat_strtonum.c \
 		   compat_vasprintf.c \
+		   dba.c \
+		   dba_array.c \
+		   dba_read.c \
+		   dba_write.c \
+		   dbm.c \
+		   dbm_map.c \
 		   demandoc.c \
 		   eqn.c \
 		   eqn_html.c \
@@ -86,7 +88,6 @@ SRCS		 = att.c \
 		   manpage.c \
 		   manpath.c \
 		   mansearch.c \
-		   mansearch_const.c \
 		   mdoc.c \
 		   mdoc_argv.c \
 		   mdoc_hash.c \
@@ -128,6 +129,11 @@ DISTFILES	 = INSTALL \
 		   compat_stringlist.h \
 		   configure \
 		   configure.local.example \
+		   dba.h \
+		   dba_array.h \
+		   dba_write.h \
+		   dbm.h \
+		   dbm_map.h \
 		   demandoc.1 \
 		   eqn.7 \
 		   gmdiff \
@@ -220,7 +226,6 @@ COMPAT_OBJS	 = compat_err.o \
 		   compat_ohash.o \
 		   compat_progname.o \
 		   compat_reallocarray.o \
-		   compat_sqlite3_errstr.o \
 		   compat_strcasestr.o \
 		   compat_strlcat.o \
 		   compat_strlcpy.o \
@@ -244,28 +249,35 @@ MANDOC_TERM_OBJS = eqn_term.o \
 		   term_ps.o \
 		   tbl_term.o
 
-BASE_OBJS	 = $(MANDOC_HTML_OBJS) \
+DBM_OBJS	 = dbm.o \
+		   dbm_map.o \
+		   mansearch.o
+
+DBA_OBJS	 = dba.o \
+		   dba_array.o \
+		   dba_read.o \
+		   dba_write.o \
+		   mandocdb.o
+
+MAIN_OBJS	 = $(MANDOC_HTML_OBJS) \
 		   $(MANDOC_MAN_OBJS) \
 		   $(MANDOC_TERM_OBJS) \
+		   $(DBM_OBJS) \
+		   $(DBA_OBJS) \
 		   main.o \
 		   manpath.o \
 		   out.o \
 		   tag.o \
 		   tree.o
 
-MAIN_OBJS	 = $(BASE_OBJS)
-
-DB_OBJS		 = mandocdb.o \
-		   mansearch.o \
-		   mansearch_const.o
-
 CGI_OBJS	 = $(MANDOC_HTML_OBJS) \
+		   $(DBM_OBJS) \
 		   cgi.o \
-		   mansearch.o \
-		   mansearch_const.o \
 		   out.o
 
-MANPAGE_OBJS	 = manpage.o mansearch.o mansearch_const.o manpath.o
+MANPAGE_OBJS	 = $(DBM_OBJS) \
+		   manpage.o \
+		   manpath.o
 
 DEMANDOC_OBJS	 = demandoc.o
 
@@ -329,7 +341,7 @@ www: $(WWW_OBJS) $(WWW_MANS)
 
 $(WWW_MANS): mandoc
 
-.PHONY: base-install cgi-install db-install install www-install
+.PHONY: base-install cgi-install install www-install
 .PHONY: clean distclean depend
 
 include Makefile.depend
@@ -341,7 +353,7 @@ distclean: clean
 
 clean:
 	rm -f libmandoc.a $(LIBMANDOC_OBJS) $(COMPAT_OBJS)
-	rm -f mandoc $(BASE_OBJS) $(DB_OBJS)
+	rm -f mandoc $(MAIN_OBJS)
 	rm -f man.cgi $(CGI_OBJS)
 	rm -f manpage $(MANPAGE_OBJS)
 	rm -f demandoc $(DEMANDOC_OBJS)
@@ -351,47 +363,40 @@ clean:
 
 base-install: base-build
 	mkdir -p $(DESTDIR)$(BINDIR)
+	mkdir -p $(DESTDIR)$(SBINDIR)
 	mkdir -p $(DESTDIR)$(LIBDIR)
 	mkdir -p $(DESTDIR)$(INCLUDEDIR)
 	mkdir -p $(DESTDIR)$(MANDIR)/man1
 	mkdir -p $(DESTDIR)$(MANDIR)/man3
 	mkdir -p $(DESTDIR)$(MANDIR)/man5
 	mkdir -p $(DESTDIR)$(MANDIR)/man7
+	mkdir -p $(DESTDIR)$(MANDIR)/man8
 	$(INSTALL_PROGRAM) mandoc demandoc $(DESTDIR)$(BINDIR)
 	$(INSTALL_PROGRAM) soelim $(DESTDIR)$(BINDIR)/$(BINM_SOELIM)
 	ln -f $(DESTDIR)$(BINDIR)/mandoc $(DESTDIR)$(BINDIR)/$(BINM_MAN)
+	ln -f $(DESTDIR)$(BINDIR)/mandoc $(DESTDIR)$(BINDIR)/$(BINM_APROPOS)
+	ln -f $(DESTDIR)$(BINDIR)/mandoc $(DESTDIR)$(BINDIR)/$(BINM_WHATIS)
+	ln -f $(DESTDIR)$(BINDIR)/mandoc \
+		$(DESTDIR)$(SBINDIR)/$(BINM_MAKEWHATIS)
 	$(INSTALL_LIB) libmandoc.a $(DESTDIR)$(LIBDIR)
 	$(INSTALL_LIB) man.h mandoc.h mandoc_aux.h mdoc.h roff.h \
 		$(DESTDIR)$(INCLUDEDIR)
 	$(INSTALL_MAN) mandoc.1 demandoc.1 $(DESTDIR)$(MANDIR)/man1
 	$(INSTALL_MAN) soelim.1 $(DESTDIR)$(MANDIR)/man1/$(BINM_SOELIM).1
 	$(INSTALL_MAN) man.1 $(DESTDIR)$(MANDIR)/man1/$(BINM_MAN).1
+	$(INSTALL_MAN) apropos.1 $(DESTDIR)$(MANDIR)/man1/$(BINM_APROPOS).1
+	ln -f $(DESTDIR)$(MANDIR)/man1/$(BINM_APROPOS).1 \
+		$(DESTDIR)$(MANDIR)/man1/$(BINM_WHATIS).1
 	$(INSTALL_MAN) mandoc.3 mandoc_escape.3 mandoc_malloc.3 \
-		mchars_alloc.3 tbl.3 $(DESTDIR)$(MANDIR)/man3
+		mansearch.3 mchars_alloc.3 tbl.3 $(DESTDIR)$(MANDIR)/man3
 	$(INSTALL_MAN) man.conf.5 $(DESTDIR)$(MANDIR)/man5/${MANM_MANCONF}.5
+	$(INSTALL_MAN) mandoc.db.5 $(DESTDIR)$(MANDIR)/man5
 	$(INSTALL_MAN) man.7 $(DESTDIR)$(MANDIR)/man7/${MANM_MAN}.7
 	$(INSTALL_MAN) mdoc.7 $(DESTDIR)$(MANDIR)/man7/${MANM_MDOC}.7
 	$(INSTALL_MAN) roff.7 $(DESTDIR)$(MANDIR)/man7/${MANM_ROFF}.7
 	$(INSTALL_MAN) eqn.7 $(DESTDIR)$(MANDIR)/man7/${MANM_EQN}.7
 	$(INSTALL_MAN) tbl.7 $(DESTDIR)$(MANDIR)/man7/${MANM_TBL}.7
 	$(INSTALL_MAN) mandoc_char.7 $(DESTDIR)$(MANDIR)/man7
-
-db-install: base-build
-	mkdir -p $(DESTDIR)$(BINDIR)
-	mkdir -p $(DESTDIR)$(SBINDIR)
-	mkdir -p $(DESTDIR)$(MANDIR)/man1
-	mkdir -p $(DESTDIR)$(MANDIR)/man3
-	mkdir -p $(DESTDIR)$(MANDIR)/man5
-	mkdir -p $(DESTDIR)$(MANDIR)/man8
-	ln -f $(DESTDIR)$(BINDIR)/mandoc $(DESTDIR)$(BINDIR)/$(BINM_APROPOS)
-	ln -f $(DESTDIR)$(BINDIR)/mandoc $(DESTDIR)$(BINDIR)/$(BINM_WHATIS)
-	ln -f $(DESTDIR)$(BINDIR)/mandoc \
-		$(DESTDIR)$(SBINDIR)/$(BINM_MAKEWHATIS)
-	$(INSTALL_MAN) apropos.1 $(DESTDIR)$(MANDIR)/man1/$(BINM_APROPOS).1
-	ln -f $(DESTDIR)$(MANDIR)/man1/$(BINM_APROPOS).1 \
-		$(DESTDIR)$(MANDIR)/man1/$(BINM_WHATIS).1
-	$(INSTALL_MAN) mansearch.3 $(DESTDIR)$(MANDIR)/man3
-	$(INSTALL_MAN) mandoc.db.5 $(DESTDIR)$(MANDIR)/man5
 	$(INSTALL_MAN) makewhatis.8 \
 		$(DESTDIR)$(MANDIR)/man8/$(BINM_MAKEWHATIS).8
 
--- /dev/null
+++ dbm.h
@@ -0,0 +1,68 @@
+/*	$Id: dbm.h,v 1.1 2016/07/19 21:31:55 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Public interface for the map-based version
+ * of the mandoc database, for read-only access.
+ * To be used by dbm*.c, dba_read.c, and man(1) and apropos(1).
+ */
+
+enum dbm_mtype {
+	DBM_EXACT = 0,
+	DBM_SUB,
+	DBM_REGEX
+};
+
+struct dbm_match {
+	regex_t		*re;
+	const char	*str;
+	enum dbm_mtype	 type;
+};
+
+struct dbm_res {
+	int32_t		 page;
+	int32_t		 bits;
+};
+
+struct dbm_page {
+	const char	*name;
+	const char	*sect;
+	const char	*arch;
+	const char	*desc;
+	const char	*file;
+	int32_t		 addr;
+};
+
+struct dbm_macro {
+	const char	*value;
+	const int32_t	*pp;
+};
+
+int		 dbm_open(const char *);
+void		 dbm_close(void);
+
+int32_t		 dbm_page_count(void);
+struct dbm_page	*dbm_page_get(int32_t);
+void		 dbm_page_byname(const struct dbm_match *);
+void		 dbm_page_bysect(const struct dbm_match *);
+void		 dbm_page_byarch(const struct dbm_match *);
+void		 dbm_page_bydesc(const struct dbm_match *);
+void		 dbm_page_bymacro(int32_t, const struct dbm_match *);
+struct dbm_res	 dbm_page_next(void);
+
+int32_t		 dbm_macro_count(int32_t);
+struct dbm_macro *dbm_macro_get(int32_t, int32_t);
+void		 dbm_macro_bypage(int32_t, int32_t);
+char		*dbm_macro_next(void);
--- /dev/null
+++ dba_read.c
@@ -0,0 +1,74 @@
+/*	$Id: dba_read.c,v 1.1 2016/07/19 21:31:55 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Function to read the mandoc database from disk into RAM,
+ * such that data can be added or removed.
+ * The interface is defined in "dba.h".
+ * This file is seperate from dba.c because this also uses "dbm.h".
+ */
+#include <regex.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "mandoc_aux.h"
+#include "mansearch.h"
+#include "dba_array.h"
+#include "dba.h"
+#include "dbm.h"
+
+
+struct dba *
+dba_read(const char *fname)
+{
+	struct dba		*dba;
+	struct dba_array	*page;
+	struct dbm_page		*pdata;
+	struct dbm_macro	*mdata;
+	const char		*cp;
+	int32_t			 im, ip, iv, npages;
+
+	if (dbm_open(fname) == -1)
+		return NULL;
+	npages = dbm_page_count();
+	dba = dba_new(npages);
+	for (ip = 0; ip < npages; ip++) {
+		pdata = dbm_page_get(ip);
+		page = dba_page_new(dba->pages, pdata->name, pdata->sect,
+		    pdata->arch, pdata->desc, pdata->file + 1, *pdata->file);
+		cp = pdata->name;
+		while (*(cp = strchr(cp, '\0') + 1) != '\0')
+			dba_page_add(page, DBP_NAME, cp);
+		cp = pdata->sect;
+		while (*(cp = strchr(cp, '\0') + 1) != '\0')
+			dba_page_add(page, DBP_SECT, cp);
+		if ((cp = pdata->arch) != NULL)
+			while (*(cp = strchr(cp, '\0') + 1) != '\0')
+				dba_page_add(page, DBP_ARCH, cp);
+		cp = pdata->file;
+		while (*(cp = strchr(cp, '\0') + 1) != '\0')
+			dba_page_add(page, DBP_FILE, cp);
+	}
+	for (im = 0; im < MACRO_MAX; im++) {
+		for (iv = 0; iv < dbm_macro_count(im); iv++) {
+			mdata = dbm_macro_get(im, iv);
+			dba_macro_new(dba, im, mdata->value, mdata->pp);
+		}
+	}
+	dbm_close();
+	return dba;
+}
--- /dev/null
+++ dba_write.h
@@ -0,0 +1,30 @@
+/*	$Id: dba_write.h,v 1.1 2016/07/19 21:31:55 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Internal interface to low-level functions
+ * for serializing allocation-based data to disk.
+ * For use by dba_array.c and dba.c only.
+ */
+
+int	 dba_open(const char *);
+int	 dba_close(void);
+int32_t	 dba_tell(void);
+void	 dba_seek(int32_t);
+int32_t	 dba_align(void);
+int32_t	 dba_skip(int32_t, int32_t);
+void	 dba_char_write(int);
+void	 dba_str_write(const char *);
+void	 dba_int_write(int32_t);
--- /dev/null
+++ dba_array.c
@@ -0,0 +1,188 @@
+/*	$Id: dba_array.c,v 1.1 2016/07/19 21:31:55 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Allocation-based arrays for the mandoc database, for read-write access.
+ * The interface is defined in "dba_array.h".
+ */
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "mandoc_aux.h"
+#include "dba_write.h"
+#include "dba_array.h"
+
+struct dba_array {
+	void	**ep;	/* Array of entries. */
+	int32_t	 *em;	/* Array of map positions. */
+	int	  flags;
+	int32_t	  ea;	/* Entries allocated. */
+	int32_t	  eu;	/* Entries used (including deleted). */
+	int32_t	  ed;	/* Entries deleted. */
+	int32_t	  ec;	/* Currently active entry. */
+	int32_t	  pos;  /* Map position of this array. */
+};
+
+
+struct dba_array *
+dba_array_new(int32_t ea, int flags)
+{
+	struct dba_array	*array;
+
+	assert(ea > 0);
+	array = mandoc_malloc(sizeof(*array));
+	array->ep = mandoc_reallocarray(NULL, ea, sizeof(*array->ep));
+	array->em = mandoc_reallocarray(NULL, ea, sizeof(*array->em));
+	array->ea = ea;
+	array->eu = 0;
+	array->ed = 0;
+	array->ec = 0;
+	array->flags = flags;
+	array->pos = 0;
+	return array;
+}
+
+void
+dba_array_free(struct dba_array *array)
+{
+	int32_t	 ie;
+
+	if (array == NULL)
+		return;
+	if (array->flags & DBA_STR)
+		for (ie = 0; ie < array->eu; ie++)
+			free(array->ep[ie]);
+	free(array->ep);
+	free(array->em);
+	free(array);
+}
+
+void
+dba_array_set(struct dba_array *array, int32_t ie, void *entry)
+{
+	assert(ie >= 0);
+	assert(ie < array->ea);
+	assert(ie <= array->eu);
+	if (ie == array->eu)
+		array->eu++;
+	if (array->flags & DBA_STR)
+		entry = mandoc_strdup(entry);
+	array->ep[ie] = entry;
+	array->em[ie] = 0;
+}
+
+void
+dba_array_add(struct dba_array *array, void *entry)
+{
+	if (array->eu == array->ea) {
+		assert(array->flags & DBA_GROW);
+		array->ep = mandoc_reallocarray(array->ep,
+		    2, sizeof(*array->ep) * array->ea);
+		array->em = mandoc_reallocarray(array->em,
+		    2, sizeof(*array->em) * array->ea);
+		array->ea *= 2;
+	}
+	dba_array_set(array, array->eu, entry);
+}
+
+void *
+dba_array_get(struct dba_array *array, int32_t ie)
+{
+	if (ie < 0 || ie >= array->eu || array->em[ie] == -1)
+		return NULL;
+	return array->ep[ie];
+}
+
+void
+dba_array_start(struct dba_array *array)
+{
+	array->ec = array->eu;
+}
+
+void *
+dba_array_next(struct dba_array *array)
+{
+	if (array->ec < array->eu)
+		array->ec++;
+	else
+		array->ec = 0;
+	while (array->ec < array->eu && array->em[array->ec] == -1)
+		array->ec++;
+	return array->ec < array->eu ? array->ep[array->ec] : NULL;
+}
+
+void
+dba_array_del(struct dba_array *array)
+{
+	if (array->ec < array->eu && array->em[array->ec] != -1) {
+		array->em[array->ec] = -1;
+		array->ed++;
+	}
+}
+
+void
+dba_array_undel(struct dba_array *array)
+{
+	memset(array->em, 0, sizeof(*array->em) * array->eu);
+}
+
+void
+dba_array_setpos(struct dba_array *array, int32_t ie, int32_t pos)
+{
+	array->em[ie] = pos;
+}
+
+int32_t
+dba_array_getpos(struct dba_array *array)
+{
+	return array->pos;
+}
+
+void
+dba_array_sort(struct dba_array *array, dba_compare_func func)
+{
+	assert(array->ed == 0);
+	qsort(array->ep, array->eu, sizeof(*array->ep), func);
+}
+
+int32_t
+dba_array_writelen(struct dba_array *array, int32_t nmemb)
+{
+	dba_int_write(array->eu - array->ed);
+	return dba_skip(nmemb, array->eu - array->ed);
+}
+
+void
+dba_array_writepos(struct dba_array *array)
+{
+	int32_t	 ie;
+
+	array->pos = dba_tell();
+	for (ie = 0; ie < array->eu; ie++)
+		if (array->em[ie] != -1)
+			dba_int_write(array->em[ie]);
+}
+
+void
+dba_array_writelst(struct dba_array *array)
+{
+	const char	*str;
+
+	dba_array_FOREACH(array, str)
+		dba_str_write(str);
+	dba_char_write('\0');
+}
--- test-sqlite3.c
+++ /dev/null
@@ -1,47 +0,0 @@
-/*	$Id: test-sqlite3.c,v 1.2 2015/10/06 18:32:20 schwarze Exp $	*/
-/*
- * Copyright (c) 2014 Ingo Schwarze <schwarze@openbsd.org>
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <stdio.h>
-#include <unistd.h>
-#include <sqlite3.h>
-
-int
-main(void)
-{
-	sqlite3	*db;
-
-	if (sqlite3_open_v2("test.db", &db,
-	    SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
-	    NULL) != SQLITE_OK) {
-		perror("test.db");
-		fprintf(stderr, "sqlite3_open_v2: %s", sqlite3_errmsg(db));
-		return 1;
-	}
-	unlink("test.db");
-
-	if (sqlite3_exec(db, "PRAGMA foreign_keys = ON",
-	    NULL, NULL, NULL) != SQLITE_OK) {
-		fprintf(stderr, "sqlite3_exec: %s", sqlite3_errmsg(db));
-		return 1;
-	}
-
-	if (sqlite3_close(db) != SQLITE_OK) {
-		fprintf(stderr, "sqlite3_close: %s", sqlite3_errmsg(db));
-		return 1;
-	}
-	return 0;
-}
--- /dev/null
+++ dba_array.h
@@ -0,0 +1,47 @@
+/*	$Id: dba_array.h,v 1.1 2016/07/19 21:31:55 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Public interface for allocation-based arrays
+ * for the mandoc database, for read-write access.
+ * To be used by dba*.c and by makewhatis(8).
+ */
+
+struct dba_array;
+
+#define	DBA_STR		0x01	/* Map contains strings, not pointers. */
+#define	DBA_GROW	0x02	/* Allow the array to grow. */
+
+#define	dba_array_FOREACH(a, e) \
+	dba_array_start(a); \
+	while (((e) = dba_array_next(a)) != NULL)
+
+typedef int dba_compare_func(const void *, const void *);
+
+struct dba_array *dba_array_new(int32_t, int);
+void		 dba_array_free(struct dba_array *);
+void		 dba_array_set(struct dba_array *, int32_t, void *);
+void		 dba_array_add(struct dba_array *, void *);
+void		*dba_array_get(struct dba_array *, int32_t);
+void		 dba_array_start(struct dba_array *);
+void		*dba_array_next(struct dba_array *);
+void		 dba_array_del(struct dba_array *);
+void		 dba_array_undel(struct dba_array *);
+void		 dba_array_setpos(struct dba_array *, int32_t, int32_t);
+int32_t		 dba_array_getpos(struct dba_array *);
+void		 dba_array_sort(struct dba_array *, dba_compare_func);
+int32_t		 dba_array_writelen(struct dba_array *, int32_t);
+void		 dba_array_writepos(struct dba_array *);
+void		 dba_array_writelst(struct dba_array *);
--- /dev/null
+++ dbm_map.h
@@ -0,0 +1,29 @@
+/*	$Id: dbm_map.h,v 1.1 2016/07/19 21:31:55 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Private interface for low-level routines for the map-based version 
+ * of the mandoc database, for read-only access.
+ * To be used by dbm*.c only.
+ */
+
+struct dbm_match;
+
+int		 dbm_map(const char *);
+void		 dbm_unmap(void);
+void		*dbm_get(int32_t);
+int32_t		*dbm_getint(int32_t);
+int32_t		 dbm_addr(const void *);
+int		 dbm_match(const struct dbm_match *, const char *);
--- test-mmap.c
+++ /dev/null
@@ -1,9 +0,0 @@
-#include <sys/types.h>
-#include <sys/mman.h>
-#include <stddef.h>
-
-int
-main(void)
-{
-	return mmap(NULL, 1, PROT_READ, MAP_SHARED, -1, 0) != MAP_FAILED;
-}
Index: main.c
===================================================================
RCS file: /home/cvs/mdocml/mdocml/main.c,v
retrieving revision 1.273
retrieving revision 1.274
diff -Lmain.c -Lmain.c -u -p -r1.273 -r1.274
--- main.c
+++ main.c
@@ -82,9 +82,7 @@ struct	curparse {
 };
 
 
-#if HAVE_SQLITE3
 int			  mandocdb(int, char *[]);
-#endif
 
 static	int		  fs_lookup(const struct manpaths *,
 				size_t ipath, const char *,
@@ -147,11 +145,9 @@ main(int argc, char *argv[])
 	setprogname(progname);
 #endif
 
-#if HAVE_SQLITE3
 	if (strncmp(progname, "mandocdb", 8) == 0 ||
 	    strcmp(progname, BINM_MAKEWHATIS) == 0)
 		return mandocdb(argc, argv);
-#endif
 
 #if HAVE_PLEDGE
 	if (pledge("stdio rpath tmppath tty proc exec flock", NULL) == -1)
@@ -349,9 +345,6 @@ main(int argc, char *argv[])
 	/* man(1), whatis(1), apropos(1) */
 
 	if (search.argmode != ARG_FILE) {
-		if (argc == 0)
-			usage(search.argmode);
-
 		if (search.argmode == ARG_NAME &&
 		    outmode == OUTMODE_ONE)
 			search.firstmatch = 1;
@@ -359,19 +352,9 @@ main(int argc, char *argv[])
 		/* Access the mandoc database. */
 
 		manconf_parse(&conf, conf_file, defpaths, auxpaths);
-#if HAVE_SQLITE3
-		mansearch_setup(1);
 		if ( ! mansearch(&search, &conf.manpath,
 		    argc, argv, &res, &sz))
 			usage(search.argmode);
-#else
-		if (search.argmode != ARG_NAME) {
-			fputs("mandoc: database support not compiled in\n",
-			    stderr);
-			return (int)MANDOCLEVEL_BADARG;
-		}
-		sz = 0;
-#endif
 
 		if (sz == 0) {
 			if (search.argmode == ARG_NAME)
@@ -474,7 +457,7 @@ main(int argc, char *argv[])
 
 			if (resp == NULL)
 				parse(&curp, fd, *argv);
-			else if (resp->form & FORM_SRC) {
+			else if (resp->form == FORM_SRC) {
 				/* For .so only; ignore failure. */
 				chdir(conf.manpath.paths[resp->ipath]);
 				parse(&curp, fd, resp->file);
@@ -522,10 +505,7 @@ main(int argc, char *argv[])
 out:
 	if (search.argmode != ARG_FILE) {
 		manconf_free(&conf);
-#if HAVE_SQLITE3
 		mansearch_free(res, sz);
-		mansearch_setup(0);
-#endif
 	}
 
 	free(defos);
@@ -629,7 +609,8 @@ fs_lookup(const struct manpaths *paths, 
 	glob_t		 globinfo;
 	struct manpage	*page;
 	char		*file;
-	int		 form, globres;
+	int		 globres;
+	enum form	 form;
 
 	form = FORM_SRC;
 	mandoc_asprintf(&file, "%s/man%s/%s.%s",
@@ -667,10 +648,8 @@ fs_lookup(const struct manpaths *paths, 
 		return 0;
 
 found:
-#if HAVE_SQLITE3
 	warnx("outdated mandoc.db lacks %s(%s) entry, run %s %s",
 	    name, sec, BINM_MAKEWHATIS, paths->paths[ipath]);
-#endif
 	*res = mandoc_reallocarray(*res, ++*ressz, sizeof(struct manpage));
 	page = *res + (*ressz - 1);
 	page->file = file;
--- test-sqlite3_errstr.c
+++ /dev/null
@@ -1,8 +0,0 @@
-#include <string.h>
-#include <sqlite3.h>
-
-int
-main(void)
-{
-	return strcmp(sqlite3_errstr(SQLITE_OK), "not an error");
-}
--- /dev/null
+++ dbm_map.c
@@ -0,0 +1,174 @@
+/*	$Id: dbm_map.c,v 1.1 2016/07/19 21:31:55 schwarze Exp $ */
+/*
+ * Copyright (c) 2016 Ingo Schwarze <schwarze@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Low-level routines for the map-based version
+ * of the mandoc database, for read-only access.
+ * The interface is defined in "dbm_map.h".
+ */
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <endian.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <regex.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mansearch.h"
+#include "dbm_map.h"
+#include "dbm.h"
+
+static struct stat	 st;
+static char		*dbm_base;
+static int		 ifd;
+static int32_t		 max_offset;
+
+/*
+ * Open a disk-based database for read-only access.
+ * Validate the file format as far as it is not mandoc-specific.
+ * Return 0 on success.  Return -1 and set errno on failure.
+ */
+int
+dbm_map(const char *fname)
+{
+	int		 save_errno;
+	const int32_t	*magic;
+
+	if ((ifd = open(fname, O_RDONLY)) == -1)
+		return -1;
+	if (fstat(ifd, &st) == -1)
+		goto fail;
+	if (st.st_size < 5) {
+		warnx("dbm_map(%s): File too short", fname);
+		errno = EFTYPE;
+		goto fail;
+	}
+	if (st.st_size > INT32_MAX) {
+		errno = EFBIG;
+		goto fail;
+	}
+	if ((dbm_base = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED,
+	    ifd, 0)) == MAP_FAILED)
+		goto fail;
+	magic = dbm_getint(0);
+	if (be32toh(*magic) != MANDOCDB_MAGIC) {
+		warnx("dbm_map(%s): Bad initial magic %x (expected %x)",
+		    fname, be32toh(*magic), MANDOCDB_MAGIC);
+		errno = EFTYPE;
+		goto fail;
+	}
+	magic = dbm_getint(1);
+	if (be32toh(*magic) != MANDOCDB_VERSION) {
+		warnx("dbm_map(%s): Bad version number %d (expected %d)",
+		    fname, be32toh(*magic), MANDOCDB_VERSION);
+		errno = EFTYPE;
+		goto fail;
+	}
+	max_offset = be32toh(*dbm_getint(3)) + sizeof(int32_t);
+	if (st.st_size != max_offset) {
+		warnx("dbm_map(%s): Inconsistent file size %llu (expected %d)",
+		    fname, st.st_size, max_offset);
+		errno = EFTYPE;
+		goto fail;
+	}
+	if ((magic = dbm_get(*dbm_getint(3))) == NULL) {
+		errno = EFTYPE;
+		goto fail;
+	}
+	if (be32toh(*magic) != MANDOCDB_MAGIC) {
+		warnx("dbm_map(%s): Bad final magic %x (expected %x)",
+		    fname, be32toh(*magic), MANDOCDB_MAGIC);
+		errno = EFTYPE;
+		goto fail;
+	}
+	return 0;
+
+fail:
+	save_errno = errno;
+	close(ifd);
+	errno = save_errno;
+	return -1;
+}
+
+void
+dbm_unmap(void)
+{
+	if (munmap(dbm_base, st.st_size) == -1)
+		warn("dbm_unmap: munmap");
+	if (close(ifd) == -1)
+		warn("dbm_unmap: close");
+	dbm_base = (char *)-1;
+}
+
+/*
+ * Take a raw integer as it was read from the database.
+ * Interpret it as an offset into the database file
+ * and return a pointer to that place in the file.
+ */
+void *
+dbm_get(int32_t offset)
+{
+	offset = be32toh(offset);
+	if (offset < 0 || offset >= max_offset) {
+		warnx("dbm_get: Database corrupt: offset %d > %d",
+		    offset, max_offset);
+		return NULL;
+	}
+	return dbm_base + offset;
+}
+
+/*
+ * Assume the database starts with some integers.
+ * Assume they are numbered starting from 0, increasing.
+ * Get a pointer to one with the number "offset".
+ */
+int32_t *
+dbm_getint(int32_t offset)
+{
+	return (int32_t *)dbm_base + offset;
+}
+
+/*
+ * The reverse of dbm_get().
+ * Take pointer into the database file
+ * and convert it to the raw integer
+ * that would be used to refer to that place in the file.
+ */
+int32_t
+dbm_addr(const void *p)
+{
+	return htobe32((char *)p - dbm_base);
+}
+
+int
+dbm_match(const struct dbm_match *match, const char *str)
+{
+	switch (match->type) {
+	case DBM_EXACT:
+		return strcmp(str, match->str) == 0;
+	case DBM_SUB:
+		return strcasestr(str, match->str) != NULL;
+	case DBM_REGEX:
+		return regexec(match->re, str, 0, NULL, 0) == 0;
+	default:
+		abort();
+	}
+}
Index: configure.local.example
===================================================================
RCS file: /home/cvs/mdocml/mdocml/configure.local.example,v
retrieving revision 1.14
retrieving revision 1.15
diff -Lconfigure.local.example -Lconfigure.local.example -u -p -r1.14 -r1.15
--- configure.local.example
+++ configure.local.example
@@ -65,7 +65,7 @@ MANPATH_DEFAULT="/usr/share/man:/usr/X11
 # If you do not want uname(3) to be called but instead want a fixed
 # string to be used, use the following line:
 
-OSNAME="OpenBSD 5.9"
+OSNAME="OpenBSD 6.0"
 
 # The following installation directories are used.
 # It is possible to set only one or a few of these variables,
@@ -111,14 +111,20 @@ MANM_ROFF="mandoc_roff"		# default is "r
 MANM_EQN="mandoc_eqn"		# default is "eqn"
 MANM_TBL="mandoc_tbl"		# default is "tbl"
 
-# Some distributions may want to avoid naming conflicts
-# with other man(1) and soelim(1) utilities.
+# Some distributions may want to avoid naming conflicts with
+# other man(1), apropos(1), makewhatis(8), or soelim(1) utilities.
 # If you want to change the names of binary programs,
 # the following alternative names are suggested.
 # Using different names is possible as well.
-# This changes the names of the installed section 1 manual pages as well.
+# This changes the names of the installed section 1 and section 8
+# manual pages as well.
+# It is possible to set only one or two of these variables,
+# there is no need to copy the whole block.
 
 BINM_MAN=mman			# default is "man"
+BINM_APROPOS=mapropos		# default is "apropos"
+BINM_WHATIS=mwhatis		# default is "whatis"
+BINM_MAKEWHATIS=mandocdb	# default is "makewhatis"
 BINM_SOELIM=msoelim		# default is "soelim"
 
 # Before falling back to the bundled version of the ohash(3) hashing
@@ -129,6 +135,13 @@ BINM_SOELIM=msoelim		# default is "soeli
 
 LD_OHASH="-lutil"
 
+# When library autodetection decides to use -L/usr/local/lib,
+# -I/usr/local/include is automatically added to CFLAGS.
+# If you manually set LD_OHASH to something including -L/usr/local/lib,
+# chances are you will also need the following line:
+
+CFLAGS="${CFLAGS} -I/usr/local/include"
+
 # Some platforms may need additional linker flags to link against libmandoc
 # that are not autodetected.
 # For example, Solaris 9 and 10 need -lrt for nanosleep(2).
@@ -150,43 +163,6 @@ INSTALL_LIB="${INSTALL} -m 0444"
 INSTALL_MAN="${INSTALL} -m 0444"
 INSTALL_DATA="${INSTALL} -m 0444"
 
-# --- user settings related to database support ------------------------
-
-# By default, building makewhatis(8) and apropos(1) is enabled.
-# To disable it, for example to avoid the dependency on SQLite3,
-# use the following line.  It that case, the remaining settings
-# in this section are irrelevant.
-
-BUILD_DB=0
-
-# Autoconfiguration tries the following linker flags to find the
-# SQLite3 library installed on your system.  If none of these work,
-# set the following variable to specify the required linker flags.
-
-LD_SQLITE3="-lsqlite3"
-LD_SQLITE3="-L/usr/local/lib -lsqlite3"
-
-# When library autodetection decides to use -L/usr/local/lib,
-# -I/usr/local/include is automatically added to CFLAGS.
-# If you manually set LD_SQLITE3 to something including -L/usr/local/lib,
-# chances are you will also need the following line:
-
-CFLAGS="${CFLAGS} -I/usr/local/include"
-
-# Some distributions may want to avoid naming conflicts
-# with another implementation of apropos(1) and makewhatis(8).
-# If you want to change the names of the binary programs,
-# the following alternative names are suggested.
-# Using other names is possible as well.
-# This changes the names of the installed section 1 and section 8
-# manual pages as well.
-# It is possible to set only one or two of these variables,
-# there is no need to copy the whole block.
-
-BINM_APROPOS=mapropos		# default is "apropos"
-BINM_WHATIS=mwhatis		# default is "whatis"
-BINM_MAKEWHATIS=mandocdb	# default is "makewhatis"
-
 # When using the "homebrew" package manager on Mac OS X, the actual
 # manuals are located in a so-called "cellar" and only symlinked
 # into the manual trees.  To allow mandoc to follow such symlinks,
@@ -200,7 +176,6 @@ HOMEBREWDIR="${PREFIX}/Cellar"
 
 # By default, building man.cgi(8) is disabled.  To enable it, copy
 # cgi.h.example to cgi.h, edit it, and use the following line.
-# Obviously, this requires that BUILD_DB is enabled, too.
 
 BUILD_CGI=1
 
@@ -258,7 +233,7 @@ HAVE_GETLINE=0
 HAVE_GETSUBOPT=0
 HAVE_ISBLANK=0
 HAVE_MKDTEMP=0
-HAVE_MMAP=0
+HAVE_OHASH=0
 HAVE_PLEDGE=0
 HAVE_PROGNAME=0
 HAVE_REALLOCARRAY=0
@@ -273,7 +248,3 @@ HAVE_STRSEP=0
 HAVE_STRTONUM=0
 HAVE_VASPRINTF=0
 HAVE_WCHAR=0
-
-HAVE_SQLITE3=0
-HAVE_SQLITE3_ERRSTR=0
-HAVE_OHASH=0
Index: TODO
===================================================================
RCS file: /home/cvs/mdocml/mdocml/TODO,v
retrieving revision 1.218
retrieving revision 1.219
diff -LTODO -LTODO -u -p -r1.218 -r1.219
--- TODO
+++ TODO
@@ -612,7 +612,6 @@ are mere guesses, and some may be wrong.
 ************************************************************************
 
 - Why are we using MAP_SHARED, not MAP_PRIVATE for mmap(2)?
-  How does SQLITE_CONFIG_PAGECACHE actually work?  Document it!
   from kristaps@  Sat, 09 Aug 2014 13:51:36 +0200
 
 Several areas can be cleaned up to make mandoc even faster.  These are
@@ -645,11 +644,6 @@ Several areas can be cleaned up to make 
 
 - struct mparse refactoring
   Steffen Nurpmeso  Thu, 04 Sep 2014 12:50:00 +0200
-
-- Consider creating some views that will make the database more
-  readable from the sqlite3 shell.  Consider using them to
-  abstract from the database structure, too.
-  suggested by espie@  Sat, 19 Apr 2014 14:52:57 +0200
 
 ************************************************************************
 * CGI issues
--
 To unsubscribe send an email to source+unsubscribe@mdocml.bsd.lv

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2016-07-19 21:32 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-07-19 21:32 mdocml: Remove the dependency on SQLite without loss of functionality schwarze

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).