public inbox for gnats-devel@sourceware.org
 help / color / mirror / Atom feed
* patch #2 - towards a generic backend datastore
@ 2005-04-19  1:11 Mel Hatzis
  2005-04-21 23:52 ` Mel Hatzis
  0 siblings, 1 reply; 3+ messages in thread
From: Mel Hatzis @ 2005-04-19  1:11 UTC (permalink / raw)
  To: Chad Walstrom, help-gnats

[-- Attachment #1: Type: text/plain, Size: 1379 bytes --]

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Please review the attached patch which contains a number of
modifications intended to restructure the code to support a
generalized backend datastore.

This second patch is rather large - larger than I originally
planned. I expect that all subsequent patches will be
significantly smaller.

The primary goal in this patch was to completely isolate the
index from the generic (i.e. non-datastore specific) GNATS
functionality and introduce a flat-file datastore library with
which all relevant binaries are linked. A small addition to
configure was made to build the flat-file datastore library
by default.

This is a significant leap forward in generalizing the backend
datastore. The GNATS/datastore interface really starts to become
more obvious with this patch.

I have attempted to adequately describe all changes in detail
in the ChangeLog. Feel free to ask me to elaborate where
necessary.

Once again, I've run these changes through my regression test
suite and they appear to work well. Of course, I encourage
more testing if people have the bandwidth.

- --
Mel Hatzis

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.2 (Darwin)
Comment: Using GnuPG with Thunderbird - http://enigmail.mozdev.org

iD8DBQFCZFpUNF74HmYqaSERAmb/AJ9lmbMNnVk0mK8CcPS5jTkKSNRwTgCdEJar
Mo9lELKzbkHdg6kAq0w3Igs=
=SrwE
-----END PGP SIGNATURE-----

[-- Attachment #2: patch2.txt --]
[-- Type: text/plain, Size: 217344 bytes --]

Index: ChangeLog
===================================================================
RCS file: /cvsroot/gnats/gnats/gnats/ChangeLog,v
retrieving revision 1.311
diff -u -p -r1.311 ChangeLog
--- ChangeLog	24 Feb 2005 21:21:22 -0000	1.311
+++ ChangeLog	19 Apr 2005 00:49:19 -0000
@@ -1,5 +1,194 @@
 2005-02-19  Mel Hatzis  <hatzis@wattes.org>
 
+	* configure.in: Added the beginnings for supporting multiple
+	backend datastores. A lot of the funtionality dealing directly
+	with the flat-file datastore has been moved into it's own
+	library (libds-file.a) which is pulled in unconditionally for now
+	- since there are no alternative backend datastores to configure.
+
+
+	* Makefile.in: Moved most of logic dealing with the flat-file
+	backend into a ds-file sub-directory for which we generate a
+	libds-file.a library. Modified all binary targets to link
+	against this library - the name of the datastore library to link
+	with is actually supplied by configure. Note that the index
+	file is now part of the flat-file datastore library; GNATS_LIBS
+	now includes a $(DS_LIB) macro defined by configure; a ds-libs
+	target has been added for building the datastore library; fconfig.y
+	is now generated (see the note about fconfig.y below).
+
+	* ds.mk: New file. Used to generate the Makefile in all datastore
+	implementations. Contains all the common elements of a datastore
+	Makefile.
+
+	* aclocal.m4: Regenerated.
+
+	* autoconf.h.in: Regenerated.
+
+	* configure: Regenerated.
+
+	* cmds.c (do_server_query, GNATS_lock, GNATS_unlk, GNATS_edit):
+	Function renames for the datastore API - iterate_prs, prExists and
+	readPRWithNum renamed to db_query, pr_exists and pr_load_by_id
+	respectively. The renames are to conform to a standard naming
+	convention used by the datastore API to assist with datastore
+	implementation.
+	(GNATS_rset): Removed references to the index, which is now only
+	relevant when GNATS if configured with the flat-file datastore.
+	This change involves a modification to the message accompanying
+	the return code when the index is reloaded - since the index is
+	no longer always relevant, it makes no sense for the GNATS server
+	to refer to it in the client/server protocol.
+
+	* database.c (struct databaseInfo, newDatabaseInfo): Replaced
+	indexDesc with a void ds_private pointer which is intended to cache
+	any datastore specific information (such as the indexDesc). The
+	indexDesc is now defined and exclusively used in the flat-file
+	datastore library and is pulled in via ds_private when needed.
+	(getIndexDesc, setIndexDesc, clearIndexDesc): Moved to index.c in
+	the flat-file datastore library since this is no longer generically
+	relevant.
+	(getDatastorePrivate, setDatastorePrivate): New functions. Used for
+	managing the datastore specific database elements such as the index
+	in the flat-file datastore implementation.
+	(loadDatabase): Replaced  all flat-file datastore specific
+	functionality with a new "db_init" function which is part of the new
+	GNATS datastore API.
+	(freeDatabaseInfo): Replaced all references to functions which free
+	the index with a call to a new "db_destroy" function which is part of
+	the new GNATS datastore API.
+
+	* database.h (setIndexDesc, setDatastorePrivate): Replaced
+	setIndexDesc with setDatastorePrivate - setIndexDesc now declared
+	in ds-file/index.h. Any datastore specific configuration elements
+	defined in the dbconfig (via ds-file/t-fconfig) are cached in the
+	DatabaseInfo struct via setDatastorePrivate.
+	(getIndexDesc, getDatastorePrivate): Replaced getIndexDesc with
+	getDatastorePrivate - getIndexDesc now declared in ds-file/index.h.
+
+	* edit.c (processPRChanges): No need to check if the PR has been
+	completely read into memory...all callers of this function do so.
+	Furthermore, the idea of reading a PR partially is not desirable
+	for a generalized backend datastore.
+	(rewrite_pr): Moved all the logic associated with reading/writing
+	to a PR file and dealing with the index into a new 'pr_update'
+	function in ds-file/pr.c.
+	(replace_pr): Renamed prExists to pr_exists, readPRWithNum to
+	pr_load_by_id and fillInPR with pr_load. These functions are
+	all part of the datastore API and have been renamed for clarity.
+
+	* ds-file/t-fconfig, fconfig.y, fconfig.y.in: fconfig.y renamed to
+	fconfig.y.in which no longer contains any datastore specific
+	grammar. The datastore specific items are now pulled in from
+	a t-fconfig file located in the ds-file directory. The fconfig.y
+	file is generated by concatenating fconfig.y.in with t-fconfig.
+	To accomodate this, fconfig.y.in now contains a dsDescription
+	section which all datastore's must define.
+
+	* file-pr.c (createNewPRFile): Renamed to createNewPR since we'll 
+	eventually be using alternatives to files for the backend. Also,
+	moved all the logic associated with reading/writing to a PR file
+	dealing with the index into a new 'pr_create' function in
+	ds-file/pr.c.
+	(run_atpr): Pass in an int for the PR number since we
+	no longer refer to a string representation of it in createNewPR.
+	(checkIfReply, append_report): Function renames for datastore API.
+	(getBugNumber): Moved to ds-file/pr.c - only relevant to flat-file
+	datastore.
+	(submit_pr): Function rename for multiple backend datastore support.
+
+	* gnats.h: No longer pull in index.h...this is only used for the
+	flat-file datastore.
+
+	* index.c, index.h, ds-file/index.c, ds-file/index.h: Moved the
+	index.[ch] files into the new ds-file sub-directory which implements
+	the flat-file backend datastore. The index is now only applicable
+	if GNATS is configured with the flat-file backend datastore. All
+	references to the index in the DatabaseInfo and PR data structures
+	have been replaced with an opaque 'ds_private' field, which is used
+	to cache all datastore specific data. The getIndexDesc/setIndexDesc
+	function definitions were moved out of database.c into index.c since
+	these functions are specific to the flat-file datastore.
+
+	* pr.c (allocPR): Replaced allocIndex with a call to the new pr_init
+	function in the datastore API. The flat-file datastore implementation
+	of pr_init is where allocIndex is now called. Also handle the case
+	where there are no fields in the dbconfig without corrupting memory.
+	(get_pr, get_pr_from_index, get_pr_path, pr_file_readable): These
+	functions have been relocated to ds-file/pr.c since they are
+	specific to the flat-file datastore.
+	(fillInPR): Renamed to pr_load and moved to ds-file/pr.c since the
+	implementation of this function is datastore specific.
+	(field_value): Replaced the call to indexValue which retrieves the
+	field value from the flat-file index with a datastore API function
+	call named field_cache_value. This allows for datastore
+	implementations to utilize a cache (such as the flat-file index),
+	for keeping some field values in memory.
+	(free_pr): Replaced calls related to freeing the index with a new
+	datastore API function named pr_destroy. The flat-file datastore
+	implementation of pr_destroy is where the index is now free'd.
+	(prExists): Renamed to pr_exists and moved to ds-file/pr.c since
+	the implementation of this function is datastore specific.
+	(readPRWithNum): Renamed to pr_load_by_id and moved to ds-file/pr.c
+	since the implementation of this function is datastore specific.
+	(pr_delete): Moved to ds-file/pr.c since the implementation of this
+	function is datastore specific.
+
+	* pr.h (PR_struct): Replaced the index pointer with an opaque
+	pointer to a struct containing datastore specific data. The index
+	is now defined within this opaque datastore data structure only when
+	GNATS is linked  with the flat-file datastore library.
+	(fillInPR): Renamed to pr_load and moved to ds.h.
+
+	* query.h (index.h): Removed index.h since the index is no longer a
+	generic part of the GNATS datastore; index.h is now only pulled in
+	when GNATS is configured with the flat-file backend.
+	(*QueryItem, SearchItem, *QueryTree, struct queryExpr): Imported
+	from query.c since these is required by the datastore library.
+	(*QueryFunc): Moved to ds.h since this is an integral part of the
+	datastore query functionality from which it is referenced.
+	(iterate_prs): Renamed to db_query and moved to ds.h since this is
+	now a part of the GNATS backend datastore API. The function was
+	renamed to conform to a standard naming convention used by the
+	datastore API to assist with datastore implementation.
+
+	* query.c (*QueryItem, SearchItem, *QueryTree, struct queryExpr):
+	Moved to query.h since these are now referenced from the datastore
+	library.
+	(fieldCompare, process_format): The index is now only relevant when
+	GNATS is configured with the flat-file backend. The check for
+	whether a PR is only partially in memory or whether we're only
+	referencing an indexed field is no longer relevant in all cases.
+	The datastore is now responsible for implementing a partial PR
+	cache (such as the index).
+	(pr_matches_tree): No longer a static - referenced by the datastore
+	backend.
+	(iterate_prs): Renamed to db_query and moved into the datastore
+	library - this is a member of the datastore API.
+
+	* ds.h: New File. Contains function declarations for all functions
+	implementing the GNATS datastore API. All datastore implementations
+	are required to define these functions.
+
+	* ds-file/Makefile.in, ds-file/ds-file.h, ds-file/db.c,
+	ds-file/pr.c, ds-file/fld.c: New files. Collectively implement the
+	GNATS flat-file datastore.
+
+	* gen-index.c: We will eventually only build this as part of the
+	flat-file datastore. For now, pull in the index include file from
+	the ds-file directory.
+
+	* getclose.c (do_prlist): Reworked so as not to use the flat-file
+	index. Use a query to get the relevant PRs for processing.
+
+	* pr-age.c (main): Function rename for multiple backend datastore.
+
+	* pr-edit.c (main): Function renames for multiple backend datastore.
+
+	* query-pr.c (main): Function renames for multiple backend datastore.
+
+2005-02-19  Mel Hatzis  <hatzis@wattes.org>
+
 	* adm.c (copy_adm_entry): Handle blank metadata fields by correctly
 	assigning the relevant field in the adm struct to NULL - and thereby
 	avoid an attempt to xstrdup a NULL value.
Index: Makefile.in
===================================================================
RCS file: /cvsroot/gnats/gnats/gnats/Makefile.in,v
retrieving revision 1.71
diff -u -p -r1.71 Makefile.in
--- Makefile.in	24 Feb 2005 21:21:22 -0000	1.71
+++ Makefile.in	19 Apr 2005 00:49:19 -0000
@@ -129,11 +129,16 @@ SOURCES = btime.c cmds.c file-pr.c gen-c
 
 EXTRA_OBJS = @EXTRA_OBJS@
 
-LIBSRC = edit.c getdate.c gnugetopt.c gnugetopt1.c internal.c misc.c pr.c index.c lists.c query.c version.c regex.c adm.c client.c pr-init.c database.c fconfig.c fconfigl.c mail.c field.c mk_auth.c
-LIBOBJS = edit.o getdate.o gnugetopt.o gnugetopt1.o internal.o misc.o pr.o index.o lists.o query.o version.o regex.o adm.o client.o pr-init.o database.o fconfig.o fconfigl.o mail.o field.o $(EXTRA_OBJS)
+LIBSRC = edit.c getdate.c gnugetopt.c gnugetopt1.c internal.c misc.c pr.c lists.c query.c version.c regex.c adm.c client.c pr-init.c database.c fconfig.c fconfigl.c mail.c field.c mk_auth.c
+LIBOBJS = edit.o getdate.o gnugetopt.o gnugetopt1.o internal.o misc.o pr.o lists.o query.o version.o regex.o adm.o client.o pr-init.o database.o fconfig.o fconfigl.o mail.o field.o $(EXTRA_OBJS)
 
 INCLUDEDIR = -I. -I$(srcdir) $(KRBINCLUDE)
 
+DSLIB_DIR = @DSLIB_DIR@
+DS_LIB = $(DSLIB_DIR)/lib$(DSLIB_DIR).a
+GNATS_LIB = libgnats.a
+GNATS_LIBS = $(GNATS_LIB) $(DS_LIB)
+
 # Variables that the send-pr Makefile will want to have changed.
 GNATS_VARS = \
   "GNATS_DEFAULT_DB_DIR=$(GNATS_DEFAULT_DB_DIR)" \
@@ -161,14 +166,20 @@ all-gnats: all-tools gnatsd queue-pr mai
 	at-pr mkcat mkdb rmcat gen-closed-date \
 	check-db delete-pr dbconfig gnats-databases gnats-pwconv $(EXTRA_STUFF)
 
-all-tools: libgnats.a query-pr pr-age pr-edit edit-pr file-pr getclose \
-	config-send-pr mail-agent dbconfig diff-prs
+all-tools: $(GNATS_LIB) ds-libs query-pr pr-age pr-edit edit-pr file-pr \
+	getclose config-send-pr mail-agent dbconfig diff-prs
 
-libgnats.a: $(LIBOBJS)
-	-rm -f tmplibgnats.a libgnats.a
-	$(AR) $(AR_FLAGS) tmplibgnats.a $(LIBOBJS)
-	$(RANLIB) tmplibgnats.a
-	mv tmplibgnats.a libgnats.a
+$(GNATS_LIB): $(LIBOBJS)
+	-rm -f tmp$@ $@
+	$(AR) $(AR_FLAGS) tmp$@ $(LIBOBJS)
+	$(RANLIB) tmp$@
+	mv tmp$@ $@
+
+ds-libs:
+	@if [ -d $(DSLIB_DIR) ]; then \
+	  echo " "; echo "  cd $(DSLIB_DIR); $(MAKE) $(GNATS_VARS) all"; \
+	  ( cd $(DSLIB_DIR); $(MAKE) $(GNATS_VARS) all ); \
+	fi
 
 .c.o:
 	$(CC) -c $(INCLUDEDIR) $(CFLAGS) $(GCC_CFLAGS) $(CPPFLAGS) $(DEFS) $<
@@ -177,40 +188,40 @@ version.c: Makefile
 	echo 'const char *version_string = "$(VERSION)";' > $@-t
 	mv $@-t $@
 
-query-pr: query-pr.o regex.o libgnats.a
+query-pr: query-pr.o regex.o $(GNATS_LIBS)
 	$(CC) $(LDFLAGS) -o $@ query-pr.o regex.o \
-		libgnats.a $(LIBS)
+		$(GNATS_LIBS) $(LIBS)
 
-gnatsd: gnatsd.o cmds.o regex.o file-pr.o btime.o libgnats.a
+gnatsd: gnatsd.o cmds.o regex.o file-pr.o btime.o $(GNATS_LIBS)
 	$(CC) $(LDFLAGS) -o $@ gnatsd.o cmds.o regex.o file-pr.o btime.o \
-	libgnats.a $(LIBRX) $(LIBS)
+	$(GNATS_LIBS) $(LIBRX) $(LIBS)
 
-queue-pr: queue-pr.o libgnats.a
+queue-pr: queue-pr.o $(GNATS_LIBS)
 	$(CC) $(LDFLAGS) -o $@ queue-pr.o \
-		libgnats.a $(LIBS)
+		$(GNATS_LIBS) $(LIBS)
 
-pr-age: pr-age.o regex.o libgnats.a
+pr-age: pr-age.o regex.o $(GNATS_LIBS)
 	$(CC) $(LDFLAGS) -o $@ pr-age.o regex.o \
-		libgnats.a $(LIBS)
+		$(GNATS_LIBS) $(LIBS)
 
-getclose: getclose.o regex.o libgnats.a
+getclose: getclose.o regex.o $(GNATS_LIBS)
 	$(CC) $(LDFLAGS) -o $@ getclose.o regex.o \
-		libgnats.a $(LIBS)
+		$(GNATS_LIBS) $(LIBS)
 
-pr-edit: pr-edit.o file-pr.o btime.o libgnats.a
+pr-edit: pr-edit.o file-pr.o btime.o $(GNATS_LIBS)
 	$(CC) $(LDFLAGS) -o $@ pr-edit.o file-pr.o btime.o \
-		libgnats.a $(LIBS)
+		$(GNATS_LIBS) $(LIBS)
 
-pr-stat: pr-stat.o regex.o libgnats.a
+pr-stat: pr-stat.o regex.o $(GNATS_LIBS)
 	$(CC) $(LDFLAGS) -o $@ pr-stat.o regex.o \
-		libgnats.a $(LIBS)
+		$(GNATS_LIBS) $(LIBS)
 
-gen-index: gen-index.o libgnats.a
-	$(CC) $(LDFLAGS) -o $@ gen-index.o libgnats.a \
+gen-index: gen-index.o $(GNATS_LIBS)
+	$(CC) $(LDFLAGS) -o $@ gen-index.o $(GNATS_LIBS) \
 		$(LIBS)
 
-gen-closed-date: gen-closed-date.o libgnats.a
-	$(CC) $(LDFLAGS) -o $@ gen-closed-date.o libgnats.a \
+gen-closed-date: gen-closed-date.o $(GNATS_LIBS)
+	$(CC) $(LDFLAGS) -o $@ gen-closed-date.o $(GNATS_LIBS) \
 		$(LIBS)
 
 at-pr: at-pr.sh Makefile
@@ -318,9 +329,9 @@ gnats-databases: gnats-databases.in Make
 	    $(srcdir)/gnats-databases.in > $@-t
 	@mv $@-t $@
 
-gnats-pwconv: gnats-pwconv.o libgnats.a
+gnats-pwconv: gnats-pwconv.o $(GNATS_LIBS)
 	$(CC) $(LDFLAGS) -o $@ gnats-pwconv.o \
-		libgnats.a $(LIBS)
+		$(GNATS_LIBS) $(LIBS)
 
 getdate.c: getdate.y
 	@echo expect 10 shift/reduce conflicts
@@ -335,7 +346,7 @@ fconfig.o: fconfig.c
 #
 fconfig.c: fconfig.y
 	@rm -f fconfig.c fconfig.h fconfig.tab.c fconfig.tab.h
-	$(YACC) -p fconf -b fconfig -d $(srcdir)/fconfig.y
+	$(YACC) -p fconf -b fconfig -d fconfig.y
 	@if [ -f y.tab.c -a ! -f fconfig.c ] ; then \
 	  mv y.tab.c fconfig.c ; \
 	  mv y.tab.h fconfig.h ; \
@@ -345,6 +356,10 @@ fconfig.c: fconfig.y
 	  mv fconfig.tab.h fconfig.h ; \
 	fi
 
+fconfig.y: fconfig.y.in $(srcdir)/$(DSLIB_DIR)/t-fconfig
+	@echo Creating fconfig.y...
+	cat $(srcdir)/fconfig.y.in $(srcdir)/$(DSLIB_DIR)/t-fconfig > $@
+
 fconfigl.c: fconfigl.l
 	$(LEX) -t $(srcdir)/fconfigl.l >fconfigl.c
 
@@ -555,7 +570,7 @@ version.texi:
 clean: mostlyclean
 	-rm -f *.o core queue-pr gnats at-pr mkcat mkdb gnats-pwconv
 	-rm -f rmcat file-pr mail-query check-db delete-pr diff-prs
-	-rm -f libgnats.a
+	-rm -f $(GNATS_LIB)
 	-rm -f pr-age pr-stat query-pr pr-edit gnats-edit-pr edit-pr gen-index
 	-rm -f gnatsd getclose gen-closed-date
 	-rm -f gnats-file-pr file-pr nfile-pr *.dvi version.c
@@ -573,7 +588,7 @@ distclean: clean
 	-rm -f TAGS
 
 maintainer-clean realclean: distclean
-	-rm -f getdate.c fconfig.c fconfig.h fconfigl.c
+	-rm -f fconfig.y getdate.c fconfig.c fconfig.h fconfigl.c
 
 # FIXME
 dist:
@@ -617,9 +632,6 @@ getclose.o: $(srcdir)/query.h
 getdate.o: autoconf.h
 gnugetopt.o: autoconf.h $(srcdir)/gnugetopt.h
 gnugetopt1.o: autoconf.h $(srcdir)/gnugetopt.h
-index.o: $(srcdir)/pr.h 
-index.o: autoconf.h
-index.o: $(srcdir)/gnats.h $(srcdir)/ansidecl.h
 internal.o: $(srcdir)/pr.h
 internal.o: autoconf.h
 misc.o: $(srcdir)/ansidecl.h 
Index: aclocal.m4
===================================================================
RCS file: /cvsroot/gnats/gnats/gnats/aclocal.m4,v
retrieving revision 1.3
diff -u -p -r1.3 aclocal.m4
--- aclocal.m4	4 Aug 2001 21:07:18 -0000	1.3
+++ aclocal.m4	19 Apr 2005 00:49:19 -0000
@@ -1,64 +1,72 @@
-dnl aclocal.m4 generated automatically by aclocal 1.4-p4
+# aclocal.m4t generated automatically by aclocal 1.6.3 -*- Autoconf -*-
 
-dnl Copyright (C) 1994, 1995-8, 1999 Free Software Foundation, Inc.
-dnl This file is free software; the Free Software Foundation
-dnl gives unlimited permission to copy and/or distribute it,
-dnl with or without modifications, as long as this notice is preserved.
-
-dnl This program is distributed in the hope that it will be useful,
-dnl but WITHOUT ANY WARRANTY, to the extent permitted by law; without
-dnl even the implied warranty of MERCHANTABILITY or FITNESS FOR A
-dnl PARTICULAR PURPOSE.
-
-#serial 1
-# This test replaces the one in autoconf.
-# Currently this macro should have the same name as the autoconf macro
-# because gettext's gettext.m4 (distributed in the automake package)
-# still uses it.  Otherwise, the use in gettext.m4 makes autoheader
-# give these diagnostics:
-#   configure.in:556: AC_TRY_COMPILE was called before AC_ISC_POSIX
-#   configure.in:556: AC_TRY_RUN was called before AC_ISC_POSIX
-
-undefine([AC_ISC_POSIX])
-
-AC_DEFUN([AC_ISC_POSIX],
-  [
-    dnl This test replaces the obsolescent AC_ISC_POSIX kludge.
-    AC_CHECK_LIB(cposix, strerror, [LIBS="$LIBS -lcposix"])
-  ]
-)
-
-
-# serial 1
-
-AC_DEFUN(AM_PATH_LISPDIR,
- [# If set to t, that means we are running in a shell under Emacs.
-  # If you have an Emacs named "t", then use the full path.
-  test "$EMACS" = t && EMACS=
-  AC_PATH_PROGS(EMACS, emacs xemacs, no)
-  if test $EMACS != "no"; then
-    AC_MSG_CHECKING([where .elc files should go])
-    dnl Set default value
-    lispdir="\$(datadir)/emacs/site-lisp"
-    emacs_flavor=`echo "$EMACS" | sed -e 's,^.*/,,'`
-    if test "x$prefix" = "xNONE"; then
-      if test -d $ac_default_prefix/share/$emacs_flavor/site-lisp; then
-	lispdir="\$(prefix)/share/$emacs_flavor/site-lisp"
-      else
-	if test -d $ac_default_prefix/lib/$emacs_flavor/site-lisp; then
-	  lispdir="\$(prefix)/lib/$emacs_flavor/site-lisp"
-	fi
-      fi
-    else
-      if test -d $prefix/share/$emacs_flavor/site-lisp; then
-	lispdir="\$(prefix)/share/$emacs_flavor/site-lisp"
-      else
-	if test -d $prefix/lib/$emacs_flavor/site-lisp; then
-	  lispdir="\$(prefix)/lib/$emacs_flavor/site-lisp"
-	fi
-      fi
-    fi
-    AC_MSG_RESULT($lispdir)
-  fi
-  AC_SUBST(lispdir)])
+# Copyright 1996, 1997, 1998, 1999, 2000, 2001, 2002
+# Free Software Foundation, Inc.
+# This file is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+
+# Copyright 1996, 1997, 1998, 1999, 2000, 2001 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+# 02111-1307, USA.
+
+# serial 5
+
+# AM_PATH_LISPDIR
+# ---------------
+AC_DEFUN([AM_PATH_LISPDIR],
+[AC_ARG_WITH(lispdir,
+ [  --with-lispdir          Override the default lisp directory ],
+ [ lispdir="$withval"
+   AC_MSG_CHECKING([where .elc files should go])
+   AC_MSG_RESULT([$lispdir])],
+ [
+ # If set to t, that means we are running in a shell under Emacs.
+ # If you have an Emacs named "t", then use the full path.
+ test x"$EMACS" = xt && EMACS=
+ AC_CHECK_PROGS(EMACS, emacs xemacs, no)
+ if test $EMACS != "no"; then
+   if test x${lispdir+set} != xset; then
+     AC_CACHE_CHECK([where .elc files should go], [am_cv_lispdir],
+       [# If $EMACS isn't GNU Emacs or XEmacs, this can blow up pretty badly
+  # Some emacsen will start up in interactive mode, requiring C-x C-c to exit,
+  #  which is non-obvious for non-emacs users.
+  # Redirecting /dev/null should help a bit; pity we can't detect "broken"
+  #  emacsen earlier and avoid running this altogether.
+  AC_RUN_LOG([$EMACS -batch -q -eval '(while load-path (princ (concat (car load-path) "\n")) (setq load-path (cdr load-path)))' </dev/null >conftest.out])
+        am_cv_lispdir=`sed -n \
+       -e 's,/$,,' \
+       -e '/.*\/lib\/\(x\?emacs\/site-lisp\)$/{s,,${libdir}/\1,;p;q;}' \
+       -e '/.*\/share\/\(x\?emacs\/site-lisp\)$/{s,,${datadir}/\1,;p;q;}' \
+       conftest.out`
+       rm conftest.out
+       if test -z "$am_cv_lispdir"; then
+         am_cv_lispdir='${datadir}/emacs/site-lisp'
+       fi
+     ])
+     lispdir="$am_cv_lispdir"
+   fi
+ fi
+])
+AC_SUBST(lispdir)
+])# AM_PATH_LISPDIR
 
Index: autoconf.h.in
===================================================================
RCS file: /cvsroot/gnats/gnats/gnats/autoconf.h.in,v
retrieving revision 1.13
diff -u -p -r1.13 autoconf.h.in
--- autoconf.h.in	24 Feb 2005 21:21:22 -0000	1.13
+++ autoconf.h.in	19 Apr 2005 00:49:19 -0000
@@ -37,6 +37,9 @@ Software Foundation, 59 Temple Place - S
 /* Define to 1 if using `alloca.c'. */
 #undef C_ALLOCA
 
+/* Use flat-file datastore. */
+#undef DS_FILE
+
 /* Define to 1 if you have `alloca', as a function or macro. */
 #undef HAVE_ALLOCA
 
Index: cmds.c
===================================================================
RCS file: /cvsroot/gnats/gnats/gnats/cmds.c,v
retrieving revision 1.71
diff -u -p -r1.71 cmds.c
--- cmds.c	30 Aug 2003 07:58:14 -0000	1.71
+++ cmds.c	19 Apr 2005 00:49:19 -0000
@@ -23,6 +23,7 @@ Software Foundation, 59 Temple Place - S
 #include "gnatsd.h"
 #include "query.h"
 #include "mail.h"
+#include "ds.h"
 
 #ifdef HAVE_KERBEROS
 #include <des.h>
@@ -381,8 +382,8 @@ do_server_query (int ac, char **av)
       return;
     }
   set_confidential_access (&query);
-  if (iterate_prs (currentDatabase, ac, av, query, queryFormat, 
-		   server_print_query, &err) < 0)
+  if (db_query (currentDatabase, ac, av, query,
+		server_print_query, queryFormat, &err) < 0)
     {
       print_server_errors (err);
     }
@@ -415,7 +416,7 @@ GNATS_lock (int ac, char **av)
       return;
     }
 
-  if (! prExists (currentDatabase, av[0], &err))
+  if (! pr_exists (currentDatabase, av[0], &err))
     {
       print_server_errors (err);
       return;
@@ -440,7 +441,7 @@ GNATS_lock (int ac, char **av)
 
   if ((result = lock_pr (currentDatabase, av[0], av[1], l, &err)) != 0)
     {
-      PR *pr = readPRWithNum (currentDatabase, av[0], 0, &err);
+      PR *pr = pr_load_by_id (currentDatabase, av[0], 0, &err);
       
       if (pr != NULL)
 	{
@@ -539,7 +540,7 @@ GNATS_unlk (int ac, char **av)
   if (! chk_nargs (ac, "PR to be unlocked"))
     return;
 
-  if (! prExists (currentDatabase, av[0], &err))
+  if (! pr_exists (currentDatabase, av[0], &err))
     {
       print_server_errors (err);
       return;
@@ -775,7 +776,7 @@ GNATS_edit (int ac, char **av)
   if (! chk_nargs (ac, "PR to be edited"))
     return;
 
-  if (! prExists (currentDatabase, av[0], &err))
+  if (! pr_exists (currentDatabase, av[0], &err))
     {
       print_server_errors (err);
       return;
@@ -1095,7 +1096,6 @@ GNATS_expr (int ac, char **av)
 void
 GNATS_rset (int ac, char **av ATTRIBUTE_UNUSED)
 {
-  int new_index = 0;
   ErrorDesc err = NULL;
 
   if (ac != 0)
@@ -1107,28 +1107,15 @@ GNATS_rset (int ac, char **av ATTRIBUTE_
   freeQueryExpr (query);
   query = NULL;
 
-  if (checkPRChain (currentDatabase, &err))
-    {
-      new_index = 1;
-    }
-
+  db_reset (currentDatabase, &err);
   if (err != NULL)
     {
       print_server_errors (err);
       freeDatabaseInfo (currentDatabase);
       currentDatabase = NULL;
+      return;
     }
-  else
-    {
-      if (new_index)
-	{
-	  printf ("%d Reset state...reloaded the index.\r\n", CODE_OK);
-	}
-      else
-	{
-	  printf ("%d Reset state.\r\n", CODE_OK);
-	}
-    }
+  printf ("%d Reset state.\r\n", CODE_OK);
 }
 
 static char *
Index: configure
===================================================================
RCS file: /cvsroot/gnats/gnats/gnats/configure,v
retrieving revision 1.36
diff -u -p -r1.36 configure
--- configure	24 Feb 2005 21:21:22 -0000	1.36
+++ configure	19 Apr 2005 00:49:19 -0000
@@ -310,7 +310,7 @@ ac_includes_default="\
 # include <unistd.h>
 #endif"
 
-ac_subst_vars='SHELL PATH_SEPARATOR PACKAGE_NAME PACKAGE_TARNAME PACKAGE_VERSION PACKAGE_STRING PACKAGE_BUGREPORT exec_prefix prefix program_transform_name bindir sbindir libexecdir datadir sysconfdir sharedstatedir localstatedir libdir includedir oldincludedir infodir mandir build_alias host_alias target_alias DEFS ECHO_C ECHO_N ECHO_T LIBS PROGS MAN CC CFLAGS LDFLAGS CPPFLAGS ac_ct_CC EXEEXT OBJEXT CPP INSTALL_PROGRAM INSTALL_SCRIPT INSTALL_DATA RANLIB ac_ct_RANLIB YACC LEX LEXLIB LEX_OUTPUT_ROOT M4 AWK EGREP ALLOCA EMACS lispdir GNATS_ALL GNATS_INSTALL KRB4 HAVE_KERBEROS KRBINCLUDE EXTRA_OBJS GNATS_SERVICE GNATS_USER DEFAULT_RELEASE DEFAULT_ORGANIZATION SUBMITTER DEFAULT_MAIL_AGENT BWEEK_START BWEEK_END BDAY_START BDAY_END GNATSD_USER_ACCESS_FILE GNATSD_HOST_ACCESS_FILE GLOBAL_DB_LIST_FILE GNATS_DEFAULT_DB_DIR GCC_CFLAGS SENDMAIL LIBOBJS LTLIBOBJS'
+ac_subst_vars='SHELL PATH_SEPARATOR PACKAGE_NAME PACKAGE_TARNAME PACKAGE_VERSION PACKAGE_STRING PACKAGE_BUGREPORT exec_prefix prefix program_transform_name bindir sbindir libexecdir datadir sysconfdir sharedstatedir localstatedir libdir includedir oldincludedir infodir mandir build_alias host_alias target_alias DEFS ECHO_C ECHO_N ECHO_T LIBS PROGS MAN CC CFLAGS LDFLAGS CPPFLAGS ac_ct_CC EXEEXT OBJEXT CPP INSTALL_PROGRAM INSTALL_SCRIPT INSTALL_DATA RANLIB ac_ct_RANLIB YACC LEX LEXLIB LEX_OUTPUT_ROOT M4 AWK EGREP ALLOCA EMACS lispdir GNATS_ALL GNATS_INSTALL KRB4 HAVE_KERBEROS KRBINCLUDE EXTRA_OBJS GNATS_SERVICE GNATS_USER DEFAULT_RELEASE DEFAULT_ORGANIZATION SUBMITTER DEFAULT_MAIL_AGENT BWEEK_START BWEEK_END BDAY_START BDAY_END GNATSD_USER_ACCESS_FILE GNATSD_HOST_ACCESS_FILE GLOBAL_DB_LIST_FILE GNATS_DEFAULT_DB_DIR GCC_CFLAGS SENDMAIL BUILD_FILE DSLIB_DIR LIBOBJS LTLIBOBJS'
 ac_subst_files=''
 
 # Initialize some variables set by options.
@@ -853,6 +853,7 @@ Optional Packages:
   --with-gnats-dblist-file=PATH  specify file containing list of databases
   --with-gnats-default-db=PATH   specify the default database directory to use
   --with-lispdir=PATH   specify the default lisp directory to use
+  --with-lispdir          Override the default lisp directory
   --with-krb4		  support Kerberos 4
 
 Some influential environment variables:
@@ -1317,6 +1318,9 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
           ac_config_headers="$ac_config_headers autoconf.h"
 
 
+# default to using the file data store
+DSLIB_DIR="ds-file"
+
 
 # Check whether --with-kerberos or --without-kerberos was given.
 if test "${with_kerberos+set}" = set; then
@@ -3562,14 +3566,13 @@ _ACEOF
 
 fi
 
-
-        echo "$as_me:$LINENO: checking for strerror in -lcposix" >&5
-echo $ECHO_N "checking for strerror in -lcposix... $ECHO_C" >&6
-if test "${ac_cv_lib_cposix_strerror+set}" = set; then
+echo "$as_me:$LINENO: checking for library containing strerror" >&5
+echo $ECHO_N "checking for library containing strerror... $ECHO_C" >&6
+if test "${ac_cv_search_strerror+set}" = set; then
   echo $ECHO_N "(cached) $ECHO_C" >&6
 else
-  ac_check_lib_save_LIBS=$LIBS
-LIBS="-lcposix  $LIBS"
+  ac_func_search_save_LIBS=$LIBS
+ac_cv_search_strerror=no
 cat >conftest.$ac_ext <<_ACEOF
 /* confdefs.h.  */
 _ACEOF
@@ -3614,24 +3617,80 @@ if { (eval echo "$as_me:$LINENO: \"$ac_l
   ac_status=$?
   echo "$as_me:$LINENO: \$? = $ac_status" >&5
   (exit $ac_status); }; }; then
-  ac_cv_lib_cposix_strerror=yes
+  ac_cv_search_strerror="none required"
 else
   echo "$as_me: failed program was:" >&5
 sed 's/^/| /' conftest.$ac_ext >&5
 
-ac_cv_lib_cposix_strerror=no
 fi
 rm -f conftest.err conftest.$ac_objext \
       conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
+if test "$ac_cv_search_strerror" = no; then
+  for ac_lib in cposix; do
+    LIBS="-l$ac_lib  $ac_func_search_save_LIBS"
+    cat >conftest.$ac_ext <<_ACEOF
+/* confdefs.h.  */
+_ACEOF
+cat confdefs.h >>conftest.$ac_ext
+cat >>conftest.$ac_ext <<_ACEOF
+/* end confdefs.h.  */
+
+/* Override any gcc2 internal prototype to avoid an error.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+/* We use char because int might match the return type of a gcc2
+   builtin and then its argument prototype would still apply.  */
+char strerror ();
+int
+main ()
+{
+strerror ();
+  ;
+  return 0;
+}
+_ACEOF
+rm -f conftest.$ac_objext conftest$ac_exeext
+if { (eval echo "$as_me:$LINENO: \"$ac_link\"") >&5
+  (eval $ac_link) 2>conftest.er1
+  ac_status=$?
+  grep -v '^ *+' conftest.er1 >conftest.err
+  rm -f conftest.er1
+  cat conftest.err >&5
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); } &&
+	 { ac_try='test -z "$ac_c_werror_flag"
+			 || test ! -s conftest.err'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; } &&
+	 { ac_try='test -s conftest$ac_exeext'
+  { (eval echo "$as_me:$LINENO: \"$ac_try\"") >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }; }; then
+  ac_cv_search_strerror="-l$ac_lib"
+break
+else
+  echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
 fi
-echo "$as_me:$LINENO: result: $ac_cv_lib_cposix_strerror" >&5
-echo "${ECHO_T}$ac_cv_lib_cposix_strerror" >&6
-if test $ac_cv_lib_cposix_strerror = yes; then
-  LIBS="$LIBS -lcposix"
+rm -f conftest.err conftest.$ac_objext \
+      conftest$ac_exeext conftest.$ac_ext
+  done
 fi
+LIBS=$ac_func_search_save_LIBS
+fi
+echo "$as_me:$LINENO: result: $ac_cv_search_strerror" >&5
+echo "${ECHO_T}$ac_cv_search_strerror" >&6
+if test "$ac_cv_search_strerror" != no; then
+  test "$ac_cv_search_strerror" = "none required" || LIBS="$ac_cv_search_strerror $LIBS"
 
-
+fi
 
 # The Ultrix 4.2 mips builtin alloca declared by alloca.h only works
 # for constant arguments.  Useless!
@@ -5509,42 +5568,49 @@ if test -n "$with_lispdir" ; then
 echo "${ECHO_T}$with_lispdir" >&6
   lispdir=$with_lispdir
 else
-  # If set to t, that means we are running in a shell under Emacs.
-  # If you have an Emacs named "t", then use the full path.
-  test "$EMACS" = t && EMACS=
-  for ac_prog in emacs xemacs
+
+# Check whether --with-lispdir or --without-lispdir was given.
+if test "${with_lispdir+set}" = set; then
+  withval="$with_lispdir"
+   lispdir="$withval"
+   echo "$as_me:$LINENO: checking where .elc files should go" >&5
+echo $ECHO_N "checking where .elc files should go... $ECHO_C" >&6
+   echo "$as_me:$LINENO: result: $lispdir" >&5
+echo "${ECHO_T}$lispdir" >&6
+else
+
+ # If set to t, that means we are running in a shell under Emacs.
+ # If you have an Emacs named "t", then use the full path.
+ test x"$EMACS" = xt && EMACS=
+ for ac_prog in emacs xemacs
 do
   # Extract the first word of "$ac_prog", so it can be a program name with args.
 set dummy $ac_prog; ac_word=$2
 echo "$as_me:$LINENO: checking for $ac_word" >&5
 echo $ECHO_N "checking for $ac_word... $ECHO_C" >&6
-if test "${ac_cv_path_EMACS+set}" = set; then
+if test "${ac_cv_prog_EMACS+set}" = set; then
   echo $ECHO_N "(cached) $ECHO_C" >&6
 else
-  case $EMACS in
-  [\\/]* | ?:[\\/]*)
-  ac_cv_path_EMACS="$EMACS" # Let the user override the test with a path.
-  ;;
-  *)
-  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+  if test -n "$EMACS"; then
+  ac_cv_prog_EMACS="$EMACS" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
 for as_dir in $PATH
 do
   IFS=$as_save_IFS
   test -z "$as_dir" && as_dir=.
   for ac_exec_ext in '' $ac_executable_extensions; do
   if $as_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
-    ac_cv_path_EMACS="$as_dir/$ac_word$ac_exec_ext"
+    ac_cv_prog_EMACS="$ac_prog"
     echo "$as_me:$LINENO: found $as_dir/$ac_word$ac_exec_ext" >&5
     break 2
   fi
 done
 done
 
-  ;;
-esac
 fi
-EMACS=$ac_cv_path_EMACS
-
+fi
+EMACS=$ac_cv_prog_EMACS
 if test -n "$EMACS"; then
   echo "$as_me:$LINENO: result: $EMACS" >&5
 echo "${ECHO_T}$EMACS" >&6
@@ -5557,31 +5623,42 @@ fi
 done
 test -n "$EMACS" || EMACS="no"
 
-  if test $EMACS != "no"; then
-    echo "$as_me:$LINENO: checking where .elc files should go" >&5
+ if test $EMACS != "no"; then
+   if test x${lispdir+set} != xset; then
+     echo "$as_me:$LINENO: checking where .elc files should go" >&5
 echo $ECHO_N "checking where .elc files should go... $ECHO_C" >&6
-        lispdir="\$(datadir)/emacs/site-lisp"
-    emacs_flavor=`echo "$EMACS" | sed -e 's,^.*/,,'`
-    if test "x$prefix" = "xNONE"; then
-      if test -d $ac_default_prefix/share/$emacs_flavor/site-lisp; then
-	lispdir="\$(prefix)/share/$emacs_flavor/site-lisp"
-      else
-	if test -d $ac_default_prefix/lib/$emacs_flavor/site-lisp; then
-	  lispdir="\$(prefix)/lib/$emacs_flavor/site-lisp"
-	fi
-      fi
-    else
-      if test -d $prefix/share/$emacs_flavor/site-lisp; then
-	lispdir="\$(prefix)/share/$emacs_flavor/site-lisp"
-      else
-	if test -d $prefix/lib/$emacs_flavor/site-lisp; then
-	  lispdir="\$(prefix)/lib/$emacs_flavor/site-lisp"
-	fi
-      fi
-    fi
-    echo "$as_me:$LINENO: result: $lispdir" >&5
-echo "${ECHO_T}$lispdir" >&6
-  fi
+if test "${am_cv_lispdir+set}" = set; then
+  echo $ECHO_N "(cached) $ECHO_C" >&6
+else
+  # If $EMACS isn't GNU Emacs or XEmacs, this can blow up pretty badly
+  # Some emacsen will start up in interactive mode, requiring C-x C-c to exit,
+  #  which is non-obvious for non-emacs users.
+  # Redirecting /dev/null should help a bit; pity we can't detect "broken"
+  #  emacsen earlier and avoid running this altogether.
+  { (echo "$as_me:$LINENO: \$EMACS -batch -q -eval '(while load-path (princ (concat (car load-path) \"\\n\")) (setq load-path (cdr load-path)))' </dev/null >conftest.out") >&5
+  ($EMACS -batch -q -eval '(while load-path (princ (concat (car load-path) "\n")) (setq load-path (cdr load-path)))' </dev/null >conftest.out) 2>&5
+  ac_status=$?
+  echo "$as_me:$LINENO: \$? = $ac_status" >&5
+  (exit $ac_status); }
+        am_cv_lispdir=`sed -n \
+       -e 's,/$,,' \
+       -e '/.*\/lib\/\(x\?emacs\/site-lisp\)$/{s,,${libdir}/\1,;p;q;}' \
+       -e '/.*\/share\/\(x\?emacs\/site-lisp\)$/{s,,${datadir}/\1,;p;q;}' \
+       conftest.out`
+       rm conftest.out
+       if test -z "$am_cv_lispdir"; then
+         am_cv_lispdir='${datadir}/emacs/site-lisp'
+       fi
+
+fi
+echo "$as_me:$LINENO: result: $am_cv_lispdir" >&5
+echo "${ECHO_T}$am_cv_lispdir" >&6
+     lispdir="$am_cv_lispdir"
+   fi
+ fi
+
+fi;
+
 
   echo "$as_me:$LINENO: result: $lispdir" >&5
 echo "${ECHO_T}$lispdir" >&6
@@ -6626,9 +6703,21 @@ if test -n "$verbose"; then
 	echo "	setting DEFAULT_MAIL_AGENT to $DEFAULT_MAIL_AGENT"
 fi
 
+if test "$DSLIB_DIR" = ds-file ; then
+  BUILD_FILE=yes
+
+cat >>confdefs.h <<\_ACEOF
+#define DS_FILE 1
+_ACEOF
+
+fi
+
+
+
+
 # ***** End of configuration section *****
 
-          ac_config_files="$ac_config_files Makefile"
+                    ac_config_files="$ac_config_files Makefile ds-file/Makefile:ds.mk:ds-file/Makefile.in"
 
 cat >confcache <<\_ACEOF
 # This file is a shell script that caches the results of configure
@@ -7156,6 +7245,7 @@ do
   case "$ac_config_target" in
   # Handling of arguments.
   "Makefile" ) CONFIG_FILES="$CONFIG_FILES Makefile" ;;
+  "ds-file/Makefile" ) CONFIG_FILES="$CONFIG_FILES ds-file/Makefile:ds.mk:ds-file/Makefile.in" ;;
   "autoconf.h" ) CONFIG_HEADERS="$CONFIG_HEADERS autoconf.h" ;;
   *) { { echo "$as_me:$LINENO: error: invalid argument: $ac_config_target" >&5
 echo "$as_me: error: invalid argument: $ac_config_target" >&2;}
@@ -7288,6 +7378,8 @@ s,@GLOBAL_DB_LIST_FILE@,$GLOBAL_DB_LIST_
 s,@GNATS_DEFAULT_DB_DIR@,$GNATS_DEFAULT_DB_DIR,;t t
 s,@GCC_CFLAGS@,$GCC_CFLAGS,;t t
 s,@SENDMAIL@,$SENDMAIL,;t t
+s,@BUILD_FILE@,$BUILD_FILE,;t t
+s,@DSLIB_DIR@,$DSLIB_DIR,;t t
 s,@LIBOBJS@,$LIBOBJS,;t t
 s,@LTLIBOBJS@,$LTLIBOBJS,;t t
 CEOF
Index: configure.in
===================================================================
RCS file: /cvsroot/gnats/gnats/gnats/configure.in,v
retrieving revision 1.36
diff -u -p -r1.36 configure.in
--- configure.in	24 Feb 2005 21:21:22 -0000	1.36
+++ configure.in	19 Apr 2005 00:49:19 -0000
@@ -25,6 +25,9 @@ AC_INIT(gnats,4.1.0,bug-gnats@gnu.org)
 
 AC_CONFIG_HEADER(autoconf.h)
 
+# default to using the file data store
+DSLIB_DIR="ds-file"
+
 AC_ARG_WITH(kerberos,
 [  --with-kerberos         include code for Kerberos authentication])
 AC_ARG_WITH(gnats-service,
@@ -338,7 +341,15 @@ if test -n "$verbose"; then
 	echo "	setting DEFAULT_MAIL_AGENT to $DEFAULT_MAIL_AGENT"
 fi
 
+if test "$DSLIB_DIR" = ds-file ; then
+  BUILD_FILE=yes
+  AC_DEFINE(DS_FILE,1,[Use flat-file datastore.])
+fi
+
+AC_SUBST(BUILD_FILE)
+AC_SUBST(DSLIB_DIR)
+
 # ***** End of configuration section *****
 
-AC_CONFIG_FILES([Makefile])
-AC_OUTPUT
+AC_CONFIG_FILES([Makefile] [ds-file/Makefile:ds.mk:ds-file/Makefile.in])
+AC_OUTPUT()
Index: database.c
===================================================================
RCS file: /cvsroot/gnats/gnats/gnats/database.c,v
retrieving revision 1.25
diff -u -p -r1.25 database.c
--- database.c	25 Nov 2002 13:58:33 -0000	1.25
+++ database.c	19 Apr 2005 00:49:19 -0000
@@ -22,6 +22,7 @@ Software Foundation, 59 Temple Place - S
 #include "mail.h"
 #include "pcodes.h"
 #include "field.h"
+#include "ds.h"
 
 struct databaseInfo
 {
@@ -42,10 +43,10 @@ struct databaseInfo
   FieldList requiredInputFields;
   DatabaseFieldInfo fieldInfo;
   AdmEntry *hostList;
-  IndexDesc indexDesc;
   MailMessageFormat mailFormatList;
   InputTemplate *inputTemplate;
   FieldIndex builtinFields[NUM_BUILTIN_FIELDS];
+  void *ds_private;    /* all the datastore specific stuff */
   /* Next and previous entries in the chain.  */
   struct databaseInfo *next, *prev;
 };
@@ -87,9 +88,9 @@ newDatabaseInfo (void)
   res->requiredInputFields = NULL;
   res->fieldInfo = NULL;
   res->hostList = NULL;
-  res->indexDesc = NULL;
   res->mailFormatList = NULL;
   res->inputTemplate = NULL;
+  res->ds_private = NULL;
 
   for (x = 0; x < NUM_BUILTIN_FIELDS; x++)
     {
@@ -275,16 +276,6 @@ setRequiredInputFields (DatabaseInfo dat
 }
 
 void
-setIndexDesc (DatabaseInfo database, IndexDesc new)
-{
-  if (databaseValid (database))
-    {
-      finishIndexDesc (database, new);
-      database->indexDesc = new;
-    }
-}
-
-void
 setInputTemplate (DatabaseInfo database, InputTemplate *list)
 {
   database->inputTemplate = list;
@@ -316,6 +307,12 @@ setMailFormatList (DatabaseInfo database
   database->mailFormatList = formatList;
 }
 
+void
+setDatastorePrivate(DatabaseInfo database, void *data)
+{
+  database->ds_private = data;
+}
+
 int
 keepReceivedHeaders (const DatabaseInfo database)
 {
@@ -583,6 +580,12 @@ getMailFormatList (const DatabaseInfo da
   return database->mailFormatList;
 }
 
+void *
+getDatastorePrivate(const DatabaseInfo database)
+{
+  return database->ds_private;
+}
+
 static AdmEntry *dbList = NULL;
 
 static int
@@ -741,25 +744,17 @@ loadDatabase (const char *databaseName, 
 	       res->databaseEnt->admFields[DatabaseListPath],
 	       databaseName);
       freeDatabaseInfo (res);
-      res = NULL;
+      return NULL;
     }
-  else
-    {
-      char *path;
 
-      initHostList (res);
-      path = gnats_adm_dir (res, "dbconfig");
-      if (fconfigParse (res, path, NULL, err))
-	{
-	  freeDatabaseInfo (res);
-	  res = NULL;
-	}
-      else
-	{
-	  initIndex (res);
-	}
-      free (path);
+  initHostList (res);
+
+  if (db_init (res, err) < 0)
+    {
+      freeDatabaseInfo (res);
+      return NULL;
     }
+
   return res;
 }
 
@@ -867,12 +862,6 @@ getRequiredInputFields (const DatabaseIn
   return database->requiredInputFields;
 }
 
-IndexDesc
-getIndexDesc (const DatabaseInfo database)
-{
-  return database->indexDesc;
-}
-
 InputTemplate *
 getInputTemplate (const DatabaseInfo database)
 {
@@ -921,16 +910,6 @@ clearGlobalChangeActions (DatabaseInfo d
 }
 
 static void
-clearIndexDesc (DatabaseInfo database)
-{
-  if (database->indexDesc != NULL)
-    {
-      freeIndexDesc (database->indexDesc);
-      database->indexDesc = NULL;
-    }
-}
-
-static void
 clearQueryFormatList (DatabaseInfo database)
 {
   if (database->queryFormatList != NULL)
@@ -965,11 +944,13 @@ freeDatabaseInfo (DatabaseInfo database)
 {
   if (database != NULL)
     {
-      clearPRChain (database);
       clearQueryFormatList (database);
-      clearFieldList (database);
       clearMessageFormatList (database);
-      clearIndexDesc (database);
+      if (database->ds_private != NULL)
+        {
+	  db_destroy (database);  /* free up the datastore specific stuff */
+        }
+      clearFieldList (database);
       clearAuditTrailFormat (database);
       clearHostList (database);
       clearInputTemplate (database);
Index: database.h
===================================================================
RCS file: /cvsroot/gnats/gnats/gnats/database.h,v
retrieving revision 1.13
diff -u -p -r1.13 database.h
--- database.h	24 Oct 2002 12:45:09 -0000	1.13
+++ database.h	19 Apr 2005 00:49:19 -0000
@@ -102,8 +102,7 @@ extern void setBuiltinDBField (DatabaseI
 extern void setMailFormatList (DatabaseInfo database,
 			       MailMessageFormat mailFormatList);
 extern void setRequiredInputFields (DatabaseInfo database, FieldList list);
-
-extern void setIndexDesc (DatabaseInfo database, const IndexDesc desc);
+extern void setDatastorePrivate(DatabaseInfo database, void *data);
 
 extern int keepReceivedHeaders (const DatabaseInfo database);
 extern int debugMode (DatabaseInfo database);
@@ -124,12 +123,12 @@ extern int createCategoryDirs (const Dat
 QueryFormat *getAuditTrailFormat (const DatabaseInfo database);
 extern int categoryDirPerms (const DatabaseInfo database);
 extern FieldList getRequiredInputFields (const DatabaseInfo database);
-extern IndexDesc getIndexDesc (const DatabaseInfo database);
 extern InputTemplate *getInputTemplate (const DatabaseInfo database);
 extern QueryFormat *getQueryFormatList (const DatabaseInfo database);
 extern MailMessageFormat getMailFormatList (const DatabaseInfo database);
 extern FieldIndex getBuiltinField (const DatabaseInfo database,
 				   int whichField);
+extern void *getDatastorePrivate(const DatabaseInfo database);
 
 extern void freeDatabaseInfo (DatabaseInfo database);
 
Index: ds.h
===================================================================
RCS file: ds.h
diff -N ds.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ ds.h	19 Apr 2005 00:49:19 -0000
@@ -0,0 +1,67 @@
+/* The GNATS datastore API.
+   Copyright (C) 2005 Free Software Foundation, Inc.
+   Contributed by Mel Hatzis <hatzis@wattes.org>.
+
+This file is part of GNU GNATS.
+
+GNU GNATS is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+GNU GNATS is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with GNU GNATS; see the file COPYING. if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA  */
+
+#ifndef _DS_H
+#define _DS_H
+
+#include "gnats.h"
+#include "database.h"
+#include "query.h"
+#include "pr.h"
+
+/* --------- ds-[db]/db.c ------------- */
+
+/* The function that is invoked from db_query () is of this type.
+   First argument is the result # (1 for the first PR matched, 2 for
+   the second, etc). The second argument is the PR entry for the
+   PR, and the final argument is the QueryFormat supplied to db_query.
+
+   The function is invoked with a NULL PR entry when we run out of PRs
+   to match; the result # is the total # of PRs that were matched. */
+typedef void (*QueryFunc)(int, PR *, QueryFormat *);
+
+extern int db_init (DatabaseInfo db, ErrorDesc *err);
+extern void db_destroy (DatabaseInfo db);
+extern int db_query (const DatabaseInfo database, int ac, char **av,
+		     QueryExpr exp, QueryFunc func, QueryFormat *query_format,
+		     ErrorDesc *err);
+extern void db_reset (const DatabaseInfo database, ErrorDesc *err);
+
+/* --------- ds-[db]/pr.c ------------- */
+
+extern void pr_init (PR *pr);
+extern void pr_destroy (PR *pr);
+extern int pr_create (PR *pr, ErrorDesc *err);
+extern int pr_update (PR *curr_pr, PR *new_pr, ErrorDesc *err);
+extern int pr_delete (const DatabaseInfo database, const char *prnum,
+		      ErrorDesc *err);
+extern int pr_load (PR *pr, ErrorDesc *err);
+extern int pr_load_fields (PR *pr, FieldList flds);
+extern PR *pr_load_by_id (const DatabaseInfo database, const char *prnum,
+			  int prune, ErrorDesc *err);
+extern int pr_load_fields (PR *pr, FieldList flds);
+extern int pr_exists (const DatabaseInfo database, const char *prnum,
+		      ErrorDesc *err);
+
+/* --------- ds-[db]/fld.c ------------- */
+                          
+extern char *field_cache_value (PR *pr, FieldIndex field);
+
+#endif /* !_DS_H */
Index: ds.mk
===================================================================
RCS file: ds.mk
diff -N ds.mk
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ ds.mk	19 Apr 2005 00:49:19 -0000
@@ -0,0 +1,85 @@
+# Common macros and targets included by all datastore Makefiles.
+# Copyright (C) 2005 Free Software Foundation, Inc.
+# Adapted from Makefile.in by Mel Hatzis <hatzis@wattes.org>.
+#
+# This file is part of GNU GNATS.
+#
+# GNU GNATS is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+# 
+# GNU GNATS is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with GNU gnats; see the file COPYING.  If not, write to
+# the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+
+#**** Start of system configuration section.
+
+#**** End of GNATS-specific configuration variables.
+
+CC = @CC@
+AR = ar
+AR_FLAGS = rc
+
+# Set RANLIB = echo if your system doesn't have or need ranlib.
+RANLIB = @RANLIB@
+
+DIFFOPT = @DIFFOPT@
+
+# These are set by autoconf.
+M4 = @M4@
+
+CFLAGS = @CFLAGS@
+GCC_CFLAGS = @GCC_CFLAGS@
+LDFLAGS = @LDFLAGS@
+
+SHELL = /bin/sh
+
+all:
+	@if [ "$(BUILD_DS)" = "yes" ]; then \
+		$(MAKE) TARGETLIB=$(TARGETLIB) $(GNATS_VARS) all-ds; \
+	else \
+		echo "run configure with $(CONFIG_OPT) to build $(TARGETLIB)"; \
+	fi
+dist:
+info:
+dvi:
+clean:    doclean
+mostlyclean: domostlyclean
+distclean: dodistclean
+maintainer-clean realclean: dodistclean
+
+.c.o:
+	$(CC) -c $(INCLUDEDIR) $(CFLAGS) $(GCC_CFLAGS) $(CPPFLAGS) $(DEFS) $<
+
+$(TARGETLIB): $(LIBOBJS)
+	-rm -f tmp_$(TARGETLIB) $(TARGETLIB)
+	$(AR) $(AR_FLAGS) tmp_$(TARGETLIB) $(LIBOBJS)
+	$(RANLIB) tmp_$(TARGETLIB)
+	mv tmp_$(TARGETLIB) $(TARGETLIB)
+
+doclean: domostlyclean
+	-rm -f *.o core
+	-rm -f $(TARGETLIB)
+	-rm -f TAGS
+
+domostlyclean:
+	-rm -f *.toc *.log *.vr *.fn *.cp *.tp *.ky *.pg *.i *.s *.aux *.cps
+
+dodistclean: doclean
+	-rm -f Makefile config.status config.cache autoconf.h config config.log
+	-rm -rf =* .\#* \#* *~*
+	-rm -f *.orig *.rej
+
+# Prevent GNU make v3 from overflowing arg limit on SysV.
+.NOEXPORT:
+
+#### -------------------------------------------------------------
Index: edit.c
===================================================================
RCS file: /cvsroot/gnats/gnats/gnats/edit.c,v
retrieving revision 1.61
diff -u -p -r1.61 edit.c
--- edit.c	24 Feb 2005 19:21:12 -0000	1.61
+++ edit.c	19 Apr 2005 00:49:19 -0000
@@ -23,6 +23,7 @@ Software Foundation, 59 Temple Place - S
 #include "regex.h"
 #include "query.h"
 #include "mail.h"
+#include "ds.h"
 
 /* Euuugh.  There's gotta be a better way to keep this around.  */
 static char *newAuditTrailEntries = NULL;
@@ -276,14 +277,6 @@ processPRChanges (const char *editUserEm
   ChangeActions globalActions = globalChangeActions (database);
   int *fieldsChanged;
 
-  if (! PR_IS_FULL (old_pr))
-    {
-      if (fillInPR (old_pr, err) != 0)
-	{
-	  return 1;
-	}
-    }
-
   fieldsChanged = (int *) xmalloc (sizeof (int) * num_fields);
 
   for (x = 0; x < num_fields; x++)
@@ -401,13 +394,6 @@ static int
 rewrite_pr (PR *pr, PR *new_pr, const char *editUserEmailAddr, ErrorDesc *err)
 {
   const DatabaseInfo database = new_pr->database;
-  FILE *prfile;
-
-  char *new_path = NULL;
-  char *old_path = NULL;
-  char *bkup_path = NULL;
-
-  int res = 1;
 
   newAuditTrailEntries = NULL;
 
@@ -430,123 +416,11 @@ rewrite_pr (PR *pr, PR *new_pr, const ch
       return 0;
     }
 
-  /* check to see if the category changes, and if so, write the 
-     new file out, and unlink the old one.  */
-  if (strcmp (field_value (pr, CATEGORY (database)),
-	      field_value (new_pr, CATEGORY (database))) != 0)
-    {
-      char *dirPath;
-
-      asprintf (&dirPath, "%s/%s", databaseDir (database), 
-		field_value (new_pr, CATEGORY (database)));
-
-      if (! fileExists (dirPath))
-	{
-	  if (createCategoryDirs (database))
-	    {
-	      mode_t mode = categoryDirPerms (database);
-
-	      if (mkdir (dirPath, mode) != 0)
-		{
-		  setError (err, CODE_FILE_ERROR, 
-			    "Error creating directory for category %s: %s",
-			    field_value (new_pr, CATEGORY (database)),
-			    strerror (errno));
-		  free (dirPath);
-		  return 0;
-		}
-	    }
-	  else
-	    {
-	      setError (err, CODE_FILE_ERROR, 
-			"Directory for category %s does not exist",
-			field_value (new_pr, CATEGORY (database)));
-	      free (dirPath);
-	      return 0;
-	    }
-	}
-      free (dirPath);
-    }
-
-  /* backup the current PR file */
-  old_path = gen_pr_path (pr);
-  asprintf (&bkup_path, "%s.old", old_path);
-  if (rename (old_path, bkup_path) < 0)
-    {
-      if (errno != EXDEV)
-	{
-	  setError (err, CODE_FILE_ERROR, "Could not rename %s: %s",
-		    old_path, strerror (errno));
-	  free (bkup_path);
-	  free (old_path);
-	  return 0;
-	}
-      if (copy_file (old_path, bkup_path) != 0)
-	{
-	  setError (err, CODE_FILE_ERROR, "Could not copy %s to %s: %s",
-		    old_path, bkup_path, strerror (errno));
-	  free (bkup_path);
-	  free (old_path);
-	  return 0;
-	}
-
-      /* Don't complain if this fails, since trying to write to it will give
-	 us the diagnostic if it's really serious.  */
-      unlink (old_path);
-    }
-
-  /* Now build the file.  */
-  new_path = gen_pr_path (new_pr);
-  prfile = fopen (new_path, "w+");
-  if (prfile == (FILE *) NULL)
-    {
-      setError (err, CODE_FILE_ERROR, "Cannot write the PR to %s: %s",
-		new_path, strerror (errno));
-      free (bkup_path);
-      free (old_path);
-      free (new_path);
-      return 0;
-    }
-
-  /* We had to wait until now because we wanted to wait and see
-     about changing the closed_date. XXX ??? !!! FIXME */
-
-  buildIndexEntry (new_pr);
-
-  write_entire_header (prfile, new_pr, "\n");
-  fprintf (prfile, "\n");
-  write_entire_pr (prfile, new_pr, "\n");
-
-  if (fclose (prfile) == EOF)
+  if (!pr_update (pr, new_pr, err))
     {
-      setError (err, CODE_FILE_ERROR, "Error writing out PR %s: %s",
-		new_path, strerror (errno));
-      free (bkup_path);
-      free (old_path);
-      free (new_path);
       return 0;
     }
 
-  unlink (bkup_path);
-
-  /* unlink the old file, if it is in another category dir. */
-  if (strcmp (field_value (pr, CATEGORY (database)),
-	      field_value (new_pr, CATEGORY (database))) != 0)
-    {
-      unlink (old_path);
-    }
-
-  free (bkup_path);
-  free (old_path);
-  free (new_path);
-
-  /* write out the new index.  */
-  replaceCurrentPRInIndex (pr, new_pr, err);
-  if (writeIndex (database, err) != 0)
-    {
-      res = 0;
-    }
-
   /* Send mail about the edit.  */
   if (newAuditTrailEntries != NULL)
     {
@@ -556,7 +430,7 @@ rewrite_pr (PR *pr, PR *new_pr, const ch
       newAuditTrailEntries = NULL;
     }
 
-  return res;
+  return 1;
 }
 
 
@@ -586,12 +460,12 @@ replace_pr (const DatabaseInfo database,
       return 0;
     }
 
-  if (! prExists (database, prnum, err))
+  if (! pr_exists (database, prnum, err))
     {
       return 0;
     }
 
-  curr_pr = readPRWithNum (database, prnum, 0, err);
+  curr_pr = pr_load_by_id (database, prnum, 0, err);
   if (curr_pr == NULL)
     {
       setError (err, CODE_NO_INDEX,
@@ -685,7 +559,7 @@ validateFieldValue (FieldIndex field, co
 	    char *prnum = get_next_field (&tptr, ' ');
 	    if (prnum[0] != '\0')
 	      {
-		if (! prExists (fieldDefForIndex (field)->database, prnum,
+		if (! pr_exists (fieldDefForIndex (field)->database, prnum,
 				err))
 		  {
 		    setError (err, CODE_INVALID_FIELD_CONTENTS,
@@ -994,7 +868,7 @@ edit_field (const DatabaseInfo database,
   int res;
   PR *pr, *new_pr;
 
-  if (! prExists (database, prnum, err))
+  if (! pr_exists (database, prnum, err))
     {
       free (newcontents);
       if (changeReason != NULL)
@@ -1034,7 +908,7 @@ edit_field (const DatabaseInfo database,
       return 0;
     }
 
-  pr = readPRWithNum (database, prnum, 0, err);
+  pr = pr_load_by_id (database, prnum, 0, err);
   if (pr == NULL)
     {
       unlock_pr (database, prnum, err);
@@ -1071,7 +945,7 @@ edit_field (const DatabaseInfo database,
   set_field (new_pr, NUMBER (database), prnum, err);
   set_field (new_pr, CATEGORY (database), field_value (pr, CATEGORY (database)),
 	     err);
-  fillInPR (new_pr, err);
+  pr_load (new_pr, err);
 
   /* set_field () verifies that the value is valid before doing the
      set.  */
Index: fconfig.y
===================================================================
RCS file: fconfig.y
diff -N fconfig.y
--- fconfig.y	13 Jan 2003 13:22:10 -0000	1.37
+++ /dev/null	1 Jan 1970 00:00:00 -0000
@@ -1,852 +0,0 @@
-%{
-#include "gnats.h"
-#include "index.h"
-#include "field.h"
-  extern DatabaseInfo databaseBeingDefined;
-  static FieldDef currField;
-  static ChangeActions currChange;
-  static FieldEdit *currEdit;
-  static QueryFormat *qformat;
-  static FieldList requiredFlds;
-  static InputTemplate *inputTemplate;
-  static MailMessageFormat mailFormat;
-  IndexDesc indexEntry;
-  static int badFile;
-  struct qstring;
-
-  extern char *takeQString (struct qstring *str);
-  extern char *qStrVal (struct qstring *str);
-%}
-
-%union {
-  int intval;
-  char *sval;
-  struct qstring *qstr;
-  AdmFieldDesc *adm_field_des;
-  FieldList flist;
-  StringList *stringlist;
-  InputTemplate *inputlist;
-  MailAddress *mailaddr;
-  MailAddressList *mailaddrlist;
-}
-
-%token FIELD STRINGTYPE QDEFAULT MATCHING ENUM MULTIENUMTOK VALUES DEFAULT
-%token EXACT_REGEXP INEXACT_REGEXP ALL FORMAT ENUMSEPARATORSTOK
-%token MULTITEXTTYPE DATETYPE ENUM_IN_FILE MULTI_ENUM_IN_FILE
-%token PATHTOK FIELDSTOK KEYTOK
-%token QSTRING INTVAL TEXTSEARCH QUERYTOK FORMATTOK INDEXTOK
-%token SEPARATORTOK RESTRICTEDTOK NOSPACESTOK INTEGERTOK INPUTDEFAULTTOK
-%token BUILTINTOK ALLOWANYVALUETOK REQUIRETOK APPENDFIELDTOK SETFIELDTOK
-%token CHANGETOK DESCRIPTIONTOK INPUTTOK DATABASEINFOTOK
-%token DEBUGMODETOK KEEPRECTOK NOTIFYEXPTOK LIBEXECDIRTOK SUBMITTERACKTOK
-%token BUSINESSDAYTOK BUSINESSWEEKTOK CREATECATEGORYDIRSTOK FALSETOK TRUETOK
-%token MAILFORMATTOK TOADDRESSESTOK FROMADDRESSTOK REPLYTOTOK FIXEDTOK
-%token BODYTOK HEADERTOK AUDITTRAILFMTTOK ADDAUDITTRAILTOK 
-%token REQUIRECHANGEREASONTOK READONLYTOK BINARYINDEXTOK RAWTOK
-%token BADTOK AUXFLAGSTOK PRLISTTOK MAXPRSTOK EDITONLYTOK VIRTUALFORMATTOK
-%token CATPERMSTOK
-%type <sval> optChangeExpr
-%type <qstr> QSTRING
-%type <intval> INTVAL
-%type <adm_field_des> enumFieldList enumFieldMember
-%type <flist> queryFieldsList FieldListMember fieldEditFieldList
-%type <stringlist> enumValueList regexpList auxFlagsList
-%type <inputlist> inputFields inputFieldsList
-%type <intval> booleanVal
-%type <flist> mailAddressTries MailAddressMember
-%type <mailaddr> mailAddress
-%type <mailaddrlist> mailAddressList
-%type <flist> requiredFieldsList
-%%
-
-config		: configEnts
-		| parseError
-		;
-
-configEnts	: databaseInfo fieldDecStmt optQueryList auditTrailFmt mailFormatList globalChangeEnts indexDescription inputDescription
-		;
-
-databaseInfo	: DATABASEINFOTOK '{' databaseInfoList '}'
-		| DATABASEINFOTOK '{' parseError '}'
-		| /* empty */ {
-		    fconferror ("Missing/bad database info section");
-		}
-		;
-
-parseError	: error { badFile = 1; } ;
-
-databaseInfoList: databaseInfoEnt
-		| databaseInfoList databaseInfoEnt
-		;
-
-databaseInfoEnt	: DEBUGMODETOK booleanVal { 
-		    setDebugMode (databaseBeingDefined, $2);
-		}
-		| KEEPRECTOK booleanVal {
-		    setKeepReceivedHeaders (databaseBeingDefined, $2);
-		}
-		| NOTIFYEXPTOK booleanVal {
-		    setNotifyExpire (databaseBeingDefined, $2);
-		}
-		| LIBEXECDIRTOK QSTRING {
-		    setBinDir (databaseBeingDefined, qStrVal ($2)); 
-		}
-		| SUBMITTERACKTOK booleanVal {
-		    setSubmitterAck (databaseBeingDefined, $2); 
-		}
-		| BUSINESSDAYTOK INTVAL '-' INTVAL {
-		  setBusinessDay (databaseBeingDefined, $2, $4);
-		}
-		| BUSINESSWEEKTOK INTVAL '-' INTVAL {
-		    setBusinessWeek(databaseBeingDefined,$2, $4);
-		}
-		| CREATECATEGORYDIRSTOK booleanVal {
-		    setCreateCategoryDirs (databaseBeingDefined, $2);
-		}
-		| CATPERMSTOK QSTRING {
-		    setCategoryDirPerms (databaseBeingDefined, qStrVal ($2));
-		}
-		;
-
-booleanVal	: FALSETOK { $$ = 0; }
-		| TRUETOK  { $$ = 1; }
-		;
-
-fieldDecStmt	: fieldDecList
-		| /* empty */ {
-		    fconferror ("Missing/bad field declarations");
-		}
-		;
-
-fieldDecList	: fieldDec
-		| fieldDecList fieldDec
-		;
-
-fieldDec	: startFieldDec '{' fieldDataList '}' {
-		    currField = NULL;
-		}
-		| startFieldDec '{' parseError '}' {
-		    currField = NULL;
-		}
-		;
-
-startFieldDec	: FIELD QSTRING {
-		    char *fname = takeQString ($2);
-		    currField = newFieldDef (databaseBeingDefined, fname);
-		    if (currField == NULL)
-		      {
-			char *msg;
-			asprintf (&msg, "Duplicate field definition for %s\n",
-				  fname);
-			fconferror (msg);
-			free (msg);
-		      }
-		    currField->default_value = NULL;
-		}
-		;
-
-fieldDataList	: fieldData
-		| fieldDataList fieldData
-		;
-
-fieldData	: fieldDataType
-		| miscOptions
-		| queryDefault
-		| virtualFieldFormat
-		;
-
-virtualFieldFormat: VIRTUALFORMATTOK plainFormat {
-		    currField->virtualFormat = qformat;
-		    qformat = NULL;
-		}
-		;
-
-fieldDataType	: stringType
-		| enumType
-		| MULTITEXTTYPE optSimple {
-		    currField->datatype = MultiText;
-		    currField->defaultSearchType = NilSearch;
-		}
-		| DATETYPE {
-		  currField->datatype = Date;
-		  currField->defaultSearchType = LessThan;
-		}
-		| INTEGERTOK optSimple {
-		    currField->datatype = Integer;
-		    currField->defaultSearchType = NilSearch;
-		}
-		| PRLISTTOK prListOpts {
-		    currField->datatype = PRListType;
-		    currField->defaultSearchType = RegCmp;
-		}
-		;
-
-stringType	: STRINGTYPE {
-		    currField->datatype = Text;
-		}
-		| STRINGTYPE MATCHING '{' regexpList '}' {
-		    currField->datatype = TextWithRegex;
-		}
-		| STRINGTYPE MATCHING '{' parseError '}'
-		;
-
-regexpList	: QSTRING {
-		    $$ = new_string_list_ent (takeQString ($1), NULL);
-		    currField->regex = $$;
-		}
-		| regexpList QSTRING {
-		    $1->next = new_string_list_ent (takeQString ($2), NULL);
-		    $$ = $1->next;
-		}
-		;
-
-enumType	: ENUM '{' enumcontents '}' {
-		    currField->datatype = Enum;
-		    currField->defaultSearchType = RegCmp;
-		}
-		| ENUM_IN_FILE '{' enumFileContents '}' {
-		    currField->datatype = Enum;
-		    currField->defaultSearchType = RegCmp;
-		    initAdmField (currField);
-		}
-		| MULTI_ENUM_IN_FILE '{' multiEnumFileContents '}' {
-		    currField->datatype = MultiEnum;
-		    currField->defaultSearchType = RegCmp;
-		    initAdmField (currField);
-		}
-		| MULTIENUMTOK '{' multienumcontents '}' {
-		   currField->datatype = MultiEnum;
-		   currField->defaultSearchType = RegCmp;
-		   if (currField->multiEnumSeparator == NULL)
-		     {
-		       currField->multiEnumSeparator
-			 = xstrdup (DEFAULT_MULTIENUM_SEPARATOR);
-		     }
-		}
-		| ENUM '{' parseError '}'
-		| ENUM_IN_FILE '{' parseError '}'
-		;
-
-globalChangeEnts: /* empty */
-		| globalChangeEnts globalChangeEnt
-		;
-
-globalChangeEnt	: changeHeader changeOpts '}' {
-		    addGlobalChangeActions (databaseBeingDefined, currChange);
-		    currChange = NULL;
-		}
-		;
-
-changeClause	: changeHeader changeOpts '}' {
-		    ChangeActions *p = &(currField->changeActions);
-		    while (*p != NULL)
-		      {
-			p = &((*p)->next);
-		      }
-		    *p = currChange;
-		    currChange = NULL;
-		}
-		;
-
-changeHeader	: CHANGETOK optChangeExpr '{' {
-		    currChange = newChangeAction (databaseBeingDefined, $2);
-		    if ($2 != NULL) 
-		      {
-			free ($2);
-		      }
-		}
-		;
-
-optChangeExpr	: QSTRING {
-		    $$ = takeQString ($1);
-		}
-		| /* Empty */ {
-		    $$ = NULL;
-		}
-		;
-
-changeOpts	: changeOpt
-		| changeOpts changeOpt
-		;
-
-changeOpt	: REQUIRETOK '{' reqFieldNameList '}'
-		| REQUIRETOK '{' parseError '}'
-		| SETFIELDTOK fieldEditOpts {
-		    currChange->edits = currEdit;
-		    currEdit = NULL;
-		}
-		| APPENDFIELDTOK fieldEditOpts {
-		    currEdit->append = 1;
-		    currChange->edits = currEdit;
-		    currEdit = NULL;
-		}
-		| ADDAUDITTRAILTOK {
-		    currChange->addAuditTrail = 1;
-		}
-		| AUDITTRAILFMTTOK plainFormat {
-		    currChange->auditTrailFormat = qformat;
-		    qformat = NULL;
-		}
-		| REQUIRECHANGEREASONTOK {
-		    currChange->requireChangeReason = 1;
-		}
-		;
-
-reqFieldNameList: reqFieldNameEnt
-		| reqFieldNameList reqFieldNameEnt
-		;
-
-reqFieldNameEnt: QSTRING {
-		    FieldList foo 
-		      = newFieldListEnt (databaseBeingDefined, qStrVal ($1),
-					 currChange->requiredFields);
-		    currChange->requiredFields = foo;
-		}
-		;
-
-fieldEditOpts	: fieldEditName '{' fieldEditFormat optFieldEditFieldList '}'
-		| fieldEditName '{' parseError '}'
-		;
-
-fieldEditName	: QSTRING {
-		    currEdit = (FieldEdit *) xmalloc (sizeof (FieldEdit));
-		    currEdit->expr = NULL;
-		    currEdit->fieldToEditName = takeQString ($1);
-		    currEdit->append = 0;
-		    currEdit->textFormat = NULL;
-		    currEdit->fieldsForFormat = NULL;
-		    currEdit->next = NULL;
-		}
-		;
-
-fieldEditFormat	: QSTRING {
-		    currEdit->textFormat = takeQString ($1);
-		}
-		;
-
-optFieldEditFieldList: /* empty */ {
-		    currEdit->fieldsForFormat = NULL;
-		}
-		| fieldEditFieldList {
-		}
-		;
-
-fieldEditFieldList: QSTRING {
-		    $$ = newFieldListEnt (databaseBeingDefined, qStrVal ($1),
-					  NULL);
-		    currEdit->fieldsForFormat = $$;
-		}
-		| fieldEditFieldList QSTRING {
-		    $$ = newFieldListEnt (databaseBeingDefined, qStrVal ($2),
-					  NULL);
-		    $1->next = $$;
-		}
-		;
-
-optSimple	: /* Empty */
-		| '{' defaultFieldVal '}'
-		| '{' parseError '}'
-		;
-
-prListOpts	: /* Empty */
-		| '{' prListOptList '}'
-		;
-
-prListOptList	: MAXPRSTOK INTVAL {
-		    currField->maxPrsPerLine = $2;
-		}
-		;
-
-enumcontents	: enumItem
-		| enumcontents enumItem
-		;
-
-multienumcontents: multiEnumItem
-		| multienumcontents multiEnumItem
-		;
-
-multiEnumItem	: enumItem
-		| ENUMSEPARATORSTOK QSTRING {
-		   currField->multiEnumSeparator = takeQString ($2);
-		}
-		;
-
-enumItem	: VALUES '{' enumValueList '}'
-		| VALUES '{' parseError '}'
-		| defaultFieldVal
-		;
-
-enumValueList	: QSTRING {
-		    $$ = new_string_list_ent (takeQString ($1), NULL);
-		    currField->enumValues = $$;
-		}
-		| enumValueList QSTRING {
-		    $1->next = new_string_list_ent (takeQString ($2), NULL);
-		    $$ = $1->next;
-		}
-		;
-
-enumFileContents: enumFileItem
-		| enumFileContents enumFileItem
-		;
-
-enumFileItem	: PATHTOK QSTRING {
-		    currField->adm_db_name = takeQString ($2);
-		}
-		| FIELDSTOK '{' enumFieldList '}' KEYTOK QSTRING {
-		    AdmFieldDesc *p;
-		    int which = 0;
-
-		    for (p = currField->adm_field_des; p != NULL; p = p->next)
-		      {
-			if (strcmp (p->name, qStrVal ($6)) == 0)
-			  {
-			    break;
-			  }
-			which++;
-		      }
-
-		    if (p != NULL)
-		      {
-			currField->key_field = which;
-		      }
-		    else
-		      {
-			char *msg;
-
-			asprintf (&msg, "Invalid adm subfield %s\n",
-				  qStrVal ($6));
-			fconferror (msg);
-			free (msg);
-		      }
-		}
-		| FIELDSTOK '{' parseError '}'
-		| defaultFieldVal
-		| ALLOWANYVALUETOK {
-		    currField->allow_any_value = 1;
-		}
-		;
-
-multiEnumFileContents: multiEnumFileItem
-		| enumFileContents multiEnumFileItem
-		;
-
-multiEnumFileItem : enumFileItem
-		| ENUMSEPARATORSTOK QSTRING {
-		   currField->multiEnumSeparator = takeQString ($2);
-		}
-		;
-
-defaultFieldVal : DEFAULT QSTRING {
-		    currField->default_value = takeQString ($2);
-		}
-		| INPUTDEFAULTTOK QSTRING {
-		    currField->input_default_value = takeQString ($2);
-		}
-		;
-
-enumFieldList	: enumFieldMember {
-		    currField->adm_db_fields = 1;
-		    currField->adm_field_des = $1;
-		    $$ = $1;
-		}
-		| enumFieldList enumFieldMember {
-		    $1->next = $2;
-		    $$ = $2;
-		    currField->adm_db_fields++;
-		}
-		;
-
-enumFieldMember	: QSTRING {
-		    $$ = (AdmFieldDesc *) 
-		      xmalloc (sizeof (AdmFieldDesc));
-		    $$->name = takeQString ($1);
-		    $$->next = NULL;
-		}
-		;
-
-queryDefault	: QDEFAULT EXACT_REGEXP {
-		    currField->defaultSearchType = RegCmp;
-		}
-		| QDEFAULT INEXACT_REGEXP {
-		    currField->defaultSearchType = RegFind;
-		}
-		;
-
-miscOptions	: TEXTSEARCH {
-		    currField->textsearch = 1;
-		}
-		| RESTRICTEDTOK {
-		    currField->restricted = 1;
-		}
-		| NOSPACESTOK {
-		     currField->nospaces = 1;
-		}
-		| BUILTINTOK QSTRING {
-		    if (setBuiltinField (currField, qStrVal ($2)) != 0)
-		      {
-			char *msg;
-			asprintf (&msg, "Invalid builtin fieldname %s",
-				  qStrVal ($2));
-			fconferror (msg);
-			free (msg);
-		      }
-		}
-		| changeClause
-		| DESCRIPTIONTOK QSTRING {
-		    currField->description = takeQString ($2);
-		}
-		| READONLYTOK {
-		    currField->readonly = 1;
-		}
-		| AUXFLAGSTOK '{' auxFlagsList '}' {
-		    currField->auxFlags = $3;
-		}
-		| EDITONLYTOK {
-		    currField->editonly = 1;
-		}
-		;
-
-auxFlagsList	: QSTRING {
-		    $$ = new_string_list_ent (takeQString ($1), NULL);
-		    currField->auxFlags = $$;
-		}
-		| auxFlagsList QSTRING {
-		    $1->next = new_string_list_ent (takeQString ($2), NULL);
-		    $$ = $1->next;
-		}
-		;
-
-optQueryList	: /* empty */
-		| queryList
-		;
-
-queryList	: query
-		| queryList query
-		;
-
-query		: queryBegin '{' queryFmt '}' {
-		    addQueryFormat (databaseBeingDefined, qformat);
-		    qformat = NULL;
-		}
-		| queryBegin '{' parseError '}' {
-		    freeQueryFormat (qformat);
-		    qformat = NULL;
-		}
-		;
-
-queryBegin	: QUERYTOK QSTRING {
-		    qformat = (QueryFormat *) xmalloc (sizeof (QueryFormat));
-		    qformat->name = takeQString ($2);
-		    qformat->printf = NULL;
-		    qformat->separator = NULL;
-		    qformat->fields = NULL;
-		    qformat->next = NULL;
-		}
-		;
-
-queryFmt	: queryFieldDesc optQueryOpts
-		| queryOpts queryFieldDesc
-		;
-
-queryFieldDesc	: FIELDSTOK ALL
-		| queryfields
-		| queryPrintf queryfields
-		| queryfields queryPrintf
-		;
-
-optQueryOpts	:/* empty */
-		| queryOpts
-		;
-
-queryOpts	: RAWTOK {
-		    qformat->rawQuery = 1;
-		}
-		;
-
-queryPrintf	: FORMATTOK QSTRING {
-		    qformat->printf = takeQString ($2);
-		}
-		;
-
-queryfields	: FIELDSTOK '{' queryFieldsList '}'
-		| FIELDSTOK '{' parseError '}'
-		;
-
-queryFieldsList	: FieldListMember {
-		    qformat->fields = $1;
-		}
-		| queryFieldsList FieldListMember {
-		    $1->next = $2;
-		    $$ = $2;
-		}
-		;
-
-FieldListMember: QSTRING {
-		    $$ = newFieldListEnt (databaseBeingDefined, qStrVal ($1),
-					  NULL);
-		    if (parseComplexFieldIndex ($$->ent) != 0)
-		      {
-			char *msg;
-
-			asprintf (&msg, "Field %s is invalid\n", qStrVal ($1));
-			fconferror (msg);
-			free (msg);
-		      }
-		}
-		;
-
-indexDescription: indexheader '{' indexlist '}' {
-		    setIndexDesc (databaseBeingDefined, indexEntry);
-		    indexEntry = NULL;
-		}
-		| indexheader '{' parseError '}' {
-		    freeIndexDesc (indexEntry);
-		    indexEntry = NULL;
-		}
-		| parseError
-		;
-
-indexheader	: INDEXTOK {
-		    indexEntry = newIndexDesc (databaseBeingDefined);
-		}
-		;
-
-indexlist	: indexEnt
-		| indexlist indexEnt
-		;
-
-indexEnt	: PATHTOK QSTRING {
-		    setIndexDescPath (indexEntry, qStrVal ($2));
-		}
-		| FIELDSTOK '{' indexFieldList '}'
-		| FIELDSTOK '{' parseError '}'
-		| indexSep
-		;
-
-indexFieldList	: FieldListMember {
-		    addFieldToIndex (indexEntry, $1);
-		}
-		| indexFieldList FieldListMember {
-		    addFieldToIndex (indexEntry, $2);
-		}
-		;
-
-indexSep	: SEPARATORTOK QSTRING {
-		    setIndexDescSeparator (indexEntry, takeQString ($2));
-		}
-		| BINARYINDEXTOK booleanVal {
-		    setIndexDescBinary (indexEntry, $2);
-		}
-		;
-
-inputDescription: INPUTTOK '{' inputEnt '}'
-		| INPUTTOK '{' parseError '}'
-		;
-
-inputEnt	: inputFields requiredFields {
-		    setInputTemplate (databaseBeingDefined, $1);
-		}
-                ;
-
-requiredFields  :/* empty */
-                | REQUIRETOK '{' requiredFieldsList '}' {
-		    setRequiredInputFields (databaseBeingDefined, requiredFlds);
-                }
-                | REQUIRETOK '{' parseError '}' {
-                    freeFieldList (requiredFlds);
-                    requiredFlds = NULL;
-                }
-		;
-
-requiredFieldsList: FieldListMember {
-		    requiredFlds = $1;
-                }
-                | requiredFieldsList FieldListMember {
-                    $1->next = $2;
-                    $$ = $2;
-                }
-		;
-
-inputFields	: FIELDSTOK '{' inputFieldsList '}' {
-		    $$ = inputTemplate;
-		    inputTemplate = NULL;
-		}
-		| FIELDSTOK '{' parseError '}' {
-		    $$ = NULL;
-		}
-		;
-
-inputFieldsList	: QSTRING {
-		     $$ = (InputTemplate *) 
-		       xmalloc (sizeof (InputTemplate));
-		     $$->index = find_field_index (databaseBeingDefined,
-						   qStrVal ($1));
-		     if ($$->index == InvalidFieldIndex)
-		       {
-			 char *msg;
-
-			 asprintf (&msg, "Field %s is invalid\n",
-				   qStrVal ($1));
-			 fconferror (msg);
-			 free ($$);
-			 free (msg);
-			 inputTemplate = NULL;
-		       }
-		     else
-		       {
-			 inputTemplate = $$;
-			 $$->next = NULL;
-		       }
-		}
-		| inputFieldsList QSTRING {
-		     $$ = (InputTemplate *)
-		       xmalloc (sizeof (InputTemplate));
-		     $$->index = find_field_index (databaseBeingDefined,
-						   qStrVal ($2));
-		     if ($$->index == InvalidFieldIndex)
-		       {
-			 char *msg;
-
-			 asprintf (&msg, "Field %s is invalid\n",
-				   qStrVal ($2));
-			 fconferror (msg);
-			 free (msg);
-			 free ($$);
-		       }
-		     else
-		       {
-			 $$->next = NULL;
-			 $1->next = $$;
-			 if (inputTemplate == NULL)
-			   {
-			     inputTemplate = $$;
-			   }
-		       }
-		}
-		;
-
-mailFormatList	: mailFormat
-		| mailFormatList mailFormat
-		;
-
-mailFormat	: mailFormatHeader '{' mailFormatBody '}' {
-		    addMessageFormat (databaseBeingDefined, mailFormat);
-		    mailFormat = NULL;
-		}
-		| mailFormatHeader '{' parseError '}' {
-		    freeMessageFormat (mailFormat);
-		    mailFormat = NULL;
-		}
-		;
-
-mailFormatHeader: MAILFORMATTOK QSTRING {
-  		    mailFormat 
-		      = (MailMessageFormat)
-		      xmalloc (sizeof (struct mail_message_format));
-		    mailFormat->name = takeQString ($2);
-		    mailFormat->toAddresses = NULL;
-		    mailFormat->fromAddress = NULL;
-		    mailFormat->replyTo = NULL;
-		    mailFormat->body = NULL;
-		    mailFormat->header = NULL;
-		    mailFormat->next = NULL;
-		}
-		;
-
-mailFormatBody	: mailFormatEnt
-		| mailFormatBody mailFormatEnt
-		;
-
-mailFormatEnt	: bodyFormat {
-		    mailFormat->body = qformat;
-		    qformat = NULL;
-		}
-		| headerFormat {
-		    mailFormat->header = qformat;
-		    qformat = NULL;
-		}
-		| TOADDRESSESTOK '{' mailAddressList '}' {
-		    mailFormat->toAddresses = $3;
-		}
-		| FROMADDRESSTOK '{' mailAddress '}'  {
-		    mailFormat->fromAddress = $3;
-		}
-		| REPLYTOTOK '{' mailAddressList '}' {
-		  mailFormat->replyTo = $3;
-		}
-		;
-
-bodyFormat	: BODYTOK plainFormat
-		;
-
-headerFormat	: HEADERTOK plainFormat
-		;
-
-plainFormat	: plainFormatHeader queryFmt '}'
-		| plainFormatHeader parseError '}' {
-		    freeQueryFormat (qformat);
-		    qformat = NULL;
-		}
-		;
-
-plainFormatHeader: '{' {
-		    qformat = (QueryFormat *) xmalloc (sizeof (QueryFormat));
-		    qformat->name = NULL;
-		    qformat->printf = NULL;
-		    qformat->separator = NULL;
-		    qformat->fields = NULL;
-		    qformat->next = NULL;
-		}
-		;
-
-mailAddressList	: mailAddress {
-		   $$ = (MailAddressList *) xmalloc (sizeof (MailAddressList));
-		   $$->address = $1;
-		   $$->next = NULL;
-		}
-		| mailAddressList mailAddress {
-		   MailAddressList *lp = $$;
-		   while (lp->next != NULL) { lp = lp->next; }
-		   lp->next =
-		     (MailAddressList *) xmalloc (sizeof (MailAddressList));
-		   lp->next->address = $2;
-		   lp->next->next = NULL;
-		}
-		;
-
-mailAddress	: FIXEDTOK QSTRING {
-		    $$ = (MailAddress *) xmalloc (sizeof (MailAddress));
-		    $$->fixedAddress = takeQString ($2);
-		    $$->addresses = NULL;
-		}
-		| mailAddressTries {
-		    $$ = (MailAddress *) xmalloc (sizeof (MailAddress));
-		    $$->fixedAddress = NULL;
-		    $$->addresses = $1;
-		}
-		;
-
-mailAddressTries: MailAddressMember {
-		    $$ = $1;
-		}
-		| mailAddressTries '|' MailAddressMember {
-		    FieldList p = $$;
-		    while (p->next != NULL) 
-		      {
-			p = p->next;
-		      }
-		    p->next = $3;
-		}
-		;
-
-MailAddressMember: QSTRING {
-		    $$ = newFieldListEnt (databaseBeingDefined, qStrVal ($1),
-					  NULL);
-		}
-		;
-
-auditTrailFmt	: AUDITTRAILFMTTOK plainFormat {
-		    setAuditTrailFormat (databaseBeingDefined, qformat);
-		}
-		;
Index: fconfig.y.in
===================================================================
RCS file: fconfig.y.in
diff -N fconfig.y.in
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ fconfig.y.in	19 Apr 2005 00:49:19 -0000
@@ -0,0 +1,812 @@
+%{
+#include "gnats.h"
+#include "field.h"
+#ifdef DS_FILE
+#include "ds-file/index.h"
+#endif
+  extern DatabaseInfo databaseBeingDefined;
+  static FieldDef currField;
+  static ChangeActions currChange;
+  static FieldEdit *currEdit;
+  static QueryFormat *qformat;
+  static FieldList requiredFlds;
+  static InputTemplate *inputTemplate;
+  static MailMessageFormat mailFormat;
+  static int badFile;
+  struct qstring;
+#ifdef DS_FILE
+  IndexDesc indexEntry;
+#endif
+
+  extern char *takeQString (struct qstring *str);
+  extern char *qStrVal (struct qstring *str);
+%}
+
+%union {
+  int intval;
+  char *sval;
+  struct qstring *qstr;
+  AdmFieldDesc *adm_field_des;
+  FieldList flist;
+  StringList *stringlist;
+  InputTemplate *inputlist;
+  MailAddress *mailaddr;
+  MailAddressList *mailaddrlist;
+}
+
+%token FIELD STRINGTYPE QDEFAULT MATCHING ENUM MULTIENUMTOK VALUES DEFAULT
+%token EXACT_REGEXP INEXACT_REGEXP ALL FORMAT ENUMSEPARATORSTOK
+%token MULTITEXTTYPE DATETYPE ENUM_IN_FILE MULTI_ENUM_IN_FILE
+%token PATHTOK FIELDSTOK KEYTOK
+%token QSTRING INTVAL TEXTSEARCH QUERYTOK FORMATTOK INDEXTOK
+%token SEPARATORTOK RESTRICTEDTOK NOSPACESTOK INTEGERTOK INPUTDEFAULTTOK
+%token BUILTINTOK ALLOWANYVALUETOK REQUIRETOK APPENDFIELDTOK SETFIELDTOK
+%token CHANGETOK DESCRIPTIONTOK INPUTTOK DATABASEINFOTOK
+%token DEBUGMODETOK KEEPRECTOK NOTIFYEXPTOK LIBEXECDIRTOK SUBMITTERACKTOK
+%token BUSINESSDAYTOK BUSINESSWEEKTOK CREATECATEGORYDIRSTOK FALSETOK TRUETOK
+%token MAILFORMATTOK TOADDRESSESTOK FROMADDRESSTOK REPLYTOTOK FIXEDTOK
+%token BODYTOK HEADERTOK AUDITTRAILFMTTOK ADDAUDITTRAILTOK 
+%token REQUIRECHANGEREASONTOK READONLYTOK BINARYINDEXTOK RAWTOK
+%token BADTOK AUXFLAGSTOK PRLISTTOK MAXPRSTOK EDITONLYTOK VIRTUALFORMATTOK
+%token CATPERMSTOK
+%type <sval> optChangeExpr
+%type <qstr> QSTRING
+%type <intval> INTVAL
+%type <adm_field_des> enumFieldList enumFieldMember
+%type <flist> queryFieldsList FieldListMember fieldEditFieldList
+%type <stringlist> enumValueList regexpList auxFlagsList
+%type <inputlist> inputFields inputFieldsList
+%type <intval> booleanVal
+%type <flist> mailAddressTries MailAddressMember
+%type <mailaddr> mailAddress
+%type <mailaddrlist> mailAddressList
+%type <flist> requiredFieldsList
+%%
+
+config		: configEnts
+		| parseError
+		;
+
+configEnts	: databaseInfo fieldDecStmt optQueryList auditTrailFmt mailFormatList globalChangeEnts dsDescription inputDescription
+		;
+
+databaseInfo	: DATABASEINFOTOK '{' databaseInfoList '}'
+		| DATABASEINFOTOK '{' parseError '}'
+		| /* empty */ {
+		    fconferror ("Missing/bad database info section");
+		}
+		;
+
+parseError	: error { badFile = 1; } ;
+
+databaseInfoList: databaseInfoEnt
+		| databaseInfoList databaseInfoEnt
+		;
+
+databaseInfoEnt	: DEBUGMODETOK booleanVal { 
+		    setDebugMode (databaseBeingDefined, $2);
+		}
+		| KEEPRECTOK booleanVal {
+		    setKeepReceivedHeaders (databaseBeingDefined, $2);
+		}
+		| NOTIFYEXPTOK booleanVal {
+		    setNotifyExpire (databaseBeingDefined, $2);
+		}
+		| LIBEXECDIRTOK QSTRING {
+		    setBinDir (databaseBeingDefined, qStrVal ($2)); 
+		}
+		| SUBMITTERACKTOK booleanVal {
+		    setSubmitterAck (databaseBeingDefined, $2); 
+		}
+		| BUSINESSDAYTOK INTVAL '-' INTVAL {
+		  setBusinessDay (databaseBeingDefined, $2, $4);
+		}
+		| BUSINESSWEEKTOK INTVAL '-' INTVAL {
+		    setBusinessWeek(databaseBeingDefined,$2, $4);
+		}
+		| CREATECATEGORYDIRSTOK booleanVal {
+		    setCreateCategoryDirs (databaseBeingDefined, $2);
+		}
+		| CATPERMSTOK QSTRING {
+		    setCategoryDirPerms (databaseBeingDefined, qStrVal ($2));
+		}
+		;
+
+booleanVal	: FALSETOK { $$ = 0; }
+		| TRUETOK  { $$ = 1; }
+		;
+
+fieldDecStmt	: fieldDecList
+		| /* empty */ {
+		    fconferror ("Missing/bad field declarations");
+		}
+		;
+
+fieldDecList	: fieldDec
+		| fieldDecList fieldDec
+		;
+
+fieldDec	: startFieldDec '{' fieldDataList '}' {
+		    currField = NULL;
+		}
+		| startFieldDec '{' parseError '}' {
+		    currField = NULL;
+		}
+		;
+
+startFieldDec	: FIELD QSTRING {
+		    char *fname = takeQString ($2);
+		    currField = newFieldDef (databaseBeingDefined, fname);
+		    if (currField == NULL)
+		      {
+			char *msg;
+			asprintf (&msg, "Duplicate field definition for %s\n",
+				  fname);
+			fconferror (msg);
+			free (msg);
+		      }
+		    currField->default_value = NULL;
+		}
+		;
+
+fieldDataList	: fieldData
+		| fieldDataList fieldData
+		;
+
+fieldData	: fieldDataType
+		| miscOptions
+		| queryDefault
+		| virtualFieldFormat
+		;
+
+virtualFieldFormat: VIRTUALFORMATTOK plainFormat {
+		    currField->virtualFormat = qformat;
+		    qformat = NULL;
+		}
+		;
+
+fieldDataType	: stringType
+		| enumType
+		| MULTITEXTTYPE optSimple {
+		    currField->datatype = MultiText;
+		    currField->defaultSearchType = NilSearch;
+		}
+		| DATETYPE {
+		  currField->datatype = Date;
+		  currField->defaultSearchType = LessThan;
+		}
+		| INTEGERTOK optSimple {
+		    currField->datatype = Integer;
+		    currField->defaultSearchType = NilSearch;
+		}
+		| PRLISTTOK prListOpts {
+		    currField->datatype = PRListType;
+		    currField->defaultSearchType = RegCmp;
+		}
+		;
+
+stringType	: STRINGTYPE {
+		    currField->datatype = Text;
+		}
+		| STRINGTYPE MATCHING '{' regexpList '}' {
+		    currField->datatype = TextWithRegex;
+		}
+		| STRINGTYPE MATCHING '{' parseError '}'
+		;
+
+regexpList	: QSTRING {
+		    $$ = new_string_list_ent (takeQString ($1), NULL);
+		    currField->regex = $$;
+		}
+		| regexpList QSTRING {
+		    $1->next = new_string_list_ent (takeQString ($2), NULL);
+		    $$ = $1->next;
+		}
+		;
+
+enumType	: ENUM '{' enumcontents '}' {
+		    currField->datatype = Enum;
+		    currField->defaultSearchType = RegCmp;
+		}
+		| ENUM_IN_FILE '{' enumFileContents '}' {
+		    currField->datatype = Enum;
+		    currField->defaultSearchType = RegCmp;
+		    initAdmField (currField);
+		}
+		| MULTI_ENUM_IN_FILE '{' multiEnumFileContents '}' {
+		    currField->datatype = MultiEnum;
+		    currField->defaultSearchType = RegCmp;
+		    initAdmField (currField);
+		}
+		| MULTIENUMTOK '{' multienumcontents '}' {
+		   currField->datatype = MultiEnum;
+		   currField->defaultSearchType = RegCmp;
+		   if (currField->multiEnumSeparator == NULL)
+		     {
+		       currField->multiEnumSeparator
+			 = xstrdup (DEFAULT_MULTIENUM_SEPARATOR);
+		     }
+		}
+		| ENUM '{' parseError '}'
+		| ENUM_IN_FILE '{' parseError '}'
+		;
+
+globalChangeEnts: /* empty */
+		| globalChangeEnts globalChangeEnt
+		;
+
+globalChangeEnt	: changeHeader changeOpts '}' {
+		    addGlobalChangeActions (databaseBeingDefined, currChange);
+		    currChange = NULL;
+		}
+		;
+
+changeClause	: changeHeader changeOpts '}' {
+		    ChangeActions *p = &(currField->changeActions);
+		    while (*p != NULL)
+		      {
+			p = &((*p)->next);
+		      }
+		    *p = currChange;
+		    currChange = NULL;
+		}
+		;
+
+changeHeader	: CHANGETOK optChangeExpr '{' {
+		    currChange = newChangeAction (databaseBeingDefined, $2);
+		    if ($2 != NULL) 
+		      {
+			free ($2);
+		      }
+		}
+		;
+
+optChangeExpr	: QSTRING {
+		    $$ = takeQString ($1);
+		}
+		| /* Empty */ {
+		    $$ = NULL;
+		}
+		;
+
+changeOpts	: changeOpt
+		| changeOpts changeOpt
+		;
+
+changeOpt	: REQUIRETOK '{' reqFieldNameList '}'
+		| REQUIRETOK '{' parseError '}'
+		| SETFIELDTOK fieldEditOpts {
+		    currChange->edits = currEdit;
+		    currEdit = NULL;
+		}
+		| APPENDFIELDTOK fieldEditOpts {
+		    currEdit->append = 1;
+		    currChange->edits = currEdit;
+		    currEdit = NULL;
+		}
+		| ADDAUDITTRAILTOK {
+		    currChange->addAuditTrail = 1;
+		}
+		| AUDITTRAILFMTTOK plainFormat {
+		    currChange->auditTrailFormat = qformat;
+		    qformat = NULL;
+		}
+		| REQUIRECHANGEREASONTOK {
+		    currChange->requireChangeReason = 1;
+		}
+		;
+
+reqFieldNameList: reqFieldNameEnt
+		| reqFieldNameList reqFieldNameEnt
+		;
+
+reqFieldNameEnt: QSTRING {
+		    FieldList foo 
+		      = newFieldListEnt (databaseBeingDefined, qStrVal ($1),
+					 currChange->requiredFields);
+		    currChange->requiredFields = foo;
+		}
+		;
+
+fieldEditOpts	: fieldEditName '{' fieldEditFormat optFieldEditFieldList '}'
+		| fieldEditName '{' parseError '}'
+		;
+
+fieldEditName	: QSTRING {
+		    currEdit = (FieldEdit *) xmalloc (sizeof (FieldEdit));
+		    currEdit->expr = NULL;
+		    currEdit->fieldToEditName = takeQString ($1);
+		    currEdit->append = 0;
+		    currEdit->textFormat = NULL;
+		    currEdit->fieldsForFormat = NULL;
+		    currEdit->next = NULL;
+		}
+		;
+
+fieldEditFormat	: QSTRING {
+		    currEdit->textFormat = takeQString ($1);
+		}
+		;
+
+optFieldEditFieldList: /* empty */ {
+		    currEdit->fieldsForFormat = NULL;
+		}
+		| fieldEditFieldList {
+		}
+		;
+
+fieldEditFieldList: QSTRING {
+		    $$ = newFieldListEnt (databaseBeingDefined, qStrVal ($1),
+					  NULL);
+		    currEdit->fieldsForFormat = $$;
+		}
+		| fieldEditFieldList QSTRING {
+		    $$ = newFieldListEnt (databaseBeingDefined, qStrVal ($2),
+					  NULL);
+		    $1->next = $$;
+		}
+		;
+
+optSimple	: /* Empty */
+		| '{' defaultFieldVal '}'
+		| '{' parseError '}'
+		;
+
+prListOpts	: /* Empty */
+		| '{' prListOptList '}'
+		;
+
+prListOptList	: MAXPRSTOK INTVAL {
+		    currField->maxPrsPerLine = $2;
+		}
+		;
+
+enumcontents	: enumItem
+		| enumcontents enumItem
+		;
+
+multienumcontents: multiEnumItem
+		| multienumcontents multiEnumItem
+		;
+
+multiEnumItem	: enumItem
+		| ENUMSEPARATORSTOK QSTRING {
+		   currField->multiEnumSeparator = takeQString ($2);
+		}
+		;
+
+enumItem	: VALUES '{' enumValueList '}'
+		| VALUES '{' parseError '}'
+		| defaultFieldVal
+		;
+
+enumValueList	: QSTRING {
+		    $$ = new_string_list_ent (takeQString ($1), NULL);
+		    currField->enumValues = $$;
+		}
+		| enumValueList QSTRING {
+		    $1->next = new_string_list_ent (takeQString ($2), NULL);
+		    $$ = $1->next;
+		}
+		;
+
+enumFileContents: enumFileItem
+		| enumFileContents enumFileItem
+		;
+
+enumFileItem	: PATHTOK QSTRING {
+		    currField->adm_db_name = takeQString ($2);
+		}
+		| FIELDSTOK '{' enumFieldList '}' KEYTOK QSTRING {
+		    AdmFieldDesc *p;
+		    int which = 0;
+
+		    for (p = currField->adm_field_des; p != NULL; p = p->next)
+		      {
+			if (strcmp (p->name, qStrVal ($6)) == 0)
+			  {
+			    break;
+			  }
+			which++;
+		      }
+
+		    if (p != NULL)
+		      {
+			currField->key_field = which;
+		      }
+		    else
+		      {
+			char *msg;
+
+			asprintf (&msg, "Invalid adm subfield %s\n",
+				  qStrVal ($6));
+			fconferror (msg);
+			free (msg);
+		      }
+		}
+		| FIELDSTOK '{' parseError '}'
+		| defaultFieldVal
+		| ALLOWANYVALUETOK {
+		    currField->allow_any_value = 1;
+		}
+		;
+
+multiEnumFileContents: multiEnumFileItem
+		| enumFileContents multiEnumFileItem
+		;
+
+multiEnumFileItem : enumFileItem
+		| ENUMSEPARATORSTOK QSTRING {
+		   currField->multiEnumSeparator = takeQString ($2);
+		}
+		;
+
+defaultFieldVal : DEFAULT QSTRING {
+		    currField->default_value = takeQString ($2);
+		}
+		| INPUTDEFAULTTOK QSTRING {
+		    currField->input_default_value = takeQString ($2);
+		}
+		;
+
+enumFieldList	: enumFieldMember {
+		    currField->adm_db_fields = 1;
+		    currField->adm_field_des = $1;
+		    $$ = $1;
+		}
+		| enumFieldList enumFieldMember {
+		    $1->next = $2;
+		    $$ = $2;
+		    currField->adm_db_fields++;
+		}
+		;
+
+enumFieldMember	: QSTRING {
+		    $$ = (AdmFieldDesc *) 
+		      xmalloc (sizeof (AdmFieldDesc));
+		    $$->name = takeQString ($1);
+		    $$->next = NULL;
+		}
+		;
+
+queryDefault	: QDEFAULT EXACT_REGEXP {
+		    currField->defaultSearchType = RegCmp;
+		}
+		| QDEFAULT INEXACT_REGEXP {
+		    currField->defaultSearchType = RegFind;
+		}
+		;
+
+miscOptions	: TEXTSEARCH {
+		    currField->textsearch = 1;
+		}
+		| RESTRICTEDTOK {
+		    currField->restricted = 1;
+		}
+		| NOSPACESTOK {
+		     currField->nospaces = 1;
+		}
+		| BUILTINTOK QSTRING {
+		    if (setBuiltinField (currField, qStrVal ($2)) != 0)
+		      {
+			char *msg;
+			asprintf (&msg, "Invalid builtin fieldname %s",
+				  qStrVal ($2));
+			fconferror (msg);
+			free (msg);
+		      }
+		}
+		| changeClause
+		| DESCRIPTIONTOK QSTRING {
+		    currField->description = takeQString ($2);
+		}
+		| READONLYTOK {
+		    currField->readonly = 1;
+		}
+		| AUXFLAGSTOK '{' auxFlagsList '}' {
+		    currField->auxFlags = $3;
+		}
+		| EDITONLYTOK {
+		    currField->editonly = 1;
+		}
+		;
+
+auxFlagsList	: QSTRING {
+		    $$ = new_string_list_ent (takeQString ($1), NULL);
+		    currField->auxFlags = $$;
+		}
+		| auxFlagsList QSTRING {
+		    $1->next = new_string_list_ent (takeQString ($2), NULL);
+		    $$ = $1->next;
+		}
+		;
+
+optQueryList	: /* empty */
+		| queryList
+		;
+
+queryList	: query
+		| queryList query
+		;
+
+query		: queryBegin '{' queryFmt '}' {
+		    addQueryFormat (databaseBeingDefined, qformat);
+		    qformat = NULL;
+		}
+		| queryBegin '{' parseError '}' {
+		    freeQueryFormat (qformat);
+		    qformat = NULL;
+		}
+		;
+
+queryBegin	: QUERYTOK QSTRING {
+		    qformat = (QueryFormat *) xmalloc (sizeof (QueryFormat));
+		    qformat->name = takeQString ($2);
+		    qformat->printf = NULL;
+		    qformat->separator = NULL;
+		    qformat->fields = NULL;
+		    qformat->next = NULL;
+		}
+		;
+
+queryFmt	: queryFieldDesc optQueryOpts
+		| queryOpts queryFieldDesc
+		;
+
+queryFieldDesc	: FIELDSTOK ALL
+		| queryfields
+		| queryPrintf queryfields
+		| queryfields queryPrintf
+		;
+
+optQueryOpts	:/* empty */
+		| queryOpts
+		;
+
+queryOpts	: RAWTOK {
+		    qformat->rawQuery = 1;
+		}
+		;
+
+queryPrintf	: FORMATTOK QSTRING {
+		    qformat->printf = takeQString ($2);
+		}
+		;
+
+queryfields	: FIELDSTOK '{' queryFieldsList '}'
+		| FIELDSTOK '{' parseError '}'
+		;
+
+queryFieldsList	: FieldListMember {
+		    qformat->fields = $1;
+		}
+		| queryFieldsList FieldListMember {
+		    $1->next = $2;
+		    $$ = $2;
+		}
+		;
+
+FieldListMember: QSTRING {
+		    $$ = newFieldListEnt (databaseBeingDefined, qStrVal ($1),
+					  NULL);
+		    if (parseComplexFieldIndex ($$->ent) != 0)
+		      {
+			char *msg;
+
+			asprintf (&msg, "Field %s is invalid\n", qStrVal ($1));
+			fconferror (msg);
+			free (msg);
+		      }
+		}
+		;
+
+inputDescription: INPUTTOK '{' inputEnt '}'
+		| INPUTTOK '{' parseError '}'
+		;
+
+inputEnt	: inputFields requiredFields {
+		    setInputTemplate (databaseBeingDefined, $1);
+		}
+                ;
+
+requiredFields  :/* empty */
+                | REQUIRETOK '{' requiredFieldsList '}' {
+		    setRequiredInputFields (databaseBeingDefined, requiredFlds);
+                }
+                | REQUIRETOK '{' parseError '}' {
+                    freeFieldList (requiredFlds);
+                    requiredFlds = NULL;
+                }
+		;
+
+requiredFieldsList: FieldListMember {
+		    requiredFlds = $1;
+                }
+                | requiredFieldsList FieldListMember {
+                    $1->next = $2;
+                    $$ = $2;
+                }
+		;
+
+inputFields	: FIELDSTOK '{' inputFieldsList '}' {
+		    $$ = inputTemplate;
+		    inputTemplate = NULL;
+		}
+		| FIELDSTOK '{' parseError '}' {
+		    $$ = NULL;
+		}
+		;
+
+inputFieldsList	: QSTRING {
+		     $$ = (InputTemplate *) 
+		       xmalloc (sizeof (InputTemplate));
+		     $$->index = find_field_index (databaseBeingDefined,
+						   qStrVal ($1));
+		     if ($$->index == InvalidFieldIndex)
+		       {
+			 char *msg;
+
+			 asprintf (&msg, "Field %s is invalid\n",
+				   qStrVal ($1));
+			 fconferror (msg);
+			 free ($$);
+			 free (msg);
+			 inputTemplate = NULL;
+		       }
+		     else
+		       {
+			 inputTemplate = $$;
+			 $$->next = NULL;
+		       }
+		}
+		| inputFieldsList QSTRING {
+		     $$ = (InputTemplate *)
+		       xmalloc (sizeof (InputTemplate));
+		     $$->index = find_field_index (databaseBeingDefined,
+						   qStrVal ($2));
+		     if ($$->index == InvalidFieldIndex)
+		       {
+			 char *msg;
+
+			 asprintf (&msg, "Field %s is invalid\n",
+				   qStrVal ($2));
+			 fconferror (msg);
+			 free (msg);
+			 free ($$);
+		       }
+		     else
+		       {
+			 $$->next = NULL;
+			 $1->next = $$;
+			 if (inputTemplate == NULL)
+			   {
+			     inputTemplate = $$;
+			   }
+		       }
+		}
+		;
+
+mailFormatList	: mailFormat
+		| mailFormatList mailFormat
+		;
+
+mailFormat	: mailFormatHeader '{' mailFormatBody '}' {
+		    addMessageFormat (databaseBeingDefined, mailFormat);
+		    mailFormat = NULL;
+		}
+		| mailFormatHeader '{' parseError '}' {
+		    freeMessageFormat (mailFormat);
+		    mailFormat = NULL;
+		}
+		;
+
+mailFormatHeader: MAILFORMATTOK QSTRING {
+  		    mailFormat 
+		      = (MailMessageFormat)
+		      xmalloc (sizeof (struct mail_message_format));
+		    mailFormat->name = takeQString ($2);
+		    mailFormat->toAddresses = NULL;
+		    mailFormat->fromAddress = NULL;
+		    mailFormat->replyTo = NULL;
+		    mailFormat->body = NULL;
+		    mailFormat->header = NULL;
+		    mailFormat->next = NULL;
+		}
+		;
+
+mailFormatBody	: mailFormatEnt
+		| mailFormatBody mailFormatEnt
+		;
+
+mailFormatEnt	: bodyFormat {
+		    mailFormat->body = qformat;
+		    qformat = NULL;
+		}
+		| headerFormat {
+		    mailFormat->header = qformat;
+		    qformat = NULL;
+		}
+		| TOADDRESSESTOK '{' mailAddressList '}' {
+		    mailFormat->toAddresses = $3;
+		}
+		| FROMADDRESSTOK '{' mailAddress '}'  {
+		    mailFormat->fromAddress = $3;
+		}
+		| REPLYTOTOK '{' mailAddressList '}' {
+		  mailFormat->replyTo = $3;
+		}
+		;
+
+bodyFormat	: BODYTOK plainFormat
+		;
+
+headerFormat	: HEADERTOK plainFormat
+		;
+
+plainFormat	: plainFormatHeader queryFmt '}'
+		| plainFormatHeader parseError '}' {
+		    freeQueryFormat (qformat);
+		    qformat = NULL;
+		}
+		;
+
+plainFormatHeader: '{' {
+		    qformat = (QueryFormat *) xmalloc (sizeof (QueryFormat));
+		    qformat->name = NULL;
+		    qformat->printf = NULL;
+		    qformat->separator = NULL;
+		    qformat->fields = NULL;
+		    qformat->next = NULL;
+		}
+		;
+
+mailAddressList	: mailAddress {
+		   $$ = (MailAddressList *) xmalloc (sizeof (MailAddressList));
+		   $$->address = $1;
+		   $$->next = NULL;
+		}
+		| mailAddressList mailAddress {
+		   MailAddressList *lp = $$;
+		   while (lp->next != NULL) { lp = lp->next; }
+		   lp->next =
+		     (MailAddressList *) xmalloc (sizeof (MailAddressList));
+		   lp->next->address = $2;
+		   lp->next->next = NULL;
+		}
+		;
+
+mailAddress	: FIXEDTOK QSTRING {
+		    $$ = (MailAddress *) xmalloc (sizeof (MailAddress));
+		    $$->fixedAddress = takeQString ($2);
+		    $$->addresses = NULL;
+		}
+		| mailAddressTries {
+		    $$ = (MailAddress *) xmalloc (sizeof (MailAddress));
+		    $$->fixedAddress = NULL;
+		    $$->addresses = $1;
+		}
+		;
+
+mailAddressTries: MailAddressMember {
+		    $$ = $1;
+		}
+		| mailAddressTries '|' MailAddressMember {
+		    FieldList p = $$;
+		    while (p->next != NULL) 
+		      {
+			p = p->next;
+		      }
+		    p->next = $3;
+		}
+		;
+
+MailAddressMember: QSTRING {
+		    $$ = newFieldListEnt (databaseBeingDefined, qStrVal ($1),
+					  NULL);
+		}
+		;
+
+auditTrailFmt	: AUDITTRAILFMTTOK plainFormat {
+		    setAuditTrailFormat (databaseBeingDefined, qformat);
+		}
+		;
Index: file-pr.c
===================================================================
RCS file: /cvsroot/gnats/gnats/gnats/file-pr.c,v
retrieving revision 1.55
diff -u -p -r1.55 file-pr.c
--- file-pr.c	24 Feb 2005 19:21:12 -0000	1.55
+++ file-pr.c	19 Apr 2005 00:49:19 -0000
@@ -25,13 +25,13 @@ Software Foundation, 59 Temple Place - S
 #include "query.h"
 #include "pcodes.h"
 #include "mail.h"
+#include "ds.h"
 
 static int needs_analysis (PR *pr);
 
 static int run_atpr(PR *pr, AdmEntry *submitter, int submitter_rtime,
-		    struct tm *expired, char *bug_name, ErrorDesc *err);
+		    struct tm *expired, int bug_number, ErrorDesc *err);
 
-static int getBugNumber        (const DatabaseInfo database, ErrorDesc *err);
 static char* derive_submitter  (PR *pr);
 
 static int missing_required_fields (const DatabaseInfo database, PR *pr,
@@ -45,18 +45,13 @@ static int missing_required_fields (cons
    should cause the category to be automatically created.  */
 
 static int
-createNewPRFile (PR *pr, int flag_autocreate, ErrorDesc *err)
+createNewPR (PR *pr, ErrorDesc *err)
 {
   const DatabaseInfo database = pr->database;
   int bug_number = 0;
-  int errnum = 0;
   AdmEntry *submitter = NULL;
   AdmEntry *category = NULL;
   AdmEntry *responsible = NULL;
-  char *path = NULL;
-  char *prPath = NULL;
-  char *bug_name = NULL;
-  char number[14]; /* No more than 13 digits in a PR #, ha ha.  */
   struct tm *expired = NULL;
   int submitter_rtime;
 
@@ -248,15 +243,6 @@ createNewPRFile (PR *pr, int flag_autocr
       }
   }
 
-  /* Set arrival date and time of response.  */
-  {
-    time_t seconds  = time ((time_t) 0);
-    char arrival_time[GNATS_TIME_LENGTH];
-    gnats_strftime (arrival_time, GNATS_TIME_LENGTH, "%a %b %d %H:%M:%S %z %Y",
-		    localtime (&seconds));
-    set_field (pr, ARRIVAL_DATE (database), arrival_time, err);
-  }
-
   /* If the submitter isn't supposed to have a response time, don't bother
      trying to get it.  */
   if (submitter_rtime >= 0)
@@ -264,58 +250,6 @@ createNewPRFile (PR *pr, int flag_autocr
       expired = get_response_time (database, submitter_rtime);
     }
 
-  /* Put together the path to where the bug will be stored.  If the dir
-     is not there, and the category is the default, auto-create that one,
-     if we want to.  If not, make the bug pending, and store in there.  */
-
-  asprintf (&path, "%s/%s", databaseDir (database), bug_group);
-
-  errnum = fileExists (path) ? 0 : -1;
-
-  if (errnum != 0 && ! flag_autocreate)
-    {
-      /* XXX ??? !!! Should append a message to the email being sent out. */
-      bug_group = defaultCategory(database);
-      set_field (pr, CATEGORY (database), defaultCategory(database), err);
-      log_msg (LOG_INFO, 1, "directory does not exist, changing to default:",
-	       path);
-      free (path);
-      asprintf (&path, "%s/%s", databaseDir (database), bug_group);
-      errnum = fileExists (path) ? 0 : -1;
-    }
-
-  /* Check ERR again, to see if default category was there.  */
-  if (errnum != 0)
-    {
-      mode_t mode = categoryDirPerms (database);
-
-      if (strcmp (bug_group, defaultCategory(database)) == 0)
-	{
-	  log_msg (LOG_INFO, 1, defaultCategory(database),
-		   " does not exist, creating...");
-	  if (mkdir (path, mode) != 0)
-	    {
-	      setError (err, CODE_FILE_ERROR,
-			"Can't create `%s' directory under %s.",
-			defaultCategory(database), databaseDir (database));
-	      return -1;
-	    }
-	}
-      else if (flag_autocreate)
-	{
-	  if (mkdir (path, mode) != 0)
-	    {
-	      setError (err, CODE_FILE_ERROR,
-			"Cannot create group: %s", path);
-	      return -1;
-	    }
-	  else
-	    {
-	      log_msg (LOG_INFO, 1, "creating directory:", path);
-	    }
-	}
-    }
-
   {
     /* Ensure the PR has a valid STATE.  We set the default state
        on any of these cases:
@@ -348,39 +282,12 @@ createNewPRFile (PR *pr, int flag_autocr
       }
   }
 
-  /* Retrieve a unique bug number.  */
-  bug_number = getBugNumber (database, err);
+  bug_number = pr_create (pr, err);
 
   if (bug_number > 0)
     {
-      sprintf (number, "%d", bug_number);
-      set_field (pr, NUMBER (database), number, err);
-
       bad_fields = checkEnumTypes (pr, bad_fields, 1);
  
-      /* Write the file out.  */
-      asprintf (&bug_name, "%s/%d", bug_group, bug_number);
-      asprintf (&prPath, "%s/%s", databaseDir (database), bug_name);
-      if (createPrFile (pr, prPath, 1, err) != 0)
-	{
-	  bug_number = -1;
-	}
-      else
-	{
-	  log_msg (LOG_INFO, 1, "PR written out:", prPath);
-
-	  /* Add a line into the index for use by query-pr.  */
-	  /* This should be done regardless of whether the bug is default or
-	     not. */
-	  if (addToIndex (pr, err) != 0)
-	    {
-	      bug_number = -1;
-	    }
-	}
-    }
-
-  if (bug_number > 0)
-    {
       /* If it isn't in the default category [pending], send all of
 	 the mail for it, create an index entry, and run at_pr.  Otherwise,
 	 just notify gnats-admin.  */
@@ -400,7 +307,7 @@ createNewPRFile (PR *pr, int flag_autocr
 	  if (submitter_rtime != -1 && notifyExpire (database)
 	      && needs_analysis (pr))
 	    {
-	      if (run_atpr (pr, submitter, submitter_rtime, expired, bug_name, 
+	      if (run_atpr (pr, submitter, submitter_rtime, expired, bug_number,
 			    err) != 0)
 		{
 		  bug_number = -1;
@@ -417,18 +324,6 @@ createNewPRFile (PR *pr, int flag_autocr
   free_adm_entry (category);
   free_adm_entry (submitter);
   free_adm_entry (responsible);
-  if (path != NULL)
-    {
-      free (path);
-    }
-  if (prPath != NULL)
-    {
-      free (prPath);
-    }
-  if (bug_name != NULL)
-    {
-      free (bug_name);
-    }
   if (def_subm != NULL)
     {
       free (def_subm);
@@ -532,7 +427,7 @@ arg_quote (const char *s)
 
 static int
 run_atpr (PR *pr, AdmEntry *submitter, int submitter_rtime, 
-	  struct tm *expired, char *bug_name, ErrorDesc *err)
+	  struct tm *expired, int bug_number, ErrorDesc *err)
 {
   char *at_pr;
   char *command = NULL;
@@ -570,9 +465,9 @@ run_atpr (PR *pr, AdmEntry *submitter, i
 	  char *cont = arg_quote (submitter->admFields[SubmitterAdmContact]);
 	  char *pa = arg_quote (gnatsAdminMailAddr (pr->database));
       
-	  fprintf (p, "%s --database %s %d %s %s '%s' '%s' '%s'\n", at_pr,
+	  fprintf (p, "%s --database %s %d %d %s '%s' '%s' '%s'\n", at_pr,
 		   databaseName (pr->database),
-		   submitter_rtime, bug_name,
+		   submitter_rtime, bug_number,
 		   submitter->admFields[SubmitterAdmKey],
 		   orig, cont, pa);
 
@@ -659,7 +554,7 @@ checkIfReply (PR *pr, ErrorDesc *err)
   free (regs.start);
   free (regs.end);
 
-  if (! prExists (pr->database, prID, err))
+  if (! pr_exists (pr->database, prID, err))
     {
       free (prID);
       prID = NULL;
@@ -787,7 +682,7 @@ append_report (FILE *infile, PR *new_pr,
       date = get_curr_date ();
     }
 
-  if (! prExists (new_pr->database, number, err))
+  if (! pr_exists (new_pr->database, number, err))
     {
       return 1;
     }
@@ -827,7 +722,7 @@ Date: %s\n\
 
   fclose (infile);
 
-  pr = readPRWithNum (new_pr->database, number, 0, err);
+  pr = pr_load_by_id (new_pr->database, number, 0, err);
   if (pr == NULL)
     {
       return 1;
@@ -865,61 +760,6 @@ Date: %s\n\
   return 0;
 }
 
-/* Return the next available unique GNATS id.  */
-static int
-getBugNumber (const DatabaseInfo database, ErrorDesc *err)
-{
-  char *sbuf;
-  int bug_number;
-  FILE *bug_file;
-
-  /* First try to find and lock the gnats lock file.  We need this since
-     they want every bug to have a unique number.  If lock doesn't exist,
-     make it, if possible.  */
-  sbuf = gnats_adm_dir (database, "current");
-
-  block_signals ();
-
-  bug_file = fopen (sbuf, "r+");
-  if (bug_file == (FILE *) NULL)
-    {
-      log_msg (LOG_INFO, 1, "file 'current' not found, creating", sbuf);
-      bug_file = fopen (sbuf, "w+");
-      if (bug_file != (FILE *) NULL)
-	{
-	  bug_number = 1;
-	}
-      else
-	{
-	  setError (err, CODE_FILE_ERROR,
-		    "Can't create the GNATS 'current' file (%s).",
-		    sbuf);
-	  return -1;
-	}
-    }
-  else
-    {
-      if (fscanf (bug_file, "%d", &bug_number) != 1)
-	{
-	  setError (err, CODE_FILE_ERROR,
-		    "Can't read from the global PR number counter (%s).",
-		    sbuf);
-	  return -1;
-	}
-
-      bug_number++;      
-      rewind (bug_file);
-    }
-
-  fprintf (bug_file, "%d", bug_number);
-
-  fclose (bug_file);
-  unblock_signals ();
-  free (sbuf);
-
-  return bug_number;
-}
-
 /* Submit the PR whose contents are referred to by FP.
  * Return the new PR number if it's a new PR, or return the PR number of
  * the PR which was appended to if it's an existing PR. */
@@ -957,7 +797,7 @@ submit_pr (const DatabaseInfo database, 
       else
 	{
 	  read_pr (pr, fp, 0);
-	  retval = createNewPRFile (pr, createCategoryDirs (database), err);
+	  retval = createNewPR (pr, err);
 	  result = (retval < 0) ? 0 : retval;
 	}
     }
Index: gen-index.c
===================================================================
RCS file: /cvsroot/gnats/gnats/gnats/gen-index.c,v
retrieving revision 1.27
diff -u -p -r1.27 gen-index.c
--- gen-index.c	28 Jul 2002 19:21:11 -0000	1.27
+++ gen-index.c	19 Apr 2005 00:49:19 -0000
@@ -21,6 +21,8 @@ Software Foundation, 59 Temple Place - S
 
 #include "gnats.h"
 #include "gnats-dirs.h"
+/* temporary - gen-index will eventually only build for flat-file backend */
+#include "ds-file/ds-file.h"
 
 /* The name this program was run with.  */
 const char *program_name;
Index: getclose.c
===================================================================
RCS file: /cvsroot/gnats/gnats/gnats/getclose.c,v
retrieving revision 1.26
diff -u -p -r1.26 getclose.c
--- getclose.c	31 Oct 2002 23:43:28 -0000	1.26
+++ getclose.c	19 Apr 2005 00:49:19 -0000
@@ -37,6 +37,7 @@ Software Foundation, 59 Temple Place - S
 
 #include "gnats.h"
 #include "query.h"
+#include "ds.h"
 
 /* The name this program was run with.  */
 const char *program_name;
@@ -164,38 +165,42 @@ do_stat (PR *pr)
 }
 
 static void
-do_prlist (const DatabaseInfo database)
+process_pr (int num ATTRIBUTE_UNUSED, PR *pr,
+	    QueryFormat *data ATTRIBUTE_UNUSED)
 {
   ErrorDesc err;
-  PR *pr = getFirstPR (database, &err);
 
   if (pr == NULL)
     {
-      client_print_errors (database, err);
-      exit (1);
+      return;
     }
 
-  while (pr != NULL)
+  if (pr_load (pr, &err) == 0)
     {
-      if (check_state_type (database, field_value (pr, STATE (pr->database)),
-			    "closed")
-	  && gnats_regcmp ("no", field_value (pr, CONFIDENTIAL (pr->database)),
-			   NULL) == 0)
-	{
-	  if (fillInPR (pr, &err) == 0)
-	    {
-	      do_stat (pr);
-	      free_pr_contents (pr);
-	    }
-	  else
-	    {
-	      client_print_errors (database, err);
-	      fprintf (stderr, "Unable to read PR %s\n",
-		       field_value (pr, NUMBER (pr->database)));
-	    }
-	}
-      pr = getNextPR (pr);
+      do_stat (pr);
+      free_pr_contents (pr);
+    }
+  else
+    {
+      client_print_errors (pr->database, err);
+      fprintf (stderr, "Unable to read PR %s\n",
+	       field_value (pr, NUMBER (pr->database)));
+    }
+}
+
+static void
+do_prlist (const DatabaseInfo database)
+{
+  QueryExpr expr;
+  ErrorDesc err;
+  const char *query_expr = "(builtinfield:State[type] ~ \"closed\") & (builtinfield:Confidential ~ \"no\")";
+
+  expr = parseQueryExpression (database, query_expr, NULL);
+  if (db_query (database, 0, NULL, expr, process_pr, NULL, &err) < 0)
+    {
+      client_print_errors (database, err);
     }
+  freeQueryExpr (expr);
 }
 
 static void
Index: gnats.h
===================================================================
RCS file: /cvsroot/gnats/gnats/gnats/gnats.h,v
retrieving revision 1.55
diff -u -p -r1.55 gnats.h
--- gnats.h	24 Feb 2005 21:21:22 -0000	1.55
+++ gnats.h	19 Apr 2005 00:49:19 -0000
@@ -249,7 +249,6 @@ extern const char *program_name;
 
 #include "field.h"
 #include "database.h"
-#include "index.h"
 #include "pr.h"
 #include "query.h"
 #include "regex.h"
Index: index.c
===================================================================
RCS file: index.c
diff -N index.c
--- index.c	24 Feb 2005 19:21:12 -0000	1.41
+++ /dev/null	1 Jan 1970 00:00:00 -0000
@@ -1,1271 +0,0 @@
-/* Miscellaneous index routines for use with GNATS. 
-   Copyright (C) 1993, 1995, 1999, 2000, 2001 Free Software Foundation, Inc.
-   Contributed by Brendan Kehoe (brendan@cygnus.com) and
-   by Tim Wicinski (wicinski@barn.com).
-   
-   Heavily revised by Bob Manson (manson@juniper.net); also added
-   binary index support.
-
-This file is part of GNU GNATS.
-
-GNU GNATS is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2, or (at your option)
-any later version.
-
-GNU GNATS is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with GNU GNATS; see the file COPYING.  If not, write to the Free
-Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111, USA.  */
-
-/* Binary format (all values are in MSB-first order):
- * 1 byte: number of fields in each index entry
- * For each record:
- *     2 bytes: total length of record, including this 2 byte length
- *     For each field:
- *         2 bytes: length of field, not including this 2 byte length or NUL
- *		    terminator
- *	   N bytes: data in field, NUL-terminated
- */
-	
-
-#include "gnats.h"
-#include "gnatsd.h"
-
-struct index_desc
-{
-  DatabaseInfo database;
-  char *path;
-  FieldList fields;
-  char *separator;
-  int isBinary;
-  int numberOfFields;
-  int *fieldInIndex;
-  time_t mtime;
-  int indexRead;
-  PR *prChain;
-};
-
-typedef struct index_file_desc
-{
-  IndexDesc desc;
-  FILE *fileDes;
-} *IndexFileDesc;
-
-struct indexEntry
-{
-  char *buf;
-  char **fields;
-  PR *nextPR;
-  PR *prevPR;
-};
-
-static PR *nextIndexEntryBinary (IndexFileDesc fp);
-
-static int
-format_field (FieldIndex field, const char *value, const char *separator,
-	      char **dest, size_t *length)
-{
-  char datebuf[30];
-  size_t valLen;
-  size_t destLen;
-  size_t sepLen;
-
-  if (value == NULL)
-    {
-      value = "";
-    }
-
-  if (fieldDefForIndex (field)->nospaces)
-    {
-      char *r = strchr (value, ' ');
-      if (r != NULL)
-	{
-	  valLen = r - value;
-	}
-    }
-
-  if (fieldDefForIndex (field)->datatype == Date)
-    {
-      time_t d = 0;
-      if (value != NULL && *value != '\0')
-	{
-	  /* We used to fail here if the date was invalid, but that's
-	     a mistake.  */
-	  d = get_any_date (value);
-	}
-      sprintf (datebuf, "%d", (int) d);
-      value = datebuf;
-    }
-
-  if (value == NULL)
-    {
-      value = "";
-    }
-  destLen = *length;
-  valLen = strlen (value);
-  sepLen = strlen (separator);
-
-  *dest = xrealloc (*dest, destLen + valLen + sepLen + 1);
-  memcpy (*dest + destLen, separator, sepLen);
-  memcpy (*dest + destLen + sepLen, value, valLen);
-  (*dest)[destLen + sepLen + valLen] = '\0';
-  *length = destLen + sepLen + valLen;
-  return sepLen + valLen;
-}
-
-char *
-createIndexEntry (PR *pr, size_t *reclen)
-{
-  FieldList f;
-  char *res;
-  IndexDesc indexDesc = getIndexDesc (pr->database);
-
-  if (indexIsBinary (pr->database))
-    {
-      return createIndexEntryBinary (pr, reclen);
-    }
-
-  /* XXX ??? !!! For now.  We need a way to refer to "PRID".  */
-  asprintf (&res, "%s/%s", field_value (pr, CATEGORY (pr->database)),
-	    field_value (pr, NUMBER (pr->database)));
-  *reclen = strlen (res);
-
-  for (f = indexDesc->fields; f != NULL; f = f->next)
-    {
-      FieldIndex i = simpleFieldIndexValue (f->ent);
-      if (format_field (i, field_value (pr, i), indexDesc->separator, &res,
-			reclen) < 0)
-	{
-	  free (res);
-	  res = NULL;
-	  break;
-	}
-    }
-  append_string (&res, "\n");
-  (*reclen)++;
-  return res;
-}
-
-static char *
-find_sep (IndexDesc indexDesc, char *start)
-{
-  char *end;
-  int seplen = strlen (indexDesc->separator);
-
-  do {
-    end = strchr (start, indexDesc->separator[0]);
-    if (end != NULL)
-      {
-	if (seplen == 1 || strncmp (start, indexDesc->separator, seplen) == 0)
-	  {
-	    return end;
-	  }
-	end++;
-      }
-  } while (end != NULL);
-
-  return end;
-}
-
-static char *
-readBinaryRecord (IndexFileDesc fp, size_t *length)
-{
-  char *res;
-  unsigned char entlen[2];
-
-  if (fread (entlen, 1, 2, fp->fileDes) != 2)
-    {
-      return NULL;
-    }
-  *length = entlen[0] * 256 + entlen[1] - 2;
-  res = xmalloc (*length);
-  if (fread (res, 1, *length, fp->fileDes) == *length)
-    {
-      return res;
-    }
-  else
-    {
-      free (res);
-      return NULL;
-    }
-}
-
-static char *
-extractBinaryField (char **record, size_t *lengthPtr)
-{
-  size_t length = *lengthPtr;
-  if (length > 0)
-    {
-      char *recLen = *record;
-      size_t fieldLen = (recLen[0] & 255) * 256 + (recLen[1] & 255);
-      if ((fieldLen + 3) <= length)
-	{
-	  char *recptr = *record + 2;
-	  *lengthPtr = length - (fieldLen + 3);
-	  (*record) = recLen + fieldLen + 3;
-	  return recptr;
-	}
-    }
-  return NULL;
-}
-
-/* Return the next index entry from FP.  */
-static PR *
-nextIndexEntry (IndexFileDesc fp)
-{
-  char *start, *end;
-  char *buf;
-  PR *res;
-  int x;  
-  int seplen;
-  FieldList f;
-  Index *p;
-
-  if (fp->desc->isBinary)
-    {
-      return nextIndexEntryBinary (fp);
-    }
-
-  res = allocPR (fp->desc->database);
-  p = res->index;
-  seplen = strlen (fp->desc->separator);
-
-  p->prevPR = NULL;
-  p->nextPR = NULL;
-  p->fields = (char **) xmalloc (sizeof (char *) 
-				 * get_num_fields (fp->desc->database));
-  memset (p->fields, 0, sizeof (char *) * get_num_fields (fp->desc->database));
-  while ((buf = read_line (fp->fileDes, NULL)) != NULL)
-    {
-      if (buf[0] != '#')
-	{
-	  break;
-	}
-      else
-	{
-	  free (buf);
-	}
-    }
-
-  if (buf == NULL)
-    {
-      goto no_entry;
-    }
-
-  p->buf = buf;
-  x = strlen (buf) - 1;
-  if (buf[x] == '\n')
-    {
-      buf[x] = '\0';
-    }
-
-  start = buf;
-  end = strchr (start, '/');
-  if (end == NULL)
-    {
-      goto no_entry;
-    }
-  *end = '\0';
-  p->fields[fieldDefForIndex (CATEGORY (fp->desc->database))->number] = start;
-
-  start = end + 1;
-  end = find_sep (fp->desc, start);
-
-  if (end == NULL)
-    {
-      goto no_entry;
-    }
-  *end = '\0';
-  p->fields[fieldDefForIndex (NUMBER (fp->desc->database))->number] = start;
-
-  for (f = fp->desc->fields; f != NULL && end != NULL; f = f->next)
-    {
-      FieldIndex i = simpleFieldIndexValue (f->ent);
-
-      if (i != InvalidFieldIndex)
-	{
-	  start = end + seplen;
-	  p->fields[fieldNumber (i)] = start;
-	}
-      if (f->next != NULL)
-	{
-	  end = find_sep (fp->desc, start);
-	  if (end != NULL)
-	    {
-	      *end = '\0';
-	    }
-	}
-    }
-
-  if (f == NULL)
-    {
-      return res;
-    }
-
-no_entry:
-
-  free_pr (res);
-  return NULL;
-}
-
-static PR *
-nextIndexEntryBinary (IndexFileDesc fp)
-{
-  char *record;
-  PR *res = NULL;
-  size_t length;
-
-  record = readBinaryRecord (fp, &length);
-  if (record != NULL)
-    {
-      FieldList f;
-      Index *indexEnt;
-
-      res = allocPR (fp->desc->database);
-      indexEnt = res->index;
-      indexEnt->prevPR = NULL;
-      indexEnt->nextPR = NULL;
-      indexEnt->fields 
-	= (char **) xmalloc (sizeof (char *) 
-			     * get_num_fields (fp->desc->database));
-      indexEnt->buf = record;
-      memset (indexEnt->fields, 0, sizeof (char *) 
-	      * get_num_fields (fp->desc->database));
-      indexEnt->fields[fieldDefForIndex (NUMBER (fp->desc->database))->number] 
-	= extractBinaryField (&record, &length);
-      indexEnt->fields[fieldDefForIndex (CATEGORY (fp->desc->database))->number] 
-	= extractBinaryField (&record, &length);
-      for (f = fp->desc->fields; res != NULL && f != NULL; f = f->next)
-	{
-	  FieldIndex i = simpleFieldIndexValue (f->ent);
-
-	  if (i != InvalidFieldIndex)
-	    {
-	      indexEnt->fields[fieldNumber (i)]
-		= extractBinaryField (&record, &length);
-	      if (indexEnt->fields[fieldNumber (i)] == NULL)
-		{
-		  free_pr (res);
-		  res = NULL;
-		}
-	    }
-	  else
-	    {
-	      free_pr (res);
-	      res = NULL;
-	    }
-	}
-    }
-  return res;
-}
-
-static void
-appendBinaryFieldContents (char **dest,
-			   const char *value, size_t *recLenPtr)
-{
-  size_t valLen;
-  size_t recLen = *recLenPtr;
-
-  if (value == NULL)
-    {
-      value = "";
-      valLen = 0;
-    }
-  else
-    {
-      valLen = strlen (value);
-    }
-  *dest = xrealloc (*dest, recLen + valLen + 3);
-  memcpy (*dest + recLen + 2, value, valLen + 1);
-  (*dest)[recLen + 0] = valLen / 256;
-  (*dest)[recLen + 1] = valLen % 256;
-  *recLenPtr += valLen + 3;
-}
-
-char *
-createIndexEntryBinary (PR *pr, size_t *recLen)
-{
-  const DatabaseInfo database = pr->database;
-  const IndexDesc indexDesc = getIndexDesc (database);
-  FieldList f;
-  char *res;
-
-  *recLen = 2;
-  res = xmalloc (2);
-  appendBinaryFieldContents (&res, field_value (pr, NUMBER (pr->database)),
-			     recLen);
-  appendBinaryFieldContents (&res, field_value (pr, CATEGORY (pr->database)),
-			     recLen);
-  for (f = indexDesc->fields; f != NULL; f = f->next)
-    {
-      FieldIndex i = simpleFieldIndexValue (f->ent);
-      size_t fieldLenOffset = *recLen;
-      int valLen;
-
-      /* Two bytes for the field length. */
-      *recLen += 2;
-      valLen = format_field (i, field_value (pr, i), "", &res, recLen);
-      res[fieldLenOffset + 0] = valLen / 256;
-      res[fieldLenOffset + 1] = valLen & 255;
-      /* One for the NUL character; format_field() always appends one,
-	 but doesn't include it in the length.  */
-      (*recLen)++;
-    }
-  res[0] = *recLen / 256;
-  res[1] = *recLen % 256;
-  return res;
-}
-
-static char *
-findPrCategoryBinary (IndexFileDesc fp, const char *number)
-{
-  unsigned char fieldcount;
-  char *record;
-  size_t recLen;
-  
-  if (fread (&fieldcount, 1, 1, fp->fileDes) != 1)
-    {
-      return NULL;
-    }
-  while ((record = readBinaryRecord (fp, &recLen)) != NULL)
-    {
-      char *recPos = record;
-      char *prNum = extractBinaryField (&recPos, &recLen);
-      if (prNum != NULL && strcmp (prNum, number) == 0)
-	{
-	  char *category = extractBinaryField (&recPos, &recLen);
-	  free (record);
-	  return category;
-	}
-      free (record);
-    }
-  return NULL;
-}
-
-/* Find problem report NUMBER in the index file, returning its category.  */
-static char *
-findPrCategory (IndexFileDesc fp, const char *number)
-{
-  char *buf;
-  char *res = NULL;
-
-  while ((res == NULL) && (buf = read_line (fp->fileDes, NULL)) != NULL)
-    {
-      if (buf[0] != '#')
-	{
-	  char *start = buf;
-	  char *end = strchr (start, '/');
-	  if (end != NULL)
-	    {
-	      char *numStart = end + 1;
-	      char *category = start;
-
-	      *end = '\0';
-	      end = find_sep (fp->desc, numStart);
-	      if (end != NULL)
-		{
-		  *end = '\0';
-
-		  if (strcmp (numStart, number) == 0)
-		    {
-		      res = xstrdup (category);
-		    }
-		}
-	    }
-	}
-      free (buf);
-    }
-
-  return res;
-}
-
-static time_t
-statIndex (char *name)
-{
-  struct stat buf;
-  int i;
-
-  i = stat (name, &buf);
-  if (i < 0)
-    {
-      return (time_t)-1;
-    }
-
-  return buf.st_mtime;
-}
-
-static IndexFileDesc
-openIndex (IndexDesc indexDesc, ErrorDesc *err)
-{
-  FILE *fp;
-  time_t t;
-  char *index_filename = gnats_adm_dir (indexDesc->database, indexDesc->path);
-  IndexFileDesc res 
-    = (IndexFileDesc) xmalloc (sizeof (struct index_file_desc));
-
-  res->desc = indexDesc;
-
-  fp = fopen (index_filename, "r");
-
-  if (fp != NULL)
-    {
-      t = statIndex (index_filename);
-      /* ??? XXX !!! Do we want to do something here? */
-      if (t != (time_t)-1)
-	{
-	  indexDesc->mtime = t;
-	}
-      res->fileDes = fp;
-      if (indexDesc->isBinary)
-	{
-	  unsigned char fieldCount;
-	  if ((fread (&fieldCount, 1, 1, res->fileDes) != 1) 
-	      || (fieldCount != indexDesc->numberOfFields))
-	    {
-	      setError (err, CODE_INVALID_INDEX, 
-			"Index file %s contains incorrect number of fields",
-			index_filename);
-	      fclose (res->fileDes);
-	      free (res);
-	      res = NULL;
-	    }
-	}
-    }
-  else
-    {
-      setError (err, CODE_FILE_ERROR, "Unable to open index file %s",
-		index_filename);
-      free (res);
-      res = NULL;
-    }
-
-  free (index_filename);
-  return res;
-}
-
-static void
-closeIndex (IndexFileDesc desc)
-{
-  fclose (desc->fileDes);
-  free (desc);
-}
-
-/* getIndex - reads in the entire set of PRs with index entries. */
-static PR *
-getIndex (IndexDesc indexDesc, ErrorDesc *err)
-{
-  IndexFileDesc fp = openIndex (indexDesc, err);
-  PR *new_chain = NULL;
-  PR *end_chain = NULL;
-
-  if (fp != NULL)
-    {
-      PR *i;
-
-      while ((i = nextIndexEntry (fp)))
-	{
-	  if (new_chain == NULL)
-	    {
-	      new_chain = i;
-	      end_chain = i;
-	    }
-	  else
-	    {
-	      i->index->prevPR = end_chain;
-	      end_chain->index->nextPR = i;
-	      end_chain = i;
-	    }
-	}
-
-      closeIndex (fp);
-    }
-  indexDesc->indexRead = 1;
-  return new_chain;
-}
-
-static PR *
-getNewIndexIfChanged (DatabaseInfo database, ErrorDesc *err)
-{
-  int rereadIndex;
-  IndexDesc indexDesc = getIndexDesc (database);
-
-  if (indexDesc->indexRead)
-    {
-      time_t t;
-      char *index_filename = gnats_adm_dir (database, indexDesc->path);
-
-      t = statIndex (index_filename);
-      free (index_filename);
-
-      if (t == (time_t)-1)
-	{
-	  setError (err, CODE_FILE_ERROR,
-		   "GNATS cannot stat the index: %s.", strerror (errno));
-	  return 0;
-	}
-      if (t > indexDesc->mtime)
-	{
-	  rereadIndex = 1;
-	}
-      else
-	{
-	  rereadIndex = 0;
-	}
-    }
-  else
-    {
-      rereadIndex = 1;
-    }
-
-  if (rereadIndex)
-    {
-      return getIndex (indexDesc, err);
-    }
-  else
-    {
-      return NULL;
-    }
-}
-
-void
-freePRIndex (PR *pr)
-{
-  if (pr->index != NULL && pr->index->fields != NULL)
-    {
-      if (pr->index->buf != NULL)
-	{
-	  free (pr->index->buf);
-	  pr->index->buf = NULL;
-	}
-      else
-	{
-	  int x;
-	  
-	  for (x = get_num_fields (pr->database) - 1; x >= 0; x--)
-	    {
-	      if (pr->index->fields[x] != NULL)
-		{
-		  free (pr->index->fields[x]);
-		  pr->index->fields[x] = NULL;
-		}
-	    }
-	}
-      free (pr->index->fields);
-      pr->index->fields = NULL;
-    }
-}
-
-void
-allocIndex (PR *pr)
-{
-  pr->index = (Index *) xmalloc (sizeof (Index));
-  pr->index->buf = NULL;
-  pr->index->fields = NULL;
-  pr->index->prevPR = NULL;
-  pr->index->nextPR = NULL;
-}
- 
-void
-buildIndexEntry (PR *pr)
-{
-  int x;
-  int num_fields = get_num_fields (pr->database);
-  Index *newIndex;
-
-  freePRIndex (pr);
-  newIndex = pr->index;
-
-  newIndex->fields = (char **) xmalloc (sizeof (char *) * num_fields);
-  memset (newIndex->fields, 0, sizeof (char *) * num_fields);
-  newIndex->buf = NULL;
-  for (x = 0; x < num_fields; x++)
-    {
-      FieldIndex field = getNthField (pr->database, x);
-      if (isIndexedFieldIndex (field))
-	{
-	  const char *fc = field_value (pr, field);
-
-	  if (fieldDefForIndex (field)->datatype == Date)
-	    {
-	      if (fc == NULL 
-		  || fc[0] == '\0' 
-		  || (strcmp (fc, "-1") == 0))
-		{
-		  newIndex->fields[x] = xstrdup ("0");
-		}
-	      else
-		{
-		  time_t t = get_any_date (fc);
-		  asprintf (&(newIndex->fields[x]), "%d", (int) t);
-		}
-	    }
-	  else
-	    {
-	      char *fcCopy;
-
-	      if (fc == NULL)
-		{
-		  fcCopy = xstrdup ("");
-		}
-	      else
-		{
-		  fcCopy = xstrdup (fc);
-		}
-
-	      /* ??? XXX Should we do this here?  It's already being
-	         done in the "write out the index" code.  */
-	      if (fieldDefForIndex (field)->nospaces)
-		{
-		  /* Try to minimize the leakage.  */
-		  char *p = strchr (fcCopy, ' ');
-		  if (p != NULL)
-		    {
-		      *p = '\0';
-		    }
-		}
-	      newIndex->fields[x] = fcCopy;
-	    }
-	}
-    }
-}
-
-int
-isIndexedFieldIndex (FieldIndex index)
-{
-  IndexDesc indexDesc;
-  if (index == InvalidFieldIndex)
-    {
-      return 0;
-    }
-  else
-    {
-      indexDesc = getIndexDesc (fieldDefForIndex (index)->database);
-      return indexDesc->fieldInIndex[fieldDefForIndex (index)->number];
-    }
-}
-
-void
-finishIndexDesc (DatabaseInfo database, IndexDesc new)
-{
-  int numFields = get_num_fields (database);
-
-  new->fieldInIndex = (int *) xmalloc (sizeof (int) * numFields);
-
-  {
-    int x;
-
-    for (x = 0; x < numFields; x++)
-      {
-	new->fieldInIndex[x] = 0;
-      }
-  }
-
-  {
-    FieldList f;
-
-    for (f = new->fields; f != NULL; f = f->next)
-      {
-	FieldIndex i = simpleFieldIndexValue (f->ent);
-	if (i != InvalidFieldIndex)
-	  {
-	    new->fieldInIndex[fieldNumber (i)] = 1;
-	  }
-      }
-  }
-  new->fieldInIndex[fieldNumber (NUMBER (database))] = 1;
-  new->fieldInIndex[fieldNumber (CATEGORY (database))] = 1;
-}
-
-int
-isIndexedField (ComplexFieldIndex ent)
-{
-  if (parseComplexFieldIndex (ent) != 0)
-    {
-      return 0;
-    }
-  return isIndexedFieldIndex (simpleFieldIndexValue (ent));
-}
-
-/* Write out the index for DATABASE's PR chain.  Returns 0 on success,
-   a non-zero value otherwise (and ERR will be filled in). */
-int
-writeIndex (const DatabaseInfo database, ErrorDesc *err)
-{
-  FILE *fp;
-  char *path, *workfile;
-  PR *pr;
-  IndexDesc indexDesc = getIndexDesc (database);
-  struct stat buf;
-
-  workfile = gnats_adm_dir (database, "indXXXXXX");
-  fp = fopen_temporary_file (workfile, "w", 0644);
-  if (fp == NULL)
-    {
-      setError (err, CODE_FILE_ERROR,
-		"writeIndex () can't open the temporary file %s",
-		workfile);
-      free (workfile);
-      return -1;
-    }
-
-  if (indexIsBinary (database))
-    {
-      unsigned char fieldCount = indexFieldCount (database);
-      fwrite (&fieldCount, 1, 1, fp);
-    }
-
-  for (pr = getFirstPR (database, err); pr != NULL ; pr = getNextPR (pr))
-    {
-      size_t length;
-      char *line = createIndexEntry (pr, &length);
-
-      if (line != NULL)
-	{
-	  fwrite (line, 1, length, fp);
-	  free (line);
-	}
-    }
-
-  fclose (fp);
-
-  block_signals ();
-
-  path = gnats_adm_dir (database, indexDesc->path);
-
-  /* sanity check -- make sure nobody has changed the index since we
-     last read it */
-  if (stat (path, &buf) < 0)
-    {
-      /* if the index ain't there, no problem.  if it's there, but
-	 unstattable, we're going to declare it a serious problem.
-	 not sure if this is the right thing to do... */
-      if (errno != ENOENT)
-	{
-	  setError (err, CODE_FILE_ERROR,
-		    "GNATS cannot stat the index: %s.", strerror (errno));
-	  log_msg (LOG_ERR, 1, "error in writeIndex:",
-		   getErrorMessage (*err));
-	  return -1;
-	}
-    }
-
-  if (buf.st_mtime > indexDesc->mtime)
-    {
-      setError (err, CODE_INVALID_INDEX,
-		"Index has been modified since last read");
-      log_msg (LOG_ERR, 1, "error in writeIndex:",
-	       getErrorMessage (*err));
-      /* the index is corrupt: alert the administrator */
-      punt (database, 0, "Attempting to write index file %s,\n\
-but index has been modified since last read.  A PR has been modified,\n\
-but the change has not been recorded in the index.  You must manually\n\
-regenerate the index.", path);
-      return -1;
-    }
-
-  if ((rename (workfile, path)) < 0)
-    {
-      if (errno != EXDEV)
-	{
-	  free (path);
-	  setError (err, CODE_FILE_ERROR,
-		    "could not rename temporary index %s into gnats-adm: %s",
-		    workfile, strerror (errno));
-	  return -1;
-	}
-
-      if (copy_file (workfile, path))
-	{
-	  setError (err, CODE_FILE_ERROR, 
-		   "could not copy temporary index %s into %s: %s",
-		   workfile, path, strerror (errno));
-	  return -1;
-	}
-
-      unlink (workfile);
-    }
-
-  free (path);
-  free (workfile);
-  unblock_signals ();
-  return 0;
-}
-
-/* Add the PR to the GNATS index file.  */
-int
-addToIndex (PR *pr, ErrorDesc *err)
-{
-  FILE *fp;
-  char *path;
-  IndexDesc indexDesc = getIndexDesc (pr->database);
-
-  buildIndexEntry (pr);
-
-  block_signals ();
-
-  path = gnats_adm_dir (pr->database, indexDesc->path);
-
-  if (indexIsBinary (pr->database) && ! fileExists (path))
-    {
-      unsigned char fieldCount = indexFieldCount (pr->database);
-      fp = fopen (path, "w");
-      if (fp != NULL)
-	{
-	  fwrite (&fieldCount, 1, 1, fp);
-	}
-    }
-  else
-    {
-      fp = fopen (path, "a+");
-    }
-  if (fp == NULL)
-    {
-      setError (err, CODE_FILE_ERROR, 
-		"Can't append to the GNATS index file (%s).", path);
-      unblock_signals ();
-      return -1;
-    }
-
-  {
-    size_t reclen;
-
-    char *line = createIndexEntry (pr, &reclen);
-    fwrite (line, 1, reclen, fp);
-    free (line);
-  }
-
-  fclose (fp);
-  free (path);
-  unblock_signals ();
-  return 0;
-}
-
-char *
-indexValue (PR *pr, FieldIndex field)
-{
-  if (pr->index != NULL && pr->index->fields != NULL)
-    {
-      return pr->index->fields[field->number];
-    }
-  else
-    {
-      return NULL;
-    }
-}
-
-char *
-getCategoryFromIndex (const DatabaseInfo database, const char *prnum,
-		      ErrorDesc *err)
-{
-  IndexDesc indexDesc = getIndexDesc (database);
-  IndexFileDesc fp = openIndex (indexDesc, err);
-  if (fp != NULL)
-    {
-      char *category;
-      if (fp->desc->isBinary)
-	{
-	  category = findPrCategoryBinary (fp, prnum);
-	}
-      else
-	{
-	  category = findPrCategory (fp, prnum);
-	}
-      closeIndex (fp);
-      if (category == NULL)
-	{
-	  setError (err, CODE_NONEXISTENT_PR,
-		    "PR %s is not listed in the index.", prnum);
-	}
-      return category;
-    }
-  else
-    {
-      return NULL;
-    }
-}
-
-void
-freeIndexDesc (IndexDesc p)
-{
-  free (p->path);
-  freeFieldList (p->fields);
-  free (p->separator);
-  if (p->fieldInIndex != NULL)
-    {
-      free (p->fieldInIndex);
-    }
-  free (p);
-}
-
-IndexDesc
-newIndexDesc (const DatabaseInfo database)
-{
-  IndexDesc res = xmalloc (sizeof (struct index_desc));
-
-  res->database = database;
-  res->prChain = NULL;
-  res->path = NULL;
-  res->fields = NULL;
-  res->separator = xstrdup ("|");
-  res->isBinary = 0;
-  res->numberOfFields = 0;
-  res->fieldInIndex = NULL;
-  res->mtime = 0;
-  res->indexRead = 0;
-
-  return res;
-}
-
-void
-addFieldToIndex (IndexDesc desc, FieldList ent)
-{
-  if (desc->fields == NULL)
-    {
-      desc->fields = ent;
-    }
-  else
-    {
-      FieldList f = desc->fields;
-      while (f->next != NULL)
-	{
-	  f = f->next;
-	}
-      f->next = ent;
-    }
-  desc->numberOfFields++;
-}
-
-void
-setIndexDescPath (IndexDesc desc, const char *path)
-{
-  desc->path = xstrdup (path);
-}
-
-void
-setIndexDescSeparator (IndexDesc desc, const char *separator)
-{
-  free (desc->separator);
-  desc->separator = xstrdup (separator);
-}
-
-void
-setIndexDescBinary (IndexDesc desc, int flag)
-{
-  desc->isBinary = flag;
-}
-
-static void
-freePRChain (PR *start)
-{
-  while (start != NULL)
-    {
-      PR *next = getNextPR (start);
-      free_pr (start);
-      start = next;
-    }
-}
-
-int
-checkPRChain (const DatabaseInfo database, ErrorDesc *err)
-{
-  IndexDesc desc = getIndexDesc (database);
-  PR *new_chain = getNewIndexIfChanged (database, err);
-
-  if (new_chain != NULL)
-    {
-      freePRChain (desc->prChain);
-      desc->prChain = new_chain;
-      return 1;
-    }
-  else
-    {
-      return 0;
-    }
-}
-
-void
-initIndex (DatabaseInfo database)
-{
-  clearPRChain (database);
-}
-
-PR *
-getFirstPR (const DatabaseInfo database, ErrorDesc *err)
-{
-  IndexDesc desc = getIndexDesc (database);
-
-  /* first check the PRChain to make sure that the index isn't stale */
-  checkPRChain (database, err);
-
-  return desc->prChain;
-}
-
-void
-clearPRChain (const DatabaseInfo database)
-{
-  IndexDesc desc = getIndexDesc (database);
-  if (desc != NULL)
-    {
-      if (desc->prChain != NULL)
-	{
-	  freePRChain (desc->prChain);
-	  desc->prChain = NULL;
-	}
-      desc->indexRead = 0;
-    }
-}
-
-/* Replace a PR in the index with a copy of a new PR.
-   By generating a copy of the new PR, we allow for the new PR
-   to be free'd without harming the index pr chain. */
-
-int
-replaceCurrentPRInIndex (PR *curr_pr, PR *new_pr, ErrorDesc *err)
-{
-  PR *pr_replace, *pr_copy;
-  const DatabaseInfo database = curr_pr->database;
-  const char *new_pr_num = field_value (new_pr, NUMBER (database));
-  const char *curr_pr_num = field_value (curr_pr, NUMBER (database));
-
-  if (strcmp (curr_pr_num, new_pr_num) != 0)
-    {
-      return 0;
-    }
-
-  /* create a copy of the new pr to be put into the index pr chain */
-  pr_copy = allocPR (database);
-  set_field (pr_copy, NUMBER (curr_pr->database), new_pr_num, err);
-  set_field (pr_copy, CATEGORY (database),
-	     field_value (new_pr, CATEGORY (database)), err);
-  setPrevPR (pr_copy, curr_pr->index->prevPR);
-  setNextPR (pr_copy, curr_pr->index->nextPR);
-  fillInPR (pr_copy, err);
-
-  /* put the copy of new_pr into place in the index pr chain... */
-
-  if (curr_pr->index->prevPR == NULL)
-    {
-      /* the pr to be replaced is at the head of the pr chain */
-      IndexDesc indexDesc = getIndexDesc (database);
-      free_pr (indexDesc->prChain);
-      indexDesc->prChain = pr_copy;
-      return 1;
-    }
-
-  /* save a pointer to the pr to be replaced so that it can later be free'd */
-  pr_replace = getNextPR (curr_pr->index->prevPR);
-
-  setNextPR (curr_pr->index->prevPR, pr_copy);
-
-  if (curr_pr->index->nextPR != NULL)
-    {
-      /* the pr to be replaced is not at the end of the pr chain */
-      setPrevPR (curr_pr->index->nextPR, pr_copy);
-    }
-
-  free_pr (pr_replace);
-
-  return 1;
-}
-
-/* Remove PR PRNUM from the index for the current database, and rewrite the
-   database file.  The database must have been locked before calling this
-   function.  */
-int
-removePRFromIndex (const DatabaseInfo database, const char *prNum,
-		   ErrorDesc *err)
-{
-  PR *list;
-  PR *prev = NULL;
-  IndexDesc indexDesc = getIndexDesc (database);
-
-  if (! is_gnats_locked (database))
-    {
-      setError (err, CODE_GNATS_NOT_LOCKED,
-		"Must lock the database before removing a PR from the index");
-      return -1;
-    }
-
-  list = getFirstPR (database, err);
-  while (list != NULL
-	 && strcmp (field_value (list, NUMBER (database)), prNum) != 0)
-    {
-      prev = list;
-      list = getNextPR (list);
-    }
-
-  if (list == NULL)
-    {
-      setError (err, CODE_NONEXISTENT_PR,
-		"PR %s not in index", prNum);
-      return -2;
-    }
-
-  if (prev != NULL)
-    {
-      prev->index->nextPR = list->index->nextPR;
-    }
-  else
-    {
-      indexDesc->prChain = list->index->nextPR;
-    }
-
-  return writeIndex (database, err);
-}
-
-PR *
-getNextPR (PR *pr)
-{
-  return pr->index->nextPR;
-}
-
-void
-setNextPR (PR *pr, PR *next_pr)
-{
-  pr->index->nextPR = next_pr;
-}
-
-PR *
-getPrevPR (PR *pr)
-{
-  return pr->index->prevPR;
-}
-
-void
-setPrevPR (PR *pr, PR *prev_pr)
-{
-  pr->index->prevPR = prev_pr;
-}
-
-int
-indexIsBinary (const DatabaseInfo database)
-{
-  IndexDesc indexDesc = getIndexDesc (database);
-  if (indexDesc != NULL)
-    {
-      return indexDesc->isBinary;
-    }
-  else
-    {
-      return 0;
-    }
-}
-
-int
-indexFieldCount (const DatabaseInfo database)
-{
-  IndexDesc indexDesc = getIndexDesc (database);
-  if (indexDesc != NULL)
-    {
-      return indexDesc->numberOfFields;
-    }
-  else
-    {
-      return 0;
-    }
-}
Index: index.h
===================================================================
RCS file: index.h
diff -N index.h
--- index.h	24 Feb 2005 19:21:12 -0000	1.18
+++ /dev/null	1 Jan 1970 00:00:00 -0000
@@ -1,72 +0,0 @@
-#ifndef _INDEX_H_
-#define _INDEX_H_
-
-typedef struct indexEntry Index;
-typedef struct index_desc *IndexDesc;
-
-#include "gnats.h"
-#include "database.h"
-#include "field.h"
-#include "pr.h"
-
-extern void initIndex (DatabaseInfo database);
-
-extern IndexDesc newIndexDesc (const DatabaseInfo database);
-extern void addFieldToIndex (IndexDesc desc, FieldList ent);
-extern void setIndexDescPath (IndexDesc desc, const char *path);
-extern void setIndexDescSeparator (IndexDesc desc, const char *separator);
-extern void setIndexDescBinary (IndexDesc desc, int flag);
-
-extern void allocIndex (PR *pr);
-
-extern void clearPRChain (const DatabaseInfo database);
-extern int checkPRChain (const DatabaseInfo database, ErrorDesc *err);
-
-extern PR *getFirstPR (const DatabaseInfo database, ErrorDesc *err);
-
-extern PR *getPrevPR (PR *pr);
-extern PR *getNextPR (PR *pr);
-extern void setPrevPR (PR *pr, PR *prev_pr);
-extern void setNextPR (PR *pr, PR *next_pr);
-
-extern void freePRIndex (PR *pr);
-
-/* Construct an index entry for PR.  */
-extern void buildIndexEntry (PR *pr);
-
-/* Return a non-zero value if FIELD is part of the index for its database. */
-extern int isIndexedFieldIndex (FieldIndex field);
-extern int isIndexedField (ComplexFieldIndex field);
-
-/* Write out the database's PR index from the copy in memory.  Returns 0 on
-   success, a non-zero value otherwise.  */
-extern int writeIndex (const DatabaseInfo database, ErrorDesc *err);
-/* Add the PR to the GNATS index file for its database.  Returns
-   0 on success, a non-zero value otherwise.  */
-extern int addToIndex (PR *pr, ErrorDesc *err);
-
-/* Create a new string representing the index entry for PR in DEST.  */
-extern char *createIndexEntry (PR *pr, size_t *entLen);
-extern char *createIndexEntryBinary (PR *pr, size_t *entLen);
-
-extern int replaceCurrentPRInIndex (PR *curr_pr, PR *newPR, ErrorDesc *err);
-
-extern char *indexValue (PR *pr, FieldIndex field);
-
-extern char *getCategoryFromIndex (const DatabaseInfo database,
-				   const char *prnum, ErrorDesc *err);
-
-extern int removePRFromIndex (const DatabaseInfo database, const char *prNum,
-			      ErrorDesc *err);
-
-extern void finishIndexDesc (const DatabaseInfo database, IndexDesc new);
-
-extern void freeIndexDesc (IndexDesc desc);
-
-extern int indexIsBinary (const DatabaseInfo database);
-
-extern int indexFieldCount (const DatabaseInfo database);
-
-extern int verifyPRExists (const DatabaseInfo database, const char *prID);
-#endif
-
Index: pr-age.c
===================================================================
RCS file: /cvsroot/gnats/gnats/gnats/pr-age.c,v
retrieving revision 1.17
diff -u -p -r1.17 pr-age.c
--- pr-age.c	31 Oct 2002 23:43:28 -0000	1.17
+++ pr-age.c	19 Apr 2005 00:49:19 -0000
@@ -22,6 +22,7 @@ Software Foundation, 59 Temple Place - S
 
 #include "gnats.h"
 #include "query.h"
+#include "ds.h"
 
 /* The name this program was run with.  */
 const char *program_name;
@@ -177,7 +178,7 @@ main (int argc, char **argv)
 	  client_print_errors (NULL, err);
 	  exit (1);
 	}
-      pr = readPRWithNum (database, argv[optind], 0, &err);
+      pr = pr_load_by_id (database, argv[optind], 0, &err);
       if (pr == NULL)
 	{
 	  client_print_errors (database, err);
Index: pr-edit.c
===================================================================
RCS file: /cvsroot/gnats/gnats/gnats/pr-edit.c,v
retrieving revision 1.38
diff -u -p -r1.38 pr-edit.c
--- pr-edit.c	30 Aug 2003 07:58:14 -0000	1.38
+++ pr-edit.c	19 Apr 2005 00:49:20 -0000
@@ -23,6 +23,7 @@ Software Foundation, 59 Temple Place - S
 
 #include "gnats.h"
 #include "pcodes.h"
+#include "ds.h"
 
 /* Name of this program.  */
 const char *program_name;
@@ -439,12 +440,12 @@ main (int argc, char **argv)
       switch (edit_options) 
 	{
 	case LOCK:
-	  if (prExists (database, prnum, &err))
+	  if (pr_exists (database, prnum, &err))
 	    {
 	      result = lock_pr (database, prnum, username, processid, &err);
 	      if (result != 0)
 		{
-		  PR *pr = readPRWithNum (database, prnum, 0, &err);
+		  PR *pr = pr_load_by_id (database, prnum, 0, &err);
 		  if (pr != NULL)
 		    {
 		      print_named_format_pr (stdout, pr, "edit", "\n", &err);
Index: pr.c
===================================================================
RCS file: /cvsroot/gnats/gnats/gnats/pr.c,v
retrieving revision 1.65
diff -u -p -r1.65 pr.c
--- pr.c	24 Feb 2005 19:21:12 -0000	1.65
+++ pr.c	19 Apr 2005 00:49:20 -0000
@@ -24,6 +24,7 @@ Software Foundation, 59 Temple Place - S
 
 #include "gnats.h"
 #include "pcodes.h"
+#include "ds.h"
 
 /* These will eventually be properly configurable.  */
 #define PR_FIELD_TAG_START		'>'
@@ -127,13 +128,20 @@ allocPR (const DatabaseInfo database)
   int field_num = get_num_fields (database);
 
   res->database = database;
-  allocIndex (res);
+  pr_init (res);  /* initialize the private ds info */
   res->private = (struct PR_private *) xmalloc (sizeof (struct PR_private));
-  res->private->value
-    = (struct PR_private_value *) xmalloc (sizeof (struct PR_private_value)
-					   * field_num);
-  memset (res->private->value, 0,
-	  sizeof (struct PR_private_value) * field_num);
+  if (field_num == -1)
+    {
+      res->private->value = NULL;
+    }
+  else
+  {
+      res->private->value
+	= (struct PR_private_value *) xmalloc (sizeof (struct PR_private_value)
+					       * field_num);
+      memset (res->private->value, 0,
+	      sizeof (struct PR_private_value) * field_num);
+  }
   res->private->header 
     = (char **) xmalloc (sizeof (char *) * NUM_HEADER_ITEMS);
   memset (res->private->header, 0, sizeof (char *) * NUM_HEADER_ITEMS);
@@ -153,90 +161,6 @@ allocPR (const DatabaseInfo database)
   return res;
 }
 
-/* Get the PR at PATH.  PR is the PR entry to be filled in.
-   If PRUNE is non-zero, don't read any multitext fields.  */
-static int
-get_pr (PR *pr, const char *path, int prune)
-{
-  FILE *fp = fopen (path, "r");
-
-  if (fp == (FILE *)NULL)
-    {
-      return 0;
-    }
-
-  if (read_header (pr, fp) < 0)
-    {
-      return 0;
-    }
-
-  read_pr (pr, fp, prune);
-
-  fclose (fp);
-
-  return 1;
-}
-
-int
-fillInPR (PR *pr, ErrorDesc *err)
-{
-  char *path = gen_pr_path (pr);
-  const char *num = field_value (pr, NUMBER (pr->database));
-  int val;
-
-  if (num == NULL || path == NULL)
-    {
-      setError (err, CODE_ERROR, "Invalid PR in fillInPR()");
-      if (path != NULL)
-	{
-	  free (path);
-	}
-      return -1;
-    }
-  else
-    {
-      val = get_pr (pr, path, 0);
-
-      free (path);
-      if (val == 0)
-	{
-	  setError (err, CODE_FILE_ERROR, "Unable to read PR %s", num);
-	  return -1;
-	}
-      else
-	{
-	  return 0;
-	}
-    }
-}
-
-static PR *
-get_pr_from_index (const DatabaseInfo database, const char *prnum,
-		   ErrorDesc *err)
-{
-  PR *pr = getFirstPR (database, err);
-
-  /* If they gave it to us with the category, remove it. */
-  if (( strrchr (prnum, '/')) != NULL)
-    {
-      prnum = strrchr (prnum, '/') + 1;
-    }
-
-  while (pr != NULL && strcmp (prnum, field_value (pr, NUMBER (database))) != 0)
-    {
-      pr = getNextPR (pr);
-    }
-
-  if (pr == NULL)
-    {
-      setError (err, CODE_NONEXISTENT_PR,
-	        "No PR %s listed in the index.", prnum);
-      return NULL;
-    }
-
-  return pr;
-}
-
 /* Initializes PR for reading.  Each line of the PR should be passed
    in via addLineToPR (). */
 
@@ -906,8 +830,8 @@ setFieldChangeReason (PR *pr, FieldIndex
   
 
 /* Returns the value of field FIELD from PR.  If the PR does not directly
-   have a value, check the PR's associated index entry; if that's also
-   empty, we return the field's default value.  
+   have a value, check the field cache; if that's also empty, we return
+   the field's default value.  
 
    The policy about returning a default value is a bit broken--we
    should only do that if the PR's actually been read in from a file.  */
@@ -929,10 +853,7 @@ field_value (PR *pr, FieldIndex field)
   
   if (result == NULL)
     {
-      /* Houston, we have a problem.  For dates, the value that's
-	 stored in the index is an integer, not the actual value of
-	 the field...  \hbadness 10000.  XXX ??? !!! FIXME */
-      result = indexValue (pr, field);
+      result = field_cache_value (pr, field);
       if (result != NULL
 	  && fieldDefForIndex (field)->datatype == Date
 	  && strcmp (result, "0") == 0)
@@ -1365,143 +1286,13 @@ free_pr (PR *pr)
 {
   free_pr_header (pr);
   free_pr_contents (pr);
-  freePRIndex (pr);
-  free (pr->index);
+  pr_destroy (pr); /* free up the private ds info */
   free (pr->private->header);
   free (pr->private->value);
   free (pr->private);
   free (pr);
 }
 
-static char *
-get_pr_path (const DatabaseInfo database, PR *pr, const char *prnum,
-	    ErrorDesc *err)
-{
-  char *path = NULL;
-  const char *category = NULL;
-  char *categoryFromIndex = NULL;
-
-  if (pr != NULL)
-    {
-      category = field_value (pr, CATEGORY (database));
-    }
-  else
-    {
-      categoryFromIndex = getCategoryFromIndex (database, prnum, err);
-      category = categoryFromIndex;
-    }
-
-  if (category != NULL)
-    {
-      asprintf (&path, "%s/%s/%s", databaseDir (database), category, prnum);
-      if (categoryFromIndex != NULL)
-	{
-	  free (categoryFromIndex);
-	}
-    }
-
-  return path;
-}
-
-int
-prExists (const DatabaseInfo database, const char *prnum, ErrorDesc *err)
-{
-  PR *pr = get_pr_from_index (database, prnum, err);
-  char *path = get_pr_path (database, pr, prnum, err);
-  int res = 0;
-  if (path != NULL)
-    {
-      res = fileExists (path);
-      free (path);
-      if (res == 0)
-	{
-	  setError (err, CODE_FILE_ERROR, "Can't open file `%s'", path);
-	}
-    }
-  return res;
-}
-
-static bool
-pr_file_readable (const char *path, ErrorDesc *err)
-{
-  FILE *fp;
-
-  if (path == NULL)
-    {
-      return FALSE;
-    }
-
-  if ((fp = fopen (path, "r")) == NULL)
-    {
-      setError (err, CODE_FILE_ERROR, "Can't open file `%s'", path);
-      return FALSE;
-    }
-
-  fclose (fp);
-  return TRUE;
-}
-
-PR *
-readPRWithNum (const DatabaseInfo database, const char *prnum,
-	       int prune, ErrorDesc *err)
-{
-  PR *pr = NULL;
-  PR *index_pr = get_pr_from_index (database, prnum, err);
-  char *path = get_pr_path (database, index_pr, prnum, err);
-
-  if (path != NULL)
-    {
-      if (pr_file_readable (path, err))
-        {
-	  pr = allocPR (database);
-	  setPrevPR (pr, getPrevPR (index_pr));
-	  setNextPR (pr, getNextPR (index_pr));
-	  if (get_pr (pr, path, prune) == 0)
-	    {
-	      free_pr (pr);
-	      pr = NULL;
-	    }
-        }
-      free (path);
-    }
-  return pr;
-}
-
-int
-pr_delete (const DatabaseInfo database, const char *prnum, ErrorDesc *err)
-{
-  PR *pr;
-  char *path = NULL;
-
-  pr = get_pr_from_index (database, prnum, err);
-  path = get_pr_path (database, pr, prnum, err);
-
-  if (path == NULL || !pr_file_readable (path, err))
-    {
-      if (path != NULL)
-	{
-	  free (path);
-	}
-      return -1;
-    }
-
-  if (removePRFromIndex (database, prnum, err))
-    {
-      free (path);
-      return -5;
-    }
-  
-  if (unlink (path))
-    {
-      setError (err, CODE_FILE_ERROR, "Unable to unlink file %s\n", path);
-      free (path);
-      return -6;
-    }
-
-  free (path);
-  return 1;
-}
-
 void
 freeInputTemplate (InputTemplate *template)
 {
Index: pr.h
===================================================================
RCS file: /cvsroot/gnats/gnats/gnats/pr.h,v
retrieving revision 1.39
diff -u -p -r1.39 pr.h
--- pr.h	24 Feb 2005 19:21:12 -0000	1.39
+++ pr.h	19 Apr 2005 00:49:20 -0000
@@ -52,15 +52,15 @@ typedef enum {
 #include <stdio.h>
 #include "gnats.h"
 #include "field.h"
-#include "index.h"
+#include "database.h"
 
 /* Describes one entire PR.  */
 struct PR_struct {
   /* The database this PR is associated with.  */
   DatabaseInfo database;
 
-  /* Index entry for this PR. */
-  Index *index;
+  /* all the datastore specific stuff */
+  void *ds_private;
 
   /* Non-zero if the PR contents are pruned.  */
   int prune;
@@ -88,7 +88,6 @@ extern int setBuiltinField (FieldIndex f
 extern FieldIndex findBuiltinField (const DatabaseInfo database,
 				    const char *name);
 
-extern int fillInPR (PR *pr, ErrorDesc *err);
 extern void initReadPR (PR *pr);
 extern void addLineToPR (PR *pr, char *buffer, char *line, size_t linelen,
 			 int prune);
@@ -131,17 +130,8 @@ extern int fconfigParse (DatabaseInfo da
 extern int createPrFile (PR *pr, const char *filename, int force,
 			 ErrorDesc *err);
 
-extern int pr_delete (const DatabaseInfo database, const char *prnum,
-		      ErrorDesc *err);
-
-extern int prExists (const DatabaseInfo database, const char *prID, 
-		     ErrorDesc *err);
-
 extern void printValidValues (FILE *outfile, FieldIndex i, const char *eol);
 
-extern PR *readPRWithNum (const DatabaseInfo database, const char *prID,
-			  int prune, ErrorDesc *err);
-
 /* Given the name of a header NAME, return its Header_Name value, or
    InvalidHeaderName if NAME is not a valid header name.  */
 extern Header_Name find_header_index (const char *name);
Index: query-pr.c
===================================================================
RCS file: /cvsroot/gnats/gnats/gnats/query-pr.c,v
retrieving revision 1.45
diff -u -p -r1.45 query-pr.c
--- query-pr.c	31 Oct 2002 23:43:28 -0000	1.45
+++ query-pr.c	19 Apr 2005 00:49:20 -0000
@@ -23,6 +23,7 @@ Software Foundation, 59 Temple Place - S
 #include "gnats.h"
 #include "query.h"
 #include "mail.h"
+#include "ds.h"
 
 /* The name this program was run with.  */
 const char *program_name;
@@ -960,8 +961,8 @@ main (int argc, char **argv)
       {
 	ErrorDesc err;
 
-	int res = iterate_prs (database, argc - optind, argv + optind, expr,
-			       query_format, client_print_query, &err);
+	int res = db_query (database, argc - optind, argv + optind, expr,
+			   client_print_query, query_format, &err);
 	if (res < 0)
 	  {
 	    client_print_errors (database, err);
Index: query.c
===================================================================
RCS file: /cvsroot/gnats/gnats/gnats/query.c,v
retrieving revision 1.57
diff -u -p -r1.57 query.c
--- query.c	25 Nov 2002 13:58:33 -0000	1.57
+++ query.c	19 Apr 2005 00:49:20 -0000
@@ -22,15 +22,10 @@ Software Foundation, 59 Temple Place - S
 #include "gnats.h"
 #include "query.h"
 #include "pcodes.h"
+#include "ds.h"
 
 /* One item to search for. */
 
-typedef struct query_item
-{
-  ComplexFieldIndex fieldIndex;
-  struct re_pattern_buffer *compRegexp;
-} *QueryItem;
-
 static QueryItem freeQueryItemEnt = NULL;
 
 static QueryItem
@@ -86,37 +81,6 @@ freeQueryItem (QueryItem i)
     }
 }
 
-typedef struct search_item
-{
-  /* The type of search to perform, of course.  */
-  SearchType searchType;
-
-  /* The items on the left and right-hand-sides. */
-  QueryItem lhs;
-  QueryItem rhs;
-} SearchItem;
-
-typedef struct queryTree
-{
-  /* The operation to perform on this node. */
-  QueryOp op;
-
-  /* The left and right sides of the expression; if this is a unary op,
-     the left side contains the expression. */
-  struct queryTree *left, *right;
-
-  /* If this is a QueryMatch expression, the actual query to perform. */
-  SearchItem ent;
-} *QueryTree;
-
-struct queryExpr
-{
-  /* Database that this query is associated with.  */
-  DatabaseInfo database;
-  /* The actual query tree.  */
-  QueryTree tree;
-};
-
 struct SearchTypes {
   const char *name;
   const char *operator;
@@ -618,15 +582,6 @@ fieldCompare (PR *pr, PR *oldPR, QueryIt
 	  searchType = RegCmp;
 	}
     }
-  if ((! PR_IS_FULL (pr)) && (! isIndexedField (fields[0]->fieldIndex)))
-    {
-      ErrorDesc err;
-
-      if (fillInPR (pr, &err) != 0)
-	{
-	  return 0;
-	}
-    }
 
   for (x = 0; x < 2; x++)
     {
@@ -721,7 +676,7 @@ pr_match_field (PR *pr, PR *oldPR, Searc
 /* Returns a non-zero value if the PR referenced by I matches the expression
    tree in QEXP.  */
 
-static int
+int
 pr_matches_tree (PR *pr, PR *oldPR, QueryTree qexp,
 		 FormatNamedParameter *params)
 {
@@ -1202,41 +1157,12 @@ int
 process_format (FILE *fp, char **res, PR *pr,  PR *oldPR, QueryFormat *fmt, 
 		const char *eolTerminator, FormatNamedParameter *parameters)
 {
-  int do_open_pr = 0;
-
   if (pr != NULL)
     {
-      if (! PR_IS_FULL (pr))
-	{
-	  FieldList p = fmt->fields;
-
-	  if (p == NULL)
-	    {
-	      do_open_pr = 1;
-	    }
-	  else
-	    {
-	      while (p != NULL)
-		{
-		  if (! isIndexedField (p->ent))
-		    {
-		      do_open_pr = 1;
-		      break;
-		    }
-		  p = p->next;
-		}
-	    }
-	}
-
-      if (do_open_pr)
-	{
-	  ErrorDesc err;
-
-	  if (fillInPR (pr, &err))
-	    {
-	      return 0;
-	    }
-	}
+      if (pr_load_fields (pr, fmt->fields) == -1)
+        {
+	  return 0;
+        }
     }
 
   if (fmt->printf != NULL)
@@ -1322,119 +1248,6 @@ print_pr (FILE *dest, PR *pr, QueryForma
 }
 
 \f
-/* Iterate through a set of PRs.  For those PRs that match the
-   expression in EXP, invoke FUNC with the count of PRs that have
-   matched so far, the PR* entry for the PR, and the supplied
-   QUERY_FORMAT.  
-
-   FUNC is invoked once more after all of the queries have been
-   searched.  The PR* pointer is NULL, and the count corresponds to
-   the total # of PRs that matched.
-
-   If AC is 0 or AV is NULL, we iterate through all of the PRs in the
-   index for the current database.  Otherwise, only those PRs that
-   match the PRIDs in AV[0], AV[1]. AV[2]...AV[AC-1] are tested.  */
-
-int
-iterate_prs (const DatabaseInfo database, int ac, char **av, QueryExpr exp,
-	     QueryFormat *query_format, QueryFunc func, ErrorDesc *err)
-{
-  int found = 0;
-  QueryTree tree = NULL;
-
-  if (exp != NULL)
-    {
-      if (exp->database != database)
-	{
-	  abort ();
-	}
-      tree = exp->tree;
-    }
-  
-  *err = NULL;
-  if (ac == 0 || av == NULL)
-    {
-      PR *pr = getFirstPR (database, err);
-
-      if (*err != NULL)
-	{
-	  return -1;
-	}
-      /* We weren't given a list of PRs to check, so we do the
-	 whole shooting match.  */
-      while (pr != NULL)
-	{
-	  if (pr_matches_tree (pr, NULL, tree, NULL))
-	    {
-	      found++;
-	      func (found, pr, query_format);
-	    }
-	  free_pr_header (pr);
-	  free_pr_contents (pr);
-	  pr = getNextPR (pr);
-	}
-    }
-  else
-    {
-      int cpr;
-
-      for (cpr = 0; cpr < ac; cpr++)
-	{
-	  char *pat, *n;
-	  char *p = av[cpr];
-	  int plen;
-	  PR *pr;
-	  struct re_pattern_buffer buf;
-
-	  memset (&buf, 0, sizeof (buf));
-
-	  /* Remove the category */
-	  if ((n = (char *) strrchr (p, '/')) != NULL) 
-	    {
-	      p = n + 1;
-	    }
-
-	  plen = strlen (p);
-	  pat = (char *) xmalloc (plen + 3);
-	  strcpy (pat, p);
-	  strcpy (pat + plen, "\\'");
-
-	  *err = NULL;
-	  pr = getFirstPR (database, err);
-	  if (*err != NULL)
-	    {
-	      return -1;
-	    }
-
-	  while (pr != NULL)
-	    {
-	      if (gnats_regcmp (pat, field_value (pr, NUMBER (pr->database)),
-				&buf) == 0)
-		{
-		  if (pr_matches_expr (pr, NULL, exp, NULL))
-		    {
-		      found++;
-		      func (found, pr, query_format);
-		    }
-		  /* Only one PR will match this PR number, because it's
-		     not really a regexp */
-		  break;
-		}
-	      free_pr_header (pr);
-	      free_pr_contents (pr);
-	      pr = getNextPR (pr);
-	    }
-	  buf.translate = NULL;
-	  regfree (&buf);
-	  free (pat);
-	}
-    }
-
-  func (found, NULL, 0);
-
-  return found;
-}
-
 static QueryTree
 newQueryTree (void)
 {
Index: query.h
===================================================================
RCS file: /cvsroot/gnats/gnats/gnats/query.h,v
retrieving revision 1.37
diff -u -p -r1.37 query.h
--- query.h	28 Oct 2002 22:57:50 -0000	1.37
+++ query.h	19 Apr 2005 00:49:20 -0000
@@ -37,11 +37,47 @@ typedef enum queryOp {
   InvalidQueryOp = -1, QueryMatch = 0, QueryAnd, QueryOr, QueryNot
 } QueryOp;
 
-#include "index.h"
 #include "builtin-fields.h"
 #include "field.h"
 #include "mail.h"
 
+typedef struct query_item
+{
+  ComplexFieldIndex fieldIndex;
+  struct re_pattern_buffer *compRegexp;
+} *QueryItem;
+
+typedef struct search_item
+{
+  /* The type of search to perform, of course.  */
+  SearchType searchType;
+
+  /* The items on the left and right-hand-sides. */
+  QueryItem lhs;
+  QueryItem rhs;
+} SearchItem;
+
+typedef struct queryTree
+{
+  /* The operation to perform on this node. */
+  QueryOp op;
+
+  /* The left and right sides of the expression; if this is a unary op,
+     the left side contains the expression. */
+  struct queryTree *left, *right;
+
+  /* If this is a QueryMatch expression, the actual query to perform. */
+  SearchItem ent;
+} *QueryTree;
+
+struct queryExpr
+{
+  /* Database that this query is associated with.  */
+  DatabaseInfo database;
+  /* The actual query tree.  */
+  QueryTree tree;
+};
+
 struct queryFormat
 {
   /* The name of this query format. */
@@ -66,16 +102,6 @@ struct queryFormat
   struct queryFormat *next;
 };
 
-/* The function that is invoked from iterate_prs () is of this type.
-   First argument is the result # (1 for the first PR matched, 2 for
-   the second, etc). The second argument is the PR entry for the
-   PR, and the final argument is the QueryFormat supplied to
-   iterate_prs.
-
-   The function is invoked with a NULL PR entry when we run out of PRs
-   to match; the result # is the total # of PRs that were matched. */
-typedef void (*QueryFunc)(int, PR *, QueryFormat *);
-
 /* Return the numeric equivalent of enum entry TEXT for field FIELD; entries
    are numbered starting from 1.  0 is returned if TEXT is not a valid
    entry for FIELD.  */
@@ -112,14 +138,12 @@ extern int process_format (FILE *fp, cha
 			   const char *eolTerminator,
 			   FormatNamedParameter *parameters);
 
+extern int pr_matches_tree (PR *pr, PR *oldPR, QueryTree qexp,
+			    FormatNamedParameter *params);
 extern int pr_matches_expr (PR *pr, PR *oldPR, QueryExpr expr,
 			    FormatNamedParameter *params);
 extern int set_query_opt (QueryExpr *, int, const char *, int);
 
-extern int iterate_prs (const DatabaseInfo database,
-			int ac, char **av, QueryExpr exp, QueryFormat *format,
-			QueryFunc func, ErrorDesc *err);
-
 extern void addQueryFormat (DatabaseInfo database, QueryFormat *format);
 extern QueryFormat *findQueryFormat (const DatabaseInfo database,
 				     const char *name, ErrorDesc *err);
Index: ds-file/Makefile.in
===================================================================
RCS file: ds-file/Makefile.in
diff -N ds-file/Makefile.in
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ ds-file/Makefile.in	19 Apr 2005 00:49:20 -0000
@@ -0,0 +1,61 @@
+# Makefile for GNU GNATS flat-file datastore.
+# Copyright (C) 2005 Free Software Foundation, Inc.
+# Adapted from <topdir>/gnats/Makefile.in by Mel Hatzis <hatzis@wattes.org>.
+#
+# This file is part of GNU GNATS.
+
+# GNU GNATS is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# GNU GNATS is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with GNU GNATS; see the file COPYING. if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+INSTALL = $(srcdir)/../../install-sh -c
+
+CONFIG_OPT = "--with-gnats-ds=file"
+
+LIBSRC = db.c pr.c fld.c index.c
+LIBOBJS = db.o pr.o fld.o index.o
+
+TARGETLIB = libds-file.a
+
+LIBS = @LIBS@
+
+INCLUDEDIR = -I. -I.. -I$(srcdir) -I$(srcdir)/.. -I$(srcdir)/../../include
+
+LIBGNATS = `if [ -f ../libgnats.a ] ; then echo ../libgnats.a ; else echo -lgnats ; fi`
+
+GNATS_LIBS = $(LIBGNATS) $(TARGETLIB)
+
+BUILD_DS = @BUILD_FILE@
+
+all-ds: $(TARGETLIB)
+
+$(TARGETLIB): $(LIBOBJS)
+
+TAGS: $(LIBSRC)
+	etags -f TAGS $(LIBSRC) *.h ;
+
+# Clean things up.
+# Don't use rm -rf in case there's other stuff in the directories
+uninstall:
+
+# Dependencies
+
+index.o: ../pr.h
+index.o: $(srcdir)/../gnats.h $(srcdir)/../../include/ansidecl.h
+pr.o: $(srcdir)/../../include/ansidecl.h
+pr.o: $(srcdir)/../gnats.h $(srcdir)/../pcodes.h
+pr.o: $(srcdir)/../../include/getopt.h ../autoconf.h
+db.o: $(srcdir)/../gnats.h $(srcdir)/../../include/ansidecl.h
+db.o: $(srcdir)/../regex.h $(srcdir)/../query.h $(srcdir)/../gnatsd.h
+fld.o: $(srcdir)/../ds.h $(srcdir)/../../include/ansidecl.h
+fld.o: $(srcdir)/../gnats.h $(srcdir)/../pr.h
Index: ds-file/db.c
===================================================================
RCS file: ds-file/db.c
diff -N ds-file/db.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ ds-file/db.c	19 Apr 2005 00:49:20 -0000
@@ -0,0 +1,238 @@
+/* Flat-file datastore API - functions operating on a single database instance.
+   Copyright (C) 2005 Free Software Foundation, Inc.
+   Contributed by Mel Hatzis <hatzis@wattes.org>.
+
+This file is part of GNU GNATS.
+
+GNU GNATS is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+GNU GNATS is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with GNU GNATS; see the file COPYING. if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA  */
+
+#include "ds.h"
+#include "gnatsd.h"
+#include "ds-file.h"
+
+/* load the contents of the PR which are required to test the PR against
+ * the passed in query expression - ie. read's the PR from disk if the
+ * query expression refers to fields which are not in the index.
+ * Returns -1 on error, 0 if the PR wasn't loaded and 1 if it was loaded. */
+static int
+get_pr_contents(PR *pr, QueryTree qexp)
+{
+  int pr_loaded = 0;
+
+  if (qexp == NULL)
+    {
+      return 1;
+    }
+
+  switch (qexp->op)
+    {
+    case QueryAnd:
+    case QueryOr:
+      {
+	pr_loaded = get_pr_contents (pr, qexp->left);
+        if (pr_loaded == 0)
+          {
+            pr_loaded = get_pr_contents (pr, qexp->right);
+          }
+	break;
+      }
+    case QueryNot:
+      {
+	pr_loaded = get_pr_contents (pr, qexp->left);
+	break;
+      }
+    case QueryMatch:
+      {
+        SearchItem *query_element = &(qexp->ent);
+        ComplexFieldIndex lhs_field = query_element->lhs->fieldIndex;
+        if ((! PR_IS_FULL (pr)) && (! isIndexedField (lhs_field)))
+          {
+            ErrorDesc err;
+            if (pr_load (pr, &err) != 0)
+              {
+                return -1;
+              }
+            pr_loaded = 1;
+          }
+	break;
+      }
+    default:
+      {
+	abort ();
+	break;
+      }
+    }
+
+  return pr_loaded;
+}
+
+int
+db_init (DatabaseInfo database, ErrorDesc *err)
+{
+  int retval = 0;
+  char *path;
+  FileDBInfo dbi = (FileDBInfo) xmalloc (sizeof (struct file_db_info));
+  setDatastorePrivate (database, (void *) dbi);
+  dbi->indexDesc = NULL;
+  path = gnats_adm_dir (database, "dbconfig");
+  if (fconfigParse (database, path, NULL, err))
+    {
+      retval = -1;
+    }
+  else
+    {
+      initIndex (database);
+    }
+  free (path);
+  return retval;
+}
+
+void
+db_destroy (DatabaseInfo database)
+{
+  FileDBInfo dbi = (FileDBInfo) getDatastorePrivate(database);
+  clearPRChain (database);
+  if (dbi != NULL)
+    {
+      if (dbi->indexDesc != NULL)
+        {
+          freeIndexDesc (dbi->indexDesc);
+        }
+      free (dbi);
+      setDatastorePrivate (database, (void *)NULL);
+    }
+}
+
+/* Iterate through a set of PRs.  For those PRs that match the
+   expression in EXP, invoke FUNC with the count of PRs that have
+   matched so far, the PR* entry for the PR, and the supplied
+   QUERY_FORMAT.  
+
+   FUNC is invoked once more after all of the queries have been
+   searched.  The PR* pointer is NULL, and the count corresponds to
+   the total # of PRs that matched.
+
+   If AC is 0 or AV is NULL, we iterate through all of the PRs in the
+   index for the current database.  Otherwise, only those PRs that
+   match the PRIDs in AV[0], AV[1]. AV[2]...AV[AC-1] are tested.  */
+
+int
+db_query (const DatabaseInfo database, int ac, char **av, QueryExpr exp,
+             QueryFunc func, QueryFormat *query_format, ErrorDesc *err)
+{
+  int found = 0;
+  QueryTree tree = NULL;
+
+  if (exp != NULL)
+    {
+      if (exp->database != database)
+	{
+	  abort ();
+	}
+      tree = exp->tree;
+    }
+
+  *err = NULL;
+  if (ac == 0 || av == NULL)
+    {
+      PR *pr = getFirstPR (database, err);
+
+      if (*err != NULL)
+	{
+	  return -1;
+	}
+      /* We weren't given a list of PRs to check, so we do the
+	 whole shooting match.  */
+      while (pr != NULL)
+	{
+	  if (get_pr_contents (pr, tree) != -1 &&
+              pr_matches_tree (pr, NULL, tree, NULL))
+	    {
+	      found++;
+	      func (found, pr, query_format);
+	    }
+	  free_pr_header (pr);
+	  free_pr_contents (pr);
+	  pr = getNextPR (pr);
+	}
+    }
+  else
+    {
+      int cpr;
+
+      for (cpr = 0; cpr < ac; cpr++)
+	{
+	  char *pat, *n;
+	  char *p = av[cpr];
+	  int plen;
+	  PR *pr;
+	  struct re_pattern_buffer buf;
+
+	  memset (&buf, 0, sizeof (buf));
+
+	  /* Remove the category */
+	  if ((n = (char *) strrchr (p, '/')) != NULL) 
+	    {
+	      p = n + 1;
+	    }
+
+	  plen = strlen (p);
+	  pat = (char *) xmalloc (plen + 3);
+	  strcpy (pat, p);
+	  strcpy (pat + plen, "\\'");
+
+	  *err = NULL;
+	  pr = getFirstPR (database, err);
+	  if (*err != NULL)
+	    {
+	      return -1;
+	    }
+
+	  while (pr != NULL)
+	    {
+	      if (gnats_regcmp (pat, field_value (pr, NUMBER (pr->database)),
+				&buf) == 0)
+		{
+		  if (get_pr_contents (pr, tree) != -1 &&
+		      pr_matches_expr (pr, NULL, exp, NULL))
+		    {
+		      found++;
+		      func (found, pr, query_format);
+		    }
+		  /* Only one PR will match this PR number, because it's
+		     not really a regexp */
+		  break;
+		}
+	      free_pr_header (pr);
+	      free_pr_contents (pr);
+	      pr = getNextPR (pr);
+	    }
+	  buf.translate = NULL;
+	  regfree (&buf);
+	  free (pat);
+	}
+    }
+
+  func (found, NULL, query_format);
+
+  return found;
+}
+
+void
+db_reset (const DatabaseInfo database, ErrorDesc *err)
+{
+  checkPRChain (database, err);
+}
+
Index: ds-file/ds-file.h
===================================================================
RCS file: ds-file/ds-file.h
diff -N ds-file/ds-file.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ ds-file/ds-file.h	19 Apr 2005 00:49:20 -0000
@@ -0,0 +1,37 @@
+/* Functions and data types used throughout the flat-file datastore.
+   Copyright (C) 2005 Free Software Foundation, Inc.
+   Contributed by Mel Hatzis <hatzis@wattes.org>.
+
+This file is part of GNU GNATS.
+
+GNU GNATS is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+GNU GNATS is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with GNU GNATS; see the file COPYING. if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA  */
+
+#ifndef _DS_FILE_H
+#define _DS_FILE_H
+
+typedef struct file_pr_info *FilePRInfo;
+typedef struct file_db_info *FileDBInfo;
+
+#include "index.h"
+
+struct file_pr_info {
+  Index *index;   /* Index entry for the PR. */
+};
+
+struct file_db_info {
+      IndexDesc indexDesc;
+};
+
+#endif /* !_DS_FILE_H */
Index: ds-file/fld.c
===================================================================
RCS file: ds-file/fld.c
diff -N ds-file/fld.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ ds-file/fld.c	19 Apr 2005 00:49:20 -0000
@@ -0,0 +1,32 @@
+/* Flat-file datastore API - functions operating on PR fields.
+   Copyright (C) 2005 Free Software Foundation, Inc.
+   Contributed by Mel Hatzis <hatzis@wattes.org>.
+
+This file is part of GNU GNATS.
+
+GNU GNATS is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+GNU GNATS is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with GNU GNATS; see the file COPYING. if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA  */
+
+#include "ds.h"
+#include "ds-file.h"
+
+char *
+field_cache_value (PR *pr, FieldIndex field)
+{
+  /* Houston, we have a problem.  For dates, the value that's
+     stored in the index is an integer, not the actual value of
+     the field...  \hbadness 10000.  XXX ??? !!! FIXME */
+  return indexValue (pr, field);
+}
+
Index: ds-file/index.c
===================================================================
RCS file: ds-file/index.c
diff -N ds-file/index.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ ds-file/index.c	19 Apr 2005 00:49:20 -0000
@@ -0,0 +1,1321 @@
+/* Miscellaneous index routines for use with GNATS. 
+   Copyright (C) 1993, 1995, 1999, 2000, 2001 Free Software Foundation, Inc.
+   Contributed by Brendan Kehoe (brendan@cygnus.com) and
+   by Tim Wicinski (wicinski@barn.com).
+   
+   Heavily revised by Bob Manson (manson@juniper.net); also added
+   binary index support.
+
+This file is part of GNU GNATS.
+
+GNU GNATS is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU GNATS is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU GNATS; see the file COPYING.  If not, write to the Free
+Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111, USA.  */
+
+/* Binary format (all values are in MSB-first order):
+ * 1 byte: number of fields in each index entry
+ * For each record:
+ *     2 bytes: total length of record, including this 2 byte length
+ *     For each field:
+ *         2 bytes: length of field, not including this 2 byte length or NUL
+ *		    terminator
+ *	   N bytes: data in field, NUL-terminated
+ */
+	
+
+#include "ds.h"
+#include "gnatsd.h"
+#include "ds-file.h"
+
+struct index_desc
+{
+  DatabaseInfo database;
+  char *path;
+  FieldList fields;
+  char *separator;
+  int isBinary;
+  int numberOfFields;
+  int *fieldInIndex;
+  time_t mtime;
+  int indexRead;
+  PR *prChain;
+};
+
+typedef struct index_file_desc
+{
+  IndexDesc desc;
+  FILE *fileDes;
+} *IndexFileDesc;
+
+struct indexEntry
+{
+  char *buf;
+  char **fields;
+  PR *nextPR;
+  PR *prevPR;
+};
+
+static PR *nextIndexEntryBinary (IndexFileDesc fp);
+
+static int
+format_field (FieldIndex field, const char *value, const char *separator,
+	      char **dest, size_t *length)
+{
+  char datebuf[30];
+  size_t valLen;
+  size_t destLen;
+  size_t sepLen;
+
+  if (value == NULL)
+    {
+      value = "";
+    }
+
+  if (fieldDefForIndex (field)->nospaces)
+    {
+      char *r = strchr (value, ' ');
+      if (r != NULL)
+	{
+	  valLen = r - value;
+	}
+    }
+
+  if (fieldDefForIndex (field)->datatype == Date)
+    {
+      time_t d = 0;
+      if (value != NULL && *value != '\0')
+	{
+	  /* We used to fail here if the date was invalid, but that's
+	     a mistake.  */
+	  d = get_any_date (value);
+	}
+      sprintf (datebuf, "%d", (int) d);
+      value = datebuf;
+    }
+
+  if (value == NULL)
+    {
+      value = "";
+    }
+  destLen = *length;
+  valLen = strlen (value);
+  sepLen = strlen (separator);
+
+  *dest = xrealloc (*dest, destLen + valLen + sepLen + 1);
+  memcpy (*dest + destLen, separator, sepLen);
+  memcpy (*dest + destLen + sepLen, value, valLen);
+  (*dest)[destLen + sepLen + valLen] = '\0';
+  *length = destLen + sepLen + valLen;
+  return sepLen + valLen;
+}
+
+char *
+createIndexEntry (PR *pr, size_t *reclen)
+{
+  FieldList f;
+  char *res;
+  IndexDesc indexDesc = getIndexDesc (pr->database);
+
+  if (indexIsBinary (pr->database))
+    {
+      return createIndexEntryBinary (pr, reclen);
+    }
+
+  /* XXX ??? !!! For now.  We need a way to refer to "PRID".  */
+  asprintf (&res, "%s/%s", field_value (pr, CATEGORY (pr->database)),
+	    field_value (pr, NUMBER (pr->database)));
+  *reclen = strlen (res);
+
+  for (f = indexDesc->fields; f != NULL; f = f->next)
+    {
+      FieldIndex i = simpleFieldIndexValue (f->ent);
+      if (format_field (i, field_value (pr, i), indexDesc->separator, &res,
+			reclen) < 0)
+	{
+	  free (res);
+	  res = NULL;
+	  break;
+	}
+    }
+  append_string (&res, "\n");
+  (*reclen)++;
+  return res;
+}
+
+static char *
+find_sep (IndexDesc indexDesc, char *start)
+{
+  char *end;
+  int seplen = strlen (indexDesc->separator);
+
+  do {
+    end = strchr (start, indexDesc->separator[0]);
+    if (end != NULL)
+      {
+	if (seplen == 1 || strncmp (start, indexDesc->separator, seplen) == 0)
+	  {
+	    return end;
+	  }
+	end++;
+      }
+  } while (end != NULL);
+
+  return end;
+}
+
+static char *
+readBinaryRecord (IndexFileDesc fp, size_t *length)
+{
+  char *res;
+  unsigned char entlen[2];
+
+  if (fread (entlen, 1, 2, fp->fileDes) != 2)
+    {
+      return NULL;
+    }
+  *length = entlen[0] * 256 + entlen[1] - 2;
+  res = xmalloc (*length);
+  if (fread (res, 1, *length, fp->fileDes) == *length)
+    {
+      return res;
+    }
+  else
+    {
+      free (res);
+      return NULL;
+    }
+}
+
+static char *
+extractBinaryField (char **record, size_t *lengthPtr)
+{
+  size_t length = *lengthPtr;
+  if (length > 0)
+    {
+      char *recLen = *record;
+      size_t fieldLen = (recLen[0] & 255) * 256 + (recLen[1] & 255);
+      if ((fieldLen + 3) <= length)
+	{
+	  char *recptr = *record + 2;
+	  *lengthPtr = length - (fieldLen + 3);
+	  (*record) = recLen + fieldLen + 3;
+	  return recptr;
+	}
+    }
+  return NULL;
+}
+
+/* Return the next index entry from FP.  */
+static PR *
+nextIndexEntry (IndexFileDesc fp)
+{
+  char *start, *end;
+  char *buf;
+  PR *res;
+  int x;  
+  int seplen;
+  FieldList f;
+  FilePRInfo pi;
+  Index *p;
+
+  if (fp->desc->isBinary)
+    {
+      return nextIndexEntryBinary (fp);
+    }
+
+  res = allocPR (fp->desc->database);
+  pi = (FilePRInfo) res->ds_private;
+  p = pi->index;
+  seplen = strlen (fp->desc->separator);
+
+  p->prevPR = NULL;
+  p->nextPR = NULL;
+  p->fields = (char **) xmalloc (sizeof (char *) 
+				 * get_num_fields (fp->desc->database));
+  memset (p->fields, 0, sizeof (char *) * get_num_fields (fp->desc->database));
+  while ((buf = read_line (fp->fileDes, NULL)) != NULL)
+    {
+      if (buf[0] != '#')
+	{
+	  break;
+	}
+      else
+	{
+	  free (buf);
+	}
+    }
+
+  if (buf == NULL)
+    {
+      goto no_entry;
+    }
+
+  p->buf = buf;
+  x = strlen (buf) - 1;
+  if (buf[x] == '\n')
+    {
+      buf[x] = '\0';
+    }
+
+  start = buf;
+  end = strchr (start, '/');
+  if (end == NULL)
+    {
+      goto no_entry;
+    }
+  *end = '\0';
+  p->fields[fieldDefForIndex (CATEGORY (fp->desc->database))->number] = start;
+
+  start = end + 1;
+  end = find_sep (fp->desc, start);
+
+  if (end == NULL)
+    {
+      goto no_entry;
+    }
+  *end = '\0';
+  p->fields[fieldDefForIndex (NUMBER (fp->desc->database))->number] = start;
+
+  for (f = fp->desc->fields; f != NULL && end != NULL; f = f->next)
+    {
+      FieldIndex i = simpleFieldIndexValue (f->ent);
+
+      if (i != InvalidFieldIndex)
+	{
+	  start = end + seplen;
+	  p->fields[fieldNumber (i)] = start;
+	}
+      if (f->next != NULL)
+	{
+	  end = find_sep (fp->desc, start);
+	  if (end != NULL)
+	    {
+	      *end = '\0';
+	    }
+	}
+    }
+
+  if (f == NULL)
+    {
+      return res;
+    }
+
+no_entry:
+
+  free_pr (res);
+  return NULL;
+}
+
+static PR *
+nextIndexEntryBinary (IndexFileDesc fp)
+{
+  char *record;
+  PR *res = NULL;
+  size_t length;
+  FilePRInfo pi;
+
+  record = readBinaryRecord (fp, &length);
+  if (record != NULL)
+    {
+      FieldList f;
+      Index *indexEnt;
+
+      res = allocPR (fp->desc->database);
+      pi = (FilePRInfo) res->ds_private;
+      indexEnt = pi->index;
+      indexEnt->prevPR = NULL;
+      indexEnt->nextPR = NULL;
+      indexEnt->fields 
+	= (char **) xmalloc (sizeof (char *) 
+			     * get_num_fields (fp->desc->database));
+      indexEnt->buf = record;
+      memset (indexEnt->fields, 0, sizeof (char *) 
+	      * get_num_fields (fp->desc->database));
+      indexEnt->fields[fieldDefForIndex (NUMBER (fp->desc->database))->number] 
+	= extractBinaryField (&record, &length);
+      indexEnt->fields[fieldDefForIndex (CATEGORY (fp->desc->database))->number] 
+	= extractBinaryField (&record, &length);
+      for (f = fp->desc->fields; res != NULL && f != NULL; f = f->next)
+	{
+	  FieldIndex i = simpleFieldIndexValue (f->ent);
+
+	  if (i != InvalidFieldIndex)
+	    {
+	      indexEnt->fields[fieldNumber (i)]
+		= extractBinaryField (&record, &length);
+	      if (indexEnt->fields[fieldNumber (i)] == NULL)
+		{
+		  free_pr (res);
+		  res = NULL;
+		}
+	    }
+	  else
+	    {
+	      free_pr (res);
+	      res = NULL;
+	    }
+	}
+    }
+  return res;
+}
+
+static void
+appendBinaryFieldContents (char **dest,
+			   const char *value, size_t *recLenPtr)
+{
+  size_t valLen;
+  size_t recLen = *recLenPtr;
+
+  if (value == NULL)
+    {
+      value = "";
+      valLen = 0;
+    }
+  else
+    {
+      valLen = strlen (value);
+    }
+  *dest = xrealloc (*dest, recLen + valLen + 3);
+  memcpy (*dest + recLen + 2, value, valLen + 1);
+  (*dest)[recLen + 0] = valLen / 256;
+  (*dest)[recLen + 1] = valLen % 256;
+  *recLenPtr += valLen + 3;
+}
+
+char *
+createIndexEntryBinary (PR *pr, size_t *recLen)
+{
+  const DatabaseInfo database = pr->database;
+  const IndexDesc indexDesc = getIndexDesc (database);
+  FieldList f;
+  char *res;
+
+  *recLen = 2;
+  res = xmalloc (2);
+  appendBinaryFieldContents (&res, field_value (pr, NUMBER (pr->database)),
+			     recLen);
+  appendBinaryFieldContents (&res, field_value (pr, CATEGORY (pr->database)),
+			     recLen);
+  for (f = indexDesc->fields; f != NULL; f = f->next)
+    {
+      FieldIndex i = simpleFieldIndexValue (f->ent);
+      size_t fieldLenOffset = *recLen;
+      int valLen;
+
+      /* Two bytes for the field length. */
+      *recLen += 2;
+      valLen = format_field (i, field_value (pr, i), "", &res, recLen);
+      res[fieldLenOffset + 0] = valLen / 256;
+      res[fieldLenOffset + 1] = valLen & 255;
+      /* One for the NUL character; format_field() always appends one,
+	 but doesn't include it in the length.  */
+      (*recLen)++;
+    }
+  res[0] = *recLen / 256;
+  res[1] = *recLen % 256;
+  return res;
+}
+
+static char *
+findPrCategoryBinary (IndexFileDesc fp, const char *number)
+{
+  unsigned char fieldcount;
+  char *record;
+  size_t recLen;
+  
+  if (fread (&fieldcount, 1, 1, fp->fileDes) != 1)
+    {
+      return NULL;
+    }
+  while ((record = readBinaryRecord (fp, &recLen)) != NULL)
+    {
+      char *recPos = record;
+      char *prNum = extractBinaryField (&recPos, &recLen);
+      if (prNum != NULL && strcmp (prNum, number) == 0)
+	{
+	  char *category = extractBinaryField (&recPos, &recLen);
+	  free (record);
+	  return category;
+	}
+      free (record);
+    }
+  return NULL;
+}
+
+/* Find problem report NUMBER in the index file, returning its category.  */
+static char *
+findPrCategory (IndexFileDesc fp, const char *number)
+{
+  char *buf;
+  char *res = NULL;
+
+  while ((res == NULL) && (buf = read_line (fp->fileDes, NULL)) != NULL)
+    {
+      if (buf[0] != '#')
+	{
+	  char *start = buf;
+	  char *end = strchr (start, '/');
+	  if (end != NULL)
+	    {
+	      char *numStart = end + 1;
+	      char *category = start;
+
+	      *end = '\0';
+	      end = find_sep (fp->desc, numStart);
+	      if (end != NULL)
+		{
+		  *end = '\0';
+
+		  if (strcmp (numStart, number) == 0)
+		    {
+		      res = xstrdup (category);
+		    }
+		}
+	    }
+	}
+      free (buf);
+    }
+
+  return res;
+}
+
+static time_t
+statIndex (char *name)
+{
+  struct stat buf;
+  int i;
+
+  i = stat (name, &buf);
+  if (i < 0)
+    {
+      return (time_t)-1;
+    }
+
+  return buf.st_mtime;
+}
+
+static IndexFileDesc
+openIndex (IndexDesc indexDesc, ErrorDesc *err)
+{
+  FILE *fp;
+  time_t t;
+  char *index_filename = gnats_adm_dir (indexDesc->database, indexDesc->path);
+  IndexFileDesc res 
+    = (IndexFileDesc) xmalloc (sizeof (struct index_file_desc));
+
+  res->desc = indexDesc;
+
+  fp = fopen (index_filename, "r");
+
+  if (fp != NULL)
+    {
+      t = statIndex (index_filename);
+      /* ??? XXX !!! Do we want to do something here? */
+      if (t != (time_t)-1)
+	{
+	  indexDesc->mtime = t;
+	}
+      res->fileDes = fp;
+      if (indexDesc->isBinary)
+	{
+	  unsigned char fieldCount;
+	  if ((fread (&fieldCount, 1, 1, res->fileDes) != 1) 
+	      || (fieldCount != indexDesc->numberOfFields))
+	    {
+	      setError (err, CODE_INVALID_INDEX, 
+			"Index file %s contains incorrect number of fields",
+			index_filename);
+	      fclose (res->fileDes);
+	      free (res);
+	      res = NULL;
+	    }
+	}
+    }
+  else
+    {
+      setError (err, CODE_FILE_ERROR, "Unable to open index file %s",
+		index_filename);
+      free (res);
+      res = NULL;
+    }
+
+  free (index_filename);
+  return res;
+}
+
+static void
+closeIndex (IndexFileDesc desc)
+{
+  fclose (desc->fileDes);
+  free (desc);
+}
+
+/* getIndex - reads in the entire set of PRs with index entries. */
+static PR *
+getIndex (IndexDesc indexDesc, ErrorDesc *err)
+{
+  IndexFileDesc fp = openIndex (indexDesc, err);
+  PR *new_chain = NULL;
+  PR *end_chain = NULL;
+
+  if (fp != NULL)
+    {
+      PR *i;
+
+      while ((i = nextIndexEntry (fp)))
+	{
+	  if (new_chain == NULL)
+	    {
+	      new_chain = i;
+	      end_chain = i;
+	    }
+	  else
+	    {
+	      FilePRInfo i_pi = (FilePRInfo) i->ds_private;
+	      FilePRInfo e_pi = (FilePRInfo) end_chain->ds_private;
+	      i_pi->index->prevPR = end_chain;
+	      e_pi->index->nextPR = i;
+	      end_chain = i;
+	    }
+	}
+
+      closeIndex (fp);
+    }
+  indexDesc->indexRead = 1;
+  return new_chain;
+}
+
+static PR *
+getNewIndexIfChanged (DatabaseInfo database, ErrorDesc *err)
+{
+  int rereadIndex;
+  IndexDesc indexDesc = getIndexDesc (database);
+
+  if (indexDesc->indexRead)
+    {
+      time_t t;
+      char *index_filename = gnats_adm_dir (database, indexDesc->path);
+
+      t = statIndex (index_filename);
+      free (index_filename);
+
+      if (t == (time_t)-1)
+	{
+	  setError (err, CODE_FILE_ERROR,
+		   "GNATS cannot stat the index: %s.", strerror (errno));
+	  return 0;
+	}
+      if (t > indexDesc->mtime)
+	{
+	  rereadIndex = 1;
+	}
+      else
+	{
+	  rereadIndex = 0;
+	}
+    }
+  else
+    {
+      rereadIndex = 1;
+    }
+
+  if (rereadIndex)
+    {
+      return getIndex (indexDesc, err);
+    }
+  else
+    {
+      return NULL;
+    }
+}
+
+void
+freePRIndex (DatabaseInfo database, FilePRInfo pi)
+{
+  if (pi->index != NULL && pi->index->fields != NULL)
+    {
+      if (pi->index->buf != NULL)
+	{
+	  free (pi->index->buf);
+	  pi->index->buf = NULL;
+	}
+      else
+	{
+	  int x;
+	  
+	  for (x = get_num_fields (database) - 1; x >= 0; x--)
+	    {
+	      if (pi->index->fields[x] != NULL)
+		{
+		  free (pi->index->fields[x]);
+		  pi->index->fields[x] = NULL;
+		}
+	    }
+	}
+      free (pi->index->fields);
+      pi->index->fields = NULL;
+    }
+}
+
+void
+allocIndex (FilePRInfo pi)
+{
+  pi->index = (Index *) xmalloc (sizeof (Index));
+  pi->index->buf = NULL;
+  pi->index->fields = NULL;
+  pi->index->prevPR = NULL;
+  pi->index->nextPR = NULL;
+}
+ 
+void
+buildIndexEntry (PR *pr)
+{
+  int x;
+  int num_fields = get_num_fields (pr->database);
+  Index *newIndex;
+  FilePRInfo pi;
+
+  if (pr->ds_private == NULL)
+    {
+      pr_init (pr);
+      pi = (FilePRInfo) pr->ds_private;
+    }
+  else
+    {
+      pi = (FilePRInfo) pr->ds_private;
+      if (pi->index != NULL)
+	{
+	  freePRIndex (pr->database, pi);
+	}
+    }
+  newIndex = pi->index;
+
+  newIndex->fields = (char **) xmalloc (sizeof (char *) * num_fields);
+  memset (newIndex->fields, 0, sizeof (char *) * num_fields);
+  newIndex->buf = NULL;
+  for (x = 0; x < num_fields; x++)
+    {
+      FieldIndex field = getNthField (pr->database, x);
+      if (isIndexedFieldIndex (field))
+	{
+	  const char *fc = field_value (pr, field);
+
+	  if (fieldDefForIndex (field)->datatype == Date)
+	    {
+	      if (fc == NULL 
+		  || fc[0] == '\0' 
+		  || (strcmp (fc, "-1") == 0))
+		{
+		  newIndex->fields[x] = xstrdup ("0");
+		}
+	      else
+		{
+		  time_t t = get_any_date (fc);
+		  asprintf (&(newIndex->fields[x]), "%d", (int) t);
+		}
+	    }
+	  else
+	    {
+	      char *fcCopy;
+
+	      if (fc == NULL)
+		{
+		  fcCopy = xstrdup ("");
+		}
+	      else
+		{
+		  fcCopy = xstrdup (fc);
+		}
+
+	      /* ??? XXX Should we do this here?  It's already being
+	         done in the "write out the index" code.  */
+	      if (fieldDefForIndex (field)->nospaces)
+		{
+		  /* Try to minimize the leakage.  */
+		  char *p = strchr (fcCopy, ' ');
+		  if (p != NULL)
+		    {
+		      *p = '\0';
+		    }
+		}
+	      newIndex->fields[x] = fcCopy;
+	    }
+	}
+    }
+}
+
+int
+isIndexedFieldIndex (FieldIndex index)
+{
+  IndexDesc indexDesc;
+  if (index == InvalidFieldIndex)
+    {
+      return 0;
+    }
+  else
+    {
+      indexDesc = getIndexDesc (fieldDefForIndex (index)->database);
+      return indexDesc->fieldInIndex[fieldDefForIndex (index)->number];
+    }
+}
+
+void
+finishIndexDesc (DatabaseInfo database, IndexDesc new)
+{
+  int numFields = get_num_fields (database);
+
+  new->fieldInIndex = (int *) xmalloc (sizeof (int) * numFields);
+
+  {
+    int x;
+
+    for (x = 0; x < numFields; x++)
+      {
+	new->fieldInIndex[x] = 0;
+      }
+  }
+
+  {
+    FieldList f;
+
+    for (f = new->fields; f != NULL; f = f->next)
+      {
+	FieldIndex i = simpleFieldIndexValue (f->ent);
+	if (i != InvalidFieldIndex)
+	  {
+	    new->fieldInIndex[fieldNumber (i)] = 1;
+	  }
+      }
+  }
+  new->fieldInIndex[fieldNumber (NUMBER (database))] = 1;
+  new->fieldInIndex[fieldNumber (CATEGORY (database))] = 1;
+}
+
+int
+isIndexedField (ComplexFieldIndex ent)
+{
+  if (parseComplexFieldIndex (ent) != 0)
+    {
+      return 0;
+    }
+  return isIndexedFieldIndex (simpleFieldIndexValue (ent));
+}
+
+/* Write out the index for DATABASE's PR chain.  Returns 0 on success,
+   a non-zero value otherwise (and ERR will be filled in). */
+int
+writeIndex (const DatabaseInfo database, ErrorDesc *err)
+{
+  FILE *fp;
+  char *path, *workfile;
+  PR *pr;
+  IndexDesc indexDesc = getIndexDesc (database);
+  struct stat buf;
+
+  workfile = gnats_adm_dir (database, "indXXXXXX");
+  fp = fopen_temporary_file (workfile, "w", 0644);
+  if (fp == NULL)
+    {
+      setError (err, CODE_FILE_ERROR,
+		"writeIndex () can't open the temporary file %s",
+		workfile);
+      free (workfile);
+      return -1;
+    }
+
+  if (indexIsBinary (database))
+    {
+      unsigned char fieldCount = indexFieldCount (database);
+      fwrite (&fieldCount, 1, 1, fp);
+    }
+
+  for (pr = getFirstPR (database, err); pr != NULL ; pr = getNextPR (pr))
+    {
+      size_t length;
+      char *line = createIndexEntry (pr, &length);
+
+      if (line != NULL)
+	{
+	  fwrite (line, 1, length, fp);
+	  free (line);
+	}
+    }
+
+  fclose (fp);
+
+  block_signals ();
+
+  path = gnats_adm_dir (database, indexDesc->path);
+
+  /* sanity check -- make sure nobody has changed the index since we
+     last read it */
+  if (stat (path, &buf) < 0)
+    {
+      /* if the index ain't there, no problem.  if it's there, but
+	 unstattable, we're going to declare it a serious problem.
+	 not sure if this is the right thing to do... */
+      if (errno != ENOENT)
+	{
+	  setError (err, CODE_FILE_ERROR,
+		    "GNATS cannot stat the index: %s.", strerror (errno));
+	  log_msg (LOG_ERR, 1, "error in writeIndex:",
+		   getErrorMessage (*err));
+	  return -1;
+	}
+    }
+
+  if (buf.st_mtime > indexDesc->mtime)
+    {
+      setError (err, CODE_INVALID_INDEX,
+		"Index has been modified since last read");
+      log_msg (LOG_ERR, 1, "error in writeIndex:",
+	       getErrorMessage (*err));
+      /* the index is corrupt: alert the administrator */
+      punt (database, 0, "Attempting to write index file %s,\n\
+but index has been modified since last read.  A PR has been modified,\n\
+but the change has not been recorded in the index.  You must manually\n\
+regenerate the index.", path);
+      return -1;
+    }
+
+  if ((rename (workfile, path)) < 0)
+    {
+      if (errno != EXDEV)
+	{
+	  free (path);
+	  setError (err, CODE_FILE_ERROR,
+		    "could not rename temporary index %s into gnats-adm: %s",
+		    workfile, strerror (errno));
+	  return -1;
+	}
+
+      if (copy_file (workfile, path))
+	{
+	  setError (err, CODE_FILE_ERROR, 
+		   "could not copy temporary index %s into %s: %s",
+		   workfile, path, strerror (errno));
+	  return -1;
+	}
+
+      unlink (workfile);
+    }
+
+  free (path);
+  free (workfile);
+  unblock_signals ();
+  return 0;
+}
+
+/* Add the PR to the GNATS index file.  */
+int
+addToIndex (PR *pr, ErrorDesc *err)
+{
+  FILE *fp;
+  char *path;
+  IndexDesc indexDesc = getIndexDesc (pr->database);
+
+  buildIndexEntry (pr);
+
+  block_signals ();
+
+  path = gnats_adm_dir (pr->database, indexDesc->path);
+
+  if (indexIsBinary (pr->database) && ! fileExists (path))
+    {
+      unsigned char fieldCount = indexFieldCount (pr->database);
+      fp = fopen (path, "w");
+      if (fp != NULL)
+	{
+	  fwrite (&fieldCount, 1, 1, fp);
+	}
+    }
+  else
+    {
+      fp = fopen (path, "a+");
+    }
+  if (fp == NULL)
+    {
+      setError (err, CODE_FILE_ERROR, 
+		"Can't append to the GNATS index file (%s).", path);
+      unblock_signals ();
+      return -1;
+    }
+
+  {
+    size_t reclen;
+
+    char *line = createIndexEntry (pr, &reclen);
+    fwrite (line, 1, reclen, fp);
+    free (line);
+  }
+
+  fclose (fp);
+  free (path);
+  unblock_signals ();
+  return 0;
+}
+
+char *
+indexValue (PR *pr, FieldIndex field)
+{
+  FilePRInfo pi = (FilePRInfo) pr->ds_private;
+  if (pi->index != NULL && pi->index->fields != NULL)
+    {
+      return pi->index->fields[field->number];
+    }
+  else
+    {
+      return NULL;
+    }
+}
+
+char *
+getCategoryFromIndex (const DatabaseInfo database, const char *prnum,
+		      ErrorDesc *err)
+{
+  IndexDesc indexDesc = getIndexDesc (database);
+  IndexFileDesc fp = openIndex (indexDesc, err);
+  if (fp != NULL)
+    {
+      char *category;
+      if (fp->desc->isBinary)
+	{
+	  category = findPrCategoryBinary (fp, prnum);
+	}
+      else
+	{
+	  category = findPrCategory (fp, prnum);
+	}
+      closeIndex (fp);
+      if (category == NULL)
+	{
+	  setError (err, CODE_NONEXISTENT_PR,
+		    "PR %s is not listed in the index.", prnum);
+	}
+      return category;
+    }
+  else
+    {
+      return NULL;
+    }
+}
+
+IndexDesc
+getIndexDesc (const DatabaseInfo database)
+{
+  FileDBInfo dbi = (FileDBInfo) getDatastorePrivate(database);
+  if (dbi == NULL)
+    {
+      return NULL;
+    }
+  return dbi->indexDesc;
+}
+
+void
+setIndexDesc (DatabaseInfo database, IndexDesc new)
+{
+  if (databaseValid (database))
+    {
+      FileDBInfo dbi = (FileDBInfo) getDatastorePrivate(database);
+      finishIndexDesc (database, new);
+      dbi->indexDesc = new;
+    }
+}
+
+void
+freeIndexDesc (IndexDesc p)
+{
+  free (p->path);
+  freeFieldList (p->fields);
+  free (p->separator);
+  if (p->fieldInIndex != NULL)
+    {
+      free (p->fieldInIndex);
+    }
+  free (p);
+}
+
+IndexDesc
+newIndexDesc (const DatabaseInfo database)
+{
+  IndexDesc res = xmalloc (sizeof (struct index_desc));
+
+  res->database = database;
+  res->prChain = NULL;
+  res->path = NULL;
+  res->fields = NULL;
+  res->separator = xstrdup ("|");
+  res->isBinary = 0;
+  res->numberOfFields = 0;
+  res->fieldInIndex = NULL;
+  res->mtime = 0;
+  res->indexRead = 0;
+
+  return res;
+}
+
+void
+addFieldToIndex (IndexDesc desc, FieldList ent)
+{
+  if (desc->fields == NULL)
+    {
+      desc->fields = ent;
+    }
+  else
+    {
+      FieldList f = desc->fields;
+      while (f->next != NULL)
+	{
+	  f = f->next;
+	}
+      f->next = ent;
+    }
+  desc->numberOfFields++;
+}
+
+void
+setIndexDescPath (IndexDesc desc, const char *path)
+{
+  desc->path = xstrdup (path);
+}
+
+void
+setIndexDescSeparator (IndexDesc desc, const char *separator)
+{
+  free (desc->separator);
+  desc->separator = xstrdup (separator);
+}
+
+void
+setIndexDescBinary (IndexDesc desc, int flag)
+{
+  desc->isBinary = flag;
+}
+
+static void
+freePRChain (PR *start)
+{
+  while (start != NULL)
+    {
+      PR *next = getNextPR (start);
+      free_pr (start);
+      start = next;
+    }
+}
+
+int
+checkPRChain (const DatabaseInfo database, ErrorDesc *err)
+{
+  IndexDesc desc = getIndexDesc (database);
+  PR *new_chain = getNewIndexIfChanged (database, err);
+
+  if (new_chain != NULL)
+    {
+      freePRChain (desc->prChain);
+      desc->prChain = new_chain;
+      return 1;
+    }
+  else
+    {
+      return 0;
+    }
+}
+
+void
+initIndex (DatabaseInfo database)
+{
+  clearPRChain (database);
+}
+
+PR *
+getFirstPR (const DatabaseInfo database, ErrorDesc *err)
+{
+  IndexDesc desc = getIndexDesc (database);
+
+  /* first check the PRChain to make sure that the index isn't stale */
+  checkPRChain (database, err);
+
+  return desc->prChain;
+}
+
+void
+clearPRChain (const DatabaseInfo database)
+{
+  IndexDesc desc = getIndexDesc (database);
+  if (desc != NULL)
+    {
+      if (desc->prChain != NULL)
+	{
+	  freePRChain (desc->prChain);
+	  desc->prChain = NULL;
+	}
+      desc->indexRead = 0;
+    }
+}
+
+/* Replace a PR in the index with a copy of a new PR.
+   By generating a copy of the new PR, we allow for the new PR
+   to be free'd without harming the index pr chain. */
+int
+replaceCurrentPRInIndex (PR *curr_pr, PR *new_pr, ErrorDesc *err)
+{
+  PR *pr_replace, *pr_copy;
+  const DatabaseInfo database = curr_pr->database;
+  const char *new_pr_num = field_value (new_pr, NUMBER (database));
+  const char *curr_pr_num = field_value (curr_pr, NUMBER (database));
+  FilePRInfo curr_pi = (FilePRInfo) curr_pr->ds_private;
+
+  if (strcmp (curr_pr_num, new_pr_num) != 0)
+    {
+      return 0;
+    }
+
+  /* create a copy of the new pr to be put into the index pr chain */
+  pr_copy = allocPR (database);
+  set_field (pr_copy, NUMBER (curr_pr->database), new_pr_num, err);
+  set_field (pr_copy, CATEGORY (database),
+	     field_value (new_pr, CATEGORY (database)), err);
+  setPrevPR (pr_copy, curr_pi->index->prevPR);
+  setNextPR (pr_copy, curr_pi->index->nextPR);
+  pr_load (pr_copy, err);
+
+  /* put the copy of new_pr into place in the index pr chain... */
+
+  if (curr_pi->index->prevPR == NULL)
+    {
+      /* the pr to be replaced is at the head of the pr chain */
+      IndexDesc indexDesc = getIndexDesc (database);
+      free_pr (indexDesc->prChain);
+      indexDesc->prChain = pr_copy;
+      return 1;
+    }
+
+  /* save a pointer to the pr to be replaced so that it can later be free'd */
+  pr_replace = getNextPR (curr_pi->index->prevPR);
+
+  setNextPR (curr_pi->index->prevPR, pr_copy);
+
+  if (curr_pi->index->nextPR != NULL)
+    {
+      /* the pr to be replaced is not at the end of the pr chain */
+      setPrevPR (curr_pi->index->nextPR, pr_copy);
+    }
+
+  free_pr (pr_replace);
+
+  return 1;
+}
+
+/* Remove PR PRNUM from the index for the current database, and rewrite the
+   database file.  The database must have been locked before calling this
+   function.  */
+int
+removePRFromIndex (const DatabaseInfo database, const char *prNum,
+		   ErrorDesc *err)
+{
+  PR *list;
+  PR *prev = NULL;
+  IndexDesc indexDesc = getIndexDesc (database);
+  FilePRInfo list_pi;
+
+  if (! is_gnats_locked (database))
+    {
+      setError (err, CODE_GNATS_NOT_LOCKED,
+		"Must lock the database before removing a PR from the index");
+      return -1;
+    }
+
+  list = getFirstPR (database, err);
+  while (list != NULL
+	 && strcmp (field_value (list, NUMBER (database)), prNum) != 0)
+    {
+      prev = list;
+      list = getNextPR (list);
+    }
+
+  if (list == NULL)
+    {
+      setError (err, CODE_NONEXISTENT_PR,
+		"PR %s not in index", prNum);
+      return -2;
+    }
+
+  list_pi = (FilePRInfo) list->ds_private;
+  if (prev != NULL)
+    {
+      FilePRInfo prev_pi = (FilePRInfo) prev->ds_private;
+      prev_pi->index->nextPR = list_pi->index->nextPR;
+    }
+  else
+    {
+      indexDesc->prChain = list_pi->index->nextPR;
+    }
+
+  return writeIndex (database, err);
+}
+
+PR *
+getNextPR (PR *pr)
+{
+  FilePRInfo pi = (FilePRInfo) pr->ds_private;
+  return pi->index->nextPR;
+}
+
+void
+setNextPR (PR *pr, PR *next_pr)
+{
+  FilePRInfo pi = (FilePRInfo) pr->ds_private;
+  pi->index->nextPR = next_pr;
+}
+
+PR *
+getPrevPR (PR *pr)
+{
+  FilePRInfo pi = (FilePRInfo) pr->ds_private;
+  return pi->index->prevPR;
+}
+
+void
+setPrevPR (PR *pr, PR *prev_pr)
+{
+  FilePRInfo pi = (FilePRInfo) pr->ds_private;
+  pi->index->prevPR = prev_pr;
+}
+
+int
+indexIsBinary (const DatabaseInfo database)
+{
+  IndexDesc indexDesc = getIndexDesc (database);
+  if (indexDesc != NULL)
+    {
+      return indexDesc->isBinary;
+    }
+  else
+    {
+      return 0;
+    }
+}
+
+int
+indexFieldCount (const DatabaseInfo database)
+{
+  IndexDesc indexDesc = getIndexDesc (database);
+  if (indexDesc != NULL)
+    {
+      return indexDesc->numberOfFields;
+    }
+  else
+    {
+      return 0;
+    }
+}
Index: ds-file/index.h
===================================================================
RCS file: ds-file/index.h
diff -N ds-file/index.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ ds-file/index.h	19 Apr 2005 00:49:20 -0000
@@ -0,0 +1,75 @@
+#ifndef _INDEX_H_
+#define _INDEX_H_
+
+typedef struct indexEntry Index;
+typedef struct index_desc *IndexDesc;
+
+#include "gnats.h"
+#include "database.h"
+#include "field.h"
+#include "pr.h"
+#include "ds-file.h"
+
+extern void initIndex (DatabaseInfo database);
+
+extern IndexDesc newIndexDesc (const DatabaseInfo database);
+extern void addFieldToIndex (IndexDesc desc, FieldList ent);
+extern void setIndexDescPath (IndexDesc desc, const char *path);
+extern void setIndexDescSeparator (IndexDesc desc, const char *separator);
+extern void setIndexDescBinary (IndexDesc desc, int flag);
+
+extern void allocIndex (FilePRInfo info);
+
+extern void clearPRChain (const DatabaseInfo database);
+extern int checkPRChain (const DatabaseInfo database, ErrorDesc *err);
+
+extern PR *getFirstPR (const DatabaseInfo database, ErrorDesc *err);
+
+extern PR *getPrevPR (PR *pr);
+extern PR *getNextPR (PR *pr);
+extern void setPrevPR (PR *pr, PR *prev_pr);
+extern void setNextPR (PR *pr, PR *next_pr);
+
+extern void freePRIndex (DatabaseInfo database, FilePRInfo info);
+
+/* Construct an index entry for PR.  */
+extern void buildIndexEntry (PR *pr);
+
+/* Return a non-zero value if FIELD is part of the index for its database. */
+extern int isIndexedFieldIndex (FieldIndex field);
+extern int isIndexedField (ComplexFieldIndex field);
+
+/* Write out the database's PR index from the copy in memory.  Returns 0 on
+   success, a non-zero value otherwise.  */
+extern int writeIndex (const DatabaseInfo database, ErrorDesc *err);
+/* Add the PR to the GNATS index file for its database.  Returns
+   0 on success, a non-zero value otherwise.  */
+extern int addToIndex (PR *pr, ErrorDesc *err);
+
+/* Create a new string representing the index entry for PR in DEST.  */
+extern char *createIndexEntry (PR *pr, size_t *entLen);
+extern char *createIndexEntryBinary (PR *pr, size_t *entLen);
+
+extern int replaceCurrentPRInIndex (PR *curr_pr, PR *newPR, ErrorDesc *err);
+
+extern char *indexValue (PR *pr, FieldIndex field);
+
+extern char *getCategoryFromIndex (const DatabaseInfo database,
+				   const char *prnum, ErrorDesc *err);
+
+extern int removePRFromIndex (const DatabaseInfo database, const char *prNum,
+			      ErrorDesc *err);
+
+extern void finishIndexDesc (const DatabaseInfo database, IndexDesc new);
+
+extern void setIndexDesc (DatabaseInfo database, const IndexDesc desc);
+extern IndexDesc getIndexDesc (const DatabaseInfo database);
+extern void freeIndexDesc (IndexDesc desc);
+
+extern int indexIsBinary (const DatabaseInfo database);
+
+extern int indexFieldCount (const DatabaseInfo database);
+
+extern int verifyPRExists (const DatabaseInfo database, const char *prID);
+#endif
+
Index: ds-file/pr.c
===================================================================
RCS file: ds-file/pr.c
diff -N ds-file/pr.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ ds-file/pr.c	19 Apr 2005 00:49:20 -0000
@@ -0,0 +1,598 @@
+/* Flat-file datastore API - functions operating on a single PR.
+   Copyright (C) 2005 Free Software Foundation, Inc.
+   Contributed by Mel Hatzis <hatzis@wattes.org>.
+
+This file is part of GNU GNATS.
+
+GNU GNATS is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+GNU GNATS is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License along
+with GNU GNATS; see the file COPYING. if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA  */
+
+#include "ds.h"
+#include "pcodes.h"
+#include "ds-file.h"
+
+/* Get the PR at PATH.  PR is the PR entry to be filled in.
+   If PRUNE is non-zero, don't read any multitext fields.  */
+static int
+get_pr (PR *pr, const char *path, int prune)
+{
+  FILE *fp = fopen (path, "r");
+
+  if (fp == (FILE *)NULL)
+    {
+      return 0;
+    }
+
+  if (read_header (pr, fp) < 0)
+    {
+      return 0;
+    }
+
+  read_pr (pr, fp, prune);
+
+  fclose (fp);
+
+  return 1;
+}
+
+static PR *
+get_pr_from_index (const DatabaseInfo database, const char *prnum,
+		   ErrorDesc *err)
+{
+  PR *pr = getFirstPR (database, err);
+
+  /* If they gave it to us with the category, remove it. */
+  if (( strrchr (prnum, '/')) != NULL)
+    {
+      prnum = strrchr (prnum, '/') + 1;
+    }
+
+  while (pr != NULL && strcmp (prnum, field_value (pr, NUMBER (database))) != 0)
+    {
+      pr = getNextPR (pr);
+    }
+
+  if (pr == NULL)
+    {
+      setError (err, CODE_NONEXISTENT_PR,
+	        "No PR %s listed in the index.", prnum);
+      return NULL;
+    }
+
+  return pr;
+}
+
+static bool
+pr_file_readable (const char *path, ErrorDesc *err)
+{
+  FILE *fp;
+
+  if (path == NULL)
+    {
+      return FALSE;
+    }
+
+  if ((fp = fopen (path, "r")) == NULL)
+    {
+      setError (err, CODE_FILE_ERROR, "Can't open file `%s'", path);
+      return FALSE;
+    }
+
+  fclose (fp);
+  return TRUE;
+}
+
+static char *
+get_pr_path (const DatabaseInfo database, PR *pr, const char *prnum,
+	    ErrorDesc *err)
+{
+  char *path = NULL;
+  const char *category = NULL;
+  char *categoryFromIndex = NULL;
+
+  if (pr != NULL)
+    {
+      category = field_value (pr, CATEGORY (database));
+    }
+  else
+    {
+      categoryFromIndex = getCategoryFromIndex (database, prnum, err);
+      category = categoryFromIndex;
+    }
+
+  if (category != NULL)
+    {
+      asprintf (&path, "%s/%s/%s", databaseDir (database), category, prnum);
+      if (categoryFromIndex != NULL)
+	{
+	  free (categoryFromIndex);
+	}
+    }
+
+  return path;
+}
+
+/* Return the next available unique GNATS id.  */
+static int
+getBugNumber (const DatabaseInfo database, ErrorDesc *err)
+{
+  char *sbuf;
+  int bug_number;
+  FILE *bug_file;
+
+  /* First try to find and lock the gnats lock file.  We need this since
+     they want every bug to have a unique number.  If lock doesn't exist,
+     make it, if possible.  */
+  sbuf = gnats_adm_dir (database, "current");
+
+  block_signals ();
+
+  bug_file = fopen (sbuf, "r+");
+  if (bug_file == (FILE *) NULL)
+    {
+      log_msg (LOG_INFO, 1, "file 'current' not found, creating", sbuf);
+      bug_file = fopen (sbuf, "w+");
+      if (bug_file != (FILE *) NULL)
+	{
+	  bug_number = 1;
+	}
+      else
+	{
+	  setError (err, CODE_FILE_ERROR,
+		    "Can't create the GNATS 'current' file (%s).",
+		    sbuf);
+	  return -1;
+	}
+    }
+  else
+    {
+      if (fscanf (bug_file, "%d", &bug_number) != 1)
+	{
+	  setError (err, CODE_FILE_ERROR,
+		    "Can't read from the global PR number counter (%s).",
+		    sbuf);
+	  return -1;
+	}
+
+      bug_number++;      
+      rewind (bug_file);
+    }
+
+  fprintf (bug_file, "%d", bug_number);
+
+  fclose (bug_file);
+  unblock_signals ();
+  free (sbuf);
+
+  return bug_number;
+}
+
+void
+pr_init(PR *pr)
+{
+  FilePRInfo pi = (FilePRInfo) xmalloc (sizeof (struct file_pr_info));
+  pr->ds_private = pi;
+  allocIndex (pi);
+}
+
+void
+pr_destroy(PR *pr)
+{
+
+  FilePRInfo pi = (FilePRInfo) pr->ds_private;
+  if (pi != NULL)
+    {
+      if (pi->index != NULL)
+        {
+          freePRIndex (pr->database, pi);
+          free (pi->index);
+        }
+      free (pi);
+      pr->ds_private = NULL;
+    }
+}
+
+/* Load a PR containing only indexed field data with it's entire data set. */
+int
+pr_load (PR *pr, ErrorDesc *err)
+{
+  char *path = gen_pr_path (pr);
+  const char *num = field_value (pr, NUMBER (pr->database));
+  int val;
+
+  if (num == NULL || path == NULL)
+    {
+      setError (err, CODE_ERROR, "Invalid PR in pr_load()");
+      if (path != NULL)
+	{
+	  free (path);
+	}
+      return -1;
+    }
+  else
+    {
+      val = get_pr (pr, path, 0);
+
+      free (path);
+      if (val == 0)
+	{
+	  setError (err, CODE_FILE_ERROR, "Unable to read PR %s", num);
+	  return -1;
+	}
+      else
+	{
+	  return 0;
+	}
+    }
+}
+
+/* Read a file into a new PR data structure. */
+PR *
+pr_load_by_id (const DatabaseInfo database, const char *prnum,
+	       int prune, ErrorDesc *err)
+{
+  PR *pr = NULL;
+  PR *index_pr = get_pr_from_index (database, prnum, err);
+  char *path = get_pr_path (database, index_pr, prnum, err);
+
+  if (path != NULL)
+    {
+      if (pr_file_readable (path, err))
+        {
+	  pr = allocPR (database);
+	  setPrevPR (pr, getPrevPR (index_pr));
+	  setNextPR (pr, getNextPR (index_pr));
+	  if (get_pr (pr, path, prune) == 0)
+	    {
+	      free_pr (pr);
+	      pr = NULL;
+	    }
+        }
+      free (path);
+    }
+  return pr;
+}
+
+int
+pr_load_fields (PR *pr, FieldList flds)
+{
+  int do_open_pr = 0;
+
+  if (! PR_IS_FULL (pr))
+    {
+      if (flds == NULL)
+        {
+          do_open_pr = 1;
+        }
+      else
+        {
+          while (flds != NULL)
+            {
+              if (! isIndexedField (flds->ent))
+                {
+                  do_open_pr = 1;
+                  break;
+                }
+              flds = flds->next;
+            }
+        }
+    }
+
+  if (do_open_pr)
+    {
+      ErrorDesc err;
+
+      if (pr_load (pr, &err))
+        {
+          return -1;
+        }
+    }
+  return 1;
+}
+
+int
+pr_exists (const DatabaseInfo database, const char *prnum, ErrorDesc *err)
+{
+  PR *pr = get_pr_from_index (database, prnum, err);
+  char *path = get_pr_path (database, pr, prnum, err);
+  int res = 0;
+  if (path != NULL)
+    {
+      res = fileExists (path);
+      free (path);
+      if (res == 0)
+	{
+	  setError (err, CODE_FILE_ERROR, "Can't open file `%s'", path);
+	}
+    }
+  return res;
+}
+
+/* Write a file out to FILENAME with PR in it. */
+int
+pr_create (PR *pr, ErrorDesc *err)
+{
+  int errnum = 0;
+  int bug_number = 0;
+  char *path = NULL;
+  char *prPath = NULL;
+  char *bug_name = NULL;
+  char number[14]; /* No more than 13 digits in a PR #, ha ha.  */
+  time_t seconds  = time ((time_t) 0);
+  char arrival_time[GNATS_TIME_LENGTH];
+  const DatabaseInfo database = pr->database;
+  const char *category = field_value (pr, CATEGORY (database));
+  int flag_autocreate = createCategoryDirs (database);
+
+  /* Set arrival date and time of response.  */
+  gnats_strftime (arrival_time, GNATS_TIME_LENGTH, "%a %b %d %H:%M:%S %z %Y",
+		  localtime (&seconds));
+  set_field (pr, ARRIVAL_DATE (database), arrival_time, err);
+
+  /* Put together the path to where the bug will be stored.  If the dir
+     is not there, and the category is the default, auto-create that one,
+     if we want to.  If not, make the bug pending, and store in there.  */
+
+  asprintf (&path, "%s/%s", databaseDir (database), category);
+
+  errnum = fileExists (path) ? 0 : -1;
+
+  if (errnum != 0 && ! flag_autocreate)
+    {
+      /* XXX ??? !!! Should append a message to the email being sent out. */
+      category = defaultCategory(database);
+      set_field (pr, CATEGORY (database), defaultCategory(database), err);
+      log_msg (LOG_INFO, 1, "directory does not exist, changing to default:",
+	       path);
+      free (path);
+      asprintf (&path, "%s/%s", databaseDir (database), category);
+      errnum = fileExists (path) ? 0 : -1;
+    }
+
+  /* Check ERR again, to see if default category was there.  */
+  if (errnum != 0)
+    {
+      mode_t mode = categoryDirPerms (database);
+
+      if (strcmp (category, defaultCategory(database)) == 0)
+	{
+	  log_msg (LOG_INFO, 1, defaultCategory(database),
+		   " does not exist, creating...");
+	  if (mkdir (path, mode) != 0)
+	    {
+	      setError (err, CODE_FILE_ERROR,
+			"Can't create `%s' directory under %s.",
+			defaultCategory(database), databaseDir (database));
+	      free (path);
+	      return -1;
+	    }
+	}
+      else if (flag_autocreate)
+	{
+	  if (mkdir (path, mode) != 0)
+	    {
+	      setError (err, CODE_FILE_ERROR,
+			"Cannot create group: %s", path);
+	      free (path);
+	      return -1;
+	    }
+	  else
+	    {
+	      log_msg (LOG_INFO, 1, "creating directory:", path);
+	    }
+	}
+    }
+
+  free (path);
+
+  bug_number = getBugNumber (database, err);
+  if (bug_number > 0)
+    {
+      sprintf (number, "%d", bug_number);
+      set_field (pr, NUMBER (database), number, err);
+
+      /* Write the file out.  */
+      asprintf (&bug_name, "%s/%d", category, bug_number);
+      asprintf (&prPath, "%s/%s", databaseDir (database), bug_name);
+      if (createPrFile (pr, prPath, 1, err) != 0)
+	{
+	  bug_number = -1;
+	}
+      else
+	{
+	  log_msg (LOG_INFO, 1, "PR written out:", prPath);
+
+	  /* Add a line into the index for use by query-pr.  */
+	  /* This should be done regardless of whether the bug is default or
+	     not. */
+	  if (addToIndex (pr, err) != 0)
+	    {
+	      bug_number = -1;
+	    }
+	}
+    }
+  if (prPath != NULL)
+    {
+      free (prPath);
+    }
+  if (bug_name != NULL)
+    {
+      free (bug_name);
+    }
+
+  return bug_number;
+}
+
+int
+pr_update (PR *curr_pr, PR *new_pr, ErrorDesc *err)
+{
+  char *new_path =  NULL;
+  char *old_path =  NULL;
+  char *bkup_path = NULL;
+  FILE *prfile;
+  const DatabaseInfo database = new_pr->database;
+
+  /* check to see if the category changes, and if so, make sure
+   * the new category dir exists */
+  if (strcmp (field_value (curr_pr, CATEGORY (database)),
+	      field_value (new_pr, CATEGORY (database))) != 0)
+    {
+      char *dirPath;
+
+      asprintf (&dirPath, "%s/%s", databaseDir (database), 
+		field_value (new_pr, CATEGORY (database)));
+      if (! fileExists (dirPath))
+	{
+	  if (createCategoryDirs (database))
+	    {
+	      mode_t mode = categoryDirPerms (database);
+
+	      if (mkdir (dirPath, mode) != 0)
+		{
+		  setError (err, CODE_FILE_ERROR, 
+			    "Error creating directory for category %s: %s",
+			    field_value (new_pr, CATEGORY (database)),
+			    strerror (errno));
+                  free (dirPath);
+                  return 0;
+		}
+	    }
+	  else
+	    {
+	      setError (err, CODE_FILE_ERROR, 
+			"Directory for category %s does not exist",
+			field_value (new_pr, CATEGORY (database)));
+              free (dirPath);
+              return 0;
+	    }
+	}
+    }
+
+  /* backup the current PR file */
+  old_path = gen_pr_path (curr_pr);
+  asprintf (&bkup_path, "%s.old", old_path);
+  if (rename (old_path, bkup_path) < 0)
+    {
+      if (errno != EXDEV)
+	{
+	  setError (err, CODE_FILE_ERROR, "Could not rename %s: %s",
+		    old_path, strerror (errno));
+          free (bkup_path);
+          free (old_path);
+          return 0;
+	}
+      if (copy_file (old_path, bkup_path) != 0)
+	{
+	  setError (err, CODE_FILE_ERROR, "Could not copy %s to %s: %s",
+		    old_path, bkup_path, strerror (errno));
+          free (bkup_path);
+          free (old_path);
+          return 0;
+	}
+
+      /* Don't complain if this fails, since trying to write to it will give
+	 us the diagnostic if it's really serious.  */
+      unlink (old_path);
+    }
+
+  /* Now build the new PR file.  */
+  new_path = gen_pr_path (new_pr);
+  prfile = fopen (new_path, "w+");
+  if (prfile == (FILE *) NULL)
+    {
+      setError (err, CODE_FILE_ERROR, "Cannot write the PR to %s: %s",
+		new_path, strerror (errno));
+      free (bkup_path);
+      free (old_path);
+      free (new_path);
+      return 0;
+    }
+
+  /* We had to wait until now because we wanted to wait and see
+     about changing the closed_date. XXX ??? !!! FIXME */
+
+  buildIndexEntry (new_pr);
+
+  write_entire_header (prfile, new_pr, "\n");
+  fprintf (prfile, "\n");
+  write_entire_pr (prfile, new_pr, "\n");
+
+  if (fclose (prfile) == EOF)
+    {
+      setError (err, CODE_FILE_ERROR, "Error writing out PR %s: %s",
+		new_path, strerror (errno));
+      free (bkup_path);
+      free (old_path);
+      free (new_path);
+      return 0;
+    }
+
+  unlink (bkup_path);
+
+  /* unlink the old file, if it is in another category dir. */
+  if (strcmp (field_value (curr_pr, CATEGORY (database)),
+	      field_value (new_pr, CATEGORY (database))) != 0)
+    {
+      unlink (old_path);
+    }
+
+  free (bkup_path);
+  free (old_path);
+  free (new_path);
+
+  /* write out the new index.  */
+  replaceCurrentPRInIndex (curr_pr, new_pr, err);
+  if (writeIndex (database, err) != 0)
+    {
+      return 0;
+    }
+
+  return 1;
+}
+
+int
+pr_delete (const DatabaseInfo database, const char *prnum, ErrorDesc *err)
+{
+  PR *pr;
+  char *path = NULL;
+
+  pr = get_pr_from_index (database, prnum, err);
+  path = get_pr_path (database, pr, prnum, err);
+
+  if (path == NULL || !pr_file_readable (path, err))
+    {
+      if (path != NULL)
+	{
+	  free (path);
+	}
+      return -1;
+    }
+
+  if (removePRFromIndex (database, prnum, err))
+    {
+      free (path);
+      return -5;
+    }
+  
+  if (unlink (path))
+    {
+      setError (err, CODE_FILE_ERROR, "Unable to unlink file %s\n", path);
+      free (path);
+      return -6;
+    }
+
+  free (path);
+  return 1;
+}
+
Index: ds-file/t-fconfig
===================================================================
RCS file: ds-file/t-fconfig
diff -N ds-file/t-fconfig
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ ds-file/t-fconfig	19 Apr 2005 00:49:20 -0000
@@ -0,0 +1,50 @@
+
+/* The flat-file datastore configuration options... */
+
+dsDescription   : indexDescription
+		;
+
+indexDescription: indexheader '{' indexlist '}' {
+		    setIndexDesc (databaseBeingDefined, indexEntry);
+		    indexEntry = NULL;
+		}
+		| indexheader '{' parseError '}' {
+		    freeIndexDesc (indexEntry);
+		    indexEntry = NULL;
+		}
+		| parseError
+		;
+
+indexheader	: INDEXTOK {
+		    indexEntry = newIndexDesc (databaseBeingDefined);
+		}
+		;
+
+indexlist	: indexEnt
+		| indexlist indexEnt
+		;
+
+indexEnt	: PATHTOK QSTRING {
+		    setIndexDescPath (indexEntry, qStrVal ($2));
+		}
+		| FIELDSTOK '{' indexFieldList '}'
+		| FIELDSTOK '{' parseError '}'
+		| indexSep
+		;
+
+indexFieldList	: FieldListMember {
+		    addFieldToIndex (indexEntry, $1);
+		}
+		| indexFieldList FieldListMember {
+		    addFieldToIndex (indexEntry, $2);
+		}
+		;
+
+indexSep	: SEPARATORTOK QSTRING {
+		    setIndexDescSeparator (indexEntry, takeQString ($2));
+		}
+		| BINARYINDEXTOK booleanVal {
+		    setIndexDescBinary (indexEntry, $2);
+		}
+		;
+

[-- Attachment #3: Type: text/plain, Size: 140 bytes --]

_______________________________________________
Help-gnats mailing list
Help-gnats@gnu.org
http://lists.gnu.org/mailman/listinfo/help-gnats

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: patch #2 - towards a generic backend datastore
  2005-04-19  1:11 patch #2 - towards a generic backend datastore Mel Hatzis
@ 2005-04-21 23:52 ` Mel Hatzis
  2005-04-22 21:04   ` Chad Walstrom
  0 siblings, 1 reply; 3+ messages in thread
From: Mel Hatzis @ 2005-04-21 23:52 UTC (permalink / raw)
  To: Chad Walstrom; +Cc: help-gnats

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

I have done some additional testing with my proposed patch and
everything appears to be in order.

*I plan on committing these changes to the mainline tomorrow.*

Note that there is a minor change introduced with this patch.
If the index is reloaded when gnatsd processes an 'rset'
command, gnatsd currently generates the following message:

     210 Reset state...reloaded the index.

After this patch is committed, the following message will
be issued (which is the same message that is generated
currently, whenever the index is not reloaded):

     210 Reset state.

This change is required in order to make the client/server
interaction datastore agnostic. Thankfully, the rset command
was the only one which referred to the index - and thus
needed to be modified.

- --
Mel Hatzis

Mel Hatzis wrote:
> Please review the attached patch which contains a number of
> modifications intended to restructure the code to support a
> generalized backend datastore.
> 
> This second patch is rather large - larger than I originally
> planned. I expect that all subsequent patches will be
> significantly smaller.
> 
> The primary goal in this patch was to completely isolate the
> index from the generic (i.e. non-datastore specific) GNATS
> functionality and introduce a flat-file datastore library with
> which all relevant binaries are linked. A small addition to
> configure was made to build the flat-file datastore library
> by default.
> 
> This is a significant leap forward in generalizing the backend
> datastore. The GNATS/datastore interface really starts to become
> more obvious with this patch.
> 
> I have attempted to adequately describe all changes in detail
> in the ChangeLog. Feel free to ask me to elaborate where
> necessary.
> 
> Once again, I've run these changes through my regression test
> suite and they appear to work well. Of course, I encourage
> more testing if people have the bandwidth.
> 
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.2 (Darwin)
Comment: Using GnuPG with Thunderbird - http://enigmail.mozdev.org

iD8DBQFCaDtKNF74HmYqaSERAqqaAJ4sKH+JurajgHwaTh6rJQDHx7yOZgCglfKh
afJu2EuD78wcvn26kWb4hnA=
=xffG
-----END PGP SIGNATURE-----


_______________________________________________
Help-gnats mailing list
Help-gnats@gnu.org
http://lists.gnu.org/mailman/listinfo/help-gnats

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: patch #2 - towards a generic backend datastore
  2005-04-21 23:52 ` Mel Hatzis
@ 2005-04-22 21:04   ` Chad Walstrom
  0 siblings, 0 replies; 3+ messages in thread
From: Chad Walstrom @ 2005-04-22 21:04 UTC (permalink / raw)
  To: help-gnats


[-- Attachment #1.1: Type: text/plain, Size: 1159 bytes --]

On Thu, Apr 21, 2005 at 04:46:18PM -0700, Mel Hatzis wrote:
> I have done some additional testing with my proposed patch and
> everything appears to be in order.

Excellent.  I haven't had much of a chance to test things out myself;
I've been a bit sick lately (flu).

> *I plan on committing these changes to the mainline tomorrow.*

Sounds good.

> Note that there is a minor change introduced with this patch.
> If the index is reloaded when gnatsd processes an 'rset'
> command, gnatsd currently generates the following message:
> 
>      210 Reset state...reloaded the index.
> 
> After this patch is committed, the following message will
> be issued (which is the same message that is generated
> currently, whenever the index is not reloaded):
> 
>      210 Reset state.
> 
> This change is required in order to make the client/server
> interaction datastore agnostic. Thankfully, the rset command
> was the only one which referred to the index - and thus
> needed to be modified.

Cool.

-- 
Chad Walstrom <chewie@wookimus.net>           http://www.wookimus.net/
           assert(expired(knowledge)); /* core dump */

[-- Attachment #1.2: Digital signature --]
[-- Type: application/pgp-signature, Size: 189 bytes --]

[-- Attachment #2: Type: text/plain, Size: 140 bytes --]

_______________________________________________
Help-gnats mailing list
Help-gnats@gnu.org
http://lists.gnu.org/mailman/listinfo/help-gnats

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2005-04-22 21:04 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2005-04-19  1:11 patch #2 - towards a generic backend datastore Mel Hatzis
2005-04-21 23:52 ` Mel Hatzis
2005-04-22 21:04   ` Chad Walstrom

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 read-only IMAP folder(s) and NNTP newsgroup(s).