Index: /tags/ipp-1-X/rel-1_1_19/dbconfig/.cvsignore
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/dbconfig/.cvsignore	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/dbconfig/.cvsignore	(revision 22073)
@@ -0,0 +1,2 @@
+ippdb.m4
+ippdb.mdc
Index: /tags/ipp-1-X/rel-1_1_19/dbconfig/Makefile
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/dbconfig/Makefile	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/dbconfig/Makefile	(revision 22073)
@@ -0,0 +1,26 @@
+SHELL=/bin/sh
+GLUEFORGE=`which glueforge`
+
+all: ippdb.mdc
+
+ippdb.mdc : ipp.m4 *.md
+	m4 ipp.m4 > ippdb.mdc
+
+## please leave the output target as ippdb.src and
+## move to ippdb by hand for cvs import
+install: ippdb.mdc
+	rm -rf ../ippdb.src
+	$(GLUEFORGE) -i ippdb.mdc --output ../ippdb.src
+	chmod +x ../ippdb.src/autogen.sh
+
+src: ippdb.mdc
+	$(GLUEFORGE) -i ippdb.mdc --output ../ippdb
+	chmod +x ../ippdb/autogen.sh
+
+foo: src
+	$(MAKE) -C ../ippdb install
+
+build : ippdb ippdb.mdc
+
+clean:
+	@rm -f *~
Index: /tags/ipp-1-X/rel-1_1_19/dbconfig/cam.md
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/dbconfig/cam.md	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/dbconfig/cam.md	(revision 22073)
@@ -0,0 +1,37 @@
+# imfiles is not here... is it needed?
+
+camPendingExp METADATA
+    cam_id      S64         0       # Primary Key AUTO_INCREMENT
+    chip_id     S64         0       # Primary Key fkey(chip_id) ref chipProcessedExp(chip_id)
+    workdir     STR         255 
+    label       STR         64      # key
+    recipe      STR         64
+    expgroup    STR         64      # key
+    dvodb       STR         255
+END
+
+camProcessedExp METADATA
+# the camPendingExp row gets deleted so we can not put a fkey on cam_id
+    cam_id      S64         0       # Primary Key
+    chip_id     S64         0       # Primary Key fkey(chip_id) ref chipProcessedExp(chip_id)
+    workdir     STR         255 
+    label       STR         64      # key
+    recipe      STR         64
+    expgroup    STR         64      # key
+    dvodb       STR         255
+    uri         STR         255
+    bg          F64         0.0
+    bg_stdev    F64         0.0
+    bg_mean_stdev   F64     0.0
+    sigma_ra    F32         0.0
+    sigma_dec   F32         0.0
+    nastro      S32         0
+    path_base   STR         255
+    zp_mean     F32         0.0
+    zp_stdev    F32         0.0
+    fault       S16         0       # Key NOT NULL
+END
+
+camMask METADATA
+    label       STR         64      # Primary Key
+END
Index: /tags/ipp-1-X/rel-1_1_19/dbconfig/chip.md
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/dbconfig/chip.md	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/dbconfig/chip.md	(revision 22073)
@@ -0,0 +1,57 @@
+# do we need nclass?
+# class is missing
+
+chipPendingExp METADATA
+    chip_id     S64         0       # Primary Key AUTO_INCREMENT
+    exp_tag     STR         64      # Primary Key
+    guide_id    S64         0       # Not NULL
+    workdir     STR         255 
+    label       STR         64      # key
+    recipe      STR         64
+    expgroup    STR         64      # key
+    dvodb       STR         255
+END
+
+# uris in & out ?
+
+chipPendingImfile METADATA
+    chip_id     S64         0       # Primary Key
+    class_id    STR         64      # Primary Key
+    uri         STR         255
+END
+
+# nclass & ndone?
+# and/or logging imfile done better?
+
+chipProcessedExp METADATA
+    chip_id     S64         0       # Primary Key
+    exp_tag     STR         64      # Primary Key
+    guide_id    S64         0       # Not NULL
+    workdir     STR         255 
+    label       STR         64      # key
+    recipe      STR         64
+    expgroup    STR         64      # key
+    dvodb       STR         255
+END
+
+chipMask METADATA
+    label       STR         64      # Primary Key
+END
+
+# uris in & out ?
+# state?
+
+chipProcessedImfile METADATA
+    chip_id     S64         0       # Primary Key
+# exp_tag/guide_id are redudant with the values stored in chipProcessedExp.
+# exp_tag can be removed at virtually any time (SQL queriers will need to be
+# fixed) but guide_id needs to wait until we know if this can be tracked at
+# just the exp level.
+    class_id    STR         64      # Primary Key
+    uri         STR         255
+    bg          F64         0.0
+    bg_stdev    F64         0.0
+    bg_mean_stdev   F64     0.0
+    path_base   STR         255
+    fault       S16         0       # Key NOT NULL
+END
Index: /tags/ipp-1-X/rel-1_1_19/dbconfig/config.md
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/dbconfig/config.md	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/dbconfig/config.md	(revision 22073)
@@ -0,0 +1,5 @@
+glueforge METADATA
+    pkg_name        STR     ippdb
+    pkg_namespace   STR     ippdb
+    pkg_version     STR     1.1.19
+END
Index: /tags/ipp-1-X/rel-1_1_19/dbconfig/det.md
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/dbconfig/det.md	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/dbconfig/det.md	(revision 22073)
@@ -0,0 +1,155 @@
+detRun METADATA
+    det_id      S64         0       # Primary Key AUTO_INCREMENT
+    iteration   S32         0       # Key
+    det_type    STR         64      # Key
+    mode        STR         64      # Key
+    state       STR         64      # Key
+    filelevel   STR         64
+    workdir     STR         255     # destination for output files
+    camera      STR         64
+    telescope   STR         64
+    exp_type    STR         64      # XXX this should be dropped
+    filter      STR         64
+    airmass_min F32         0.0
+    airmass_max F32         0.0
+    exp_time_min F32        0.0
+    exp_time_max F32        0.0
+    ccd_temp_min F32        0.0
+    ccd_temp_max F32        0.0
+    posang_min  F64         0.0 
+    posang_max  F64         0.0 
+    registered  TAI         0001-01-01T00:00:00Z
+    time_begin  TAI         0001-01-01T00:00:00Z
+    time_end    TAI         0001-01-01T00:00:00Z
+    use_begin   TAI         0001-01-01T00:00:00Z
+    use_end     TAI         0001-01-01T00:00:00Z
+    solang_min  F32         0.0
+    solang_max  F32         0.0
+    label       STR         64      # key
+    parent      S32         0       # Key
+END
+
+detInputExp METADATA
+    det_id      S64         0       # Primary Key
+    iteration   S32         0       # Primary Key
+    exp_tag     STR         64      # Primary Key
+    include     BOOL        f
+END
+
+detProcessedImfile METADATA
+    det_id      S64         0      # Primary Key
+    exp_tag     STR         64     # Primary Key
+    class_id    STR         64     # Primary Key
+    uri         STR         255
+    recipe      STR         64
+    bg          F64         0.0
+    bg_stdev    F64         0.0
+    bg_mean_stdev   F64     0.0
+    path_base   STR         255
+    fault       S16         0       # Key NOT NULL
+END
+
+detProcessedExp METADATA
+    det_id      S64         0      # Primary Key
+    exp_tag     STR         64     # Primary Key
+    recipe      STR         64
+    bg          F64         0.0
+    bg_stdev    F64         0.0
+    bg_mean_stdev   F64     0.0
+    path_base   STR         255
+    fault       S16         0       # Key NOT NULL
+END
+
+detStackedImfile METADATA
+    det_id      S64         0       # Primary Key
+    iteration   S32         0       # Primary Key
+    class_id    STR         64      # Primary Key
+    uri         STR         255
+    recipe      STR         64
+    bg          F64         0.0
+    bg_stdev    F64         0.0
+    bg_mean_stdev   F64     0.0
+    fault       S16         0       # Key NOT NULL
+END
+
+detNormalizedStatImfile METADATA
+    det_id      S64         0       # Primary Key
+    iteration   S32         0       # Primary Key
+    class_id    STR         64      # Primary Key
+    norm        F32         0.0
+    fault       S16         0       # Key NOT NULL
+END
+
+detNormalizedImfile METADATA
+    det_id      S64         0       # Primary Key
+    iteration   S32         0       # Primary Key
+    class_id    STR         64      # Primary Key
+    uri         STR         255
+    bg          F64         0.0
+    bg_stdev    F64         0.0
+    bg_mean_stdev   F64     0.0
+    path_base   STR         255
+    fault       S16         0       # Key NOT NULL
+END
+
+detNormalizedExp METADATA
+    det_id      S64         0       # Primary Key
+    iteration   S32         0       # Primary Key
+    recipe      STR         64
+    bg          F64         0.0
+    bg_stdev    F64         0.0
+    bg_mean_stdev   F64     0.0
+    path_base   STR         255
+    fault       S16         0       # Key NOT NULL
+END
+
+#detMasterFrame METADATA
+#    det_id      S64         0       # Primary Key
+#    iteration   S32         0       # Primary Key
+#    comment     STR         255
+#END
+#
+## drop?
+#detMasterImfile METADATA
+#    det_id      S64         0       # Primary Key
+#    class_id    STR         64      # Primary Key
+#    uri         STR         255
+#    recipe      STR         64
+#END
+
+detResidImfile METADATA
+    det_id      S64         0       # Primary Key
+    iteration   S32         0       # Primary Key
+    exp_tag     STR         64      # Primary Key
+    class_id    STR         64      # Primary Key
+    uri         STR         255
+    recipe      STR         64
+    bg          F64         0.0
+    bg_stdev    F64         0.0
+    bg_mean_stdev   F64     0.0
+    path_base   STR         255
+    fault       S16         0       # Key NOT NULL
+END
+
+detResidExp METADATA
+    det_id      S64         0       # Primary Key
+    iteration   S32         0       # Primary Key
+    exp_tag     STR         64      # Primary Key
+    recipe      STR         64
+    bg          F64         0.0
+    bg_stdev    F64         0.0
+    bg_mean_stdev   F64     0.0
+    path_base   STR         255
+    accept      BOOL        f
+    fault       S16         0       # Key NOT NULL
+END
+
+detRunSummary METADATA
+    det_id      S64         0       # Primary Key
+    iteration   S32         0       # Primary Key
+    bg          F64         0.0
+    bg_stdev    F64         0.0
+    bg_mean_stdev   F64     0.0
+    accept      BOOL        f
+    fault       S16         0       # Key NOT NULL
+END
Index: /tags/ipp-1-X/rel-1_1_19/dbconfig/diff.md
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/dbconfig/diff.md	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/dbconfig/diff.md	(revision 22073)
@@ -0,0 +1,33 @@
+# $Id: diff.md,v 1.2 2007-03-22 03:13:04 jhoblitt Exp $
+
+diffRun METADATA
+    diff_id     S64         0       # Primary Key AUTO_INCREMENT
+    state       STR         64      # Key
+    workdir     STR         255
+    dvodb       STR         255
+    registered  TAI         NULL
+    skycell_id  STR         64      # Key
+    tess_id     STR         64      # Key
+END
+
+#
+# Diff Sky Cells Mode
+#
+
+# only ever 2 per run
+diffInputSkyfile METADATA
+    diff_id     S64         0       # Primary Key fkey(diff_id) ref diffRun(diff_id)
+    warp_id     S64         0       # Primary Key fkey(warp_id, skycell_id, tess_id) ref warpSkyfile(warp_id, skycell_id, tess_id)
+    skycell_id  STR         64      # Primary Key
+    tess_id     STR         64      # Primary Key
+# either a input or a template
+    kind        STR         64      # Key 
+    template    BOOL        f
+END
+
+diffSkyfile METADATA
+    diff_id     S64         0       # Primary Key fkey(diff_id) ref diffRun(diff_id)
+    uri         STR         255
+    bg          F64         0.0
+    bg_stdev   F64     0.0
+END
Index: /tags/ipp-1-X/rel-1_1_19/dbconfig/dimm.md
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/dbconfig/dimm.md	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/dbconfig/dimm.md	(revision 22073)
@@ -0,0 +1,21 @@
+#              Table 13: DIMM Measurements Table
+# Column Name Datatype Description
+# Time          date/time   The time the DIMM observation was taken.
+# sigmax        float Raw dispersion in x.
+# sigmay        float Raw dispersion in y.
+# FWHM          float  Dervied seeing full width at half maximum.
+# RA            float  The coordinates of the measured star.
+# DEC           float  The coordinates of the measured star.
+# Exposure time float  The exposure time of the DIMM observation.
+# Telescope ID  string source of the DIMM data
+
+dimm METADATA
+#    time        DATETIME    2006-01-11T00:00:00
+    sigmax      F32         0.0
+    sigmay      F32         0.0
+    fwhm        F32         0.0
+    ra          F64         0.0
+    decl        F64         0.0
+    expttime    F32         0.0
+    telescope_id    STR     255
+END
Index: /tags/ipp-1-X/rel-1_1_19/dbconfig/dome.md
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/dbconfig/dome.md	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/dbconfig/dome.md	(revision 22073)
@@ -0,0 +1,16 @@
+#                     Table 15: Dome Status Table
+# Column Name   Datatype Description
+# Time          date/time     The time for which the dome status is valid.
+# Azimuth       float         The azimuth of the dome.
+# Open status   boolean       Whether the dome is open or not.
+# Lights status boolean       Whether lights are on in the dome or not.
+# Track status  boolean       Whether dome is tracking telescope or not.
+
+dome METADATA
+#    time        DATETIME    2006-01-11T00:00:00
+    az          F32         0.0
+    open        BOOL        t
+    light       BOOL        t
+    # XXX is it possible for the dome slit to not track the telescope? ;)
+    track       BOOL        t
+END
Index: /tags/ipp-1-X/rel-1_1_19/dbconfig/guide.md
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/dbconfig/guide.md	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/dbconfig/guide.md	(revision 22073)
@@ -0,0 +1,5 @@
+guidePendingExp METADATA
+    guide_id    S64         0       # Primary Key AUTO_INCREMENT
+    exp_tag     STR         64      # Key
+    recipe      STR         64
+END
Index: /tags/ipp-1-X/rel-1_1_19/dbconfig/ipp.m4
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/dbconfig/ipp.m4	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/dbconfig/ipp.m4	(revision 22073)
@@ -0,0 +1,18 @@
+include(config.md)
+dnl include(weather.md)
+dnl include(skyp_transparency.md)
+dnl include(skyp_absorption.md)
+dnl include(skyp_emission.md)
+dnl include(dimm.md)
+dnl include(skyp_ir.md)
+dnl include(dome.md)
+dnl include(telescope.md)
+include(tasks.md)
+include(guide.md)
+include(chip.md)
+include(cam.md)
+include(warp.md)
+include(diff.md)
+include(stack.md)
+include(det.md)
+dnl include(skycell.md)
Index: /tags/ipp-1-X/rel-1_1_19/dbconfig/magic.md
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/dbconfig/magic.md	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/dbconfig/magic.md	(revision 22073)
@@ -0,0 +1,85 @@
+# $Id: magic.md,v 1.1 2007-02-15 02:12:42 jhoblitt Exp $
+
+#
+# Feed imfiles to Magic Mode
+#
+
+# also uses p4InputExp
+
+# output from magic
+p4MagicMaskImfile METADATA
+    p4_id       S32         0       # Primary Key
+    exp_tag     STR         64      # Primary Key
+    p3_version  S32         0       # Primary Key
+# class is not yet consistently carried through pXtools
+#    class       STR         64      # Primary Key
+    class_id    STR         64      # Primary Key
+    uri         STR         255
+END
+
+# output from magic
+# p4MagicMaskImfile METADATA
+#     p4_id       S32         0       # Primary Key
+#     exp_tag     STR         64      # Primary Key
+#     p3_version  S32         0       # Primary Key
+# # class is not yet consistently carried through pXtools
+# #    class       STR         64      # Primary Key
+#     class_id    STR         64      # Primary Key
+#     uri         STR         255
+# END
+# 
+# # the list of imfiles in our skycell keyed against phase 3
+# p4InputImfile  METADATA
+#     p4_id       S32         0       # Primary Key
+#     skycell_id  STR         64      # Primary Key
+#     tess_id     STR         64      # Primary Key
+#     exp_tag     STR         64      # Primary Key
+#     p3_version  S32         0       # Primary Key
+# # class is not yet consistently carried through pXtools
+# #    class       STR         64      # Primary Key
+#     class_id    STR         64      # Primary Key
+# # either a input or a template
+#     kind        STR         64      # Key 
+# END
+# 
+# 
+# # one diff image per input to the current stack
+# # XXX how will diff against the static sky be handled?
+# p4DiffImfile METADATA
+#     p4_id       S32         0       # Primary Key
+#     skycell_id  STR         64      # Primary Key
+#     tess_id     STR         64      # Primary Key
+#     exp_tag     STR         64      # Primary Key
+#     p3_version  S32         0       # Primary Key
+# # class is not yet consistently carried through pXtools
+# #    class       STR         64      # Primary Key
+#     class_id    STR         64      # Primary Key
+#     uri         STR         255
+#     bg          F64         0.0
+#     bg_mean_stdev   F64     0.0
+# END
+# 
+# p4StackedImfile METADATA
+#     p4_id       S32         0       # Primary Key
+#     skycell_id  STR         64      # Primary Key
+#     tess_id     STR         64      # Primary Key
+#     exp_tag     STR         64      # Primary Key
+#     p3_version  S32         0       # Primary Key
+# # class is not yet consistently carried through pXtools
+# #    class       STR         64      # Primary Key
+#     class_id    STR         64      # Primary Key
+#     uri         STR         255
+#     bg          F64         0.0
+#     bg_mean_stdev   F64     0.0
+# END
+# 
+# # output from magic
+# p4MagicMaskImfile METADATA
+#     p4_id       S32         0       # Primary Key
+#     exp_tag     STR         64      # Primary Key
+#     p3_version  S32         0       # Primary Key
+# # class is not yet consistently carried through pXtools
+# #    class       STR         64      # Primary Key
+#     class_id    STR         64      # Primary Key
+#     uri         STR         255
+# END
Index: /tags/ipp-1-X/rel-1_1_19/dbconfig/skycell.md
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/dbconfig/skycell.md	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/dbconfig/skycell.md	(revision 22073)
@@ -0,0 +1,25 @@
+# $Id: skycell.md,v 1.3 2007-02-13 22:33:30 jhoblitt Exp $
+
+skyCell METADATA
+    skycell_id  STR         64      # Primary Key
+    tess_id     STR         64      # Primary Key
+    ra1         F64         0.0
+    decl1       F64         0.0
+    ra2         F64         0.0
+    decl2       F64         0.0
+    ra3         F64         0.0
+    decl3       F64         0.0
+    ra4         F64         0.0
+    decl4       F64         0.0
+END
+
+skyCellMap METADATA
+    skycell_id  STR         64      # Primary Key
+    tess_id     STR         64      # Primary Key
+    exp_tag     STR         64      # Primary Key
+    p3_version  S32         0       # Primary Key
+# class is not yet consistently carried through pXtools
+#    class       STR         64      # Primary Key
+    class_id    STR         64      # Primary Key
+END
+
Index: /tags/ipp-1-X/rel-1_1_19/dbconfig/skyp_absorption.md
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/dbconfig/skyp_absorption.md	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/dbconfig/skyp_absorption.md	(revision 22073)
@@ -0,0 +1,25 @@
+#                Table 11: Skyprobe Line Absorption Table (sample entries)
+# Column Name         Datatype Description
+# Time                date/time   The time the LRProbe observation was taken.
+# Disperser ID        string      ID of the dispersing element
+# Atm Component 1 float           The strength of the 1st atmospheric component.
+# Atm Component 2 float           The strength of the 2nd atmospheric component.
+# Atm Component 3 float           The strength of the 3rd atmospheric component.
+# Disperser ID        string      ID of the dispersing element
+# Number of stars     int         Number of stars used to measure the absorptions.
+# Astrometry          coords      The astrometry used on the LRProbe image.
+# Exposure time       float       The exposure time of the LRProbe image.
+# Sky brightness      float       The measured sky (surface) brightness, in physical units.
+
+skyp_absorption METADATA
+#    time        DATETIME    2006-01-10T00:00:00
+    disperser_id    STR     255
+    atmcomp1    F32         0.0
+    atmcomp2    F32         0.0
+    atmcomp3    F32         0.0
+    nstars      S32         0
+    ra          F64         0.0
+    decl        F64         0.0
+    exptime     F32         0.0
+    sky_bright  F64         0.0
+END
Index: /tags/ipp-1-X/rel-1_1_19/dbconfig/skyp_emission.md
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/dbconfig/skyp_emission.md	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/dbconfig/skyp_emission.md	(revision 22073)
@@ -0,0 +1,20 @@
+#             Table 12: Skyprobe Line Emission Table (sample entries)
+# Column Name         Datatype Description
+# Time                date/time   The time the LRProbe observation was taken.
+# Disperser ID        string      ID of the dispersing element
+# Atm Component 1 float           The strength of the 1st atmospheric component.
+# Atm Component 2 float           The strength of the 2nd atmospheric component.
+# Atm Component 3 float           The strength of the 3rd atmospheric component.
+# Continuum           float       The strength of the continuum emission.
+# Disperser ID        string      ID of the dispersing element
+# Exposure time       float       The exposure time of the LRProbe image.
+
+skyp_emission METADATA
+#    time        DATETIME    2006-01-11T00:00:00
+    disperser_id    STR     255
+    atmcomp1    F32         0.0
+    atmcomp2    F32         0.0
+    atmcomp3    F32         0.0
+    continuum   F32         0.0
+    exptime     F32         0.0
+END
Index: /tags/ipp-1-X/rel-1_1_19/dbconfig/skyp_ir.md
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/dbconfig/skyp_ir.md	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/dbconfig/skyp_ir.md	(revision 22073)
@@ -0,0 +1,18 @@
+#               Table 14: Near IR Wide-field Camera Results Table
+# Column Name Datatype Description
+# Time            date/time    The time the NIR observation was taken.
+# Sky brightness float         The sky (surface) brightness in the NIR observation.
+# Sky variance    float        The variance in the sky (surface) brightness.
+# Astrometry      coords       The astrometry used on the NIR image.
+# FOV X           float        field width
+# FOV Y           float        field height
+
+skyp_ir METADATA
+#    time        DATETIME    2006-01-11T00:00:00
+    sky_bright  F64         0.0 
+    sky_var     F64         0.0 
+    ra          F64         0.0
+    decl        F64         0.0
+    fov_x       F32         0.0
+    fov_y       F32         0.0
+END
Index: /tags/ipp-1-X/rel-1_1_19/dbconfig/skyp_transparency.md
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/dbconfig/skyp_transparency.md	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/dbconfig/skyp_transparency.md	(revision 22073)
@@ -0,0 +1,21 @@
+#                Table 10: SkyProbe Transparency Table (sample entries)
+# Column Name Datatype Description
+# Time              date/time   The time the SkyProbe image was taken.
+# Filter            string      Filter used for SkyProbe image.
+# Transparency      float       The derived transparency.
+# Number of stars int           The number of stars used to measure the transparency.
+# Astrometry        coords      The astrometry used on the SkyProbe image.
+# Exposure time     float       The exposure time of the SkyProbe image.
+# Sky brightness    float       The measured sky (surface) brightness, counts / second
+
+skyp_transparency METADATA
+#    time        DATETIME    2006-01-10T00:00:00    # Primary Key
+    filter      STR         255
+    trans       F64         0.0
+    nstars      S32         0
+#    astrom
+    ra          F64         0.0
+    decl        F64         0.0
+    exptime     F32         0.0
+    sky_bright  F64         0.0
+END
Index: /tags/ipp-1-X/rel-1_1_19/dbconfig/stack.md
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/dbconfig/stack.md	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/dbconfig/stack.md	(revision 22073)
@@ -0,0 +1,23 @@
+# $Id: stack.md,v 1.2 2007-03-22 03:13:04 jhoblitt Exp $
+
+stackRun METADATA
+    stack_id    S64         0       # Primary Key AUTO_INCREMENT
+    state       STR         64      # Key
+    workdir     STR         255
+    dvodb       STR         255
+    registered  TAI         NULL
+    skycell_id  STR         64      # Key
+    tess_id     STR         64      # Key
+END
+
+stackInputSkyfile METADATA
+    stack_id    S64         0       # Primary Key fkey(stack_id) ref stackRun(stack_id)
+    warp_id     S64         0       # Primary Key fkey(warp_id) ref warpSkyfile(warp_id)
+END
+
+stackSumSkyfile METADATA
+    stack_id    S64         0       # Primary Key fkey(stack_id) ref stackRun(stack_id)
+    uri         STR         255
+    bg          F64         0.0
+    bg_stdev    F64         0.0
+END
Index: /tags/ipp-1-X/rel-1_1_19/dbconfig/tasks.md
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/dbconfig/tasks.md	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/dbconfig/tasks.md	(revision 22073)
@@ -0,0 +1,158 @@
+# $Id: tasks.md,v 1.132 2007-03-22 01:10:12 jhoblitt Exp $
+
+# this table records all exposure ID ever seen from the summit
+# exp_id == fileset
+
+# note that dec is a MySQL reserved word
+# note that use is a MySQL reserved word
+# note that exp is a MySQL reserved word
+
+# for use with this stored procedure to generate exp_tags under Mysql 5+
+#
+#CREATE FUNCTION genTag (exp_id varchar(64)) RETURNS VARCHAR(64)
+#BEGIN
+#    UPDATE expTagCounter SET counter = LAST_INSERT_ID(counter + 1);
+#    RETURN CONCAT(exp_id, '.', LAST_INSERT_ID());
+#END
+#
+expTagCounter METADATA
+   counter     U64         0 
+END
+
+# list of source exposures -- updated as exposures are seen
+# summitExp.imfiles is updated as filesets are queried default value should be
+# -1 as NULL or 0 might be a valid value (empty fileset)
+summitExp METADATA
+    exp_id      STR         64      # Primary Key
+    camera      STR         64      # Primary Key
+    telescope   STR         64      # Primary Key
+    dateobs     UTC         NULL
+    exp_type    STR         64
+    uri         STR         255
+    imfiles     S32         0
+END
+
+# class == type of file
+# class_id == type set id
+# list of source images -- updated as exposures/filesets are queried
+summitImfile METADATA
+    exp_id      STR         64      # Primary Key
+    camera      STR         64      # Primary Key
+    telescope   STR         64      # Primary Key
+    file_id     STR         64      # Key
+    bytes       S32         0
+    md5sum      STR         32
+    class       STR         64      # Primary Key
+    class_id    STR         64      # Primary Key
+    uri         STR         255
+END
+
+# list of exposures that have had their imfiles/files registered (but not
+# downloaded) 
+pzPendingExp METADATA
+    exp_id      STR         64      # Primary Key
+    camera      STR         64      # Primary Key
+    telescope   STR         64      # Primary Key
+END
+
+pzPendingImfile METADATA
+    exp_id      STR         64      # Primary Key
+    camera      STR         64      # Primary Key
+    telescope   STR         64      # Primary Key
+    class       STR         64      # Primary Key
+    class_id    STR         64      # Primary Key
+    exp_tag     STR         64      # Unique Key
+END
+
+# list of exposures that have had all of their imfiles downloaded
+pzDoneExp METADATA
+    exp_id      STR         64      # Primary Key
+    camera      STR         64      # Primary Key
+    telescope   STR         64      # Primary Key
+END
+
+pzDoneImfile METADATA
+    exp_id      STR         64      # Primary Key
+    camera      STR         64      # Primary Key
+    telescope   STR         64      # Primary Key
+    class       STR         64      # Primary Key
+    class_id    STR         64      # Primary Key
+    exp_tag     STR         64      # Unique Key
+    uri         STR         255
+END
+
+newExp METADATA
+    exp_tag     STR         64      # Primary Key
+    exp_id      STR         64      # Key
+    camera      STR         64      # Key
+    telescope   STR         64      # Key
+    imfiles     S32         0
+    workdir     STR         255     # destination for output files
+END
+
+# class needs to be carried here so it can go into rawImfile and be normalized
+# from there
+newImfile METADATA
+    exp_tag     STR         64      # Primary Key
+    class       STR         64      # Primary Key
+    class_id    STR         64      # Primary Key
+    uri         STR         255
+END
+
+# paired with rawImfile
+rawExp METADATA
+    exp_tag     STR         64      # Primary Key
+    exp_id      STR         64      # Key
+    camera      STR         64
+    telescope   STR         64
+    dateobs     UTC         0001-01-01T00:00:00Z
+    exp_type    STR         64
+    imfiles     S32         0
+    filelevel   STR         64
+    workdir     STR         255     # destination for output files
+    filter      STR         64
+    airmass     F32         0.0
+    ra          F64         0.0
+    decl        F64         0.0
+    exp_time    F32         0.0
+    bg          F64         0.0
+    bg_stdev    F64         0.0
+    bg_mean_stdev   F64     0.0
+    alt         F64         0.0
+    az          F64         0.0
+    ccd_temp    F32         0.0
+    posang      F64         0.0 
+    object      STR         64
+    solang      F32         0.0
+    fault       S16         0       # Key NOT NULL
+END
+
+rawImfile METADATA
+    exp_tag     STR         64      # Primary Key
+    class_id    STR         64      # Primary Key
+    uri         STR         255
+    exp_type    STR         64
+# This field is used to set the per exp filelevel. Thus the values for all of
+# the imfiles in an exposure need to be sanity checked to make sure that this
+# value is in argeement.
+    filelevel   STR         64 
+    filter      STR         64
+    airmass     F32         0.0
+    ra          F64         0.0
+    decl        F64         0.0
+    exp_time    F32         0.0
+    bg          F64         0.0
+    bg_stdev    F64         0.0
+    bg_mean_stdev   F64     0.0
+    alt         F64         0.0
+    az          F64         0.0
+    ccd_temp    F32         0.0
+    posang      F64         0.0 
+    object      STR         64
+    dateobs     UTC         0001-01-01T00:00:00Z
+    fault       S16         0       # Key NOT NULL
+END
+
+#
+# XXX - temporarily (???) stot carrying around class in imfiles after rawImfile
+#
Index: /tags/ipp-1-X/rel-1_1_19/dbconfig/telescope.md
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/dbconfig/telescope.md	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/dbconfig/telescope.md	(revision 22073)
@@ -0,0 +1,18 @@
+#                      Table 16: Telescope Status
+# Column Name  Datatype Description
+# Time         date/time   The time for which the telescope status is valid.
+# Guide status enum        The status of the guiding.
+# Altitude     float       The telescope altitude.
+# Azimuth      float       The telescope azimuth.
+# RA           float The telescope Right Ascension (ICRS ~ J2000).
+# Dec          float The telescope Declination (ICRS ~ J2000).
+
+telescope METADATA
+#    time        DATETIME    2006-01-11T00:00:00 # Primary Key
+# XXX there is currently no way to declare an enum - use str or int instead?
+    guide       STR         255
+    alt         F32         0.0
+    az          F32         0.0
+    ra          F64         0.0
+    decl        F64         0.0
+END
Index: /tags/ipp-1-X/rel-1_1_19/dbconfig/warp.md
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/dbconfig/warp.md	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/dbconfig/warp.md	(revision 22073)
@@ -0,0 +1,52 @@
+# $Id: warp.md,v 1.2 2007-03-22 03:13:04 jhoblitt Exp $
+
+#
+# We have at least 3 different run types
+# a) single epoch differing
+# b) magic run operates on "complete" exposures
+# c) multiple epoch differing/stacking operations
+#
+
+# define a new warprun for a single skycell
+warpRun METADATA
+    warp_id     S64         0       # Primary Key AUTO_INCREMENT
+    mode        STR         64      # Key
+    state       STR         64      # Key
+    workdir     STR         255
+    dvodb       STR         255
+    registered  TAI         NULL
+END
+
+#
+# Warp to Sky Cell Mode
+#
+
+# the list of imfiles in our skycell keyed against phase 3
+# only allow one cam_version of an exp_tag per warpRun
+warpInputExp METADATA
+    warp_id     S64         0       # Primary Key fkey(warp_id) ref warpRun(warp_id)
+    cam_id      S64         0       # Primary Key fkey(cam_id) ref camProcessedExp(cam_id)
+# if magic is T then look for the exp_tag in the magic output tables
+    magiced     BOOL        f       # Key
+END
+
+warpSkyCellMap METADATA
+    warp_id     S64         0       # Primary Key fkey(warp_id, cam_id) ref warpInputExp(warp_id, cam_id)
+    skycell_id  STR         64      # Primary Key
+    tess_id     STR         64      # Primary Key
+    cam_id      S64         0       # Primary Key
+# class is not yet consistently carried through pXtools
+#    class       STR         64      # Primary Key
+    class_id    STR         64      # Primary Key
+    fault       S16         0       # Key
+END
+
+warpSkyfile METADATA
+    warp_id     S64         0       # Primary Key fkey(warp_id, skycell_id, tess_id) ref warpSkyCellMap(warp_id, skycell_id, tess_id)
+    skycell_id  STR         64      # Primary Key
+    tess_id     STR         64      # Primary Key
+    uri         STR         255
+    bg          F64         0.0
+    bg_stdev   F64     0.0
+END
+
Index: /tags/ipp-1-X/rel-1_1_19/dbconfig/weather.md
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/dbconfig/weather.md	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/dbconfig/weather.md	(revision 22073)
@@ -0,0 +1,19 @@
+#             Table 9: Weather Table: some sample weather points
+# Column Name Datatype Description
+# Time            date/time    The time the weather information was measured.
+# Temperature 01 float         The external temperature
+# Temperature 02 float         The temperature at top of the dome
+# Temperature 03 float         The temperature on the primary mirror
+# Humidity        float        The relative humidity.
+# Pressure        float        The (external) atmospheric pressure.
+
+weather METADATA
+#    time        DATETIME    2006-01-10T00:00:00 # Primary Key
+    temp01      F32         0.0
+    humi01      F32         0.0
+    temp02      F32         0.0
+    humi02      F32         0.0
+    temp03      F32         0.0
+    humi03      F32         0.0
+    pressure    F32         0.0
+END
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/.cvsignore
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/.cvsignore	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/.cvsignore	(revision 22073)
@@ -0,0 +1,16 @@
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+compile
+config.guess
+config.log
+config.status
+config.sub
+configure
+depcomp
+install-sh
+libtool
+ltmain.sh
+missing
+pxtools.pc
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/COPYING
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/COPYING	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/COPYING	(revision 22073)
@@ -0,0 +1,345 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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 of the License, 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
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/Makefile.am
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/Makefile.am	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/Makefile.am	(revision 22073)
@@ -0,0 +1,12 @@
+SUBDIRS = src share
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA= pxtools.pc
+
+EXTRA_DIST = \
+	pxtools.pc.in \
+	autogen.sh
+
+CLEANFILES = *~ core core.*
+
+ACLOCAL_AMFLAGS = -I m4
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/TODO
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/TODO	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/TODO	(revision 22073)
@@ -0,0 +1,14 @@
+- change dettol -pretend -define... to return a metadata structure that shows what the created detRun would look like
+- rename dettool -residdetrun -> -todetrunsummary
+- add foreign key constraints to the database
+- add the ability to remove dettool detResidExps
+- change p4tool to accept input from/with magic masks
+- change p5tool to accept input from warp * stack
+- difftool/stacktool error handling
+- move all large SQL statements out into separate files
+- add the option of not automatically flowing through from reg -> chip -> cam
+  tools
+- add workdir inheritance rooted in rawExp, eg.
+    select chipProcessedExp.workdir, rawExp.workdir, IFNULL(chipProcessedExp.workdir, rawExp.workdir) from chipProcessedExp join rawExp using(exp_tag);
+- dettool/detRun support for filelevel
+- combind dettool -definebytag & -definebyquery
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/autogen.sh
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/autogen.sh	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/autogen.sh	(revision 22073)
@@ -0,0 +1,103 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+ORIGDIR=`pwd`
+cd $srcdir
+
+PROJECT=pxtools
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL="aclocal $ACLOCAL_FLAGS"
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+#($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+#        echo
+#        echo "You must have $LIBTOOlIZE installed to compile $PROJECT."
+#        echo "Download the appropriate package for your distribution,"
+#        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+#        DIE=1
+#}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+#($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+#        echo
+#        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+#        echo "Download the appropriate package for your distribution,"
+#        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+#        DIE=1
+#}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+$LIBTOOLIZE --copy --force || echo "$LIBTOOlIZE failed"
+$ACLOCAL -I m4 || echo "$ACLOCAL failed"
+$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/configure.ac
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/configure.ac	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/configure.ac	(revision 22073)
@@ -0,0 +1,64 @@
+AC_PREREQ(2.59)
+
+AC_INIT([ipptools], [1.1.19], [ipp-support@ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([autogen.sh])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_CONFIG_HEADER([src/config.h])
+AM_MAINTAINER_MODE
+
+IPP_STDCFLAGS
+
+AC_LANG(C)
+AC_GNU_SOURCE
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+
+PKG_CHECK_MODULES([PSLIB], [pslib >= 1.1.0])
+PKG_CHECK_MODULES([PSMODULES], [psmodules >= 1.1.0])
+PKG_CHECK_MODULES([IPPDB], [ippdb >= 1.1.19]) 
+
+PXTOOLS_CFLAGS="${PSLIB_CFLAGS=} ${PSMODULES_CFLAGS=} ${IPPDB_CFLAGS=}"
+PXTOOLS_LIBS="${PSLIB_LIBS=} ${PSMODULES_LIBS=} ${IPPDB_LIBS=}"
+AC_SUBST(PXTOOLS_CFLAGS,[$PXTOOLS_CFLAGS])
+AC_SUBST(PXTOOLS_LIBS,[$PXTOOLS_LIBS])
+
+dnl check for PSDB support
+TMP_CFLAGS=${CFLAGS}
+CFLAGS="${CFLAGS=} ${PSLIB_CFLAGS}"
+
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+    [[#if !defined HAVE_PSDB
+      error: PSDB support is not avaiable
+      #endif
+    ]])],
+  [],
+  [AC_MSG_ERROR([psLib was built without PSDB support, bailing out.])]
+)
+
+CFLAGS=${TMP_CFLAGS}
+
+dnl --  check for psParseErrorCodes --------------------------------------
+
+AC_PATH_PROG([ERRORCODES], [psParseErrorCodes], [missing])
+if test "$ERRORCODES" = "missing" ; then
+  AC_MSG_ERROR([psParseErrorCodes is required])
+fi
+
+dnl check for perl
+AC_PATH_PROG([PERL], [perl], [missing])
+if test "$PERL" = "missing" ; then
+  AC_MSG_ERROR([perl is required])
+fi
+
+IPP_STDOPTS
+CFLAGS="${CFLAGS=} -Wall -Werror -std=c99"
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+  share/Makefile
+  pxtools.pc
+])
+AC_OUTPUT
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/doc/dettest.sh
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/doc/dettest.sh	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/doc/dettest.sh	(revision 22073)
@@ -0,0 +1,120 @@
+#!/bin/sh
+
+#
+# the default value for -iteration (e.g., you didn't specify -iteration) is 0
+# it may make sense to make it default to the latest iteration number for a
+# given det run
+#
+# so of the sanity checking still needs to be improved/tested 
+#
+# also worth noting is that the validity of the iteration value isn't yet
+# checked
+#
+
+# create a new detrun
+det_id=`dettool -define -exp_id 1 -exp_id 2 -exp_id 3 -det_type flat`
+
+# list detruns (active only?)
+dettool -runs
+
+# get detrun status (iteration #)
+
+# list the input exposures in a detRun
+dettool -input -det_id $det_id
+
+# list raw imfiles
+dettool -raw -det_id $det_id
+
+# register a processed imfile
+dettool -addprocessed -det_id $det_id ...
+
+# processed imfiles should mask out raw imfiles
+dettool -raw -det_id $det_id
+
+# list processed imfiles 
+dettool -processed -det_id $det_id [-iteration]
+
+# list processed imfiles for a chip (all or none)
+dettool -processed -chip -det_id $det_id [-iteration]
+
+# list all processed imfiles (even those that have been stacked)
+dettool -processed -unmask -det_id $det_id [-iteration]
+
+# register a stacked imfile
+dettool -addstacked -det_id $det_id [-iteration] -class_id ...
+
+# stacked imfiles should mask out processed imfiles
+dettool -processed -det_id $det_id [-iteration]
+
+# list stacked imfiles
+dettool -stacked -det_id $det_id [-iteration]
+# list stacked imfiles only for complete frames
+dettool -stackedframe -det_id $det_id [-iteration]
+
+# register a normalized imfile
+dettool -addnormalized -det_id $det_id [-iteration] ...
+
+# normalized imfiles should mask out stacked imfiles
+dettool -stacked -det_id $det_id [-iteration]
+# does a single normalized imfile mask out the entire frame?
+dettool -stackedframe -det_id $det_id [-iteration]
+
+# list normalized imfiles 
+dettool -normalized -det_id $det_id[ -iteration]
+
+# register a residual result
+dettool -addresid -det_id $det_id [-iteration] -exp_id ... -class_id ... -stats ...
+
+# residuals mask out normalized imfiles
+dettool -normalized -det_id $det_id [-iteration]
+
+# list residual imfile analyses
+dettool -resid -det_id $det_id [iteration] -exp_id ... -class_id ... -stats ...
+
+# register a residual exp XXX not sanity checked against all input class_ids having a resid registered
+dettool -addresidexp -det_id $det_id [iteration] -stats ... -accept
+
+# list residual exps
+dettool -residexp -det_id $det_id [iteration]  
+
+# register a/the master
+dettool -declaremasterframe -det_id $det_id [-iteration ...]
+
+# list masters
+dettool -masterframe -det_id
+
+# list imfiles in a master frame
+dettool -master -det_id $det_id [-iteration ...]
+
+# start another iteration but use only these input exps (must be a subset of
+# the original input exps)
+dettool -rerun -det_id $det_id -exp_id 1 -exp_id 2 -exp_id 3 
+
+
+# always print out the det_type in the output for imfiles
+
+# -addresid -uri
+# -addresidexp -uri (big/small jpeg)
+
+# -updatedresid for -accept
+# -masterframe needs 'class'
+# -nukemaster
+
+pxinject
+
+- class is currently in db at the imfile level, not the exp level.
+
+dettool
+
+- list of det_ids without registered masters
+- -chip option is a bad name
+
+for normalize to resid, support script needs to run different commands on basis
+of type
+
+
+# need a way to quiery live det runs
+# add det run state tracking (alive or dead)
+# die at the end of a det run... -rerun makes it live again ??
+# -processed -chip (no -det_id) returns a list of ready det_id/class_id pairs
+
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/doc/dettool.txt
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/doc/dettool.txt	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/doc/dettool.txt	(revision 22073)
@@ -0,0 +1,181 @@
+# returns a list of unassociated rawDetrendExp (rawDetrendExps that don't
+# correspond to a exp_id in detInputExp)
+dettool -pending -exp_type -inst ..
+
+# define a new detRun semi-automatically by selecting the component exps with
+# the specified search criteria
+dettool -definebyquery -det_type ...
+
+# create a detRun with the specified input exp_ids
+dettool -definebyexp -det_type -exp_id 1 -exp_id 2 ...
+
+--
+# returns a list of rawImfiles that are associated with an detInputExp that do
+# not appear in detProcessedImfiles
+dettool -raw
+
+# add an imfile to detProcessedImfiles
+dettool -addprocessed
+
+--
+# the list of class_ids that are ready to be stacked
+# returns a list of det_id/iter/class_id/det_typs  where class_id is unique per
+# det_id (not a list of all exp_ids/class_ids) that do not have an entry in
+# detStackedImfile
+# results are masked by entries in detResidExpAnalysis
+# may need to check detResidExp's even on iter 0 - ???
+dettool -tostack
+
+# list all of imfiles from different exps with the same class_id that have
+# been processed
+dettool -processed -wholestack -det_id ...
+
+# adds a stacked class_id and and optionally marks it for normalization
+detool -addstacked [-pleasenormalize] -det_id -iter -class_id ...
+
+--
+# returns a list of det_ids/iters where the entire set of class_ids has been
+# processed
+dettool -tonormalize
+
+# returns the complete list of processed imfiles for the specified det_id
+dettool -processed -unmask -det_id -iter
+
+# add per stacked imfile normalization statistics
+# O: this could accept a MDC doc as input from stdin
+dettool -addnormstat -det_id -iter -class_idi -norm F32
+
+--
+# returns a list of det_ids/iter/class_id/uri from detNormStats.  Stops
+# returning entries after the class_is is inserted into detNormalizedImfile
+dettool -normstat -det_id 
+
+# inserts an entry into detNormalizedImfile
+dettool -addnormalized -det_id -ter -class_id -uri
+
+--
+# returns a list of processed imfiles that have also been normalized with
+# processed imfiles being masked out per detResidImfileAnalysis
+# also returns stackedimfiles that have -pleasenormalize set to false
+dettool -toresid -det_id -iter
+
+# inserts a per det_id/iter/exp_id/class_id residual
+dettool -addresidimfile -det_id -exp_id -class_id -iter -stat F32 -stat_stdev F32 -uri -b1 -b2
+
+# returns a list of det_id/iter/exp_ids but only for exp_ids that have resids
+# for all of their imfiles
+dettool -toresidexp
+
+# returns a list of residual imfile data
+dettool -residimfile -det_id -iter -exp_id
+
+# inserts into detResidExpAnalysis 
+# sets the accept accept bool unless -reject is specified
+dettool -addresidexp -det_id -iter -exp_id -jpeg1 -jpeg2 -stat F32 -stat_stdev
+F32 [-reject]
+
+--
+# lists det_id/iter for detruns that have completed all residexps for their
+# current iteration 
+dettool -residdetrun
+
+# lists all residexps for the given det_id/iter
+dettool -residexp -det_id -iter
+
+# updates the given residexp to be accept/reject
+dettool -updateresid -det_id -iter [-reject]
+
+# changes the state of the detrun.  -rerun cause a new iteration to be started
+# with the accepted with just the exp_ids
+dettool -updatedetrun -det_id -iter -stat F32 -stat_stdev [-rerun|-ok|-stop]
+
+# manual starts a new detrun iteration starting from the specified -iter
+dettool -rerun -det_id -iter -accept expid -accept expid -reject expid -reject expid
+dettool -rerun -det_id -iter
+
+# detInputExp needs to track...
+det_id iter exp_id use bool accept bool
+
+
+
+dettool
+
+    -define | creates a a new detrend Run
+        input:
+            exp IDs?
+            type of det run?
+        creates a new detRun ID automatically
+    
+        output: the new det_id
+
+    -raw    | lists raw imfiles needing to be processed
+        input: 
+            det ID
+            exp ID
+            class ID
+            iteration number?
+        output: list of unprocessed imfiles
+    
+    -addprocessed? | marks a raw imfile as processed
+        input:
+            det ID
+            exp ID
+            class ID
+            iteration number?
+            uri
+            stat
+            recipe
+        output: outout on error only
+    -processed | lists processed imfiles
+        input:
+            det ID
+            exp ID
+            class ID
+            iteration number?
+        output: list of processed imfiles
+    -addstacked | adds a stacked imfiles
+        input:
+            det ID
+            class ID
+            iteration number?
+            URI
+            recipe
+            stats
+        output: output on error only
+    -stacked     | lists stacked imfiles
+        input:
+            det ID
+            class ID
+            iteration number?
+        output: list of stacked imfiles
+    -stacked     | lists stacked imfiles
+        input:
+            det ID
+            class ID
+            iteration number?
+        output: list of stacked imfiles 
+    -stackedframe     | lists stacked imfiles for COMPLETE frames
+        input:
+            det ID  // required for simplicity
+            iteration number?
+        output: list of stacked imfiles
+    -addmaster | adds a master imfiles
+        input:
+            det ID
+            class ID
+            iteration number?
+            URI
+            recipe
+            stats
+        output: output on error only
+    -master     | lists master imfiles
+        input:
+            det ID  // required for simplicity
+            class ID
+            iteration number?
+        output: a list of master imfiles
+    -masterframe     | lists master imfiles for COMPLETE frames
+        input:
+            det ID  // required for simplicity
+            iteration number?
+        output: a list of master imfiles
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/doc/dsfilesetls
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/doc/dsfilesetls	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/doc/dsfilesetls	(revision 22073)
@@ -0,0 +1,6 @@
+#!/usr/bin/env perl
+
+print "# uri fileid bytes md5sum type \n";
+print "http://example.org/1 foo1.0 123 asadfasdfasdfasfdasdf CELL\n";
+print "http://example.org/2 foo1.1 123 asadfasdfasdfasdfasdf CELL\n";
+print "http://example.org/3 foo1.2 123 asadfasdfasdfasdfasdf CELL\n";
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/doc/dsproductls
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/doc/dsproductls	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/doc/dsproductls	(revision 22073)
@@ -0,0 +1,8 @@
+#!/usr/bin/env perl
+
+print "# uri fileset datetime type\n";
+print "http://example.org/1 foo1 2006-04-17T00:00:12 OBJECT\n";
+print "http://example.org/2 foo2 2006-04-17T00:00:12 OBJECT\n";
+print "http://example.org/3 foo3 2006-04-17T00:00:12 OBJECT\n";
+
+
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/doc/install.pod
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/doc/install.pod	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/doc/install.pod	(revision 22073)
@@ -0,0 +1,251 @@
+=pod
+
+=head1 SYNOPSIS
+
+C<pXtools> is a collection of utilties for tracking image processing
+tasks.  Very little decision making logic is built into these programs.  The
+intent is for higher level programs and scripts to make the real I<decisions>
+and then use C<pXtools> to encode those choices in a persistent manner.
+
+=head1 INSTALL
+
+=head2 Prerequisites
+
+C<pXtools> depends on C<psLib> (built with C<psDB> support), C<psModules>, and
+C<ippdb> and requires a C<C99> capable compiler.
+
+XXX document required library versions
+
+=head2 Build Procedure
+
+C<pXtools> is built with the so-called suite of autotools.   The build
+procedure is pretty typical for software built with these tools.  E.g.
+    
+    ./configure
+    make
+    make install
+
+If you checked out the sources directly from CVS you need to run the
+C<autogen.sh> script.  C<autogen.sh> will run C<configure> for you so that
+changes the build procedure to just:
+
+    sh autogen.sh
+    make
+    make install
+
+=head2 jhbuild Build Procedure
+
+If you are using one of the C<ippdev> modulesets jhbuild can build and install C<pXtools> for you.  E.g.
+
+    jhbuild build pxtools
+    
+=head2 Database Setup
+
+All of the C<pXtools>' persistent storage is ultimately done through C<psLib>'s
+C<psDB> facilties.  In turn, C<psDB> uses C<MySQL> to impliment storage (and
+relational querying).  C<MySQL> requires that each namespace (aka database) is
+configured with basic access controls.  These include usernames, passwords,
+access filtering, and privilege restrictions.  Below is a simple (but somewhat
+dangerous) example of configuring a database named C<ipp>.
+
+    mysql -u root mysql -p
+    (enter your MySQL root password)
+
+    DROP DATABASE IF EXISTS ipp;
+    CREATE DATABASE ipp;
+    GRANT ALL PRIVILEGES ON *.* TO 'ippuser'@'localhost' IDENTIFIED BY 'ipppass';
+    FLUSH PRIVILEGES;
+    quit
+
+After setting up the database you should verify that you can connect to with the username and password you configured
+
+    mysql -u ipp ipp -pipp
+
+Next, C<pXtools> needs to be told where to to find MySQL, the name of the
+database you created, the username, and the password you created.  C<pXtools>
+uses the typical C<psModules> configuration system.  The simplest method is to
+add the follow lines to your $HOME/.ipprc file.
+
+    ### Database configuration
+    DBSERVER        STR     localhost   # Database host name (for psDBInit)
+    DBNAME          STR     ipp
+    DBUSER          STR     ipp         # Database user name (for psDBInit)
+    DBPASSWORD      STR     ipp         # Database password (for psDBInit)
+
+The C<pxTools> utilities require the a schema is pre-loaded into the database.  The C<pxadmin> tool can do this for you.  E.g.
+
+    pxadmin -create
+
+You should always verify that the scheme was actually loaded.  E.g.
+
+    mysql -u ipp ipp -pipp
+    Welcome to the MySQL monitor.  Commands end with ; or \g.
+    Your MySQL connection id is 6 to server version: 4.1.20-log
+
+    Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
+
+    mysql> show tables;
+    +------------------------+
+    | Tables_in_ipp          |
+    +------------------------+
+    | detInputExp            |
+    | detMasterFrame         |
+    | detMasterImfile        |
+    | detNormalizedImfile    |
+    | detProcessedImfile     |
+    | detResidExpAnalysis    |
+    | detResidImfileAnalysis |
+    | detRun                 |
+    | detStackedImfile       |
+    | newExp                 |
+    | newImfile              |
+    | p1PendingExp           |
+    | p2DoneExp              |
+    | p2DoneImfile           |
+    | p2PendingExp           |
+    | p2PendingImfile        |
+    | p3PendingExp           |
+    | pzPendingExp           |
+    | pzPendingImfile        |
+    | rawDetrendExp          |
+    | rawImfile              |
+    | rawScienceExp          |
+    | summitExp              |
+    +------------------------+
+    23 rows in set (0.00 sec)
+
+=head1 USE
+
+The workflow is as follows:
+
+XXX
+
+=head2 Phase 0 tools
+
+The primary C<pXtools> utility for handling Phase 0 is C<p0search>.  This tool
+has two modes C<-pending> and C<-update>. The pending mode lists all
+C<newImfile>s that are part of an exposure that I<has not> yet been classified
+as either science or detrend data.
+
+For example:
+
+    p0search -pending
+
+Would output something like:
+
+    newImfile MULTI #
+
+    newImfile  METADATA
+       exp_id STR  10
+       class STR  OTA
+       class_id STR  0
+       uri STR  file://0
+    END
+
+    newImfile  METADATA
+       exp_id STR  10
+       class STR  OTA
+       class_id STR  1
+       uri STR  file://1
+    END
+
+    newImfile  METADATA
+       exp_id STR  10
+       class STR  OTA
+       class_id STR  2
+       uri STR  file://2
+    END
+
+    newImfile  METADATA
+       exp_id STR  10
+       class STR  OTA
+       class_id STR  3
+       uri STR  file://3
+    END
+
+To declare C<exp_id 10> as science data, you would invoke C<p0search> like this:
+    
+    p0search -update -exp_id 10
+
+Note that by default C<-update> flags the exposure as science data.  To declare it to be detrend data the command would be:
+
+    p0search -update -exp_id 10 -detrend
+
+Also keep in mind that this is a one time declaration.  Once it has been
+declared as either science or detrend data this decision can not be undone
+(without manually editing the database)
+
+=head2 Phase 1 tools
+
+These tools are not yet in use.
+
+=head2 Phase 2 tools
+
+C<p2search> handles all of state for Phase 2.  It has four basic modes:
+
+=over 4
+
+=item * C<-quick>
+
+=item * C<-define>
+
+=item * C<-pending>
+
+    p0search -pending
+
+=item * C<-done>
+
+=head2 Administrative tools
+
+=over 4
+
+=item * C<pxadmin>
+
+This tool can be used to load/unload the database schema.  It has three modes of operation:
+
+=over 4
+
+=item * C<-create>
+
+Loads the database schema.
+
+=item * C<-delete>
+
+Deletes the database schema.
+
+=item * C<-recreate>
+
+Deletes the database schema then reloads it.
+
+=back
+
+=item * C<pxinject>
+
+I<Injects> data directly into the database.  It has one mode parameter for each database table.  Note: support for only a few tables has been impliemented.
+
+=back
+
+=head2 Bypassing 'Summit Copy'
+
+The C<pXtools> suite assumes that exposures and image data are initially
+registered with the C<pz*> set of I<Summit Copy step> tools.  Sometimes this is
+inconvenient as you want to work with local data and bypass this set.  This is
+what the C<pxinject> utility is for.  C<pxinject> directly inserts data into
+the database, side stepping any ordering rules or sanity checking.
+
+The end result of the C<pz*> utilities is that new exposures are registered in the C<newExp> table and raw image data is in the C<newImfile> table.  You can insert this data yourself, preparing the system to run I<phase 0>.
+
+Here is an example of inserting an exposure that has 4 component image files.
+
+    #!//bin/sh
+
+    inject="./pxinject"
+
+    $inject -newExp -exp_id 10 -inst gpc -telescope ps1 -exp_type object -imfiles 4
+
+    `$inject -newImfile -exp_id 10 -class OTA -class_id 0 -uri file://0`
+    `$inject -newImfile -exp_id 10 -class OTA -class_id 1 -uri file://1`
+    `$inject -newImfile -exp_id 10 -class OTA -class_id 2 -uri file://2`
+    `$inject -newImfile -exp_id 10 -class OTA -class_id 3 -uri file://3`
+
+=cut
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p0tools.sh
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p0tools.sh	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p0tools.sh	(revision 22073)
@@ -0,0 +1,71 @@
+
+## PanTasks scripts for Phase 0
+
+# tasks: p0ready p0update p0cleanup
+#
+# queues: 
+#   p0pending: (expID) (cameraID) (state)
+#
+# globals: 
+
+# p0pending : search for images to be processed
+task	       p0pending
+  command      p0search -pending
+  host         local
+
+  # timeout shorter than exec so jobs do not build up
+  periods      -exec     5
+  periods      -poll     1
+  periods      -timeout  4
+
+  # success
+  task.exit    0
+    local i Nstdout
+    # keep only new, unique entries (URL is key)
+    queuesize stdout -var Nstdout
+    for i 0 $Nstdout
+      queuepop stdout -var line
+      queuepush p0pending -uniq -key 0 "$line new"
+    end
+  end
+end
+
+# p0update : submit an exposure to be updated
+task	       p0update
+  periods      -exec 1
+  periods      -timeout 10
+
+  task.exec
+    local Npending
+    queuesize  p0pending -var Npending
+    if ($Npending == 0) break
+
+    queuepop p0pending -var line -key 2 new
+    if ("$line" == "NULL") break
+
+    list tmp -split $line
+    $expID  = $tmp:0
+    $camera = $tmp:1
+    queuepush p0pending "$expID $camera run"
+
+    host any
+    command p0search -update $expID $camera
+  end
+
+  # success
+  task.exit    0
+    queueinit stdout
+    queuepush p0pending $taskargs:0 $taskargs:1 done
+  end
+end
+
+# p0update : search for images to be processed
+task	       p0cleanup
+  command      p0search -cleanup
+  host         local
+
+  # timeout shorter than exec so jobs do not build up
+  periods      -exec     30
+  periods      -poll     1
+  periods      -timeout  10
+end
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p0tools.txt
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p0tools.txt	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p0tools.txt	(revision 22073)
@@ -0,0 +1,40 @@
+
+Phase 0 pipeline tools:
+
+p0search -pending :
+  * examine the new.imfiles,new.exposures tables and select exposures ready for analysis
+  * output is: (expID) (camera)
+
+p0search -update (expID) (camera):
+  * select a the corresponding images from the new.imfiles/new.exposures table
+  * extract the specified header information
+  * search the summit metadata db tables
+  * write an entry to the raw.imfiles and raw.exposure tables
+  * set the state on the new.imfiles,new.exposure tables
+  * based on the camera config information;
+    * add an entry to the p1.pending table (mosaic) 
+    - or
+    * add an entry to the p2.pending table (single)
+
+p0search -stats (expID) (camera):
+  * select a specified image in the new.imfiles/new.exposures table
+  * extract the specified header information
+  * search the summit metadata db tables
+  * report the image stats
+  [-update without output to MDDB]
+
+p0search -mkraw (expID) (camera):
+  * select a specified image in the new.imfiles/new.exposures table
+  * extract the specified header information
+  * search the summit metadata db tables
+  * write an entry to the raw.imfiles and raw.exposure tables
+  * set the state on the new.imfiles,new.exposure tables
+
+p0search -cleanup:
+  * remove completed entries from the new.imfiles,new.exposure tables
+
+** note : the division of -pending and -update allows separate processes
+   to be examining the image headers and measuring some stats.  these
+   jobs can be run via pcontrol to reduce the load on the PanTasks
+   machines
+
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p1tools.sh
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p1tools.sh	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p1tools.sh	(revision 22073)
@@ -0,0 +1,72 @@
+
+## PanTasks scripts for Phase 1
+
+# tasks: p1pending p1astro p1done
+#
+# queues: 
+#   p1pending: (expID) (version) (cameraID) (state)
+#
+# globals: 
+
+# p1pending : search for images to be processed
+task	       p1pending
+  command      p1search -pending
+  host         local
+
+  # timeout shorter than exec so jobs do not build up
+  periods      -exec     5
+  periods      -poll     1
+  periods      -timeout  4
+
+  # success
+  task.exit    0
+    local i Nstdout
+    # keep only new, unique entries (URL is key)
+    queuesize stdout -var Nstdout
+    for i 0 $Nstdout
+      queuepop stdout -var line
+      queuepush p1pending -uniq -key 0 "$line new"
+    end
+  end
+end
+
+# p1update : submit an exposure to be updated
+task	       p1astro
+  periods      -exec 1
+  periods      -timeout 10
+
+  task.exec
+    local expID version camera Npending
+    queuesize  p1pending -var Npending
+    if ($Npending == 0) break
+
+    queuepop p1pending -var line -key 2 new
+    if ("$line" == "NULL") break
+
+    list tmp -split $line
+    $expID  = $tmp:0
+    $version = $tmp:1
+    $camera = $tmp:2
+    queuepush p1pending "$expID $version $camera run"
+
+    host any
+    command p1astro $expID $version $camera
+  end
+
+  # success
+  task.exit    0
+    queueinit stdout
+    queuepush p1pending $taskargs:0 $taskargs:1 $taskargs:2 done
+  end
+end
+
+# p1update : search for images to be processed
+task	       p1done
+  command      p1search -done
+  host         local
+
+  # timeout shorter than exec so jobs do not build up
+  periods      -exec     30
+  periods      -poll     1
+  periods      -timeout  10
+end
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p1tools.txt
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p1tools.txt	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p1tools.txt	(revision 22073)
@@ -0,0 +1,16 @@
+
+Phase 1 pipeline tools:
+
+p1search -define [constraints]:
+  * examine the raw.exposures tables and select exposures matching the given criteria
+  * add entries which are allowed (mosaic) to the p1.pending table
+
+p1search -pending :
+  * examine the p1.pending table and select exposures waiting for p1
+  * output: lines consisting of:
+    (expID) (p1version) (camera)
+
+p1search -done :
+  * select completed entries in the p1.pending table
+  * move to the p1.done table
+  * add new entry to the p2.pending tables
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p2search.sh
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p2search.sh	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p2search.sh	(revision 22073)
@@ -0,0 +1,8 @@
+#!/bin/csh -f
+
+echo "# expID class classID url"
+echo "654320o fpa megacam 654320o.fits"
+echo "654321o fpa megacam 654321o.fits"
+echo "654322o fpa megacam 654322o.fits"
+echo "654323o fpa megacam 654323o.fits"
+echo "654324o fpa megacam 654324o.fits"
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p2tools.sh
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p2tools.sh	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p2tools.sh	(revision 22073)
@@ -0,0 +1,137 @@
+
+## PanTasks scripts for Phase 2
+
+# tasks: p2pending p2update ppImage
+#
+# queues: 
+#   p2pending: expID class classID url
+#
+# globals: 
+
+queueinit stdout
+queueinit stderr
+queueinit p2pending
+
+macro p2parse
+  if ($0 != 7)
+    echo "usage: p2parse (key) (expID) (class) (classID) (url) (state)"
+    break
+  end
+  $expID   = $2
+  $class   = $3
+  $classID = $4
+  $url     = $5
+  $key     = $expID.$classID
+end
+
+# p2pending : search for images to be processed
+task	       p2pending
+  command      p2search.sh -pending
+  host         local
+
+  # timeout shorter than exec so jobs do not build up
+  periods      -exec     5
+  periods      -poll     1
+  periods      -timeout  4
+
+  # success
+  task.exit    0
+    local i Nstdout
+
+    # pop off the header line
+    queuepop stdout
+
+    # keep only new, unique entries (URL is key)
+    queuesize stdout -var Nstdout
+    for i 0 $Nstdout
+      queuepop stdout -var line
+      p2parse unknown $line new
+      queuepush p2pending -uniq -key 0 "$key $line new"
+    end
+    echo "loaded p2pending, current state:"
+    queueprint p2pending
+  end
+end
+
+# ppImage : submit an image for processing by ppImage
+task	       ppImage
+  periods      -exec 1
+  periods      -timeout 120
+
+  task.exec
+    local Npending
+    queuesize  p2pending -var Npending
+    if ($Npending == 0) break
+
+    # add in system status interruptions later
+    # if ($network == 0) break
+    
+    queuepop p2pending -var line -key 5 new
+    if ("$line" == "NULL") break
+    p2parse $line
+
+    echo "new ppImaage: $key $expID $class $classID $url run"
+    queuepush p2pending "$key $expID $class $classID $url run"
+
+    # need to decide real name for this function
+    # $host = `chip.host $camera $chip`
+    # host $host
+
+    host local
+    echo command ppImage.sh $key $url $classID
+    command ppImage.sh $key $url $classID
+  end
+
+  # success
+  task.exit    0
+    # if ppImage updates the DB:
+    echo "done with $taskarg:1"
+    queueinit stdout
+    queuepop p2pending -key 0 $taskarg:1 -var line
+    echo "got line: $line"
+
+    if ("$line" == "NULL") 
+      echo "missing entry in p2pending queue?"
+      break
+    end
+
+    p2parse $line
+    queuepush p2pending "$key $expID $class $classID $url done"
+    echo queuepush p2pending "$key $expID $class $classID $url done"
+    
+    # if ppImage does NOT update the DB:
+  end
+
+end
+
+# p2update : search for images to be processed
+task	       p2update
+  command      p2search -update
+  host         local
+
+  # timeout shorter than exec so jobs do not build up
+  periods      -exec     100
+  periods      -poll     1
+  periods      -timeout  4
+
+  # task is never allowed to run
+  task.exec
+    break
+  end
+end
+
+# p2done : search for images to be processed
+task	       p2done
+  command      p2search -done
+  host         local
+
+  # timeout shorter than exec so jobs do not build up
+  periods      -exec     100
+  periods      -poll     1
+  periods      -timeout  4
+
+  # task is never allowed to run
+  task.exec
+    break
+  end
+end
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p2tools.txt
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p2tools.txt	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p2tools.txt	(revision 22073)
@@ -0,0 +1,41 @@
+
+Phase 2 pipeline tools:
+
+p2search -quick 
+  * search for images which match in raw.exp,raw.imfiles
+  * output in format which can be used by ppImage pantasks script
+
+p2search -define [options]
+  * input: searches mddb:raw_exposures,raw_images
+        - compares against p2Pending[Exp|Imfile]
+        - compares against p2Done[Exp|Imfile]
+        - XXX should be implemented as a special wrapper function that
+          implements this comparison as a SQL query
+  * output: updates mddb:P2_exposures_pending,P2_images_pending
+  * alternative output: identical to p2pending
+ 
+p2search -pending 
+  * input: searches mddb:P2_exposures_pending,P2_images_pending
+  * output: Nlines consisting of:
+    (URL) (expID) (class)
+  * options: ?
+
+p2search -update
+  * examine the imfiles and identify any completed exposures
+
+p2search -done
+  * get exp_id/class/class_id from the CLI
+  * add the completed imfile to the p2DoneImfile tables
+  * remove corresponding entries from the p2PendingImfile table
+  * check to see if any p2PendingExps have no associated p2PendingImfiles
+        if so move the p2PendingExp(s) to p2DoneExp
+//  * send new entry to the pending p3 table
+
+ppImage file://path/filename file://path/outroot -recipe (recipe) 
+ppImage neb://nebname neb://outroot -recipe (recipe)
+
+restriction options:
+  -time (start) (stop)
+  -camera (camera) 
+  -region (ra,dec) (ra,dec)
+
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p3tools.sh
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p3tools.sh	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p3tools.sh	(revision 22073)
@@ -0,0 +1,72 @@
+
+## PanTasks scripts for Phase 3
+
+# tasks: p3pending p3astro p3done
+#
+# queues: 
+#   p3pending: (expID) (version) (cameraID) (state)
+#
+# globals: 
+
+# p3pending : search for images to be processed
+task	       p3pending
+  command      p3search -pending
+  host         local
+
+  # timeout shorter than exec so jobs do not build up
+  periods      -exec     5
+  periods      -poll     1
+  periods      -timeout  4
+
+  # success
+  task.exit    0
+    local i Nstdout
+    # keep only new, unique entries (URL is key)
+    queuesize stdout -var Nstdout
+    for i 0 $Nstdout
+      queuepop stdout -var line
+      queuepush p3pending -uniq -key 0 "$line new"
+    end
+  end
+end
+
+# p3update : submit an exposure to be updated
+task	       p3astro
+  periods      -exec 1
+  periods      -timeout 10
+
+  task.exec
+    local expID version camera Npending
+    queuesize  p3pending -var Npending
+    if ($Npending == 0) break
+
+    queuepop p3pending -var line -key 2 new
+    if ("$line" == "NULL") break
+
+    list tmp -split $line
+    $expID  = $tmp:0
+    $version = $tmp:1
+    $camera = $tmp:2
+    queuepush p3pending "$expID $version $camera run"
+
+    host any
+    command p3astro $expID $version $camera
+  end
+
+  # success
+  task.exit    0
+    queueinit stdout
+    queuepush p3pending $taskargs:0 $taskargs:1 $taskargs:2 done
+  end
+end
+
+# p3update : search for images to be processed
+task	       p3done
+  command      p3search -done
+  host         local
+
+  # timeout shorter than exec so jobs do not build up
+  periods      -exec     30
+  periods      -poll     1
+  periods      -timeout  10
+end
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p3tools.txt
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p3tools.txt	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/doc/p3tools.txt	(revision 22073)
@@ -0,0 +1,22 @@
+
+Phase 3 pipeline tools:
+
+p3search -define :
+  * examine the raw.exposures tables and select exposures matching the given criteria
+  * add entries which are allowed (mosaic) to the p3.pending table
+
+p3search -quick :
+  * examine the raw.exposures tables and select exposures matching the given criteria
+  * return list of entries for p3 processing
+  * output: lines consisting of:
+    (expID) (p3version) (camera)
+
+p3search -pending :
+  * examine the p3.pending exposures table and select exposures waiting for p3
+  * return list of entries for p3 processing
+  * output: lines consisting of:
+    (expID) (p3version) (camera)
+
+p3search -done :
+  * select completed entries in the p3.pending table
+  * move to the p3.done table
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/doc/ppImage.sh
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/doc/ppImage.sh	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/doc/ppImage.sh	(revision 22073)
@@ -0,0 +1,7 @@
+#!/bin/csh -f
+
+sleep 3
+echo $*
+echo done
+
+exit 0
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/doc/summit_copy.txt
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/doc/summit_copy.txt	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/doc/summit_copy.txt	(revision 22073)
@@ -0,0 +1,49 @@
+The summit copy phase (phase Z or pz) consists of three basic steps.
+Discovering what exposures and available, discovering what image files are
+associated with those exposures, and the downloading of image files.  All
+"external" communication with the DataStore is done with three utilities;
+pzgetexp, pzgetimfiles & dsget.  While local state information is accessed and
+updated via the 'pztool' utility.
+
+The basic work follow is:
+
+pzgetexp retrieves a list of all of the exposures (filesets) available from a
+DataStore "product".  If any previously unknown file exposures are found they
+are added to the pzPendingExp tables and the summitExp table.  New entries to
+the summitExp table have a imfile value of NULL as this information can't yet
+be known.  If any exposures are already in the summitExp table the most recent
+exp_id is used to constrain the result set requested from the DataStore.
+
+    ./pzgetexp -uri http://otis/ds/skyprobe/ -inst skyprobe -telescope ps1
+
+The results of pzgetexp and accessible with pztool -pendingexp`.  Which will
+return a list of new exposures from the pzPendingExp table.
+
+    ./pztool -pendingexp
+
+The output of which is used to invoke pzgetimfiles.
+
+pzgetimfiles retrieves a list of all of the imfiles (files) in an exposure
+(fileset) from the DataStore.  It then updates the imfiles count in the
+summitExp table and adds each imfile to the pzPendingImfile table.
+
+./pzgetimfiles -uri http://otis/ds/skyprobe/sep8_twi_05/ -filesetid sep8_twi_05 -inst skyprobe -telescope ps1
+
+pztool -pendingimfile returns the list of imfiles that need to be downloaded.
+
+    ./pztool -pendingimfile
+
+The results of which are used to invoke dsget and pztool -copydone after dsget
+completes it's download.
+
+dsget from the 'DataStore' package takes the follow parameters.
+
+    dsget --uri <uri> --filename <filename> [--bytes <nbytes>] [--md5 <hex>]
+
+Where "filename" may be either just a filename relative to the CWD or a fully
+qualified path.  After dsget has copied the file pztool -copydone needs to be
+invoked
+
+    ./pztool -copydone -exp_id sep8_twi_03 -inst skyprobe -telescope ps1 -class chip -class_id sep8_twi_03.fits -uri file///data/alala/sep8_twi_03.fits
+
+And then we're Pau.
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/m4/ac_prog_perl_modules.m4
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/m4/ac_prog_perl_modules.m4	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/m4/ac_prog_perl_modules.m4	(revision 22073)
@@ -0,0 +1,53 @@
+dnl @synopsis AC_PROG_PERL_MODULES([MODULES], [ACTION-IF-TRUE], [ACTION-IF-FALSE])
+dnl
+dnl Checks to see if the the given perl modules are available. If true
+dnl the shell commands in ACTION-IF-TRUE are executed. If not the shell
+dnl commands in ACTION-IF-FALSE are run. Note if $PERL is not set (for
+dnl example by calling AC_CHECK_PROG, or AC_PATH_PROG),
+dnl AC_CHECK_PROG(PERL, perl, perl) will be run.
+dnl
+dnl Example:
+dnl
+dnl   AC_CHECK_PERL_MODULES(Text::Wrap Net::LDAP, ,
+dnl                         AC_MSG_WARN(Need some Perl modules)
+dnl
+dnl @category InstalledPackages
+dnl @author Dean Povey <povey@wedgetail.com>
+dnl @version 2002-09-25
+dnl @license AllPermissive
+
+AC_DEFUN([AC_PROG_PERL_MODULES],[
+    ac_perl_modules="$1"
+    # Make sure we have perl
+    if test -z "$PERL"; then
+        AC_CHECK_PROG(PERL,perl,perl)
+    fi
+
+    if test "x$PERL" != x; then
+        ac_perl_modules_failed=0
+        for ac_perl_module in $ac_perl_modules; do
+            AC_MSG_CHECKING(for perl module $ac_perl_module)
+
+            # Would be nice to log result here, but can't rely on autoconf
+            # internals
+            $PERL "-M$ac_perl_module" -e exit > /dev/null 2>&1
+            if test $? -ne 0; then
+                AC_MSG_RESULT(no);
+                ac_perl_modules_failed=1
+            else
+                AC_MSG_RESULT(ok);
+            fi
+        done
+
+        # Run optional shell commands
+        if test "$ac_perl_modules_failed" = 0; then
+            :
+            $2
+        else
+            :
+            $3
+        fi
+    else
+        AC_MSG_WARN(could not find perl)
+    fi
+])
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/pxtools.pc.in
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/pxtools.pc.in	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/pxtools.pc.in	(revision 22073)
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@/@PACKAGE_NAME@
+
+Name: @PACKAGE_NAME@
+Description: Pan-STARRS IPP Tools Library
+Version: @VERSION@
+Requires: pslib psmodules ippdb
+Libs: -L${libdir} -lpxtools
+Cflags: -I${includedir} @PXTOOLS_CFLAGS@
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/scripts/.cvsignore
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/scripts/.cvsignore	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/scripts/.cvsignore	(revision 22073)
@@ -0,0 +1,2 @@
+Makefile
+Makefile.in
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/scripts/camtest.sh
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/scripts/camtest.sh	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/scripts/camtest.sh	(revision 22073)
@@ -0,0 +1,11 @@
+#!//bin/sh
+
+set -o verbose
+
+chiptest.sh || exit 1
+
+camtool -pendingexp || exit 1
+camtool -pendingimfile || exit 1
+
+camtool -addprocessedexp -cam_id 1 -uri file:///cam -bg 1 -bg_stdev 2 -bg_mean_stdev 3 -sigma_ra 1 -sigma_dec 2 -nastro 42 -path_base file:///foo -zp_mean 1 -zp_stdev 2 || exit 1
+camtool -addprocessedexp -cam_id 2 -uri file:///cam -bg 1 -bg_stdev 2 -bg_mean_stdev 3 -sigma_ra 1 -sigma_dec 2 -nastro 42 -path_base file:///foo -zp_mean 1 -zp_stdev 2 || exit 1
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/scripts/chiptest.sh
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/scripts/chiptest.sh	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/scripts/chiptest.sh	(revision 22073)
@@ -0,0 +1,17 @@
+#!//bin/sh
+
+set -o verbose
+
+regtest.sh || exit 1
+
+chiptool -pendingimfile || exit 1
+
+for ID in `seq 0 3`; do
+    chiptool -addprocessedimfile -chip_id 1 -class_id $ID -uri file://chipp-t10.$ID -bg 1 -bg_stdev 2 -bg_mean_stdev 3 -path_base file:///foo || exit 1
+done;
+
+for ID in `seq 0 3`; do
+    chiptool -addprocessedimfile -chip_id 2 -class_id $ID -uri file://chipp-t11.$ID -bg 1 -bg_stdev 2 -bg_mean_stdev 3 -path_base file:///foo || exit 1
+done;
+
+chiptool -pendingimfile || exit 1
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/scripts/dettest.sh
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/scripts/dettest.sh	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/scripts/dettest.sh	(revision 22073)
@@ -0,0 +1,65 @@
+#!//bin/sh
+
+set -o verbose
+
+det_id=1
+
+./regtest.sh -detrend || exit 1
+
+det_id=`dettool -definebyquery -det_type bias -filelevel fpa -select_exp_type bias -airmass_min 1 -airmass_max 10 -exp_time_min 10 -exp_time_max 30.0 -workdir file::///some/path -simple | cut -f1 -d" "` || exit 1
+
+dettool -raw || exit 1
+
+for ID in `seq 0 3` ; do
+    dettool -addprocessedimfile -det_id $det_id -exp_tag t10.1 -class_id $ID -uri file://proc-$ID -recip myrecip -bg 1 -bg_stdev 1 -bg_mean_stdev 1 || exit 1
+done;
+
+for ID in `seq 0 3` ; do
+    dettool -addprocessedimfile -det_id $det_id -exp_tag t11.2 -class_id $ID -uri file://proc-$ID -recip myrecip -bg 1 -bg_stdev 1 -bg_mean_stdev 1 || exit 1
+done;
+
+
+dettool -tostacked || exit 1
+
+for ID in `seq 0 3` ; do
+    dettool -addstacked -det_id $det_id -uri file://stacked-$ID -class_id $ID -recip myrecipe -bg 1 -bg_stdev 2 -bg_mean_stdev 3 || exit 1
+done;
+
+dettool -tonormalizedstat || exit 1
+
+for ID in `seq 0 3` ; do
+    dettool -addnormalizedstat -det_id $det_id -class_id $ID -norm 0.12345 || exit 1
+done;
+
+dettool -tonormalize || exit 1
+for ID in `seq 0 3` ; do
+    dettool -addnormalizedimfile -det_id $det_id -class_id $ID -uri file://normalized-$ID -bg 1 -bg_stdev 2 -bg_mean_stdev 3 -path_base banana1 || exit 1
+done;
+
+dettool -tonormalizedexp || exit 1
+dettool -addnormalizedexp -det_id $det_id -recip myrecipe -bg 1 -bg_stdev 2 -bg_mean_stdev 3 -path_base file://normalizedexp || exit 1
+
+dettool -toresidimfile || exit 1
+
+for ID in `seq 0 3` ; do
+    dettool -addresidimfile -det_id $det_id -exp_tag t10.1 -class_id $ID -recip myrecip -bg 1 -bg_stdev 1 -bg_mean_stdev 1 -uri file://resid-$ID || exit 1
+done;
+
+for ID in `seq 0 3` ; do
+    dettool -addresidimfile -det_id $det_id -exp_tag t11.2 -class_id $ID -recip myrecip -bg 1 -bg_stdev 1 -bg_mean_stdev 1 -uri file://resid-$ID || exit 1
+done;
+
+dettool -toresidexp || exit 1
+dettool -addresidexp -det_id $det_id -exp_tag t10.1 -recip myrecipe -bg 1 -bg_stdev 2 -bg_mean_stdev 3 -path_base jpeg1 || exit 1
+dettool -addresidexp -det_id $det_id -exp_tag t11.2 -recip myrecipe -bg 1 -bg_stdev 2 -bg_mean_stdev 3 -path_base jpeg1 -reject || exit 1
+
+dettool -residdetrun || exit 1
+dettool -residexp || exit 1
+dettool -updateresidexp -det_id $det_id -iteration 0 -recip yourrecipe || exit 1
+dettool -updateresidexp -det_id $det_id -iteration 0 -exp_tag t11.2 -reject || exit 1
+dettool -updateresidexp -det_id $det_id -iteration 0 -exp_tag t11.2 || exit 1
+
+dettool -adddetrunsummary -det_id $det_id -iteration 0 -bg 1 -bg_stdev 2 -bg_mean_stdev 3 -accept || exit 1
+dettool -residexp || exit 1
+
+dettool -updatedetrun -det_id $det_id -state stop || exit 1
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/scripts/difftest.sh
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/scripts/difftest.sh	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/scripts/difftest.sh	(revision 22073)
@@ -0,0 +1,16 @@
+#!//bin/sh
+
+set -o verbose
+
+warptest.sh || exit 1
+
+
+difftool -definerun -workdir file:///tmp/diff -skycell_id foo -tess_id bar || exit 1
+difftool -addinputskyfile -diff_id 1 -warp_id 1 -skycell_id foo1 -tess_id bar -kind warped -template || exit 1
+difftool -addinputskyfile -diff_id 1 -warp_id 1 -skycell_id foo2 -tess_id bar -kind warped || exit 1
+difftool -updaterun -state run -diff_id 1 || exit 1
+difftool -todiffskyfile || exit 1
+difftool -inputskyfile || exit 1
+difftool -adddiffskyfile -diff_id 1 -uri file:///tmp/diff/skyfile -bg 1 -bg_stdev 2 || exit 1
+difftool -diffskyfile -diff_id 1 || exit 1
+difftool -updaterun -state stop -diff_id 1 || exit 1
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/scripts/mapfile.txt
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/scripts/mapfile.txt	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/scripts/mapfile.txt	(revision 22073)
@@ -0,0 +1,19 @@
+warpSkyCellMap MULTI
+
+warpSkyCellMap METADATA
+    warp_id         S64     1
+    cam_id          S64     1
+    skycell_id      STR     foo1
+    tess_id         STR     bar
+    class_id        STR     quix1
+    fault           S16     0
+END
+
+warpSkyCellMap METADATA
+    warp_id         S64     1
+    cam_id          S64     1
+    skycell_id      STR     foo2
+    tess_id         STR     bar
+    class_id        STR     quix2
+    fault           S16     0
+END
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/scripts/regtest.sh
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/scripts/regtest.sh	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/scripts/regtest.sh	(revision 22073)
@@ -0,0 +1,40 @@
+#!//bin/sh
+
+set -o verbose
+
+inject="pxinject"
+regtool="regtool"
+
+echo -e "YES\nipp\n\n" | pxadmin -recreate || exit 1
+
+exp_tag1=`$inject -newExp -exp_id t10 -inst gpc -telescope ps1 -imfiles 4 -workdir file::///some/path -simple` || exit 1
+
+for ID in `seq 0 3`; do
+    $inject -newImfile -exp_tag $exp_tag1 -class OTA -class_id $ID -uri file://$ID || exit 1
+done;
+
+exp_tag2=`$inject -newExp -exp_id t11 -inst gpc -telescope ps1 -imfiles 4 -workdir file::///some/path -simple` || exit 1
+
+for ID in `seq 0 3`; do
+    $inject -newImfile -exp_tag $exp_tag2 -class OTA -class_id $ID -uri file://$ID || exit 1
+done;
+
+$regtool -pendingimfile || exit 1
+
+for ID in `seq 0 3`; do
+    $regtool -addprocessedimfile -exp_tag $exp_tag1 -class_id $ID -filter r -airmass 10 -ra 1 -decl 2 -exp_time 0 -bg 1 -bg_stdev 1 -bg_mean_stdev 10 -alt 10 -az 10 -ccd_temp 10 -posang 10 -object dog -dateobs 2006-10-20T10:10:10Z || exit 1
+done;
+
+$regtool -pendingexp|| exit 1
+
+$regtool -addprocessedexp -exp_tag $exp_tag1 -filelevel OTA -filter r -airmass 10 -ra 1 -decl 2 -exp_type bias -exp_time 0 -bg 10 -bg_stdev 1 -bg_mean_stdev 10 -alt 10 -az 10 -ccd_temp 45 -posang 10 -object dog -dateobs "2006-10-20T10:10:10Z" -label foobar $* || exit 1
+
+$regtool -pendingimfile || exit 1
+
+for ID in `seq 0 3`; do
+    $regtool -addprocessedimfile -exp_tag $exp_tag2 -exp_type object -class_id $ID -filter r -airmass 10 -ra 1 -decl 2 -exp_time 0 -bg 1 -bg_stdev 1 -bg_mean_stdev 10 -alt 10 -az 10 -ccd_temp 10 -posang 10 -object dog -dateobs 2006-10-20T10:10:10Z || exit 1
+done;
+
+$regtool -pendingexp|| exit 1
+
+$regtool -addprocessedexp -exp_tag $exp_tag2 -filelevel OTA -filter r -airmass 11 -ra 1 -decl 2 -exp_type bias -exp_time 0 -bg 11 -bg_stdev 1 -bg_mean_stdev 11 -alt 11 -az 11 -ccd_temp 45 -posang 11 -object dog -dateobs 2006-10-20T10:10:10Z -label foobar $* || exit 1
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/scripts/warptest.sh
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/scripts/warptest.sh	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/scripts/warptest.sh	(revision 22073)
@@ -0,0 +1,31 @@
+#!//bin/sh
+
+set -o verbose
+
+camtest.sh || exit 1
+
+warptool -definerun -mode warp -workdir file:///foo || exit 1
+
+warptool -addinputexp -warp_id 1 -cam_id 1 || exit 1
+warptool -addinputexp -warp_id 1 -cam_id 2 || exit 1
+
+warptool -updaterun -warp_id 1 -state run || exit 1
+
+warptool -exp -warp_id 1 || exit 1
+
+warptool -imfile -warp_id 1 || exit 1
+
+warptool -tooverlap -warp_id 1 || exit 1
+
+warptool -addoverlap -mapfile mapfile.txt || exit 1
+
+warptool -scmap || exit 1
+
+warptool -towarped || exit 1
+
+warptool -addwarped -warp_id 1 -skycell_id foo1 -tess_id bar -uri file:///tmp/foo -bg 1 -bg_stdev 2 || exit 1
+warptool -addwarped -warp_id 1 -skycell_id foo2 -tess_id bar -uri file:///tmp/foo -bg 1 -bg_stdev 2 || exit 1
+
+warptool -warped -warp_id 1 || exit 1
+
+#warptool -updaterun -warp_id 1 -state stop || exit 1
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/.cvsignore
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/.cvsignore	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/.cvsignore	(revision 22073)
@@ -0,0 +1,2 @@
+Makefile.in
+Makefile
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/Makefile.am
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/Makefile.am	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/Makefile.am	(revision 22073)
@@ -0,0 +1,33 @@
+dist_pkgdata_DATA = \
+	camtool_find_pendingexp.sql \
+	camtool_find_pendingimfile.sql \
+	camtool_find_processedexp.sql \
+	camtool_queue_chip_id.sql \
+	chiptool_completely_processed_exp.sql \
+	chiptool_find_rawexp.sql \
+	chiptool_find_unprocessed_imfile.pl \
+	chiptool_pendingimfile.sql \
+	chiptool_processedimfile.sql \
+	chiptool_queuerawexp.sql \
+	chiptool_queuerawimfile.sql \
+	detselect_search.sql \
+	detselect_select.sql \
+	dettool_find_completed_runs.sql \
+	difftool_inputskyfile.sql \
+	difftool_skyfile.sql \
+	difftool_todiffskyfile.sql \
+	regtool_find_unprocessed_exp.sql \
+	regtool_find_unprocessed_imfile.sql \
+	regtool_pendingexp.sql \
+	regtool_pendingimfile.sql \
+	regtool_processedexp.sql \
+	regtool_processedimfile.sql \
+	stacktool_inputskyfile.sql \
+	stacktool_sumskyfile.sql \
+	stacktool_tosum.sql \
+	warptool_exp.sql \
+	warptool_imfile.sql \
+	warptool_scmap.sql \
+	warptool_tooverlap.sql \
+	warptool_towarped.sql \
+	warptool_warped.sql
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/camtool_find_pendingexp.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/camtool_find_pendingexp.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/camtool_find_pendingexp.sql	(revision 22073)
@@ -0,0 +1,22 @@
+-- this query is used by both camtool -pendingexp & camtool -addprocessedexp it
+-- does a little more work then is necessary for -addprocessed but it seems
+-- "cleaner" to use the same query for both cases 
+SELECT
+    camPendingExp.*,
+    rawExp.exp_tag,
+    rawExp.exp_id,
+    rawExp.camera,
+    rawExp.telescope,
+    rawExp.filelevel
+FROM camPendingExp
+JOIN chipProcessedExp
+    USING(chip_id)
+JOIN rawExp
+    ON chipProcessedExp.exp_tag = rawExp.exp_tag
+LEFT JOIN camProcessedExp
+    ON camPendingExp.cam_id = camProcessedExp.cam_id
+LEFT JOIN camMask
+    ON camPendingExp.label = camMask.label
+WHERE
+    camProcessedExp.cam_id IS NULL
+    AND camMask.label IS NULL
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/camtool_find_pendingimfile.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/camtool_find_pendingimfile.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/camtool_find_pendingimfile.sql	(revision 22073)
@@ -0,0 +1,31 @@
+SELECT * FROM 
+     -- the subselect is so where criteria can be specified without knowing
+     -- which table the field came from
+    (SELECT
+        camPendingExp.*,
+        chipProcessedExp.exp_tag,
+        chipProcessedExp.guide_id,
+        chipProcessedImfile.class_id,
+        chipProcessedImfile.uri,
+        chipProcessedImfile.bg,
+        chipProcessedImfile.bg_stdev,
+        chipProcessedImfile.bg_mean_stdev,
+        chipProcessedImfile.path_base,
+        rawExp.exp_id,
+        rawExp.camera,
+        rawExp.telescope,
+        rawExp.filelevel
+    FROM camPendingExp
+    JOIN chipProcessedExp
+        USING(chip_id)
+    JOIN chipProcessedImfile
+        USING(chip_id)
+    JOIN rawExp
+        ON chipProcessedExp.exp_tag = rawExp.exp_tag
+    LEFT JOIN camProcessedExp
+        ON camPendingExp.cam_id = camProcessedExp.cam_id
+    LEFT JOIN camMask
+        ON camPendingExp.label = camMask.label
+    WHERE
+        camProcessedExp.cam_id IS NULL
+        AND camMask.label IS NULL) as foo
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/camtool_find_processedexp.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/camtool_find_processedexp.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/camtool_find_processedexp.sql	(revision 22073)
@@ -0,0 +1,13 @@
+SELECT
+    camProcessedExp.*,
+    rawExp.exp_id,
+    rawExp.camera,
+    rawExp.telescope,
+    rawExp.filelevel
+FROM camProcessedExp
+JOIN chipProcessedExp
+    USING(chip_id)
+JOIN rawExp
+    chipProcessedExp.exp_tag = rawExp.exp_tag
+WHERE -- bogus condition so there is a pre-existing where to append to
+    camProcessedExp.cam_id IS NOT NULL
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/camtool_queue_chip_id.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/camtool_queue_chip_id.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/camtool_queue_chip_id.sql	(revision 22073)
@@ -0,0 +1,14 @@
+-- camtool only operates on exposures so we can safely queue more then one at a
+-- time without worrying about losing the track of the generated cam_id
+INSERT INTO camPendingExp
+    SElECT
+        0,              -- cam_id
+        chip_id,        -- chip_id
+        '%s',           -- workdir
+        '%s',           -- label
+        '%s',           -- recipe
+        '%s',           -- expgroup
+        '%s'            -- dvodb 
+    FROM chipProcessedExp
+    WHERE
+        chipProcessedExp.chip_id = %lld
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/chiptool_completely_processed_exp.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/chiptool_completely_processed_exp.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/chiptool_completely_processed_exp.sql	(revision 22073)
@@ -0,0 +1,25 @@
+-- select * from chipPendingExp
+-- where exp_tag is not in chipProcessedExp
+-- where exp_tag is not in chipPendingImfile
+-- where the number of entries in chipProccessedImfile matches the .imfiles
+-- entry in rawExp
+SELECT DISTINCT
+    chipPendingExp.*,
+    rawExp.imfiles,
+    chipProcessedImfile.class_id
+FROM chipPendingExp
+JOIN rawExp
+    ON chipPendingExp.exp_tag = rawExp.exp_tag
+LEFT JOIN chipProcessedExp
+    ON chipPendingExp.chip_id = chipProcessedExp.chip_id
+LEFT JOIN chipPendingImfile
+    ON chipPendingExp.chip_id = chipPendingImfile.chip_id
+LEFT JOIN chipProcessedImfile
+    ON chipPendingExp.chip_id = chipProcessedImfile.chip_id
+WHERE
+    chipProcessedExp.chip_id IS NULL
+    AND chipPendingImfile.chip_id IS NULL
+    AND chipProcessedImfile.chip_id IS NOT NULL
+GROUP BY
+    chipPendingExp.chip_id
+HAVING rawExp.imfiles = COUNT(chipProcessedImfile.class_id)
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/chiptool_find_rawexp.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/chiptool_find_rawexp.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/chiptool_find_rawexp.sql	(revision 22073)
@@ -0,0 +1,7 @@
+-- this query is used to find potental rawExps to be queued for chiptool
+-- processeing
+SELECT
+    exp_tag
+FROM rawExp
+WHERE
+    rawExp.fault = 0
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/chiptool_find_unprocessed_imfile.pl
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/chiptool_find_unprocessed_imfile.pl	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/chiptool_find_unprocessed_imfile.pl	(revision 22073)
@@ -0,0 +1,8 @@
+SELECT DISTINCT
+   chipPendingImfile.*
+FROM chipPendingImfile
+LEFT JOIN chipProcessedImfile
+    USING(chip_id, class_id)
+WHERE
+    chipProcessedImfile.chip_id IS NULL
+    AND chipProcessedImfile.class_id IS NULL
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/chiptool_pendingimfile.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/chiptool_pendingimfile.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/chiptool_pendingimfile.sql	(revision 22073)
@@ -0,0 +1,17 @@
+SELECT
+    chipPendingExp.*,
+    chipPendingImfile.class_id,
+    chipPendingImfile.uri,
+    rawExp.exp_id,
+    rawExp.camera,
+    rawExp.telescope,
+    rawExp.filelevel
+FROM chipPendingImfile
+JOIN chipPendingExp
+    USING(chip_id)
+JOIN rawExp
+    ON chipPendingExp.exp_tag = rawExp.exp_tag
+LEFT JOIN chipMask
+    ON chipPendingExp.label = chipMask.label
+WHERE
+    chipMask.label IS NULL
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/chiptool_processedimfile.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/chiptool_processedimfile.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/chiptool_processedimfile.sql	(revision 22073)
@@ -0,0 +1,21 @@
+SELECT
+    chipProcessedExp.*,
+    chipProcessedImfile.class_id,
+    chipProcessedImfile.uri,
+    chipProcessedImfile.bg,
+    chipProcessedImfile.bg_stdev,
+    chipProcessedImfile.bg_mean_stdev,
+    chipProcessedImfile.path_base,
+    rawExp.exp_id,
+    rawExp.camera,
+    rawExp.telescope,
+    rawExp.filelevel
+FROM chipProcessedImfile
+JOIN chipProcessedExp
+    USING(chip_id)
+JOIN rawExp
+    ON chipProcessedExp.exp_tag = rawExp.exp_tag
+WHERE
+-- bogus test; just here so there there is a 'WHERE' stmt to append conditionals too
+    chipProcessedImfile.exp_tag is NOT NULL
+
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/chiptool_queuerawexp.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/chiptool_queuerawexp.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/chiptool_queuerawexp.sql	(revision 22073)
@@ -0,0 +1,16 @@
+-- we can only enqueue one exp_tag at a time or the generated chip_id of all
+-- put the last one inserted will be lost
+INSERT INTO chipPendingExp
+    SElECT
+        0,              -- chip_id
+        exp_tag,        -- exp_tag
+        0xdeadbeef,     -- guide_id
+        '%s',           -- workdir
+        '%s',           -- label
+        '%s',           -- recipe
+        '%s',           -- expgroup
+        '%s'            -- dvodb 
+   FROM rawExp
+   WHERE
+        rawExp.fault = 0
+        AND rawExp.exp_tag = '%s'
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/chiptool_queuerawimfile.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/chiptool_queuerawimfile.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/chiptool_queuerawimfile.sql	(revision 22073)
@@ -0,0 +1,11 @@
+INSERT INTO chipPendingImfile
+    SELECT
+        chipPendingExp.chip_id,
+        rawImfile.class_id,
+        rawImfile.uri
+    FROM chipPendingExp
+    JOIN rawImfile
+        USING(exp_tag)
+    WHERE
+        rawImfile.fault = 0
+        AND chipPendingExp.chip_id = LAST_INSERT_ID()
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/detselect_search.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/detselect_search.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/detselect_search.sql	(revision 22073)
@@ -0,0 +1,15 @@
+SELECT DISTINCT
+    detRunSummary.det_id,
+    detRunSummary.iteration,
+    detRun.filelevel
+FROM detRun
+JOIN detRunSummary
+    USING(det_id)
+JOIN detInputExp
+    USING(det_id)
+JOIN rawExp
+    ON detInputExp.exp_tag = rawExp.exp_tag
+WHERE
+    detRun.state = 'stop'
+   AND detRun.mode  = 'master'
+   AND detRunSummary.accept = 1
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/detselect_select.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/detselect_select.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/detselect_select.sql	(revision 22073)
@@ -0,0 +1,12 @@
+SELECT DISTINCT
+    detNormalizedImfile.*
+FROM detNormalizedImfile
+JOIN detRun
+    USING(det_id)
+JOIN detRunSummary
+    ON detNormalizedImfile.det_id = detRunSummary.det_id
+    AND detNormalizedImfile.iteration = detRunSummary.iteration
+WHERE
+    detRun.state = 'stop'
+    AND detRun.mode  = 'master'
+    AND detRunSummary.accept = 1
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/dettool_find_completed_runs.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/dettool_find_completed_runs.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/dettool_find_completed_runs.sql	(revision 22073)
@@ -0,0 +1,26 @@
+SELECT DISTINCT
+   det_id,
+   iteration
+FROM
+    (SELECT DISTINCT
+        detRun.det_id,
+        detRun.iteration,
+        detInputExp.exp_tag
+    FROM detRun
+        LEFT JOIN detInputExp
+        ON detRun.det_id = detInputExp.det_id
+        AND detRun.iteration = detInputExp.iteration
+    LEFT JOIN rawExp
+        ON detInputExp.exp_tag = rawExp.exp_tag
+    LEFT JOIN detResidExp
+        ON detRun.det_id = detResidExp.det_id
+        AND detRun.iteration = detResidExp.iteration
+        AND detInputExp.exp_tag = detResidExp.exp_tag
+   WHERE
+        detRun.state = 'run'
+   GROUP BY
+        detRun.det_id,
+        detRun.iteration
+    HAVING
+        COUNT(detResidExp.exp_tag) = COUNT(detInputExp.exp_tag)
+    ) AS residdetrun
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/difftool_inputskyfile.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/difftool_inputskyfile.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/difftool_inputskyfile.sql	(revision 22073)
@@ -0,0 +1,22 @@
+SELECT
+    warpSkyfile.*,
+    diffInputSkyfile.template,
+    rawExp.camera
+FROM diffRun
+JOIN diffInputSkyfile
+    USING(diff_id)
+JOIN warpSkyfile
+    ON  diffInputSkyfile.warp_id    = warpSkyfile.warp_id
+    AND diffInputSkyfile.skycell_id = warpSkyfile.skycell_id
+    AND diffInputSkyfile.tess_id    = warpSkyfile.tess_id
+JOIN warpInputExp
+    ON diffInputSkyfile.warp_id = warpInputExp.warp_id
+JOIN camProcessedExp
+    ON warpInputExp.cam_id = camProcessedExp.cam_id
+JOIN chipProcessedExp
+    ON camProcessedExp.chip_id = chipProcessedExp.chip_id
+JOIN rawExp
+    ON chipProcessedExp.exp_tag = rawExp.exp_tag
+WHERE
+    diffRun.state = 'run'
+
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/difftool_skyfile.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/difftool_skyfile.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/difftool_skyfile.sql	(revision 22073)
@@ -0,0 +1,10 @@
+SELECT
+    diffRun.skycell_id,
+    diffRun.tess_id,
+    diffSkyfile.*
+FROM diffRun
+JOIN diffSkyfile
+    USING(diff_id)
+WHERE
+    diffRun.state = 'run'
+
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/difftool_todiffskyfile.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/difftool_todiffskyfile.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/difftool_todiffskyfile.sql	(revision 22073)
@@ -0,0 +1,16 @@
+SELECT DISTINCT
+    diffRun.diff_id,
+    diffRun.workdir
+FROM diffRun
+JOIN diffInputSkyfile
+    USING(diff_id)
+JOIN warpSkyfile
+    ON  diffInputSkyfile.warp_id    = warpSkyfile.warp_id
+    AND diffInputSkyfile.skycell_id = warpSkyfile.skycell_id
+    AND diffInputSkyfile.tess_id    = warpSkyfile.tess_id
+LEFT JOIN diffSkyfile
+    ON diffInputSkyfile.diff_id = diffSkyfile.diff_id
+WHERE
+  diffRun.state = 'run'
+  AND diffSkyfile.diff_id IS NULL
+
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/regtool_find_unprocessed_exp.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/regtool_find_unprocessed_exp.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/regtool_find_unprocessed_exp.sql	(revision 22073)
@@ -0,0 +1,15 @@
+SELECT
+   newExp.*
+FROM newExp
+LEFT JOIN newImfile
+   USING(exp_tag)
+LEFT JOIN rawExp
+   USING(exp_tag)
+WHERE
+    newExp.exp_tag IS NOT NULL
+    AND newImfile.exp_tag IS NULL
+    AND rawExp.exp_tag IS NULL
+    AND newExp.imfiles =
+    (SELECT COUNT(exp_tag) FROM rawImfile
+        WHERE rawImfile.exp_tag = newExp.exp_tag)
+    AND newExp.exp_tag = '%s'
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/regtool_find_unprocessed_imfile.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/regtool_find_unprocessed_imfile.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/regtool_find_unprocessed_imfile.sql	(revision 22073)
@@ -0,0 +1,8 @@
+SELECT
+    *
+FROM
+    (SELECT newImfile.* FROM newImfile
+        LEFT JOIN newExp USING(exp_tag)
+        LEFT JOIN rawExp USING(exp_tag)
+        WHERE newExp.exp_tag IS NOT NULL
+        AND rawExp.exp_tag IS NULL) as Foo
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/regtool_pendingexp.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/regtool_pendingexp.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/regtool_pendingexp.sql	(revision 22073)
@@ -0,0 +1,15 @@
+SELECT
+    newExp.*
+FROM newExp
+LEFT JOIN newImfile
+    USING(exp_tag)
+LEFT JOIN rawExp
+    USING(exp_tag)
+WHERE
+    newImfile.exp_tag IS NULL
+    AND rawExp.exp_tag IS NULL
+    AND newExp.imfiles =
+    (SELECT COUNT(exp_tag) FROM rawImfile
+        WHERE
+            rawImfile.exp_tag = newExp.exp_tag
+            AND rawImfile.fault = 0)
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/regtool_pendingimfile.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/regtool_pendingimfile.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/regtool_pendingimfile.sql	(revision 22073)
@@ -0,0 +1,11 @@
+SELECT
+   newImfile.*,
+   newExp.workdir
+FROM newImfile
+LEFT JOIN newExp
+    USING(exp_tag)
+LEFT JOIN rawExp
+    USING(exp_tag)
+WHERE
+    newExp.exp_tag is NOT NULL
+    AND rawExp.exp_tag IS NULL
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/regtool_processedimfile.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/regtool_processedimfile.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/regtool_processedimfile.sql	(revision 22073)
@@ -0,0 +1,10 @@
+SELECT
+    rawImfile.*,
+    newExp.exp_id,
+    newExp.camera,
+    newExp.telescope
+FROM rawImfile
+JOIN newExp
+    USING(exp_tag)
+-- bogus conditional so there is a where clause to append to
+WHERE rawImfile.exp_tag is NOT NULL
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/stacktool_inputskyfile.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/stacktool_inputskyfile.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/stacktool_inputskyfile.sql	(revision 22073)
@@ -0,0 +1,20 @@
+ SELECT
+    warpSkyfile.*,
+    rawExp.camera
+FROM stackRun
+JOIN stackInputSkyfile
+    USING(stack_id)
+JOIN warpSkyfile
+    ON  stackInputSkyfile.warp_id = warpSkyfile.warp_id
+    AND stackRun.skycell_id       = warpSkyfile.skycell_id
+    AND stackRun.tess_id          = warpSkyfile.tess_id
+JOIN warpInputExp
+    ON stackInputSkyfile.warp_id = warpInputExp.warp_id
+JOIN camProcessedExp
+    ON warpInputExp.cam_id = camProcessedExp.cam_id
+JOIN chipProcessedExp
+    ON camProcessedExp.chip_id = chipProcessedExp.chip_id
+JOIN rawExp
+    ON chipProcessedExp.exp_tag = rawExp.exp_tag
+WHERE
+    stackRun.state = 'run'
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/stacktool_sumskyfile.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/stacktool_sumskyfile.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/stacktool_sumskyfile.sql	(revision 22073)
@@ -0,0 +1,7 @@
+SELECT
+    stackSumSkyfile.*
+FROM stackRun
+JOIN stackSumSkyfile
+    USING(stack_id)
+WHERE
+    stackRun.state = 'run'
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/stacktool_tosum.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/stacktool_tosum.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/stacktool_tosum.sql	(revision 22073)
@@ -0,0 +1,9 @@
+SELECT
+    stackRun.stack_id,
+    stackRun.workdir
+FROM stackRun
+LEFT JOIN stackSumSkyfile
+    USING(stack_id)
+WHERE
+    stackRun.state = 'run'
+    AND stackSumSkyfile.stack_id IS NULL
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/warptool_exp.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/warptool_exp.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/warptool_exp.sql	(revision 22073)
@@ -0,0 +1,10 @@
+SELECT
+    camProcessedExp.*
+FROM warpRun
+JOIN warpInputExp
+    USING(warp_id)
+JOIN camProcessedExp
+    ON warpInputExp.cam_id = camProcessedExp.cam_id
+WHERE
+    warpRun.state = 'run'
+    AND camProcessedExp.fault = 0
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/warptool_imfile.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/warptool_imfile.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/warptool_imfile.sql	(revision 22073)
@@ -0,0 +1,15 @@
+SELECT
+    rawImfile.*,
+    warpInputExp.cam_id
+FROM warpRun
+JOIN warpInputExp
+    USING(warp_id)
+JOIN camProcessedExp
+    ON warpInputExp.cam_id = camProcessedExp.cam_id
+JOIN chipProcessedExp
+    ON camProcessedExp.chip_id = chipProcessedExp.chip_id
+JOIN rawImfile -- is there any reason not to refer back to rawimfiles?
+    ON chipProcessedExp.exp_tag = rawImfile.exp_tag
+WHERE
+    warpRun.state = 'run'
+
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/warptool_scmap.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/warptool_scmap.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/warptool_scmap.sql	(revision 22073)
@@ -0,0 +1,14 @@
+SELECT
+    warpSkyCellMap.*,
+    chipProcessedImfile.uri
+FROM warpRun
+JOIN warpSkyCellMap
+    USING(warp_id)
+JOIN camProcessedExp
+    ON warpSkyCellMap.cam_id = camProcessedExp.cam_id
+JOIN chipProcessedImfile
+    ON camProcessedExp.chip_id = chipProcessedImfile.chip_id
+    AND warpSkyCellMap.class_id = chipProcessedImfile.class_id
+WHERE
+    warpRun.state = 'run'
+
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/warptool_tooverlap.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/warptool_tooverlap.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/warptool_tooverlap.sql	(revision 22073)
@@ -0,0 +1,22 @@
+SELECT
+    warpRun.warp_id,
+    camProcessedExp.cam_id,
+    warpRun.workdir,
+    rawExp.camera
+FROM warpRun
+JOIN warpInputExp
+    USING(warp_id)
+JOIN camProcessedExp
+    ON warpInputExp.cam_id = camProcessedExp.cam_id
+JOIN chipProcessedExp
+    ON camProcessedExp.chip_id = chipProcessedExp.chip_id
+JOIN rawExp
+    ON chipProcessedExp.exp_tag = rawExp.exp_tag
+LEFT JOIN warpSkyCellMap
+    ON warpInputExp.warp_id = warpSkyCellMap.warp_id
+    AND warpInputExp.cam_id = warpSkyCellMap.cam_id
+WHERE
+    warpRun.state = 'run'
+    AND camProcessedExp.fault = 0
+    AND warpSkyCellMap.warp_id IS NULL
+    AND warpSkyCellMap.cam_id IS NULL
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/warptool_towarped.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/warptool_towarped.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/warptool_towarped.sql	(revision 22073)
@@ -0,0 +1,23 @@
+SELECT
+    warpSkyCellMap.*,
+    rawExp.camera,
+    warpRun.workdir
+FROM warpRun
+JOIN warpSkyCellMap
+    USING(warp_id)
+JOIN camProcessedExp
+    ON warpSkyCellMap.cam_id = camProcessedExp.cam_id
+JOIN chipProcessedExp
+    ON camProcessedExp.chip_id = chipProcessedExp.chip_id
+JOIN rawExp
+    ON chipProcessedExp.exp_tag = rawExp.exp_tag
+LEFT JOIN warpSkyfile
+    ON warpRun.warp_id = warpSkyfile.warp_id
+    AND warpSkyCellMap.skycell_id = warpSkyfile.skycell_id
+    AND warpSkyCellMap.tess_id = warpSkyfile.tess_id
+WHERE
+    warpRun.state = 'run'
+    AND warpSkyfile.warp_id IS NULL
+    AND warpSkyfile.skycell_id IS NULL
+    AND warpSkyfile.tess_id IS NULL
+
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/share/warptool_warped.sql
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/share/warptool_warped.sql	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/share/warptool_warped.sql	(revision 22073)
@@ -0,0 +1,7 @@
+SELECT
+    warpSkyfile.*
+FROM warpRun
+JOIN warpSkyfile
+    USING(warp_id)
+WHERE
+    warpRun.state = 'run'
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/.cvsignore
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/.cvsignore	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/.cvsignore	(revision 22073)
@@ -0,0 +1,27 @@
+.deps
+.gdb_history
+.libs
+Makefile
+Makefile.in
+config.h
+config.h.in
+stamp-h1
+*.la
+*.lo
+pxtoolsErrorCodes.c
+pxtoolsErrorCodes.h
+pxadmin
+pxinject
+pztool
+pzgetexp
+pzgetimfiles
+regtool
+guidetool
+chiptool
+camtool
+warptool
+difftool
+stacktool
+dettool
+detselect
+pxdata.c
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/Makefile.am
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/Makefile.am	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/Makefile.am	(revision 22073)
@@ -0,0 +1,165 @@
+
+bin_PROGRAMS = \
+	detselect \
+	dettool \
+	regtool \
+	chiptool \
+	camtool \
+	warptool \
+	difftool \
+	stacktool \
+	pxadmin \
+	pxinject \
+	pzgetexp \
+	pzgetimfiles \
+	pztool 
+
+include_HEADERS = \
+	pxtoolsErrorCodes.h \
+	pxtools.h
+
+noinst_HEADERS = \
+	dettool.h \
+	detselect.h \
+	regtool.h \
+	guidetool.h \
+	chiptool.h \
+	camtool.h \
+	warptool.h \
+	difftool.h \
+	stacktool.h \
+	pxadmin.h \
+	pxinject.h \
+	pxdata.h \
+	pxio.h \
+	pxtag.h \
+	pzgetexp.h \
+	pzgetimfiles.h \
+	pztool.h 
+
+lib_LTLIBRARIES = libpxtools.la
+libpxtools_la_CFLAGS	= $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+libpxtools_la_LIBS      = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS)
+libpxtools_la_LDFLAGS   = -release $(PACKAGE_VERSION)
+libpxtools_la_SOURCES   = \
+	pxtoolsErrorCodes.c \
+	pxerrors.c \
+	pxconfig.c \
+	pxfault.c \
+	pxtables.c \
+	pxdata.c \
+	pxio.c \
+	pxtag.c \
+	chipqueue.c \
+	camqueue.c
+
+# for pxtools.h
+AM_CPPFLAGS = -I$(top_srcdir)/src$
+
+pztool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS) 
+pztool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+pztool_SOURCES = \
+    pztool.c \
+    pztoolConfig.c
+
+regtool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+regtool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+regtool_SOURCES = \
+    regtool.c \
+    regtoolConfig.c
+
+guidetool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS) 
+guidetool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+guidetool_SOURCES = \
+    guidetool.c \
+    guidetoolConfig.c
+
+chiptool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+chiptool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+chiptool_SOURCES = \
+    chiptool.c \
+    chiptoolConfig.c
+
+camtool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+camtool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+camtool_SOURCES = \
+    camtool.c \
+    camtoolConfig.c
+
+warptool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+warptool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+warptool_SOURCES = \
+    warptool.c \
+    warptoolConfig.c
+
+difftool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+difftool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+difftool_SOURCES = \
+    difftool.c \
+    difftoolConfig.c
+
+stacktool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+stacktool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+stacktool_SOURCES = \
+    stacktool.c \
+    stacktoolConfig.c
+
+pxadmin_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+pxadmin_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+pxadmin_SOURCES = \
+    pxadmin.c \
+    pxadminConfig.c
+
+pzgetexp_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+pzgetexp_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+pzgetexp_SOURCES = \
+    pzgetexp.c \
+    pzgetexpConfig.c
+
+pzgetimfiles_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+pzgetimfiles_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+pzgetimfiles_SOURCES = \
+    pzgetimfiles.c \
+    pzgetimfilesConfig.c
+
+dettool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+dettool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+dettool_SOURCES = \
+    dettool.c \
+    dettoolConfig.c
+
+detselect_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+detselect_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+detselect_SOURCES = \
+    detselect.c \
+    detselectConfig.c
+
+pxinject_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+pxinject_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+pxinject_SOURCES = \
+    pxinject.c \
+    pxinjectConfig.c
+
+clean-local:
+	-rm -f TAGS
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
+
+# Error codes.
+BUILT_SOURCES = pxtoolsErrorCodes.h pxtoolsErrorCodes.c
+CLEANFILES = pxtoolsErrorCodes.h pxtoolsErrorCodes.c
+EXTRA_DIST = pxtoolsErrorCodes.dat pxtoolsErrorCodes.c.in pxtoolsErrorCodes.h.in
+
+pxtoolsErrorCodes.h : pxtoolsErrorCodes.dat pxtoolsErrorCodes.h.in
+	$(ERRORCODES) --data=pxtoolsErrorCodes.dat --outdir=. pxtoolsErrorCodes.h
+
+pxtoolsErrorCodes.c : pxtoolsErrorCodes.dat pxtoolsErrorCodes.c.in pxtoolsErrorCodes.h
+	$(ERRORCODES) --data=pxtoolsErrorCodes.dat --outdir=. pxtoolsErrorCodes.c
+
+BUILT_SOURCES += pxdata.c
+EXTRA_DIST += pxdata.c.template
+
+pxdata.c: $(srcdir)/pxdata.c.template
+	$(PERL) -pe 's|PKGDATADIR|$(pkgdatadir)|' $? > $@
+
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/camqueue.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/camqueue.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/camqueue.c	(revision 22073)
@@ -0,0 +1,72 @@
+/*
+ * camqueue.c
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "pxtools.h"
+#include "pxdata.h"
+#include "camtool.h"
+
+bool camQueueChipID(pxConfig *config,
+                    psS64 chip_id,
+                    psString workdir,
+                    psString label,
+                    psString recipe,
+                    psString expgroup,
+                    psString dvodb)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // load the SQL to enqueue our exp_tags from disk once
+    static psString query = NULL;
+    if (!query) {
+        query = pxDataGet("camtool_queue_chip_id.sql");
+        psMemSetPersistent(query, true);
+        if (!query) {
+            psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+            return false;
+        }
+    }
+
+    // queue the exp
+    // XXX chip_id is being cast here work around psS64 have a different type
+    // different on 32/64
+    if (!p_psDBRunQuery(config->dbh, query,
+                workdir  ? workdir  : "NULL",
+                label    ? label    : "NULL",
+                recipe   ? recipe   : "NULL",
+                expgroup ? expgroup : "NULL",
+                dvodb    ? dvodb    : "NULL",
+                (long long)chip_id
+    )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // just to be safe, we should have changed at least one row
+    if (psDBAffectedRows(config->dbh) < 1) {
+        psError(PS_ERR_UNKNOWN, false,
+                "no rows affected - should have changed at least one row");
+        return false;
+    }
+
+    return true;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/camtool.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/camtool.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/camtool.c	(revision 22073)
@@ -0,0 +1,832 @@
+/*
+ * camtool.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <inttypes.h>
+
+#include "pxtools.h"
+#include "camtool.h"
+
+static bool queueMode(pxConfig *config);
+static bool pendingexpMode(pxConfig *config);
+static bool pendingimfileMode(pxConfig *config);
+static bool addprocessedexpMode(pxConfig *config);
+static bool processedexpMode(pxConfig *config);
+static bool updateprocessedexpMode(pxConfig *config);
+static bool blockMode(pxConfig *config);
+static bool maskedMode(pxConfig *config);
+static bool unblockMode(pxConfig *config);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = camtoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(CAMTOOL_MODE_QUEUE,                queueMode);
+        MODECASE(CAMTOOL_MODE_PENDINGEXP,           pendingexpMode);
+        MODECASE(CAMTOOL_MODE_PENDINGIMFILE,        pendingimfileMode);
+        MODECASE(CAMTOOL_MODE_ADDPROCESSEDEXP,      addprocessedexpMode);
+        MODECASE(CAMTOOL_MODE_PROCESSEDEXP,         processedexpMode);
+        MODECASE(CAMTOOL_MODE_UPDATEPROCESSEDEXP,   updateprocessedexpMode);
+        MODECASE(CAMTOOL_MODE_BLOCK,                blockMode);
+        MODECASE(CAMTOOL_MODE_MASKED,               maskedMode);
+        MODECASE(CAMTOOL_MODE_UNBLOCK,              unblockMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+
+#define ADDPARAMSTR(from, to, name) \
+{ \
+    bool status = false; \
+    psString str = psMetadataLookupStr(&status, from, "-" name); \
+    if (!status) { \
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -" name); \
+        return false; \
+    } \
+    if (str) { \
+        if (!psMetadataAddStr(to, PS_LIST_TAIL, name, 0, "==", str)) { \
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " name); \
+            psFree(to); \
+            return false; \
+        } \
+    } \
+}
+
+
+#define ADDPARAMF(from, to, type, name) \
+{ \
+    bool status = false; \
+    ps##type var = psMetadataLookup##type(&status, from, "-" name); \
+    if (!status) { \
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -" name); \
+        return false; \
+    } \
+    if (!isnan(var)) { \
+        if (!psMetadataAdd##type(to, PS_LIST_TAIL, #name, 0, "==", var)) { \
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " name); \
+            psFree(to); \
+            return false; \
+        } \
+    } \
+}
+
+static bool queueMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    psMetadata *where = psMetadataAlloc();
+{
+    bool status = false;
+    psString var = psMetadataLookupStr(&status, config->args, "-chip_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -chip_id");
+        return false;
+    }
+    if (var) {
+        if (!psMetadataAddS64(where, PS_LIST_TAIL, "chip_id", 0, "==", (psS64)atoll(var))) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
+            psFree(where);
+            return false;
+        }
+    }
+}
+
+    ADDPARAMSTR(config->args, where, "exp_tag");
+    ADDPARAMSTR(config->args, where, "exp_id");
+    ADDPARAMSTR(config->args, where, "inst");
+    ADDPARAMSTR(config->args, where, "telescope");
+    ADDPARAMSTR(config->args, where, "dateobs");
+    ADDPARAMSTR(config->args, where, "exp_type");
+    ADDPARAMSTR(config->args, where, "imfiles");
+    ADDPARAMSTR(config->args, where, "workdir");
+    ADDPARAMSTR(config->args, where, "filter");
+    ADDPARAMF(config->args, where, F32, "airmass");
+    ADDPARAMF(config->args, where, F32, "ra");
+    ADDPARAMF(config->args, where, F32, "decl");
+    ADDPARAMF(config->args, where, F32, "exp_time");
+    ADDPARAMF(config->args, where, F64, "bg");
+    ADDPARAMF(config->args, where, F64, "bg_stdev");
+    ADDPARAMF(config->args, where, F64, "bg_mean_stdev");
+    ADDPARAMF(config->args, where, F64, "alt");
+    ADDPARAMF(config->args, where, F64, "az");
+    ADDPARAMF(config->args, where, F32, "ccd_temp");
+    ADDPARAMF(config->args, where, F64, "posang");
+    ADDPARAMSTR(config->args, where, "object");
+
+    if (where->list->n < 1) {
+        psFree(where);
+        where = NULL;
+    }
+
+    bool status = false;
+    psString workdir = psMetadataLookupStr(&status, config->args, "-set_workdir");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_workdir");
+        return false;
+    }
+
+    psString label = psMetadataLookupStr(&status, config->args, "-set_label");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_label");
+        return false;
+    }
+
+    psString recipe = psMetadataLookupStr(&status, config->args, "-set_recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_recipe");
+        return false;
+    }
+
+    psString expgroup = psMetadataLookupStr(&status, config->args, "-set_expgroup");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_expgroup");
+        return false;
+    }
+
+    psString dvodb = psMetadataLookupStr(&status, config->args, "-set_dvodb");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_dvodb");
+        return false;
+    }
+
+    // find the chipProcessedExp exposures that we want to queue up.
+    psString query = psStringCopy("SELECT * FROM chipProcessedExp");
+
+    if (where) {
+        psString whereClause = psDBGenerateWhereSQL(where, "chipProcessedExp");
+        psFree(where);
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("chiptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(output);
+        return false;
+    }
+
+    // would could do this "all in the database" if we didn't want the option
+    // of changing the label/recipe/expgroup/dvodb/etc.  So we're pulling the
+    // data out so we have the option of changing these values or leaving the
+    // old values in place (i.e., passing the values through).
+
+    // loop over our list of chipProcessedExp rows
+    for (long i = 0; i < psArrayLength(output); i++) {
+        psMetadata *md = output->data[i];
+
+        chipProcessedExpRow *row = chipProcessedExpObjectFromMetadata(md);
+        if (!row) {
+            psError(PS_ERR_UNKNOWN, false, "failed to convert metadata into chipProcessedExp");
+            psFree(output);
+            return false;
+        }
+
+        // queue the exp
+        if (!camQueueChipID(config,
+                    row->chip_id,
+                    workdir     ? workdir   : row->workdir,
+                    label       ? label     : row->label,
+                    recipe      ? recipe    : row->recipe,
+                    expgroup    ? expgroup  : row->expgroup,
+                    dvodb       ? dvodb     : row->dvodb
+        )) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false,
+                    "failed to trying to queue chip_id: %" PRId64, row->chip_id);
+            psFree(row);
+            psFree(output);
+            return false;
+        }
+        psFree(row);
+    }
+    psFree(output);
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool pendingexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    psString query = pxDataGet("camtool_find_pendingexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "camPendingExp");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("camtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (!convertIdToStr(output)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
+        psFree(output);
+        return false;
+    }
+
+    // negate simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "camPendingExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool pendingimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psString query = pxDataGet("camtool_find_pendingimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereSQL(config->where, NULL);
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("camtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (!convertIdToStr(output)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
+        psFree(output);
+        return false;
+    }
+
+    // negate simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "chipProcessedImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool addprocessedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psString cam_id = psMetadataLookupStr(&status, config->args, "-cam_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -cam_id");
+        return false;
+    }
+    if (!cam_id) {
+        psError(PS_ERR_UNKNOWN, true, "-cam_id is required");
+        return false;
+    }
+
+    psString uri = psMetadataLookupStr(&status, config->args, "-uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -uri");
+        return false;
+    }
+    if (!uri) {
+        psError(PS_ERR_UNKNOWN, true, "-uri is required");
+        return false;
+    }
+
+    psF64 bg = psMetadataLookupF64(&status, config->args, "-bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg");
+        return false;
+    }
+
+    psF64 bg_stdev = psMetadataLookupF64(&status, config->args, "-bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_stdev");
+        return false;
+    }
+
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, config->args, "-bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_mean_stdev");
+        return false;
+    }
+
+    psF32 sigma_ra = psMetadataLookupF32(&status, config->args, "-sigma_ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -sigma_ra");
+        return false;
+    }
+    if (isnan(sigma_ra)) {
+        psError(PS_ERR_UNKNOWN, true, "-sigma_ra is required");
+        return false;
+    }
+
+    psF32 sigma_dec = psMetadataLookupF32(&status, config->args, "-sigma_dec");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -sigma_dec");
+        return false;
+    }
+    if (isnan(sigma_dec)) {
+        psError(PS_ERR_UNKNOWN, true, "-sigma_dec is required");
+        return false;
+    }
+
+    psS32 nastro = psMetadataLookupS32(&status, config->args, "-nastro");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -nastro");
+        return false;
+    }
+    if (nastro < 0) {
+        psError(PS_ERR_UNKNOWN, true, "-nastro is required");
+        return false;
+    }
+
+    psF32 zp_mean = psMetadataLookupF32(&status, config->args, "-zp_mean");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -zp_mean");
+        return false;
+    }
+    if (isnan(zp_mean)) {
+        psError(PS_ERR_UNKNOWN, true, "-zp_mean is required");
+        return false;
+    }
+
+    psF32 zp_stdev = psMetadataLookupF32(&status, config->args, "-zp_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -zp_stdev");
+        return false;
+    }
+    if (isnan(zp_stdev)) {
+        psError(PS_ERR_UNKNOWN, true, "-zp_stdev is required");
+        return false;
+    }
+
+    // optional
+    psString path_base = psMetadataLookupStr(&status, config->args, "-path_base");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -path_base");
+        return false;
+    }
+
+    // default
+    psS8 code = psMetadataLookupS8(&status, config->args, "-code");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -code");
+        return false;
+    }
+
+    psString query = pxDataGet("camtool_find_pendingexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query, cam_id)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("camtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // start a transaction so we don't end up with an exp_tag is both
+    // camPendingExp & camProcessedExp
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(output);
+        return false;
+    }
+
+    camPendingExpRow *pendingRow = camPendingExpObjectFromMetadata(output->data[0]);
+    psFree(output);
+    // create a new camProcessedImfile object
+    camProcessedExpRow *row = camProcessedExpRowAlloc(
+        pendingRow->cam_id,
+        pendingRow->chip_id,
+        pendingRow->workdir,
+        pendingRow->label,
+        pendingRow->recipe,
+        pendingRow->expgroup,
+        pendingRow->dvodb,
+        uri,
+        bg,
+        bg_stdev,
+        bg_mean_stdev,
+        sigma_ra,
+        sigma_dec,
+        nastro,
+        path_base,
+        zp_mean,
+        zp_stdev,
+        code
+    );
+
+    // insert the new row into the camProcessedImfile table
+    if (!camProcessedExpInsertObject(config->dbh, row)) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(row);
+        psFree(pendingRow);
+        return false;
+    }
+    psFree(row);
+
+    // delete the camPendingExp row from the database
+    if (!camPendingExpDeleteObject(config->dbh, pendingRow)) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(pendingRow);
+        return false;
+    }
+
+    psFree(pendingRow);
+
+    // point of no return for camPendingExp -> camProcessedExp
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool processedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    bool faulted = psMetadataLookupU64(&status, config->args, "-faulted");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -faulted");
+        return false;
+    }
+
+    psString query = pxDataGet("camtool_find_processedexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, NULL);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND camProcessedExp.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND camProcessedExp.fault = 0");
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("camtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (!convertIdToStr(output)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
+        psFree(output);
+        return false;
+    }
+
+    // negate simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "camProcessedExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool updateprocessedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psS8 code = psMetadataLookupS8(&status, config->args, "-code");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -code");
+        return false;
+    }
+
+    if (!pxSetFaultCode(config->dbh, "camProcessedExp", config->where, code)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to set set fault flag");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool blockMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psString label = psMetadataLookupStr(&status, config->args, "-label");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -label");
+        return false;
+    }
+    if (!label) {
+        psError(PS_ERR_UNKNOWN, true, "-label is required");
+        return false;
+    }
+
+    if (!camMaskInsert(config->dbh, label)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool maskedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psString query = psStringCopy("SELECT * FROM camMask");
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("camtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (!convertIdToStr(output)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
+        psFree(output);
+        return false;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "camMask", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool unblockMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psString label = psMetadataLookupStr(&status, config->args, "-label");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -label");
+        return false;
+    }
+    if (!label) {
+        psError(PS_ERR_UNKNOWN, true, "-label is required");
+        return false;
+    }
+
+    char *query = "DELETE FROM camMask WHERE label = '%s'";
+
+    if (!p_psDBRunQuery(config->dbh, query, label)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+
+    return true;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/camtool.h
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/camtool.h	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/camtool.h	(revision 22073)
@@ -0,0 +1,48 @@
+/*
+ * camtool.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef CAMTOOL_H
+#define CAMTOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    CAMTOOL_MODE_NONE      = 0x0,
+    CAMTOOL_MODE_QUEUE,
+    CAMTOOL_MODE_PENDINGEXP,
+    CAMTOOL_MODE_PENDINGIMFILE,
+    CAMTOOL_MODE_ADDPROCESSEDEXP,
+    CAMTOOL_MODE_PROCESSEDEXP,
+    CAMTOOL_MODE_UPDATEPROCESSEDEXP,
+    CAMTOOL_MODE_BLOCK,
+    CAMTOOL_MODE_MASKED,
+    CAMTOOL_MODE_UNBLOCK
+} camtoolMode;
+
+pxConfig *camtoolConfig(pxConfig *config, int argc, char **argv);
+
+bool camQueueChipID(pxConfig *config,
+                    psS64 chip_id,
+                    psString workdir,
+                    psString label,
+                    psString recipe,
+                    psString expgroup,
+                    psString dvodb);
+
+#endif // CAMTOOL_H
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/camtoolConfig.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/camtoolConfig.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/camtoolConfig.c	(revision 22073)
@@ -0,0 +1,353 @@
+/*
+ * camtoolConfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+#include <math.h>
+
+#include "pxtools.h"
+#include "camtool.h"
+
+pxConfig *camtoolConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    // -queue
+    psMetadata *queueArgs = psMetadataAlloc();
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-chip_id",  0,
+            "search by chip_id", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-exp_tag",  0,
+            "search by exp_tag", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-exp_id",  0,
+            "search by exp_id", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-inst",  0,
+            "search by camera", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-telescope",  0,
+            "search by telescope", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-dateobs",  0,
+            "search by observation time", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-exp_type",  0,
+            "search by exposure type", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-imfiles",  0,
+            "search by number of imfiles", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-workdir",  0,
+            "search by workdir", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-filter",  0,
+            "search by filter ", NULL);
+    psMetadataAddF32(queueArgs, PS_LIST_TAIL, "-airmass",  0,
+            "search by airmass", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-ra",  0,
+            "search by RA", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-decl",  0,
+            "search by DEC", NAN);
+    psMetadataAddF32(queueArgs, PS_LIST_TAIL, "-exp_time",  0,
+            "search by exposure time", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-bg",  0,
+            "search by exposue background", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-bg_stdev",  0,
+            "search by exposue background stdev", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,
+            "search by exposue background mean stdev", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-alt",  0,
+            "search by altitute", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-az",  0,
+            "search by azimuth", NAN);
+    psMetadataAddF32(queueArgs, PS_LIST_TAIL, "-ccd_temp",  0,
+            "search by ccd tempature", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-posang",  0,
+            "search by rotator position angle", NAN);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-object",  0,
+            "search by exposure object", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-set_workdir",  0,
+            "define workdir", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-set_label",  0,
+            "define label", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-set_recipe",  0,
+            "define recipe", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-set_expgroup",  0,
+            "define exposure group", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-set_dvodb",  0,
+            "define DVO db", NULL);
+
+    // -pendingexp
+    psMetadata *pendingexpArgs = psMetadataAlloc();
+    psMetadataAddStr(pendingexpArgs, PS_LIST_TAIL, "-cam_id", 0,
+            "search by camtool ID", NULL);
+    psMetadataAddStr(pendingexpArgs, PS_LIST_TAIL, "-chip_id", 0,
+            "search by chiptool ID", NULL);
+    psMetadataAddU64(pendingexpArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(pendingexpArgs, PS_LIST_TAIL, "-simple", 0,
+            "use the simple output format", false);
+
+    
+    // -pendingimfile
+    psMetadata *pendingimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-cam_id", 0,
+            "search by camtool ID", NULL);
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-chip_id", 0,
+            "search by chiptool ID", NULL);
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-class", 0,
+            "search by class", NULL);
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-class_id", 0,
+            "search by class ID", NULL);
+    psMetadataAddBool(pendingimfileArgs, PS_LIST_TAIL, "-simple", 0,
+            "use the simple output format", false);
+
+
+    // -addprocessedexp
+    psMetadata *addprocessedexpArgs = psMetadataAlloc();
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-cam_id", 0,
+            "define camtool ID (required)", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-chip_id", 0,
+            "define chiptool ID (required)", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-uri", 0,
+            "define URI (required)", NULL);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-bg", 0,
+            "define exposue background (required)", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-bg_stdev", 0,
+            "define exposue background stdev (required)", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-bg_mean_stdev", 0,
+            "define exposue background mean stdev (required)", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-sigma_ra", 0,
+            "define exposure E ra (required)", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-sigma_dec", 0,
+            "define exposure E dec (required)", NAN);
+    psMetadataAddS32(addprocessedexpArgs, PS_LIST_TAIL, "-nastro", 0,
+            "define number of objects (required)", -1);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-zp_mean", 0,
+            "define zero point mean (required)", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-zp_stdev", 0,
+            "define zero point stdev (required)", NAN);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-path_base", 0,
+            "define banana 1", NULL);
+    psMetadataAddS16(addprocessedexpArgs, PS_LIST_TAIL, "-code",  0,
+            "set fault code (required)", 0);
+    psMetadataAddBool(addprocessedexpArgs, PS_LIST_TAIL, "-faulted",  0,
+            "only return imfiles with a fault status set", false);
+
+    // -processedexp
+    psMetadata *processedexpArgs = psMetadataAlloc();
+    psMetadataAddStr(processedexpArgs, PS_LIST_TAIL, "-cam_id", 0,
+            "search by camtool ID", NULL);
+    psMetadataAddStr(processedexpArgs, PS_LIST_TAIL, "-chip_id", 0,
+            "search by chiptool ID", NULL);
+    psMetadataAddU64(processedexpArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(processedexpArgs, PS_LIST_TAIL, "-simple", 0,
+            "use the simple output format", false);
+    psMetadataAddBool(processedexpArgs, PS_LIST_TAIL, "-faulted",  0,
+            "only return imfiles with a fault status set", false);
+
+    // -updateprocessedexp
+    psMetadata *updateprocessedexpArgs = psMetadataAlloc();
+    psMetadataAddStr(updateprocessedexpArgs, PS_LIST_TAIL, "-cam_id", 0,
+            "search by camtool ID", NULL);
+    psMetadataAddStr(updateprocessedexpArgs, PS_LIST_TAIL, "-chip_id",  0,
+            "search by chiptool ID", NULL);
+    psMetadataAddStr(updateprocessedexpArgs, PS_LIST_TAIL, "-class",  0,
+            "search by class", NULL);
+    psMetadataAddStr(updateprocessedexpArgs, PS_LIST_TAIL, "-class_id",  0,
+            "search by class ID", NULL);
+    psMetadataAddS16(updateprocessedexpArgs, PS_LIST_TAIL, "-code",  0,
+            "set fault code (required)", 0);
+
+    // -block
+    psMetadata *blockArgs = psMetadataAlloc();
+    psMetadataAddStr(blockArgs, PS_LIST_TAIL, "-label",  0,
+            "name of a label to mask out", NULL);
+    
+    // -masked
+    psMetadata *maskedArgs = psMetadataAlloc();
+    psMetadataAddBool(maskedArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+    
+    // -unblock
+    psMetadata *unblockArgs = psMetadataAlloc();
+    psMetadataAddStr(unblockArgs, PS_LIST_TAIL, "-label",  0,
+            "name of a label to unmask", NULL);
+
+#define PXTOOL_MODE(option, modeval, argset) \
+{ \
+    int N = 0; \
+    if ((N = psArgumentGet (argc, argv, option))) { \
+        psArgumentRemove (N, &argc, argv); \
+        if (config->mode) { \
+            psError(PS_ERR_UNKNOWN, true, "only one mode selection is allowed"); \
+            psFree(config); \
+            return NULL; \
+        } \
+        config->mode = modeval; \
+        config->args = psMemIncrRefCounter(argset); \
+    } \
+    if (!psMetadataAddMetadata(argSets, PS_LIST_TAIL, option, 0, NULL, argset)) {;\
+        psError(PS_ERR_UNKNOWN, false, "failed to add argset for %s", option);\
+    } \
+    psFree(argset); \
+}
+
+    psMetadata *argSets = psMetadataAlloc();
+    // find which mode we're running under
+    PXTOOL_MODE("-queue",           CAMTOOL_MODE_QUEUE,         queueArgs);
+    PXTOOL_MODE("-pendingexp",      CAMTOOL_MODE_PENDINGEXP,    pendingexpArgs);
+    PXTOOL_MODE("-pendingimfile",CAMTOOL_MODE_PENDINGIMFILE, pendingimfileArgs);
+    PXTOOL_MODE("-addprocessedexp", CAMTOOL_MODE_ADDPROCESSEDEXP, addprocessedexpArgs);
+    PXTOOL_MODE("-processedexp", CAMTOOL_MODE_PROCESSEDEXP,  processedexpArgs);
+    PXTOOL_MODE("-updateprocessedexp", CAMTOOL_MODE_UPDATEPROCESSEDEXP,updateprocessedexpArgs);
+    PXTOOL_MODE("-block",           CAMTOOL_MODE_BLOCK,         blockArgs);
+    PXTOOL_MODE("-masked",          CAMTOOL_MODE_MASKED,        maskedArgs);
+    PXTOOL_MODE("-unblock",         CAMTOOL_MODE_UNBLOCK,       unblockArgs);
+
+    bool argErr = false;
+    if (config->mode == CAMTOOL_MODE_NONE) {
+        argErr = true;
+        fprintf (stderr, "mode argument is required\n");
+    } else if (! psArgumentParse(config->args, &argc, argv) || argc != 1) {
+        argErr = true;
+        fprintf (stderr, "error parsing arguments\n");
+    }
+
+    if (argErr) {
+        printf("\nPan-STARRS Camera Tool\n");
+        printf("Usage: %s <mode> [<options>]\n\n", argv[0]);
+        printf(" <mode> :\n\n");
+
+        psMetadataIterator *iter = psMetadataIteratorAlloc(argSets, 0, NULL);
+        psMetadataItem *item = NULL;
+        while ((item = psMetadataGetAndIncrement(iter))) {
+            if (!item->type == PS_DATA_METADATA) {
+                psAbort("all options must be specified as a metadata");            }
+
+            fprintf(stdout, "%s ", item->name);
+            psArgumentHelp(item->data.md);
+        }
+        psFree(iter);
+
+        psFree(argSets);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+
+    // setup search criterion
+#define addWhereStr(name) \
+{ \
+    psString str = NULL; \
+    bool status = false; \
+    if ((str = psMetadataLookupStr(&status, config->args, "-" #name))) { \
+        if (!psMetadataAddStr(config->where, PS_LIST_TAIL, #name, 0, "==", str)) {\
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+            psFree(config); \
+            return NULL; \
+        } \
+    } \
+}
+
+    // generate SQL where clause
+    config->where = psMetadataAlloc();
+
+{
+    psString str = NULL;
+    bool status = false;
+    if ((str = psMetadataLookupStr(&status, config->args, "-cam_id"))) {
+        if (!psMetadataAddS64(config->where, PS_LIST_TAIL, "cam_id", 0, "==", (psS64)atoll(str))) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item cam_id");
+            psFree(config);
+            return NULL;
+        }
+    }
+}
+
+{
+    psString str = NULL;
+    bool status = false;
+    if ((str = psMetadataLookupStr(&status, config->args, "-chip_id"))) {
+        if (!psMetadataAddS64(config->where, PS_LIST_TAIL, "chip_id", 0, "==", (psS64)atoll(str))) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
+            psFree(config);
+            return NULL;
+        }
+    }
+}
+
+    // convert '-inst' to 'camera'
+    {
+        psString str = NULL; 
+        bool status = false;
+        if ((str = psMetadataLookupStr(&status, config->args, "-inst"))) {
+            if (!psMetadataAddStr(config->where, PS_LIST_TAIL, "camera", 0, "==", str)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+                psFree(config);
+                return NULL;
+            }
+        }
+    }
+    addWhereStr(telescope);
+    addWhereStr(exp_type);
+    {
+        int imfiles = 0; 
+        bool status = false;
+        if ((imfiles = psMetadataLookupS32(&status, config->args, "-imfiles"))) {
+            if (!psMetadataAddS32(config->where, PS_LIST_TAIL, "imfiles", 0, "==", imfiles)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+                psFree(config);
+                return NULL;
+            }
+        }
+    }
+    addWhereStr(class);
+    addWhereStr(class_id);
+    addWhereStr(filter);
+
+    if (psListLength(config->where->list) < 1) {
+        psFree(config->where);
+        config->where = NULL;
+    }
+
+
+    // define Database handle, if used
+    config->dbh = pmConfigDB(config->modules);
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    // save argv/argc
+    config->argv = argv;
+    config->argc = argc;
+
+    return config;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/chipqueue.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/chipqueue.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/chipqueue.c	(revision 22073)
@@ -0,0 +1,93 @@
+/*
+ * chipqueue.c
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "pxtools.h"
+#include "pxdata.h"
+#include "chiptool.h"
+
+bool chipQueueExpTag(pxConfig *config,
+                     psString exp_tag,
+                     psString workdir,
+                     psString label,
+                     psString recipe,
+                     psString expgroup,
+                     psString dvodb)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // load the SQL to enqueue our exp_tags from disk once
+    static psString queuerawexp_query = NULL;
+    if (!queuerawexp_query) {
+        queuerawexp_query = pxDataGet("chiptool_queuerawexp.sql");
+        psMemSetPersistent(queuerawexp_query, true);
+        if (!queuerawexp_query) {
+            psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+            return false;
+        }
+    }
+
+    static psString queuerawimfile_query = NULL;
+    if (!queuerawimfile_query) {
+        queuerawimfile_query = pxDataGet("chiptool_queuerawimfile.sql");
+        psMemSetPersistent(queuerawimfile_query, true);
+        if (!queuerawexp_query) {
+            psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+            return false;
+        }
+    }
+
+    // queue the exp
+    if (!p_psDBRunQuery(config->dbh, queuerawexp_query,
+                workdir  ? workdir  : "NULL",
+                label    ? label    : "NULL",
+                recipe   ? recipe   : "NULL",
+                expgroup ? expgroup : "NULL",
+                dvodb    ? dvodb    : "NULL",
+                exp_tag
+    )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // just to be safe, we should have changed at least one row
+    if (psDBAffectedRows(config->dbh) < 1) {
+        psError(PS_ERR_UNKNOWN, false,
+                "no rows affected - should have changed at least one row");
+        return false;
+    }
+
+    // queue the imfiles for the exp we just queued
+    if (!p_psDBRunQuery(config->dbh, queuerawimfile_query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // just to be safe, we should have changed at least one row
+    if (psDBAffectedRows(config->dbh) < 1) {
+        psError(PS_ERR_UNKNOWN, false,
+                "no rows affected - should have changed at least one row");
+        return false;
+    }
+
+    return true;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/chiptool.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/chiptool.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/chiptool.c	(revision 22073)
@@ -0,0 +1,897 @@
+/*
+ * chiptool.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "pxtools.h"
+#include "pxdata.h"
+#include "chiptool.h"
+#include "camtool.h"
+
+static bool queueMode(pxConfig *config);
+static bool pendingimfileMode(pxConfig *config);
+static bool addprocessedimfileMode(pxConfig *config);
+static bool processedimfileMode(pxConfig *config);
+static bool updateprocessedimfileMode(pxConfig *config);
+static bool blockMode(pxConfig *config);
+static bool maskedMode(pxConfig *config);
+static bool unblockMode(pxConfig *config);
+
+static chipProcessedImfileRow *chipPendingToProcessedImfile(pxConfig *config, chipPendingImfileRow *imfile);
+static chipProcessedExpRow *chipPendingToProcessedExp(pxConfig *config, chipPendingExpRow *pendingExp);
+static bool chipProcessedCompleteExp(pxConfig *config);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv) {
+    psLibInit(NULL);
+
+    pxConfig *config = chiptoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(CHIPTOOL_MODE_QUEUE,                   queueMode);
+        MODECASE(CHIPTOOL_MODE_PENDINGIMFILE,           pendingimfileMode);
+        MODECASE(CHIPTOOL_MODE_ADDPROCESSEDIMFILE,      addprocessedimfileMode);
+        MODECASE(CHIPTOOL_MODE_PROCESSEDIMFILE,         processedimfileMode);
+        MODECASE(CHIPTOOL_MODE_UPDATEPROCESSEDIMFILE,updateprocessedimfileMode);
+        MODECASE(CHIPTOOL_MODE_BLOCK,                   blockMode);
+        MODECASE(CHIPTOOL_MODE_MASKED,                  maskedMode);
+        MODECASE(CHIPTOOL_MODE_UNBLOCK,                 unblockMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+#define ADDPARAMSTR(from, to, name) \
+{ \
+    bool status = false; \
+    psString str = psMetadataLookupStr(&status, from, "-" name); \
+    if (!status) { \
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -" name); \
+        return false; \
+    } \
+    if (str) { \
+        if (!psMetadataAddStr(to, PS_LIST_TAIL, name, 0, "==", str)) { \
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " name); \
+            psFree(to); \
+            return false; \
+        } \
+    } \
+}
+
+
+#define ADDPARAMF(from, to, type, name) \
+{ \
+    bool status = false; \
+    ps##type var = psMetadataLookup##type(&status, from, "-" name); \
+    if (!status) { \
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -" name); \
+        return false; \
+    } \
+    if (!isnan(var)) { \
+        if (!psMetadataAdd##type(to, PS_LIST_TAIL, #name, 0, "==", var)) { \
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " name); \
+            psFree(to); \
+            return false; \
+        } \
+    } \
+}
+
+static bool queueMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    psMetadata *where = psMetadataAlloc();
+    ADDPARAMSTR(config->args, where, "exp_tag");
+    ADDPARAMSTR(config->args, where, "exp_id");
+    ADDPARAMSTR(config->args, where, "inst");
+    ADDPARAMSTR(config->args, where, "telescope");
+    ADDPARAMSTR(config->args, where, "dateobs");
+    ADDPARAMSTR(config->args, where, "exp_type");
+    ADDPARAMSTR(config->args, where, "imfiles");
+    ADDPARAMSTR(config->args, where, "workdir");
+    ADDPARAMSTR(config->args, where, "filter");
+    ADDPARAMF(config->args, where, F32, "airmass");
+    ADDPARAMF(config->args, where, F32, "ra");
+    ADDPARAMF(config->args, where, F32, "decl");
+    ADDPARAMF(config->args, where, F32, "exp_time");
+    ADDPARAMF(config->args, where, F64, "bg");
+    ADDPARAMF(config->args, where, F64, "bg_stdev");
+    ADDPARAMF(config->args, where, F64, "bg_mean_stdev");
+    ADDPARAMF(config->args, where, F64, "alt");
+    ADDPARAMF(config->args, where, F64, "az");
+    ADDPARAMF(config->args, where, F32, "ccd_temp");
+    ADDPARAMF(config->args, where, F64, "posang");
+    ADDPARAMSTR(config->args, where, "object");
+
+    if (where->list->n < 1) {
+        psFree(where);
+        where = NULL;
+    }
+
+    bool status = false;
+    psString workdir = psMetadataLookupStr(&status, config->args, "-set_workdir");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_workdir");
+        return false;
+    }
+
+    psString label = psMetadataLookupStr(&status, config->args, "-set_label");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_label");
+        return false;
+    }
+
+    psString recipe = psMetadataLookupStr(&status, config->args, "-set_recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_recipe");
+        return false;
+    }
+
+    psString expgroup = psMetadataLookupStr(&status, config->args, "-set_expgroup");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_expgroup");
+        return false;
+    }
+
+    psString dvodb = psMetadataLookupStr(&status, config->args, "-set_dvodb");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_dvodb");
+        return false;
+    }
+
+    // find the exp_tag of all the exposures that we want to queue up.
+    psString query = pxDataGet("chiptool_find_rawexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "rawExp");
+        psFree(where);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("chiptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // start a transaction so we don't end up with an exp without any associted
+    // imfiles
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(output);
+        return false;
+    }
+
+    // loop over our list of exp_tags
+    for (long i = 0; i < psArrayLength(output); i++) {
+        psMetadata *md = output->data[i];
+
+        psString exp_tag = psMetadataLookupStr(&status, md, "exp_tag");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for exp_tag");
+            psFree(output);
+            return false;
+        }
+
+        // queue the exp
+        if (!chipQueueExpTag(config, exp_tag, workdir, label, recipe, expgroup, dvodb)) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false,
+                    "failed to trying to queue exp_tag: %s", exp_tag);
+            psFree(output);
+            return false;
+        }
+    }
+    psFree(output);
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool pendingimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    // XXX does this need to be constrained so that it won't return any results
+    // if a match chipPendingExp hasn't been registered?
+    psString query = pxDataGet("chiptool_pendingimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "chipPendingImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("chiptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (!convertIdToStr(output)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
+        psFree(output);
+        return false;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "chipPendingImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool addprocessedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // select * from chipProcessedImfiles
+    // where
+    // exp_tag & class_id are not in chipPendingImfile
+
+    // add the completed imfile to
+    // the chipProcessedeImfile tables
+    // remove corresponding entries from the
+    // chipPendingImfile table
+    // check to see if any chipPendingExps have no
+    // associated chipPendingImfiles
+    // if so move the chipPendingExp(s) to chipProcessedExp
+
+    psString query = pxDataGet("chiptool_find_unprocessed_imfile.pl");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    {
+        psMetadata *where = psMetadataAlloc();
+        bool status = false;
+        psString chip_id = psMetadataLookupStr(&status, config->args, "-chip_id");        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -chip_id");
+            psFree(query);
+            return false;
+        }
+        if (chip_id) {
+            if (!psMetadataAddStr(where, PS_LIST_TAIL, "chip_id", 0, "==", chip_id)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
+                psFree(where);
+                psFree(query);
+                return false;
+            }
+        }
+
+        psString class_id = psMetadataLookupStr(&status, config->args, "-class_id");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -class_id");
+            psFree(query);
+            return false;
+        }
+        if (class_id) {
+            if (!psMetadataAddStr(where, PS_LIST_TAIL, "class_id", 0, "==", class_id)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+                psFree(where);
+                psFree(query);
+                return false;
+            }
+        }
+
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "chipPendingImfile");
+        psFree(where);
+        if (whereClause) {
+            psStringAppend(&query, " AND %s", whereClause);
+            psFree(whereClause);
+        }
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        // XXX check psError here
+        psError(PS_ERR_UNKNOWN, false, "no chipPendingImfile rows found");
+        psFree(output);
+        return false;
+    }
+
+    // start a transaction so we don't end up with an incremented iteration
+    // count but no detInputExps
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(output);
+        return false;
+    }
+
+    // insert into chipProcessedImfile
+    // remove chipPendingImfile entry
+    for (long i = 0; i < psArrayLength(output); i++) {
+        psMetadata *row = output->data[i];
+        // convert metadata into a chipPendingImfile object
+        chipPendingImfileRow *object = chipPendingImfileObjectFromMetadata(row);
+        // convert chipPendingImfile object into a chipProcessedImfile object
+        chipProcessedImfileRow *imfile = chipPendingToProcessedImfile(config, object);
+        if (!imfile) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to convert chipPendingImfile to chipProcessedImfile");
+            psFree(object);
+            psFree(output);
+            return false;
+        }
+        // insert chipProccessedImfile object into the database
+        if (!chipProcessedImfileInsertObject(config->dbh, imfile)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(imfile);
+            psFree(object);
+            psFree(output);
+            return false;
+        }
+        psFree(imfile);
+        // delete the chipPendingImfile object from the database
+        if (!chipPendingImfileDeleteObject(config->dbh, object)) {
+            // there must be atleast 1 Imfile to get this far
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(object);
+            psFree(output);
+            return false;
+        }
+        psFree(object);
+    }
+
+    psFree(output);
+
+    // XXX I've decided to make the transaction cover the Exp migration as
+    // well.  Otherwise, if the last imfile in an exp is moved and the exp
+    // migration fails then the data base is left in a satuation where the exp
+    // migration can't happen.
+
+    if (!chipProcessedCompleteExp(config)) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // point of no return for chipPendingImfile -> chipProcessedImfile
+    // point of no return for chipPendingExp -> chipProcessedExp
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool processedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    bool faulted = psMetadataLookupU64(&status, config->args, "-faulted");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -faulted");
+        return false;
+    }
+
+    // XXX does this need to be constrained so that it won't return any results
+    // if a match chipPendingExp hasn't been registered?
+    psString query = pxDataGet("chiptool_processedimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "chipProcessedImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND chipProcessedImfile.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND chipProcessedImfile.fault = 0");
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("chiptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (!convertIdToStr(output)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
+        psFree(output);
+        return false;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "chipProcessedImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool updateprocessedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psS8 code = psMetadataLookupS8(&status, config->args, "-code");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -code");
+        return false;
+    }
+
+    if (!pxSetFaultCode(config->dbh, "chipProcessedImfile", config->where, code)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to set set fault flag");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool blockMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psString label = psMetadataLookupStr(&status, config->args, "-label");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -label");
+        return false;
+    }
+    if (!label) {
+        psError(PS_ERR_UNKNOWN, true, "-label is required");
+        return false;
+    }
+
+    if (!chipMaskInsert(config->dbh, label)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool maskedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psString query = psStringCopy("SELECT * FROM chipMask");
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("chiptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (!convertIdToStr(output)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
+        psFree(output);
+        return false;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "chipMask", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool unblockMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psString label = psMetadataLookupStr(&status, config->args, "-label");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -label");
+        return false;
+    }
+    if (!label) {
+        psError(PS_ERR_UNKNOWN, true, "-label is required");
+        return false;
+    }
+
+    char *query = "DELETE FROM chipMask WHERE label = '%s'";
+
+    if (!p_psDBRunQuery(config->dbh, query, label)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool chipProcessedCompleteExp(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // look for completed chipPendingExp
+    // migrate them to chipProccessedExp & camPendingExp
+    psString query = pxDataGet("chiptool_completely_processed_exp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("chiptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // insert into chipProcessedExp
+    // insert into camPendingExp
+    // remove chipPendingExp entry
+    for (long i = 0; i < psArrayLength(output); i++) {
+        psMetadata *row = output->data[i];
+
+        // convert metadata into a chipPendingExp object
+        chipPendingExpRow *pendingExp = chipPendingExpObjectFromMetadata(row);
+        if (!pendingExp) {
+            psError(PS_ERR_UNKNOWN, false, "failed to convert metadata into chipPendingExp");
+            psFree(output);
+            return false;
+        }
+
+        // convert chipPendingExp object into a chipProcesseExp object
+        chipProcessedExpRow *processedExp = chipPendingToProcessedExp(config, pendingExp);
+        if (!processedExp) {
+            psError(PS_ERR_UNKNOWN, false, "failed to convert chipPendingExp to chipProcessedExp");
+            psFree(pendingExp);
+            psFree(output);
+            return false;
+        }
+
+        // delete the chipPendingExp object from the database
+        if (!chipPendingExpDeleteObject(config->dbh, pendingExp)) {
+            // there must be atleast 1 Imfile to get this far
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(processedExp);
+            psFree(pendingExp);
+            psFree(output);
+            return false;
+        }
+        psFree(pendingExp);
+
+        // insert chipProccessedExp object into the database
+        if (!chipProcessedExpInsertObject(config->dbh, processedExp)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(processedExp);
+            psFree(output);
+            return false;
+        }
+
+        // camQueueChipID() can only be run after the chipProcessedExp entry
+        // has been inserted.
+        // queue the chip_id in the camPendingExp table 
+        if (!camQueueChipID(config,
+                    processedExp->chip_id,
+                    processedExp->workdir,
+                    processedExp->label,
+                    processedExp->recipe,
+                    processedExp->expgroup,
+                    processedExp->dvodb
+        )) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to queue camPendingExp");
+            psFree(processedExp);
+            psFree(output);
+            return false;
+        }
+
+        psFree(processedExp);
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static chipProcessedImfileRow *chipPendingToProcessedImfile(pxConfig *config, chipPendingImfileRow *imfile)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(imfile, NULL);
+
+    bool status = false;
+    psString uri = psMetadataLookupStr(&status, config->args, "-uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -uri");
+        return false;
+    }
+    if (!uri) {
+        psError(PS_ERR_UNKNOWN, true, "-uri is required");
+        return false;
+    }
+
+    psF64 bg = psMetadataLookupF64(&status, config->args, "-bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg");
+        return false;
+    }
+
+    psF64 bg_stdev = psMetadataLookupF64(&status, config->args, "-bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_stdev");
+        return false;
+    }
+
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, config->args, "-bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_mean_stdev");
+        return false;
+    }
+
+    // optional
+    psString path_base = psMetadataLookupStr(&status, config->args, "-path_base");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -path_base");
+        return false;
+    }
+
+    // default values
+    psS8 code = psMetadataLookupS8(&status, config->args, "-code");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -code");
+        return false;
+    }
+
+    return chipProcessedImfileRowAlloc(
+        imfile->chip_id,
+        imfile->class_id,
+        uri,
+        bg,
+        bg_stdev,
+        bg_mean_stdev,
+        path_base,
+        code
+    );
+}
+
+
+static chipProcessedExpRow *chipPendingToProcessedExp(pxConfig *config, chipPendingExpRow *pendingExp)
+{
+    PS_ASSERT_PTR_NON_NULL(pendingExp, NULL);
+ 
+    return chipProcessedExpRowAlloc(
+        pendingExp->chip_id,
+        pendingExp->exp_tag,
+        pendingExp->guide_id,
+        pendingExp->workdir,
+        pendingExp->label,
+        pendingExp->recipe,
+        pendingExp->expgroup,
+        pendingExp->dvodb
+    );
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/chiptool.h
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/chiptool.h	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/chiptool.h	(revision 22073)
@@ -0,0 +1,47 @@
+/*
+ * chiptool.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef CHIPTOOL_H
+#define CHIPTOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    CHIPTOOL_MODE_NONE      = 0x0,
+    CHIPTOOL_MODE_QUEUE,
+    CHIPTOOL_MODE_PENDINGIMFILE,
+    CHIPTOOL_MODE_ADDPROCESSEDIMFILE,
+    CHIPTOOL_MODE_PROCESSEDIMFILE,
+    CHIPTOOL_MODE_UPDATEPROCESSEDIMFILE,
+    CHIPTOOL_MODE_BLOCK,
+    CHIPTOOL_MODE_MASKED,
+    CHIPTOOL_MODE_UNBLOCK
+} chiptoolMode;
+
+pxConfig *chiptoolConfig(pxConfig *config, int argc, char **argv);
+
+bool chipQueueExpTag(pxConfig *config,
+                     psString exp_tag,
+                     psString workdir,
+                     psString label,
+                     psString recipe,
+                     psString expgroup,
+                     psString dvodb);
+
+#endif // CHIPTOOL_H
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/chiptoolConfig.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/chiptoolConfig.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/chiptoolConfig.c	(revision 22073)
@@ -0,0 +1,328 @@
+/*
+ * chiptoolConfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "chiptool.h"
+
+pxConfig *chiptoolConfig(pxConfig *config, int argc, char **argv) {
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (! config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    // -queue
+    psMetadata *queueArgs = psMetadataAlloc();
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-exp_tag",  0,
+            "search by exp_tag", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-exp_id",  0,
+            "search by exp_id", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-inst",  0,
+            "search by camera", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-telescope",  0,
+            "search by telescope", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-dateobs",  0,
+            "search by observation time", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-exp_type",  0,
+            "search by exposure type", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-imfiles",  0,
+            "search by number of imfiles", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-workdir",  0,
+            "search by workdir", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-filter",  0,
+            "search by filter ", NULL);
+    psMetadataAddF32(queueArgs, PS_LIST_TAIL, "-airmass",  0,
+            "search by airmass", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-ra",  0,
+            "search by RA", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-decl",  0,
+            "search by DEC", NAN);
+    psMetadataAddF32(queueArgs, PS_LIST_TAIL, "-exp_time",  0,
+            "search by exposure time", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-bg",  0,
+            "search by exposue background", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-bg_stdev",  0,
+            "search by exposue background stdev", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,
+            "search by exposue background mean stdev", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-alt",  0,
+            "search by altitute", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-az",  0,
+            "search by azimuth", NAN);
+    psMetadataAddF32(queueArgs, PS_LIST_TAIL, "-ccd_temp",  0,
+            "search by ccd tempature", NAN);
+    psMetadataAddF64(queueArgs, PS_LIST_TAIL, "-posang",  0,
+            "search by rotator position angle", NAN);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-object",  0,
+            "search by exposure object", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-set_workdir",  0,
+            "define workdir", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-set_label",  0,
+            "define label", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-set_recipe",  0,
+            "define recipe", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-set_expgroup",  0,
+            "define exposure group", NULL);
+    psMetadataAddStr(queueArgs, PS_LIST_TAIL, "-set_dvodb",  0,
+            "define DVO db", NULL);
+
+
+    // -pendingimfile
+    psMetadata *pendingimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-chip_id",  0,
+            "search by chip ID", NULL);
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-exp_tag",  0,
+            "search by exposure ID", NULL);
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-class",  0,
+            "search by class", NULL);
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-class_id",  0,
+            "search by class ID", NULL);
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-inst",  0,
+            "search by camera of interest", NULL);
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-filter",  0,
+            "search by filter of interest", NULL); 
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-uri",  0,
+            "search by URL", NULL);
+    psMetadataAddU64(pendingimfileArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(pendingimfileArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+ 
+    // -addprocessedimfile
+    psMetadata *addprocessedimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-chip_id",  0,
+            "define chip ID (required)", NULL);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-class_id",  0,
+            "define class ID", NULL);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-uri",  0,
+            "define URL", NULL);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-bg",  0,
+            "define exposue background", NAN);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-bg_stdev",  0,
+            "define exposue background stdev", NAN);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,
+            "define exposue background mean stdev", NAN);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-path_base",  0,
+            "define banana 1", NULL);
+    psMetadataAddS8(addprocessedimfileArgs, PS_LIST_TAIL, "-code",  0,
+            "set fault code (required)", 0);
+
+    // -processedimfile
+    psMetadata *processedimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(processedimfileArgs, PS_LIST_TAIL, "-chip_id",  0,
+            "define chip ID (required)", NULL);
+    psMetadataAddStr(processedimfileArgs, PS_LIST_TAIL, "-exp_tag",  0,
+            "define exposure ID", NULL);
+    psMetadataAddStr(processedimfileArgs, PS_LIST_TAIL, "-class",  0,
+            "define class", NULL);
+    psMetadataAddStr(processedimfileArgs, PS_LIST_TAIL, "-class_id",  0,
+            "define class ID", NULL);
+    psMetadataAddStr(processedimfileArgs, PS_LIST_TAIL, "-inst",  0,
+            "define camera of interest", NULL);
+    psMetadataAddStr(processedimfileArgs, PS_LIST_TAIL, "-filter",  0,
+            "define filter of interest", NULL); 
+    psMetadataAddStr(processedimfileArgs, PS_LIST_TAIL, "-uri",  0,
+            "define URL", NULL);
+    psMetadataAddU64(processedimfileArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(processedimfileArgs, PS_LIST_TAIL, "-faulted",  0,
+            "only return imfiles with a fault status set", false);
+    psMetadataAddBool(processedimfileArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -updateprocessedimfile
+    psMetadata *updateprocessedimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(updateprocessedimfileArgs, PS_LIST_TAIL, "-chip_id",  0,
+            "define chip ID (required)", NULL);
+    psMetadataAddStr(updateprocessedimfileArgs, PS_LIST_TAIL, "-exp_tag",  0,
+            "search by exposure ID", NULL);
+    psMetadataAddStr(updateprocessedimfileArgs, PS_LIST_TAIL, "-class",  0,
+            "search by class", NULL);
+    psMetadataAddStr(updateprocessedimfileArgs, PS_LIST_TAIL, "-class_id",  0,
+            "search by class ID", NULL);
+    psMetadataAddS8(updateprocessedimfileArgs, PS_LIST_TAIL, "-code",  0,
+            "set fault code (required)", 0);
+
+
+    // -block
+    psMetadata *blockArgs = psMetadataAlloc();
+    psMetadataAddStr(blockArgs, PS_LIST_TAIL, "-label",  0,
+            "name of a label to mask out", NULL);
+    
+    // -masked
+    psMetadata *maskedArgs = psMetadataAlloc();
+    psMetadataAddBool(maskedArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+    
+    // -unblock
+    psMetadata *unblockArgs = psMetadataAlloc();
+    psMetadataAddStr(unblockArgs, PS_LIST_TAIL, "-label",  0,
+            "name of a label to unmask", NULL);
+
+#define PXTOOL_MODE(option, modeval, argset) \
+{ \
+    int N = 0; \
+    if ((N = psArgumentGet (argc, argv, option))) { \
+        psArgumentRemove (N, &argc, argv); \
+        if (config->mode) { \
+            psError(PS_ERR_UNKNOWN, true, "only one mode selection is allowed"); \
+            psFree(config); \
+            return NULL; \
+        } \
+        config->mode = modeval; \
+        config->args = psMemIncrRefCounter(argset); \
+    } \
+    if (!psMetadataAddMetadata(argSets, PS_LIST_TAIL, option, 0, NULL, argset)) {; \
+        psError(PS_ERR_UNKNOWN, false, "failed to add argset for %s", option); \
+    } \
+    psFree(argset); \
+}
+
+    psMetadata *argSets = psMetadataAlloc();
+    PXTOOL_MODE("-queue",           CHIPTOOL_MODE_QUEUE,          queueArgs);
+    PXTOOL_MODE("-pendingimfile",   CHIPTOOL_MODE_PENDINGIMFILE,  pendingimfileArgs);
+    PXTOOL_MODE("-addprocessedimfile",CHIPTOOL_MODE_ADDPROCESSEDIMFILE,addprocessedimfileArgs);
+    PXTOOL_MODE("-processedimfile",CHIPTOOL_MODE_PROCESSEDIMFILE, processedimfileArgs);
+    PXTOOL_MODE("-updateprocessedimfile",CHIPTOOL_MODE_UPDATEPROCESSEDIMFILE,    updateprocessedimfileArgs);
+    PXTOOL_MODE("-block",           CHIPTOOL_MODE_BLOCK,          blockArgs);
+    PXTOOL_MODE("-masked",          CHIPTOOL_MODE_MASKED,         maskedArgs);
+    PXTOOL_MODE("-unblock",         CHIPTOOL_MODE_UNBLOCK,        unblockArgs);
+
+    bool argErr = false;
+    if (config->mode == CHIPTOOL_MODE_NONE) {
+            argErr = true;
+        fprintf (stderr, "mode argument is required\n");
+    } else if (! psArgumentParse(config->args, &argc, argv) || argc != 1) {
+        argErr = true;
+        fprintf (stderr, "error parsing arguments\n");
+    }
+
+    if (argErr) {
+        printf("\nPan-STARRS Chip Tool\n\n");
+        printf("Usage: %s <mode> [<options>]\n\n", argv[0]);
+        printf(" <mode> :\n\n");
+
+        psMetadataIterator *iter = psMetadataIteratorAlloc(argSets, 0, NULL);
+        psMetadataItem *item = NULL;
+        while ((item = psMetadataGetAndIncrement(iter))) {
+            if (!item->type == PS_DATA_METADATA) {
+                psAbort("all options must be specified as a metadata");            }
+
+            fprintf(stdout, "%s ", item->name);
+            psArgumentHelp(item->data.md);
+        }
+        psFree(iter);
+
+        psFree(argSets);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+
+    // setup search criterion
+#define addWhereStr(name) \
+{ \
+    psString str = NULL; \
+    bool status = false; \
+    if ((str = psMetadataLookupStr(&status, config->args, "-" #name))) { \
+        if (!psMetadataAddStr(config->where, PS_LIST_TAIL, #name, 0, "==", str)) {\
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+            psFree(config); \
+            return NULL; \
+        } \
+    } \
+}
+
+
+
+    // generate SQL where clause
+    config->where = psMetadataAlloc();
+
+{
+    psString str = NULL;
+    bool status = false;
+    if ((str = psMetadataLookupStr(&status, config->args, "-chip_id"))) {
+        if (!psMetadataAddS64(config->where, PS_LIST_TAIL, "chip_id", 0, "==", (psS64)atoll(str))) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
+            psFree(config);
+            return NULL;
+        }
+    }
+}
+
+    addWhereStr(exp_tag);
+    // convert '-inst' to 'camera'
+    {
+        psString str = NULL;
+        bool status = false;
+        if ((str = psMetadataLookupStr(&status, config->args, "-inst"))) {
+            if (!psMetadataAddStr(config->where, PS_LIST_TAIL, "camera", 0, "==", str)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+                psFree(config);
+                return NULL;
+            }
+        }
+    }
+
+
+    addWhereStr(filter);
+    addWhereStr(class);
+    addWhereStr(class_id);
+
+    /*
+    // psMetadataConfig does not support times yet
+    if (!psTimeIsZero(config->start) && !psTimeIsZero(config->stop)) {
+        psMetadataAddTime (where, PS_LIST_TAIL, "TIME_START", 0, "<", config->stop);
+        psMetadataAddTime (where, PS_LIST_TAIL, "TIME_STOP", 0, ">", config->start);
+    }
+    */
+
+    if (config->where->list->n < 1) {
+        psFree(config->where);
+        config->where = NULL;
+    }
+
+    // define Database handle, if used
+    config->dbh = pmConfigDB(config->modules);
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    // save argv/argc
+    config->argv = argv;
+    config->argc = argc;
+
+    return config;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/detselect.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/detselect.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/detselect.c	(revision 22073)
@@ -0,0 +1,293 @@
+/*
+ * detselect.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include "pxtools.h"
+#include "detselect.h"
+
+static bool searchMode(pxConfig *config);
+static bool selectMode(pxConfig *config);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = detselectConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(DETSELECT_MODE_SEARCH,        searchMode);
+        MODECASE(DETSELECT_MODE_SELECT,        selectMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+static bool searchMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+
+    // airmass_min  < airmass  < airmass_max
+    {
+        bool status = false;
+        psF32 airmass = psMetadataLookupF32(&status, config->args, "-airmass");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -airmass");
+            psFree(where);
+            return false;
+        }
+
+        if (!isnan(airmass)) {
+            psMetadataAddF32(where, PS_LIST_TAIL, "airmass_min", 0, "<=", airmass);
+            psMetadataAddF32(where, PS_LIST_TAIL, "airmass_max", 0, ">=", airmass);
+        }
+    }
+
+    // exp_time_min < exp_time < exp_time_max
+    {
+        bool status = false;
+        psF32 exp_time = psMetadataLookupF32(&status, config->args, "-exp_time");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_time");
+            psFree(where);
+            return false;
+        }
+
+        if (!isnan(exp_time)) {
+            psMetadataAddF32(where, PS_LIST_TAIL, "exp_time_min", 0, "<=", exp_time);
+            psMetadataAddF32(where, PS_LIST_TAIL, "exp_time_max", 0, ">=", exp_time);
+        }
+    }
+
+    // ccd_temp_min < ccd_temp < ccd_temp_max
+    {
+        bool status = false;
+        psF32 ccd_temp = psMetadataLookupF32(&status, config->args, "-ccd_temp");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -ccd_temp");
+            psFree(where);
+            return false;
+        }
+
+        if (!isnan(ccd_temp)) {
+            psMetadataAddF32(where, PS_LIST_TAIL, "ccd_temp_min", 0, "<=", ccd_temp);
+            psMetadataAddF32(where, PS_LIST_TAIL, "ccd_temp_max", 0, ">=", ccd_temp);
+        }
+    }
+    
+    {
+        bool status = false;
+        psF64 posang = psMetadataLookupF64(&status, config->args, "-posang");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -posang");
+            psFree(where);
+            return false;
+        }
+
+        if (!isnan(posang)) {
+            psMetadataAddF32(where, PS_LIST_TAIL, "posang_min", 0, "<=", posang);
+            psMetadataAddF32(where, PS_LIST_TAIL, "posang_max", 0, ">=", posang);
+        }
+    }
+
+    // time_begin    < time     < time_end
+    {
+        bool status = false;
+        psString timeStr = psMetadataLookupStr(&status, config->args, "-time");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -time");
+            psFree(where);
+            return false;
+        }
+
+        if (timeStr) {
+            psTime *time = psTimeFromISO(timeStr, PS_TIME_UTC);
+            if (!time) {
+                psError(PS_ERR_UNKNOWN, false, "error in time format %s", timeStr);
+                psFree(where);
+                return false;
+            }
+
+            // the == NULL tests invokes some psDB magic to make an OR
+            // conditional query
+            psMetadataAddTime(where, PS_LIST_TAIL, "time_begin", 0, "<=", time);
+            psMetadataAddTime(where, PS_LIST_TAIL, "time_begin", PS_META_DUPLICATE_OK, "==", NULL);
+            psMetadataAddTime(where, PS_LIST_TAIL, "time_end", 0, ">=", time);
+            psMetadataAddTime(where, PS_LIST_TAIL, "time_end", PS_META_DUPLICATE_OK, "==", NULL);
+            psFree(time);
+        }
+    }
+
+    psString query = pxDataGet("detselect_search.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "detRun");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "detRun");
+        psFree(where);
+        if (whereClause) {
+            psStringAppend(&query, " AND %s", whereClause);
+            psFree(whereClause);
+        }
+    }
+
+    // XXX this needs to be more controlled: we should specifically choose the 
+    // single detrend image which matches all criteria and has the latest 
+    // insertion date
+    psStringAppend(&query, " ORDER BY detRun.registered DESC LIMIT 1");
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        // XXX check psError here
+        psError(PS_ERR_UNKNOWN, false, "no detrend exposures found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negate simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool selectMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psString query = pxDataGet("detselect_select.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "detNormalizedImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        // XXX check psError here
+        psError(PS_ERR_UNKNOWN, false, "no detNormalizedImfile rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detNormalizedImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/detselect.h
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/detselect.h	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/detselect.h	(revision 22073)
@@ -0,0 +1,33 @@
+/*
+ * detselect.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef DETSELECT_H
+#define DETSELECT_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    DETSELECT_MODE_NONE      = 0x0,
+    DETSELECT_MODE_SEARCH,
+    DETSELECT_MODE_SELECT,
+} detselectMode;
+
+pxConfig *detselectConfig(pxConfig *config, int argc, char **argv);
+
+#endif // DETSELECT_H
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/detselectConfig.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/detselectConfig.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/detselectConfig.c	(revision 22073)
@@ -0,0 +1,269 @@
+/*
+ * detselectConfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+#include <math.h>
+
+#include "pxtools.h"
+#include "detselect.h"
+
+pxConfig *detselectConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    // -search
+    psMetadata *searchArgs = psMetadataAlloc();
+    psMetadataAddStr(searchArgs, PS_LIST_TAIL, "-inst", 0,
+            "search by camera", NULL);
+    psMetadataAddStr(searchArgs, PS_LIST_TAIL, "-telescope", 0,
+            "search by telescope", NULL);
+    psMetadataAddStr(searchArgs, PS_LIST_TAIL, "-det_type", 0,
+            "search by detrend type", NULL);
+    psMetadataAddStr(searchArgs, PS_LIST_TAIL, "-filter", 0,
+            "search by filter", NULL);
+    psMetadataAddF32(searchArgs, PS_LIST_TAIL, "-airmass", 0,
+            "define airmass", NAN);
+    psMetadataAddF32(searchArgs, PS_LIST_TAIL, "-exp_time", 0,
+            "search by exposure time", NAN);
+    psMetadataAddF32(searchArgs, PS_LIST_TAIL, "-ccd_temp", 0,
+            "search by ccd tempature", NAN);
+    psMetadataAddF64(searchArgs, PS_LIST_TAIL, "-posang", 0,
+            "search by rotator position angle", NAN);
+    psMetadataAddStr(searchArgs, PS_LIST_TAIL, "-time", 0,
+            "define time for desired detrend data", NULL);
+    psMetadataAddBool(searchArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+ 
+    // -select
+    psMetadata *selectArgs = psMetadataAlloc();
+    psMetadataAddStr(selectArgs, PS_LIST_TAIL, "-det_id", 0,
+            "search by detrend ID", NULL);
+    psMetadataAddS32(selectArgs, PS_LIST_TAIL, "-iteration", 0,
+            "search by iteration number", 0);
+    psMetadataAddStr(selectArgs, PS_LIST_TAIL, "-class_id", 0,
+            "search by class ID", NULL);
+    psMetadataAddBool(selectArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+    
+#define PXTOOL_MODE(option, modeval, argset) \
+{ \
+    int N = 0; \
+    if ((N = psArgumentGet (argc, argv, option))) { \
+        psArgumentRemove (N, &argc, argv); \
+        if (config->mode) { \
+            psError(PS_ERR_UNKNOWN, true, "only one mode selection is allowed"); \
+            psFree(config); \
+            return NULL; \
+        } \
+        config->mode = modeval; \
+        config->args = psMemIncrRefCounter(argset); \
+    } \
+    if (!psMetadataAddMetadata(argSets, PS_LIST_TAIL, option, 0, NULL, argset)) {;\
+        psError(PS_ERR_UNKNOWN, false, "failed to add argset for %s", option);\
+    } \
+    psFree(argset); \
+}
+
+    psMetadata *argSets = psMetadataAlloc();
+    // find which mode we're running under
+    PXTOOL_MODE("-search",  DETSELECT_MODE_SEARCH, searchArgs);
+    PXTOOL_MODE("-select",  DETSELECT_MODE_SELECT, selectArgs);
+
+    bool argErr = false;
+    if (config->mode == DETSELECT_MODE_NONE) {
+        argErr = true;
+        fprintf (stderr, "mode argument is required\n");
+    } else if (! psArgumentParse(config->args, &argc, argv) || argc != 1) {
+        argErr = true;
+        fprintf (stderr, "error parsing arguments\n");
+    }
+
+    if (argErr) {
+        printf("\nPan-STARRS detrend select Tool\n");
+        printf("Usage: %s <mode> [<options>]\n\n", argv[0]);
+        printf(" <mode> :\n\n");
+
+        psMetadataIterator *iter = psMetadataIteratorAlloc(argSets, 0, NULL);
+        psMetadataItem *item = NULL;
+        while ((item = psMetadataGetAndIncrement(iter))) {
+            if (!item->type == PS_DATA_METADATA) {
+                psAbort("all options must be specified as a metadata");            }
+
+            fprintf(stdout, "%s ", item->name);
+            psArgumentHelp(item->data.md);
+        }
+        psFree(iter);
+
+        psFree(argSets);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+
+    // setup search criterion
+#define addWhereStr(name) \
+{ \
+    psString str = NULL; \
+    bool status = false; \
+    if ((str = psMetadataLookupStr(&status, config->args, "-" #name))) { \
+        if (!psMetadataAddStr(config->where, PS_LIST_TAIL, #name, 0, "==", str)) {\
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+            psFree(config); \
+            return NULL; \
+        } \
+    } \
+}
+
+#define addWhereS32(name) \
+{ \
+    psS32 s32 = 0; \
+    bool status = false; \
+    if ((s32= psMetadataLookupS32(&status, config->args, "-" #name))) { \
+        if (!psMetadataAddS32(config->where, PS_LIST_TAIL, #name, 0, "==", s32)) { \
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+            psFree(config); \
+            return NULL; \
+        } \
+    } \
+}
+
+#define addWhereF32(name) \
+{ \
+    psF32 var = 0; \
+    bool status = false; \
+    if ((var = psMetadataLookupF32(&status, config->args, "-" #name))) { \
+        if (!isnan(var)) { \
+            if (!psMetadataAddF32(config->where, PS_LIST_TAIL, #name, 0, "==", var)) { \
+                psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+                psFree(config); \
+                return NULL; \
+            } \
+        } \
+    } \
+}
+
+#define addWhereF64(name) \
+{ \
+    psF64 var = 0; \
+    bool status = false; \
+    if ((var = psMetadataLookupF64(&status, config->args, "-" #name))) { \
+        if (!isnan(var))  { \
+            if (!psMetadataAddF64(config->where, PS_LIST_TAIL, #name, 0, "==", var)) { \
+                psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+                psFree(config); \
+                return NULL; \
+            } \
+        } \
+    } \
+}
+
+#define addWhereTimeStr(name) \
+{ \
+    psString str = NULL; \
+    bool status = false; \
+    if ((str = psMetadataLookupStr(&status, config->args, "-" #name))) { \
+        psTime *time = psTimeFromISO(str, PS_TIME_UTC); \
+        if (!time) { \
+            psError(PS_ERR_UNKNOWN, false, "failed to convert " #name " into a psTime object"); \
+            psFree(config); \
+            return NULL; \
+        } \
+        psMetadataItem *item = psMetadataLookup(config->args, "-" #name); \
+        if (item) { \
+            str = item->comment; \
+        } else { \
+            str = NULL; \
+        } \
+        if (!psMetadataAddTime(config->where, PS_LIST_TAIL, #name, 0, str, time)) {\
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+            psFree(config); \
+            return NULL; \
+        } \
+        psFree(time); \
+    } \
+}
+
+    // generate SQL where clause
+    config->where = psMetadataAlloc();
+
+    addWhereStr(det_id);
+    addWhereS32(iteration);
+    addWhereStr(exp_tag);
+    // convert '-inst' to 'camera'
+    {
+        psString str = NULL; 
+        bool status = false;
+        if ((str = psMetadataLookupStr(&status, config->args, "-inst"))) {
+            if (!psMetadataAddStr(config->where, PS_LIST_TAIL, "camera", 0, "==", str)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+                psFree(config);
+                return NULL;
+            }
+        }
+    }
+    addWhereStr(telescope);
+    addWhereStr(det_type);
+    addWhereStr(exp_type);
+    addWhereS32(imfiles);
+    addWhereStr(class);
+    addWhereStr(class_id);
+    addWhereStr(filter);
+//    addWhereF32(exp_time);
+//    addWhereF64(ccd_temp);
+//    addWhereF32(airmass);
+//    addWhereTimeStr(use_begin);
+//    addWhereTimeStr(use_end);
+
+    if (config->where->list->n < 1) {
+        psFree(config->where);
+        config->where = NULL;
+    }
+
+
+    // define Database handle, if used
+    config->dbh = pmConfigDB(config->modules);
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    // save argv/argc
+    config->argv = argv;
+    config->argc = argc;
+
+    return config;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/dettool.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/dettool.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/dettool.c	(revision 22073)
@@ -0,0 +1,6574 @@
+/*
+ * dettool.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVB_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#include <ippdb.h>
+
+#include "pxtools.h"
+#include "dettool.h"
+
+static bool pendingMode(pxConfig *config);
+static bool definebytagMode(pxConfig *config);
+static bool definebyqueryMode(pxConfig *config);
+static bool definebydetrunMode(pxConfig *config);
+static bool runsMode(pxConfig *config);
+static bool childlessrunMode(pxConfig *config);
+static bool inputMode(pxConfig *config);
+static bool rawMode(pxConfig *config);
+static bool toprocessedimfileMode(pxConfig *config);
+static bool addprocessedimfileMode(pxConfig *config);
+static bool toprocessedexpMode(pxConfig *config);
+static bool addprocessedexpMode(pxConfig *config);
+static bool processedexpMode(pxConfig *config);
+static bool tostackedMode(pxConfig *config);
+static bool processedimfileMode(pxConfig *config);
+static bool addstackedMode(pxConfig *config);
+static bool stackedMode(pxConfig *config);
+static bool tonormalizeMode(pxConfig *config);
+static bool addnormalizedstatMode(pxConfig *config);
+static bool tonormalizedstatMode(pxConfig *config);
+static bool addnormalizedimfileMode(pxConfig *config);
+static bool tonormalizedexpMode(pxConfig *config);
+static bool addnormalizedexpMode(pxConfig *config);
+static bool normalizedexpMode(pxConfig *config);
+static bool toresidimfileMode(pxConfig *config);
+static bool addresidimfileMode(pxConfig *config);
+static bool normalizedimfileMode(pxConfig *config);
+static bool toresidexpMode(pxConfig *config);
+static bool residimfileMode(pxConfig *config);
+static bool addresidexpMode(pxConfig *config);
+static bool residexpMode(pxConfig *config);
+static bool residdetrunMode(pxConfig *config);
+static bool updateresidexpMode(pxConfig *config);
+static bool adddetrunsummaryMode(pxConfig *config);
+static bool detrunsummaryMode(pxConfig *config);
+static bool updatedetrunMode(pxConfig *config);
+static bool rerunMode(pxConfig *config);
+static bool register_detrendMode(pxConfig *config);
+static bool register_detrend_imfileMode(pxConfig *config);
+
+static detNormalizedStatImfileRow *detStackedToDetNormalizedStatImfile(pxConfig *config, detStackedImfileRow *stackedImfile);
+static  detNormalizedImfileRow *detNormalizedStatToDetNormalizedmfile(pxConfig *config, detNormalizedStatImfileRow *statImfile);
+static  detResidImfileRow *detNormalizedToDetResidImfile(pxConfig *config, detNormalizedImfileRow *normalizedImfile);
+static detResidExpRow *mdToDetResidExp(pxConfig *config, psMetadata *row);
+static detRunSummaryRow *mdToDetRunSummary(pxConfig *config, psMetadata *row);
+//static psArray *validDetInputClassIds(pxConfig *config, const char *det_id);
+//static psArray *searchInputImfiles(pxConfig *config, const char *det_id);
+static detInputExpRow *rawDetrenTodetInputExpRow(rawExpRow *rawExp, psS32 det_id, psS32 iteration);
+static psArray *searchRawImfiles(pxConfig *config, psMetadata *where);
+static psS32 incrementIteration(pxConfig *config, const char *det_id);
+static bool setDetRunState(pxConfig *config, const char *det_id, const char *state);
+static bool isValidMode(pxConfig *config, const char *mode);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = dettoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(DETTOOL_MODE_PENDING,          pendingMode);
+        MODECASE(DETTOOL_MODE_DEFINEBYTAG,      definebytagMode);
+        MODECASE(DETTOOL_MODE_DEFINEBYQUERY,    definebyqueryMode);
+        MODECASE(DETTOOL_MODE_DEFINEBYDETRUN,   definebydetrunMode);
+        MODECASE(DETTOOL_MODE_RUNS,             runsMode);
+        MODECASE(DETTOOL_MODE_CHILDLESSRUN,     childlessrunMode);
+        MODECASE(DETTOOL_MODE_INPUT,            inputMode);
+        MODECASE(DETTOOL_MODE_RAW,              rawMode);
+        MODECASE(DETTOOL_MODE_TOPROCESSEDIMFILE,toprocessedimfileMode);
+        MODECASE(DETTOOL_MODE_ADDPROCESSEDIMFILE,addprocessedimfileMode);
+        MODECASE(DETTOOL_MODE_PROCESSEDIMFILE,  processedimfileMode);
+        MODECASE(DETTOOL_MODE_TOPROCESSEDEXP,   toprocessedexpMode);
+        MODECASE(DETTOOL_MODE_ADDPROCESSEDEXP,  addprocessedexpMode);
+        MODECASE(DETTOOL_MODE_PROCESSEDEXP,     processedexpMode);
+        MODECASE(DETTOOL_MODE_TOSTACKED,        tostackedMode);
+        MODECASE(DETTOOL_MODE_ADDSTACKED,       addstackedMode);
+        MODECASE(DETTOOL_MODE_STACKED,          stackedMode);
+        MODECASE(DETTOOL_MODE_TONORMALIZE,      tonormalizeMode);
+        MODECASE(DETTOOL_MODE_ADDNORMALIZEDSTAT,addnormalizedstatMode);
+        MODECASE(DETTOOL_MODE_TONORMALIZEDSTAT, tonormalizedstatMode);
+        MODECASE(DETTOOL_MODE_ADDNORMALIZEDIMFILE,addnormalizedimfileMode);
+        MODECASE(DETTOOL_MODE_NORMALIZEDIMFILE, normalizedimfileMode);
+        MODECASE(DETTOOL_MODE_TONORMALIZEDEXP,  tonormalizedexpMode);
+        MODECASE(DETTOOL_MODE_ADDNORMALIZEDEXP, addnormalizedexpMode);
+        MODECASE(DETTOOL_MODE_NORMALIZEDEXP,    normalizedexpMode);
+        MODECASE(DETTOOL_MODE_TORESIDIMFILE,    toresidimfileMode);
+        MODECASE(DETTOOL_MODE_ADDRESIDIMFILE,   addresidimfileMode);
+        MODECASE(DETTOOL_MODE_TORESIDEXP,       toresidexpMode);
+        MODECASE(DETTOOL_MODE_RESIDIMFILE,      residimfileMode);
+        MODECASE(DETTOOL_MODE_ADDRESIDEXP,      addresidexpMode);
+        MODECASE(DETTOOL_MODE_RESIDEXP,         residexpMode);
+        MODECASE(DETTOOL_MODE_RESIDDETRUN,      residdetrunMode);
+        MODECASE(DETTOOL_MODE_UPDATERESIDEXP,   updateresidexpMode);
+        MODECASE(DETTOOL_MODE_ADDDETRUNSUMMARY, adddetrunsummaryMode);
+        MODECASE(DETTOOL_MODE_DETRUNSUMMARY,    detrunsummaryMode);
+        MODECASE(DETTOOL_MODE_UPDATEDETRUN,     updatedetrunMode);
+        MODECASE(DETTOOL_MODE_RERUN,            rerunMode);
+        MODECASE(DETTOOL_MODE_REGISTER_DETREND, register_detrendMode);
+        MODECASE(DETTOOL_MODE_REGISTER_DETREND_IMFILE, register_detrend_imfileMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+static bool pendingMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+     psString query = psStringCopy(
+        "SELECT"
+        "   rawExp.*"
+        " FROM rawExp"
+        " LEFT JOIN detInputExp"
+        "   ON rawExp.exp_tag = detInputExp.exp_tag"
+        " WHERE"
+        "    detInputExp.exp_tag IS NULL"
+        "    AND rawExp.obstype != 'object'"
+    );
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "rawExp");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "rawExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool definebytagMode(pxConfig *config)
+{
+    bool status     = false;
+
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // what type of detRun is this?
+    // det_type is required
+    psString det_type = psMetadataLookupStr(&status, config->args, "-det_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -det_type");
+        return false;
+    }
+    if (!det_type) {
+        psError(PS_ERR_UNKNOWN, true, "-det_type is required");
+        return false;
+    }
+
+    // optional
+    psString filelevel = psMetadataLookupStr(&status, config->args, "-filelevel");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -filelevel");
+        return false;
+    }
+
+    psString workdir = psMetadataLookupStr(&status, config->args, "-workdir");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -workdir");
+        return false;
+    }
+    if (!workdir) {
+        psError(PS_ERR_UNKNOWN, true, "-workdir is required");
+        return false;
+    }
+
+    // optional
+    psString mode = psMetadataLookupStr(&status, config->args, "-mode");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -mode");
+        return false;
+    }
+    // check mode
+    if (mode && !isValidMode(config, mode)) {
+        psError(PS_ERR_UNKNOWN, false, "invalud mode");
+        return false;
+    }
+    psString camera = psMetadataLookupStr(&status, config->args, "-inst");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -inst");
+        return false;
+    }
+
+    psString telescope = psMetadataLookupStr(&status, config->args, "-telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -telescope");
+        return false;
+    }
+
+    psString exp_type = psMetadataLookupStr(&status, config->args, "-exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_type");
+        return false;
+    }
+
+    psString filter = psMetadataLookupStr(&status, config->args, "-filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -filter");
+        return false;
+    }
+
+    psF32 airmass_min = psMetadataLookupF32(&status, config->args, "-airmass_min");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -airmass_min");
+        return false;
+    }
+
+    psF32 airmass_max = psMetadataLookupF32(&status, config->args, "-airmass_max");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -airmass_max");
+        return false;
+    }
+
+    psF32 exp_time_min = psMetadataLookupF32(&status, config->args, "-exp_time_min");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_time_min");
+        return false;
+    }
+
+    psF32 exp_time_max = psMetadataLookupF32(&status, config->args, "-exp_time_max");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_time_max");
+        return false;
+    }
+
+    psF32 ccd_temp_min = psMetadataLookupF32(&status, config->args, "-ccd_temp_min");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -ccd_temp_min");
+        return false;
+    }
+
+    psF32 ccd_temp_max = psMetadataLookupF32(&status, config->args, "-ccd_temp_max");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -ccd_temp_max");
+        return false;
+    }
+
+    psF64 posang_min = psMetadataLookupF32(&status, config->args, "-posang_min");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -posang_min");
+        return false;
+    }
+
+    psF64 posang_max = psMetadataLookupF32(&status, config->args, "-posang_max");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -posang_max");
+        return false;
+    }
+
+    psTime *registered = NULL;
+    {
+        psString registeredStr = psMetadataLookupStr(&status, config->args, "-registered");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -registered");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (registeredStr) {
+            registered = psTimeFromISO(registeredStr, PS_TIME_UTC);
+            if (!registered) {
+                psError(PS_ERR_UNKNOWN, false, "error in time format %s", registeredStr);
+                return false;
+            }
+        } else {
+            registered = NULL;
+        }
+    }
+
+    psTime *time_begin = NULL;
+    {
+        psString time_beginStr = psMetadataLookupStr(&status, config->args, "-time_begin");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -time_begin");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (time_beginStr) {
+            time_begin = psTimeFromISO(time_beginStr, PS_TIME_UTC);
+            if (!time_begin) {
+                psError(PS_ERR_UNKNOWN, false, "error in time format %s", time_beginStr);
+                return false;
+            }
+        } else {
+            time_begin = NULL;
+        }
+    }
+
+    psTime *time_end = NULL;
+    {
+        psString time_endStr = psMetadataLookupStr(&status, config->args, "-time_end");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -time_end");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (time_endStr) {
+            time_end = psTimeFromISO(time_endStr, PS_TIME_UTC);
+            if (!time_end) {
+                psError(PS_ERR_UNKNOWN, false, "error in time format %s", time_endStr);
+                return false;
+            }
+        } else {
+            time_end = NULL;
+        }
+    }
+
+    psTime *use_begin = NULL;
+    {
+        psString use_beginStr = psMetadataLookupStr(&status, config->args, "-use_begin");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -use_begin");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (use_beginStr) {
+            use_begin = psTimeFromISO(use_beginStr, PS_TIME_UTC);
+            if (!use_begin) {
+                psError(PS_ERR_UNKNOWN, false, "error in time format %s", use_beginStr);
+                return false;
+            }
+        } else {
+            use_begin = NULL;
+        }
+    }
+
+    psTime *use_end = NULL;
+    {
+        psString use_endStr = psMetadataLookupStr(&status, config->args, "-use_end");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -use_end");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (use_endStr) {
+            use_end = psTimeFromISO(use_endStr, PS_TIME_UTC);
+            if (!use_end) {
+                psError(PS_ERR_UNKNOWN, false, "error in time format %s", use_endStr);
+                return false;
+            }
+        } else {
+            use_end = NULL;
+        }
+    }
+
+    psString label = psMetadataLookupStr(&status, config->args, "-label");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -label");
+        return false;
+    }
+
+    // we have to support multipe exp_tags
+    psMetadataItem *item = psMetadataLookup(config->args, "-exp_tag");
+    if (!item) {
+        // this shouldn't actually happen when using psArgs
+        psError(PS_ERR_UNKNOWN, true, "-exp_tag is required");
+        return false;
+    }
+    psMetadata *where = psMetadataAlloc();
+
+    // make sure that -exp_tag was parsed correctly
+    // XXX this can be removed someday
+    if (item->type == PS_DATA_METADATA_MULTI) {
+        psListIterator *iter = psListIteratorAlloc(item->data.list, 0, false);
+        psMetadataItem *mItem = NULL;
+        while ((mItem = psListGetAndIncrement(iter))) {
+            psString exp_tag = mItem->data.V;
+            // if exp_tag is NULL then it means that -exp_tag has not been
+            // specified
+            if (!exp_tag) {
+                psError(PS_ERR_UNKNOWN, true,
+                        "at least one -exp_tag is required");
+                psFree(where);
+                return false;
+            }
+
+            if (!psMetadataAddStr(where, PS_LIST_TAIL, "exp_tag",
+                 PS_META_DUPLICATE_OK, "==", exp_tag)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+                psFree(iter);
+                psFree(where);
+                return false;
+            }
+        }
+        psFree(iter);
+    } else {
+        psAbort(                "-exp_tag was not parsed correctly (this should not happen");
+    }
+
+    if (psListLength(where->list) < 1) {
+        psFree(where);
+        where = NULL;
+    }
+
+    // check that the specified exp_tags actually exist
+    psArray *detrendExps = rawExpSelectRowObjects(config->dbh, where, 0);
+    psFree(where);
+    if (!detrendExps) {
+        psError(PS_ERR_UNKNOWN, false, "no rawExp rows found");
+        return false;
+    }
+
+    // we should have one rawExp row per exp_tag specified
+    if (psListLength(item->data.list) != psArrayLength(detrendExps)) {
+        psAbort(    "an -exp_tag matched more then one rawExp (this should not happen");
+
+    }
+
+    // check to see if -filelevel was set on the command line
+    if (!filelevel) {
+        filelevel = psStringCopy(((rawExpRow *)detrendExps->data[0])->filelevel);
+    }
+
+    // start a transaction so we don't end up with childlessed det_ids
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(detrendExps);
+        return false;
+    }
+
+    // the first iteration is always 0
+    // XXX the camera name is set from the first inputExp
+    // XXX det_id
+    detRunInsert(config->dbh,
+            0,
+            0,
+            det_type,
+            mode,
+            "run",
+            filelevel,
+            workdir,
+            camera,
+            telescope,
+            exp_type,
+            filter,
+            airmass_min,
+            airmass_max,
+            exp_time_min,
+            exp_time_max,
+            ccd_temp_min,
+            ccd_temp_max,
+            posang_min,
+            posang_max,
+            registered,
+            time_begin,
+            time_end,
+            use_begin,
+            use_end,
+            0.0,    // solang min
+            0.0,    // solang max
+            label,
+            0       // parent
+        );
+    psFree(registered);
+    psFree(time_begin);
+    psFree(time_end);
+    psFree(use_begin);
+    psFree(use_end);
+    long det_id = psDBLastInsertID(config->dbh);
+
+    // create new detInputExp row(s) from the rawExp row(s)
+    psArray *inputExps = psArrayAllocEmpty(psArrayLength(detrendExps));
+    for (long i = 0; i < psArrayLength(detrendExps); i++) {
+        detInputExpRow *inputExp = rawDetrenTodetInputExpRow(
+            detrendExps->data[i],
+            det_id,
+            0 // the first iteration is explicitly 0
+        );
+        psArrayAdd(inputExps, 0, inputExp);
+        psFree(inputExp);
+    }
+
+    psFree(detrendExps);
+
+    // insert detInputExp objects into the database
+    if (!detInputExpInsertObjects(config->dbh, inputExps)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psFree(inputExps);
+        return false;
+    }
+    psFree(inputExps);
+
+    // point of no return for det_id creation
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // print the new det_id
+    psArray *detRuns = NULL;
+    {
+        psMetadata *where = psMetadataAlloc();
+        psMetadataAddS32(where, PS_LIST_TAIL, "det_id", 0, "==", det_id);
+        detRuns = psDBSelectRows(config->dbh, "detRun", where, 0);
+        psFree(where);
+    }
+    if (!detRuns) {
+        psError(PS_ERR_UNKNOWN, false, "can't find the detRun we just created");
+        return false;
+    }
+    // sanity check results
+    if (psArrayLength(detRuns) != 1) {
+        psAbort("found more then one detRun matching det_id %ld(this should not happen)", det_id);
+        return false;
+    }
+
+    if (!convertIdToStr(detRuns)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
+        psFree(detRuns);
+        return false;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, detRuns, "detRun", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(detRuns);
+        return false;
+    }
+    psFree(detRuns);
+
+    return true;
+}
+
+#if 0
+// This function is used to convert the det_id from an int, as it is used
+// internally, to be a string for external use.  The rational being that we may
+// want to change how det_id is generated in the future and don't want to
+// external programs to become depending on this value being an int.
+static bool convertDetIdToStr(psArray *mds)
+{
+    PS_ASSERT_PTR_NON_NULL(mds, false);
+
+    for (long i = 0; i < psArrayLength(mds); i++) {
+        psMetadata *md = mds->data[i];
+        bool status = false;
+        psS32 det_id = psMetadataLookupS32(&status, md, "det_id");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for det_id");
+            return false;
+        }
+        psMetadataRemoveKey(md, "det_id");
+        psString det_idStr = psDBIntToString((psU64)det_id);
+        psMetadataAddStr(mds->data[i], PS_LIST_HEAD, "det_id", 0, NULL, det_idStr);
+        psFree(det_idStr);
+    }
+
+    return true;
+}
+#endif
+
+static bool definebyqueryMode(pxConfig *config)
+{
+    bool status     = false;
+
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // what type of detRun is this?
+    // det_type is required
+    psString det_type = psMetadataLookupStr(&status, config->args, "-det_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -det_type");
+        return false;
+    }
+    if (!det_type) {
+        psError(PS_ERR_UNKNOWN, true, "-det_type is required");
+        return false;
+    }
+
+    // optional
+    psString filelevel = psMetadataLookupStr(&status, config->args, "-filelevel");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -filelevel");
+        return false;
+    }
+
+    psString workdir = psMetadataLookupStr(&status, config->args, "-workdir");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -workdir");
+        return false;
+    }
+    if (!workdir) {
+        psError(PS_ERR_UNKNOWN, true, "-workdir is required");
+        return false;
+    }
+
+    psMetadata *where = psMetadataAlloc();
+    {
+        bool status = false;
+        psString exp_type = psMetadataLookupStr(&status, config->args, "-select_exp_type");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -select_exp_type");
+            return false;
+        }
+        if (exp_type) {
+            if (!psMetadataAddStr(where, PS_LIST_TAIL, "exp_type", 0, "==", exp_type)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+                psFree(where);
+                return false;
+            }
+        }
+
+        // map -inst -> camera
+        psString camera = psMetadataLookupStr(&status, config->args, "-select_inst");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -select_inst");
+            return false;
+        }
+        if (camera) {
+            if (!psMetadataAddStr(where, PS_LIST_TAIL, "camera", 0, "==", camera)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item inst");
+                psFree(where);
+                return false;
+            }
+        }
+
+        psString telescope = psMetadataLookupStr(&status, config->args, "-select_telescope");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -select_telescope");
+            return false;
+        }
+        if (telescope) {
+            if (!psMetadataAddStr(where, PS_LIST_TAIL, "telescope", 0, "==", telescope)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+                psFree(where);
+                return false;
+            }
+        }
+
+        psString filter = psMetadataLookupStr(&status, config->args, "-select_filter");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -select_filter");
+            return false;
+        }
+        if (filter) {
+            if (!psMetadataAddStr(where, PS_LIST_TAIL, "filter", 0, "==", filter)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+                psFree(where);
+                return false;
+            }
+        }
+
+        psString uri = psMetadataLookupStr(&status, config->args, "-select_uri");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -select_uri");
+            return false;
+        }
+        if (uri) {
+            if (!psMetadataAddStr(where, PS_LIST_TAIL, "uri", 0, "==", uri)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+                psFree(where);
+                return false;
+            }
+        }
+
+        psString dateobs_begin = psMetadataLookupStr(&status, config->args, "-select_dateobs_begin");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -select_dateobs_begin");
+            psFree(where);
+            return false;
+        }
+        if (dateobs_begin) {
+            psTime *time = psTimeFromISO(dateobs_begin, PS_TIME_UTC);
+            if (!time) {
+                psError(PS_ERR_UNKNOWN, false, "error in time format %s", dateobs_begin);
+                psFree(time);
+                psFree(where);
+                return false;
+            }
+            if (!psMetadataAddTime(where, PS_LIST_TAIL, "dateobs", PS_META_DUPLICATE_OK, ">=", time)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
+                psFree(time);
+                psFree(where);
+                return false;
+            }
+            psFree(time);
+        }
+
+        psString dateobs_end = psMetadataLookupStr(&status, config->args, "-select_dateobs_end");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -select_dateobs_end");
+            psFree(where);
+            return false;
+        }
+        if (dateobs_end) {
+            psTime *time = psTimeFromISO(dateobs_end, PS_TIME_UTC);
+            if (!time) {
+                psError(PS_ERR_UNKNOWN, false, "error in time format %s", dateobs_end);
+                psFree(time);
+                psFree(where);
+                return false;
+            }
+            if (!psMetadataAddTime(where, PS_LIST_TAIL, "dateobs", PS_META_DUPLICATE_OK, "<", time)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
+                psFree(time);
+                psFree(where);
+                return false;
+            }
+            psFree(time);
+        }
+
+        /** selection based on airmass range **/
+        psF32 select_airmass_min = psMetadataLookupF32(&status, config->args, "-select_airmass_min");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -select_airmass_min");
+            psFree(where);
+            return false;
+        }
+        if (isfinite(select_airmass_min)) {
+            if (!psMetadataAddF32(where, PS_LIST_TAIL, "airmass", PS_META_DUPLICATE_OK, ">=", select_airmass_min)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+                psFree(where);
+                return false;
+            }
+        }
+        psF32 select_airmass_max = psMetadataLookupF32(&status, config->args, "-select_airmass_max");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -select_airmass_max");
+            psFree(where);
+            return false;
+        }
+        if (isfinite(select_airmass_max)) {
+            if (!psMetadataAddF32(where, PS_LIST_TAIL, "airmass", PS_META_DUPLICATE_OK, "<=", select_airmass_max)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+                psFree(where);
+                return false;
+            }
+        }
+
+        /** selection based on exp_time range **/
+        psF32 select_exp_time_min = psMetadataLookupF32(&status, config->args, "-select_exp_time_min");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -select_exp_time_min");
+            psFree(where);
+            return false;
+        }
+        if (isfinite(select_exp_time_min)) {
+            if (!psMetadataAddF32(where, PS_LIST_TAIL, "exp_time", PS_META_DUPLICATE_OK, ">=", select_exp_time_min)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+                psFree(where);
+                return false;
+            }
+        }
+        psF32 select_exp_time_max = psMetadataLookupF32(&status, config->args, "-select_exp_time_max");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -select_exp_time_max");
+            psFree(where);
+            return false;
+        }
+        if (isfinite(select_exp_time_max)) {
+            if (!psMetadataAddF32(where, PS_LIST_TAIL, "exp_time", PS_META_DUPLICATE_OK, "<=", select_exp_time_max)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+                psFree(where);
+                return false;
+            }
+        }
+
+        /** selection based on ccd_temp range **/
+        psF32 select_ccd_temp_min = psMetadataLookupF32(&status, config->args, "-select_ccd_temp_min");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -select_ccd_temp_min");
+            psFree(where);
+            return false;
+        }
+        if (isfinite(select_ccd_temp_min)) {
+            if (!psMetadataAddF32(where, PS_LIST_TAIL, "ccd_temp", PS_META_DUPLICATE_OK, "<=", select_ccd_temp_min)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp");
+                psFree(where);
+                return false;
+            }
+        }
+        psF32 select_ccd_temp_max = psMetadataLookupF32(&status, config->args, "-select_ccd_temp_max");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -select_ccd_temp_max");
+            psFree(where);
+            return false;
+        }
+        if (isfinite(select_ccd_temp_max)) {
+            if (!psMetadataAddF32(where, PS_LIST_TAIL, "ccd_temp", PS_META_DUPLICATE_OK, ">=", select_ccd_temp_max)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp");
+                psFree(where);
+                return false;
+            }
+        }
+
+        /** selection based on posang **/
+        psF32 select_posang_min = psMetadataLookupF32(&status, config->args, "-select_posang_min");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -select_posang_min");
+            psFree(where);
+            return false;
+        }
+        if (isfinite(select_posang_min)) {
+            if (!psMetadataAddF32(where, PS_LIST_TAIL, "posang", PS_META_DUPLICATE_OK, "<=", select_posang_min)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item posang");
+                psFree(where);
+                return false;
+            }
+        }
+        psF32 select_posang_max = psMetadataLookupF32(&status, config->args, "-select_posang_max");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -select_posang_max");
+            psFree(where);
+            return false;
+        }
+        if (isfinite(select_posang_max)) {
+            if (!psMetadataAddF32(where, PS_LIST_TAIL, "posang", PS_META_DUPLICATE_OK, ">=", select_posang_max)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item posang");
+                psFree(where);
+                return false;
+            }
+        }
+
+    }
+
+    if (!psListLength(where->list)) {
+        psFree(where);
+        where = NULL;
+    }
+
+    // there is some namespace overlap between the names of the fields we'd
+    // like to search by to setup a detrun and the names of the fields we'd
+    // like to assign values to so I've seperated them but prepending set- to
+    // the assigned values
+
+    // optional
+    psString mode = psMetadataLookupStr(&status, config->args, "-mode");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -mode");
+        return false;
+    }
+    // check mode
+    if (mode && !isValidMode(config, mode)) {
+        psError(PS_ERR_UNKNOWN, false, "invalud mode");
+        return false;
+    }
+
+    psString camera = psMetadataLookupStr(&status, config->args, "-inst");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -inst");
+        return false;
+    }
+
+    psString telescope = psMetadataLookupStr(&status, config->args, "-telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -telescope");
+        return false;
+    }
+
+    psString filter = psMetadataLookupStr(&status, config->args, "-filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -filter");
+        return false;
+    }
+
+    psF32 airmass_min = psMetadataLookupF32(&status, config->args, "-airmass_min");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -airmass_min");
+        return false;
+    }
+
+    psF32 airmass_max = psMetadataLookupF32(&status, config->args, "-airmass_max");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -airmass_max");
+        return false;
+    }
+
+    psF32 exp_time_min = psMetadataLookupF32(&status, config->args, "-exp_time_min");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_time_min");
+        return false;
+    }
+
+    psF32 exp_time_max = psMetadataLookupF32(&status, config->args, "-exp_time_max");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_time_max");
+        return false;
+    }
+
+    psF32 ccd_temp_min = psMetadataLookupF32(&status, config->args, "-ccd_temp_min");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -ccd_temp_min");
+        return false;
+    }
+
+    psF32 ccd_temp_max = psMetadataLookupF32(&status, config->args, "-ccd_temp_max");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -ccd_temp_max");
+        return false;
+    }
+
+    psF64 posang_min = psMetadataLookupF32(&status, config->args, "-posang_min");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -posang_min");
+        return false;
+    }
+
+    psF64 posang_max = psMetadataLookupF32(&status, config->args, "-posang_max");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -posang_max");
+        return false;
+    }
+
+    psTime *registered = NULL;
+    {
+        psString registeredStr = psMetadataLookupStr(&status, config->args, "-registered");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -registered");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (registeredStr) {
+            registered = psTimeFromISO(registeredStr, PS_TIME_UTC);
+            if (!registered) {
+                psError(PS_ERR_UNKNOWN, false, "error in time format %s", registeredStr);
+                return false;
+            }
+        } else {
+            registered = NULL;
+        }
+    }
+
+    psTime *time_begin = NULL;
+    {
+        psString time_beginStr = psMetadataLookupStr(&status, config->args, "-time_begin");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -time_begin");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (time_beginStr) {
+            time_begin = psTimeFromISO(time_beginStr, PS_TIME_UTC);
+            if (!time_begin) {
+                psError(PS_ERR_UNKNOWN, false, "error in time format %s", time_beginStr);
+                return false;
+            }
+        } else {
+            time_begin = NULL;
+        }
+    }
+
+    psTime *time_end = NULL;
+    {
+        psString time_endStr = psMetadataLookupStr(&status, config->args, "-time_end");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -time_end");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (time_endStr) {
+            time_end = psTimeFromISO(time_endStr, PS_TIME_UTC);
+            if (!time_end) {
+                psError(PS_ERR_UNKNOWN, false, "error in time format %s", time_endStr);
+                return false;
+            }
+        } else {
+            time_end = NULL;
+        }
+    }
+
+    psTime *use_begin = NULL;
+    {
+        psString use_beginStr = psMetadataLookupStr(&status, config->args, "-use_begin");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -use_begin");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (use_beginStr) {
+            use_begin = psTimeFromISO(use_beginStr, PS_TIME_UTC);
+            if (!use_begin) {
+                psError(PS_ERR_UNKNOWN, false, "error in time format %s", use_beginStr);
+                return false;
+            }
+        } else {
+            use_begin = NULL;
+        }
+    }
+
+    psTime *use_end = NULL;
+    {
+        psString use_endStr = psMetadataLookupStr(&status, config->args, "-use_end");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -use_end");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (use_endStr) {
+            use_end = psTimeFromISO(use_endStr, PS_TIME_UTC);
+            if (!use_end) {
+                psError(PS_ERR_UNKNOWN, false, "error in time format %s", use_endStr);
+                return false;
+            }
+        } else {
+            use_end = NULL;
+        }
+    }
+
+    psString label = psMetadataLookupStr(&status, config->args, "-label");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -label");
+        return false;
+    }
+
+    bool simple = psMetadataLookupBool(&status, config->args, "-simple");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+        psFree(registered);
+        psFree(use_begin);
+        psFree(use_end);
+        return false;
+    }
+
+    bool pretend = psMetadataLookupBool(&status, config->args, "-pretend");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -pretend");
+        psFree(registered);
+        psFree(use_begin);
+        psFree(use_end);
+        return false;
+    }
+
+    // search for rawExps with the specified options
+    psArray *detrendExps = rawExpSelectRowObjects(config->dbh, where, 0);
+    psFree(where);
+    // make sure that we found at least one rawExp
+    if (!detrendExps) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(registered);
+        psFree(use_begin);
+        psFree(use_end);
+        return false;
+    }
+    if (!psArrayLength(detrendExps)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(detrendExps);
+        psFree(registered);
+        psFree(use_begin);
+        psFree(use_end);
+        return true;
+    }
+
+    // check to see if -filelevel was set on the command line
+    if (!filelevel) {
+        filelevel = psStringCopy(((rawExpRow *)detrendExps->data[0])->filelevel);
+    }
+
+    if (pretend) {
+        // negative simple so the default is true
+        if (!rawExpPrintObjects(stdout, detrendExps, !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(detrendExps);
+            psFree(registered);
+            psFree(use_begin);
+            psFree(use_end);
+            return false;
+        }
+        psFree(detrendExps);
+        psFree(registered);
+        psFree(use_begin);
+        psFree(use_end);
+        return true;
+    }
+
+    // start a transaction so we don't end up with childlessed det_ids
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(detrendExps);
+        psFree(registered);
+        psFree(use_begin);
+        psFree(use_end);
+        return false;
+    }
+
+    // the first iteration is always 0
+    // XXX det_id
+    detRunInsert(config->dbh,
+            0,      // det_id
+            0,      // iteration
+            det_type,
+            mode,
+            "run",  // state
+            filelevel,
+            workdir,
+            camera,
+            telescope,
+            "NA",
+            filter,
+            airmass_min,
+            airmass_max,
+            exp_time_min,
+            exp_time_max,
+            ccd_temp_min,
+            ccd_temp_max,
+            posang_min,
+            posang_max,
+            registered,
+            time_begin,
+            time_end,
+            use_begin,
+            use_end,
+            0.0,    // solang min
+            0.0,    // solang max
+            label,
+            0       // parent
+        );
+    psFree(registered);
+    psFree(time_begin);
+    psFree(time_end);
+    psFree(use_end);
+    psFree(use_begin);
+    psFree(use_end);
+    long det_id = psDBLastInsertID(config->dbh);
+
+    // create new detInputExp row(s) from the rawExp row(s)
+    psArray *inputExps = psArrayAllocEmpty(psArrayLength(detrendExps));
+    for (long i = 0; i < psArrayLength(detrendExps); i++) {
+        detInputExpRow *inputExp = rawDetrenTodetInputExpRow(
+            detrendExps->data[i],
+            det_id,
+            0 // the first iteration is explicitly 0
+        );
+        psArrayAdd(inputExps, 0, inputExp);
+        psFree(inputExp);
+    }
+
+    psFree(detrendExps);
+
+    // insert detInputExp objects into the database
+    if (!detInputExpInsertObjects(config->dbh, inputExps)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psFree(inputExps);
+        return false;
+    }
+    psFree(inputExps);
+
+    // point of no return for det_id creation
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+
+    // print the new det_id
+    psArray *detRuns = NULL;
+    {
+        psMetadata *where = psMetadataAlloc();
+        psMetadataAddS32(where, PS_LIST_TAIL, "det_id", 0, "==", det_id);
+        detRuns = psDBSelectRows(config->dbh, "detRun", where, 0);
+        psFree(where);
+    }
+    if (!detRuns) {
+        psError(PS_ERR_UNKNOWN, false, "can't find the detRun we just created");
+        return false;
+    }
+    // sanity check results
+    if (psArrayLength(detRuns) != 1) {
+        psAbort("found more then one detRun matching det_id %ld(this should not happen)", det_id);
+        return false;
+    }
+
+    // convert det_id to a string externaly
+    if (!convertIdToStr(detRuns)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to convert det_id to a string");
+        psFree(detRuns);
+        return false;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, detRuns, "detRun", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(detRuns);
+        return false;
+    }
+    psFree(detRuns);
+
+    return true;
+}
+
+static bool definebydetrunMode(pxConfig *config)
+{
+    bool status     = false;
+
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // det_id is the only required input
+    psString det_id = psMetadataLookupStr(&status, config->args, "-det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -det_id");
+        return false;
+    }
+    if (!det_id) {
+        psError(PS_ERR_UNKNOWN, true, "-det_id is required");
+        return false;
+    }
+
+    // lookup the detRun that we will be basing this one on
+    psArray *detRuns = NULL;
+    {
+        psMetadata *where = psMetadataAlloc();
+        psMetadataAddS64(where, PS_LIST_TAIL, "det_id", 0, "==", (psS64)atoll(det_id));
+        detRuns = detRunSelectRowObjects(config->dbh, where, 0);
+        psFree(where);
+    }
+    if (!detRuns) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    // sanity check the result... we should have only found one det_id
+    if (psArrayLength(detRuns) != 1) {
+        psAbort("found more then one detRun matching det_id %" PRId64 " (this should not happen)", (psS64)atoll(det_id));
+        return false;                   // unreachable
+    }
+
+    // pull the detRun object out the result array
+    detRunRow *detRun = psMemIncrRefCounter(detRuns->data[0]);
+
+    // discard the resultarray
+    psFree(detRuns);
+
+    // set the det_id to 0/NULL so the database can assign it
+    detRun->det_id = 0;
+
+    // reset the iteration to 0
+    detRun->iteration = 0;
+
+    // reset the state to "run"
+    psFree(detRun->state);
+    detRun->state = psStringCopy("run");
+
+    // walk through the optional values and update the detRun as required
+    psString det_type = psMetadataLookupStr(&status, config->args, "-set_det_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_det_type");
+        return false;
+    }
+    if (det_type) {
+        psFree(detRun->det_type);
+        detRun->det_type = psStringCopy(det_type);
+    }
+
+    psString mode = psMetadataLookupStr(&status, config->args, "-set_mode");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_mode");
+        return false;
+    }
+    // check mode
+    if (mode && !isValidMode(config, mode)) {
+        psError(PS_ERR_UNKNOWN, false, "invalud mode");
+        return false;
+    }
+    if (mode) {
+        psFree(detRun->mode);
+        detRun->mode = psStringCopy(mode);
+    }
+
+    psString camera = psMetadataLookupStr(&status, config->args, "-set_inst");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_inst");
+        return false;
+    }
+    if (camera) {
+        psFree(detRun->camera);
+        detRun->camera = psStringCopy(camera);
+    }
+
+    psString telescope = psMetadataLookupStr(&status, config->args, "-set_telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_telescope");
+        return false;
+    }
+    if (telescope) {
+        psFree(detRun->telescope);
+        detRun->telescope = psStringCopy(telescope);
+    }
+
+    psString exp_type = psMetadataLookupStr(&status, config->args, "-set_exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_exp_type");
+        return false;
+    }
+    if (exp_type) {
+        psFree(detRun->exp_type);
+        detRun->exp_type = psStringCopy(exp_type);
+    }
+
+    psString filelevel = psMetadataLookupStr(&status, config->args, "-set_filelevel");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_filelevel");
+        return false;
+    }
+    if (filelevel) {
+        psFree(detRun->filelevel);
+        detRun->filelevel = psStringCopy(filelevel);
+    }
+
+    psString workdir = psMetadataLookupStr(&status, config->args, "-set_workdir");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_workdir");
+        return false;
+    }
+    if (workdir) {
+        psFree(detRun->workdir);
+        detRun->workdir = psStringCopy(workdir);
+    }
+
+    psString filter = psMetadataLookupStr(&status, config->args, "-set_filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_filter");
+        return false;
+    }
+    if (filter) {
+        psFree(detRun->filter);
+        detRun->filter = psStringCopy(filter);
+    }
+
+    psF32 airmass_min = psMetadataLookupF32(&status, config->args, "-set_airmass_min");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_airmass_min");
+        return false;
+    }
+    if (!isnan(airmass_min)) {
+        detRun->airmass_min = airmass_min;
+    }
+
+    psF32 airmass_max = psMetadataLookupF32(&status, config->args, "-set_airmass_max");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_airmass_max");
+        return false;
+    }
+    if (!isnan(airmass_max)) {
+        detRun->airmass_max = airmass_max;
+    }
+
+    psF32 exp_time_min = psMetadataLookupF32(&status, config->args, "-set_exp_time_min");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_exp_time_min");
+        return false;
+    }
+    if (!isnan(exp_time_min)) {
+        detRun->exp_time_min = exp_time_min;
+    }
+
+    psF32 exp_time_max = psMetadataLookupF32(&status, config->args, "-set_exp_time_max");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_exp_time_max");
+        return false;
+    }
+    if (!isnan(exp_time_max)) {
+        detRun->exp_time_max = exp_time_max;
+    }
+
+    psF32 ccd_temp_min = psMetadataLookupF32(&status, config->args, "-set_ccd_temp_min");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_ccd_temp_min");
+        return false;
+    }
+    if (!isnan(ccd_temp_min)) {
+        detRun->ccd_temp_min = ccd_temp_min;
+    }
+
+    psF32 ccd_temp_max = psMetadataLookupF32(&status, config->args, "-set_ccd_temp_max");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_ccd_temp_max");
+        return false;
+    }
+    if (!isnan(ccd_temp_max)) {
+        detRun->ccd_temp_max = ccd_temp_max;
+    }
+
+    psF64 posang_min = psMetadataLookupF32(&status, config->args, "-set_posang_min");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_posang_min");
+        return false;
+    }
+    if (!isnan(posang_min)) {
+        detRun->posang_min = posang_min;
+    }
+
+    psF64 posang_max = psMetadataLookupF32(&status, config->args, "-set_posang_max");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_posang_max");
+        return false;
+    }
+    if (!isnan(posang_max)) {
+        detRun->posang_max = posang_max;
+    }
+
+    psString label = psMetadataLookupStr(&status, config->args, "-set_label");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_label");
+        return false;
+    }
+    if (label) {
+        detRun->label = label;
+    }
+
+    psTime *registered = NULL;
+    {
+        psString registeredStr = psMetadataLookupStr(&status, config->args, "-set_registered");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_registered");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (registeredStr) {
+            psFree(detRun->registered);
+            registered = psTimeFromISO(registeredStr, PS_TIME_UTC);
+            detRun->registered = psMemIncrRefCounter(registered);
+            psFree(registered);
+        }
+    }
+
+    psTime *time_begin = NULL;
+    {
+        psString time_beginStr = psMetadataLookupStr(&status, config->args, "-set_time_begin");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_time_begin");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (time_beginStr) {
+            psFree(detRun->time_begin);
+            time_begin = psTimeFromISO(time_beginStr, PS_TIME_UTC);
+            detRun->time_begin = psMemIncrRefCounter(time_begin);
+            psFree(time_begin);
+        }
+    }
+
+    psTime *time_end = NULL;
+    {
+        psString time_endStr = psMetadataLookupStr(&status, config->args, "-set_time_end");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_time_end");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (time_endStr) {
+            psFree(detRun->time_end);
+            time_end = psTimeFromISO(time_endStr, PS_TIME_UTC);
+            detRun->time_end = psMemIncrRefCounter(time_end);
+            psFree(time_end);
+        }
+    }
+
+    psTime *use_begin = NULL;
+    {
+        psString use_beginStr = psMetadataLookupStr(&status, config->args, "-set_use_begin");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_use_begin");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (use_beginStr) {
+            psFree(detRun->use_begin);
+            use_begin = psTimeFromISO(use_beginStr, PS_TIME_UTC);
+            detRun->use_begin = psMemIncrRefCounter(use_begin);
+            psFree(use_begin);
+        }
+    }
+
+    psTime *use_end = NULL;
+    {
+        psString use_endStr = psMetadataLookupStr(&status, config->args, "-set_use_end");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -set_use_end");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (use_endStr) {
+            psFree(detRun->use_end);
+            use_end = psTimeFromISO(use_endStr, PS_TIME_UTC);
+            detRun->use_end = psMemIncrRefCounter(use_end);
+            psFree(use_end);
+        }
+    }
+
+    // create a metadata to restrict detInputExp's be in in the specified range
+
+    psMetadata *time_filter = psMetadataAlloc();
+
+    {
+        psString timeStr = psMetadataLookupStr(&status, config->args, "-filter_input_begin");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -filter_input_begin");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (timeStr) {
+            psTime *time = psTimeFromISO(timeStr, PS_TIME_UTC);
+            if (!psMetadataAddTime(time_filter, PS_LIST_TAIL, "dateobs", PS_META_DUPLICATE_OK, ">=", time)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
+                psFree(time);
+                psFree(time_filter);
+                return false;
+            }
+            psFree(time);
+        }
+    }
+
+    {
+        psString timeStr = psMetadataLookupStr(&status, config->args, "-filter_input_end");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -filter_input_end");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (timeStr) {
+            psTime *time = psTimeFromISO(timeStr, PS_TIME_UTC);
+            if (!psMetadataAddTime(time_filter, PS_LIST_TAIL, "dateobs", PS_META_DUPLICATE_OK, "<", time)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
+                psFree(time);
+                psFree(time_filter);
+                return false;
+            }
+            psFree(time);
+        }
+    }
+
+
+    // start a transaction so we don't end up with childlessed det_ids
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(time_filter);
+        psFree(detRun);
+        return false;
+    }
+
+    if (!detRunInsertObject(config->dbh, detRun)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psFree(time_filter);
+        psFree(detRun);
+        return false;
+    }
+    psFree(detRun);
+
+    // get the det_id
+    long newDet_id = psDBLastInsertID(config->dbh);
+
+    psString query = psStringCopy(
+        "INSERT INTO detInputExp"
+        "   SELECT"
+        "       %d,"
+        "       0,"
+        "       detResidExp.exp_tag,"
+        "       detResidExp.accept"
+        "   FROM detResidExp"
+        "   JOIN rawExp"
+        "       USING(exp_tag)"
+        "   WHERE det_id = %d"
+    );
+
+    if (time_filter->list->n) {
+        psString whereClause = psDBGenerateWhereConditionSQL(time_filter, "rawExp");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+    psFree(time_filter);
+
+    if (!p_psDBRunQuery(config->dbh, query, (psS64)newDet_id, (psS64)atoll(det_id))) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    // point of no return for det_id creation
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // print the new det_id
+    psArray *newDetRuns = NULL;
+    {
+        psMetadata *where = psMetadataAlloc();
+        psMetadataAddS32(where, PS_LIST_TAIL, "det_id", 0, "==", newDet_id);
+        newDetRuns = psDBSelectRows(config->dbh, "detRun", where, 0);
+        psFree(where);
+    }
+    if (!newDetRuns) {
+        psError(PS_ERR_UNKNOWN, false, "can't find the detRun we just created");
+        return false;
+    }
+    // sanity check the result... we should have only found one det_id
+    if (psArrayLength(newDetRuns) != 1) {
+        psAbort("found more then one detRun matching det_id %ld(this should not happen)", newDet_id);
+        return false;                   // unreachable
+    }
+
+    // convert det_id to a string externaly
+    if (!convertIdToStr(detRuns)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
+        psFree(detRuns);
+        return false;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, newDetRuns, "detRun", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(newDetRuns);
+        return false;
+    }
+    psFree(newDetRuns);
+
+    return true;
+}
+
+static bool runsMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // XXX fix the hard coding of the table name
+    psArray *runs = psDBSelectRows(config->dbh, "detRun", config->where, 0);
+    if (!runs) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!psArrayLength(runs)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(runs);
+        return true;
+    }
+
+    // convert det_id to a string externaly
+    if (!convertIdToStr(runs)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
+        psFree(runs);
+        return false;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, runs, "detRun", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(runs);
+        return false;
+    }
+
+    psFree(runs);
+
+    return true;
+}
+
+static bool childlessrunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    psString query = psStringCopy(
+            "SELECT DISTINCT\n"
+            "   detRun.*\n"
+            " FROM detRun\n"
+            " LEFT JOIN detRun as foo\n"
+            "   ON foo.parent = detRun.det_id\n"
+            " WHERE\n"
+            "   detRun.state = 'stop'\n"
+            "   AND detRun.mode = 'master'\n"
+            "   AND foo.det_id IS NULL\n"
+        );
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "detRun");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // convert det_id to a string externaly
+    if (!convertIdToStr(output)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
+        psFree(output);
+        return false;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            psFree(output);
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detRun", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static detInputExpRow *rawDetrenTodetInputExpRow(rawExpRow *rawExp, psS32 det_id, psS32 iteration)
+{
+    PS_ASSERT_PTR_NON_NULL(rawExp, NULL);
+
+    return detInputExpRowAlloc(
+        det_id,
+        iteration,
+        rawExp->exp_tag,
+        true            // use
+    );
+}
+
+static bool inputMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // select detInputExp.*
+    // select rawExp.*
+    // by:
+    // exp_tag
+
+    psString query = psStringCopy(
+        "SELECT DISTINCT *"
+        " FROM detInputExp"
+        " JOIN rawExp"
+        " USING(exp_tag)"
+        );
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereSQL(config->where, "detInputExp");
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detInputExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool rawMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psString query = psStringCopy(
+            "SELECT"
+            "   detInputExp.det_id,"
+            "   detRun.det_type,"
+            "   rawImfile.*,"
+            "   rawExp.camera"
+            " FROM rawImfile"
+            " JOIN detInputExp"
+            "   USING(exp_tag) "
+            " JOIN rawExp"
+            "   USING(exp_tag) "
+            " JOIN detRun"
+            "   ON detInputExp.det_id = detRun.det_id"
+            " WHERE"
+            "   detRun.state = 'run'"
+            "   AND detRun.mode = 'master'"
+        );
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "rawImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "rawImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool toprocessedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    // select det_id, det_type & rawImfile *
+    // get det_id from detInputExp.det_id
+    // get det_type from detRun.det_id
+    //
+    // det_id is in detInputExp
+    // exp_tag is in detInputExp
+    // det_id is not in detProccessedImfile
+    // exp_tag is not in detProccessedImfile
+    // class_is is not in detProccessedImfile
+    psString query = psStringCopy(
+            "SELECT"
+            "   detRun.det_id,"
+            "   detRun.det_type,"
+            "   detRun.workdir,"
+            "   rawImfile.*,"
+            "   rawExp.camera"
+            " FROM detRun"
+            " JOIN detInputExp"
+            "    USING(det_id, iteration)"
+            " JOIN rawExp"
+            "    ON detInputExp.exp_tag = rawExp.exp_tag"
+            " JOIN rawImfile"
+            "    ON detInputExp.exp_tag = rawImfile.exp_tag"
+            " LEFT JOIN detProcessedImfile"
+            "   ON detInputExp.det_id = detProcessedImfile.det_id"
+            "   AND rawImfile.exp_tag = detProcessedImfile.exp_tag"
+            "   AND rawImfile.class_id = detProcessedImfile.class_id"
+            " WHERE"
+            "   detRun.state = 'run'"
+            "   AND detRun.mode = 'master'"
+            "   AND detProcessedImfile.det_id IS NULL"
+            "   AND detProcessedImfile.exp_tag IS NULL"
+            "   AND detProcessedImfile.class_id IS NULL"
+        );
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "rawImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detPendingProcessedImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static psArray *searchRawImfiles(pxConfig *config, psMetadata *where)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    // use the default where if "where" is NULL
+    if (!where) {
+        where = config->where;
+    }
+
+    // select exp_tags from detInputExp matching det_idp
+    // where query should be pre-generated
+    psArray *detInputExp =
+        detInputExpSelectRowObjects(config->dbh, where, 0);
+    if (!detInputExp) {
+        psError(PS_ERR_UNKNOWN, false, "no rawExp rows found");
+        return NULL;
+    }
+
+    // generate where query with just the exp_tags
+    psMetadata *where_exp_tags = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(detInputExp); i++) {
+        detInputExpRow *row = detInputExp->data[i];
+        if (!psMetadataAddStr(where_exp_tags, PS_LIST_TAIL, "exp_tag",
+                PS_META_DUPLICATE_OK, "==", row->exp_tag)
+        ) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+            psFree(detInputExp);
+            psFree(where_exp_tags);
+            return NULL;
+        }
+    }
+    psFree(detInputExp);
+
+    // select rawImfiles with matching exp_tags
+    psArray *rawImfiles =
+        rawImfileSelectRowObjects(config->dbh, where_exp_tags, 0);
+    psFree(where_exp_tags);
+    if (!rawImfiles) {
+        psError(PS_ERR_UNKNOWN, false, "no rawImfile rows found");
+        return NULL;
+    }
+
+    return rawImfiles;
+}
+
+static bool addprocessedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // det_id, exp_tag, class_id, uri, recipe, -bg, -bg_stdev
+    // are required
+    bool status = false;
+    psString det_id = psMetadataLookupStr(&status, config->args, "-det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -det_id");
+        return false;
+    }
+    if (!det_id) {
+        psError(PS_ERR_UNKNOWN, true, "-det_id is required");
+        return false;
+    }
+    psString exp_tag = psMetadataLookupStr(&status, config->args, "-exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_tag");
+        return false;
+    }
+    if (!exp_tag) {
+        psError(PS_ERR_UNKNOWN, true, "-exp_tag is required");
+        return false;
+    }
+    psString class_id = psMetadataLookupStr(&status, config->args, "-class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -class_id");
+        return false;
+    }
+    if (!class_id) {
+        psError(PS_ERR_UNKNOWN, true, "-class_id is required");
+        return false;
+    }
+    psString uri    = psMetadataLookupStr(&status, config->args, "-uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -uri");
+        return false;
+    }
+    if (!uri) {
+        psError(PS_ERR_UNKNOWN, true, "-uri is required");
+        return false;
+    }
+    psString recipe = psMetadataLookupStr(&status, config->args, "-recip");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -recip");
+        return false;
+    }
+    if (!recipe) {
+        psError(PS_ERR_UNKNOWN, true, "-recip is required");
+        return false;
+    }
+    psF64 bg = psMetadataLookupF64(&status, config->args, "-bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg");
+        return false;
+    }
+    if (isnan(bg)) {
+        psError(PS_ERR_UNKNOWN, true, "-bg is required");
+        return false;
+    }
+    psF64 bg_stdev = psMetadataLookupF64(&status, config->args, "-bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_stdev");
+        return false;
+    }
+    if (isnan(bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, true, "-bg_stdev is required");
+        return false;
+    }
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, config->args, "-bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_mean_stdev");
+        return false;
+    }
+    // optional
+    psString path_base = psMetadataLookupStr(&status, config->args, "-path_base");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -path_base");
+        return false;
+    }
+
+    // find the matching rawImfile by exp_tag/class_id
+    psMetadata *where = psMetadataAlloc();
+    if (!psMetadataAddStr(where, PS_LIST_TAIL, "exp_tag", 0, "==", exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(where);
+        return false;
+    }
+    if (!psMetadataAddStr(where, PS_LIST_TAIL, "class_id", 0, "==", class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(where);
+        return false;
+    }
+
+    // default values
+    psS8 code = psMetadataLookupS8(&status, config->args, "-code");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -code");
+        return false;
+    }
+
+    psArray *rawImfiles = rawImfileSelectRowObjects(config->dbh, where, 0);
+    psFree(where);
+    if (!rawImfiles) {
+        psError(PS_ERR_UNKNOWN, false, "no rawImfile rows found ");
+        return false;
+    }
+
+
+    // create a new detProcessedImfile object
+    detProcessedImfileRow *detRow = detProcessedImfileRowAlloc(
+        (psS32)atol(det_id),
+        exp_tag,
+        class_id,
+        uri,
+        recipe,
+        bg,
+        bg_stdev,
+        bg_mean_stdev,
+        path_base,
+        code
+    );
+    psFree(rawImfiles);
+
+    // insert the new row into the detProcessedImfile table
+    if (!detProcessedImfileInsertObject(config->dbh, detRow)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(detRow);
+        return false;
+    }
+
+    psFree(detRow);
+
+    return true;
+}
+
+static bool toprocessedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    // select detProcessedImfile.det_id
+    // select detRun.iteration
+    // select detRun.det_type
+    // select detProcessedImfile.exp_tag
+    // by:
+    // find the current iteration bassed on det_id
+    // find all exp_tags in the current det_id/iteration from detInputExp
+    // find all rawImfiles in the current exp_tags
+    // compare to detProcessedImfiles by det_id/exp_tag
+    // found how many imfile there are in each class_id
+    // and:
+    // det_id is not in detProcessedExp;
+    // iteration is not in detProcessedExp;
+
+    psString query = psStringCopy(
+        " SELECT DISTINCT"
+        "    det_id,"
+        "    iteration,"
+        "    det_type,"
+        "    exp_tag,"
+        "    camera,"
+        "    workdir"
+        " FROM"
+        " (SELECT DISTINCT"
+        "    detRun.det_id,"
+        "    detRun.iteration,"
+        "    detRun.det_type,"
+        "    detRun.workdir,"
+        "    detProcessedImfile.exp_tag,"
+        "    rawExp.camera,"
+        "    detProcessedImfile.class_id,"
+        "    rawExp.imfiles"
+        " FROM detRun"
+        " JOIN detInputExp"
+        "   USING(det_id, iteration)"
+        " JOIN rawExp"
+        "   ON detInputExp.exp_tag = rawExp.exp_tag"
+        " JOIN rawImfile"
+        "   ON rawExp.exp_tag = rawImfile.exp_tag"
+        " LEFT JOIN detProcessedImfile"
+        "   ON detRun.det_id = detProcessedImfile.det_id"
+        "   AND detInputExp.exp_tag = detProcessedImfile.exp_tag"
+        "   AND rawImfile.class_id = detProcessedImfile.class_id"
+        " LEFT JOIN detProcessedExp"
+        "   ON detProcessedImfile.det_id = detProcessedExp.det_id"
+        "   AND detProcessedImfile.exp_tag = detProcessedExp.exp_tag"
+        " WHERE"
+        "   detRun.state = 'run'"
+        "   AND detRun.mode = 'master'"
+        "   AND detProcessedImfile.fault = 0"
+        "   AND detProcessedExp.det_id IS NULL"
+        "   AND detProcessedExp.exp_tag IS NULL"
+        "   AND detInputExp.include = 1"
+        " GROUP BY"
+        "    rawExp.exp_tag,"
+        "    detRun.det_id"
+        " HAVING"
+        "    COUNT(detProcessedImfile.class_id) = rawExp.imfiles"
+        " ) AS detProcessedExp"
+        );
+
+    // XXX does it make sense to accept any search params?
+#if 0
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+#endif
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detPendingProcessedExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool addprocessedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // det_id, exp_tag, recip, -bg, -bg_stdev
+    // are required
+    bool status = false;
+    psString det_id = psMetadataLookupStr(&status, config->args, "-det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -det_id");
+        return false;
+    }
+    if (!det_id) {
+        psError(PS_ERR_UNKNOWN, true, "-det_id is required");
+        return false;
+    }
+    psString exp_tag = psMetadataLookupStr(&status, config->args, "-exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_tag");
+        return false;
+    }
+    if (!exp_tag) {
+        psError(PS_ERR_UNKNOWN, true, "-exp_tag is required");
+        return false;
+    }
+    psString recipe = psMetadataLookupStr(&status, config->args, "-recip");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -recip");
+        return false;
+    }
+    if (!recipe) {
+        psError(PS_ERR_UNKNOWN, true, "-recip is required");
+        return false;
+    }
+    psF64 bg = psMetadataLookupF64(&status, config->args, "-bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg");
+        return false;
+    }
+    if (isnan(bg)) {
+        psError(PS_ERR_UNKNOWN, true, "-bg is required");
+        return false;
+    }
+    psF64 bg_stdev = psMetadataLookupF64(&status, config->args, "-bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_stdev");
+        return false;
+    }
+    if (isnan(bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, true, "-bg_stdev is required");
+        return false;
+    }
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, config->args, "-bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_mean_stdev");
+        return false;
+    }
+    // optional
+    psString path_base = psMetadataLookupStr(&status, config->args, "-path_base");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -path_base");
+        return false;
+    }
+
+    // default values
+    psS8 code = psMetadataLookupS8(&status, config->args, "-code");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -code");
+        return false;
+    }
+
+    psString query = psStringCopy(
+        " SELECT DISTINCT"
+        "    detProcessedImfile.det_id,"
+        "    detRun.iteration,"
+        "    detRun.det_type,"
+        "    detProcessedImfile.exp_tag"
+        " FROM detRun"
+        " JOIN detInputExp"
+        "    ON detRun.det_id = detInputExp.det_id"
+        "    AND detRun.iteration = detInputExp.iteration"
+        " JOIN rawExp"
+        "    ON detInputExp.exp_tag = rawExp.exp_tag"
+        " JOIN detProcessedImfile"
+        "    ON detInputExp.det_id = detProcessedImfile.det_id"
+        "    AND detInputExp.exp_tag = detProcessedImfile.exp_tag"
+        " LEFT JOIN detProcessedExp"
+        "    ON detInputExp.det_id = detProcessedExp.det_id"
+        "    AND detProcessedImfile.exp_tag= detProcessedExp.exp_tag"
+        " LEFT JOIN rawImfile"
+        "    ON detInputExp.exp_tag = rawImfile.exp_tag"
+        "    AND detProcessedImfile.class_id = rawImfile.class_id"
+        " WHERE"
+        "   detRun.state = 'run'"
+        "   AND detRun.mode = 'master'"
+        "   AND detProcessedExp.det_id IS NULL"
+        "   AND detProcessedExp.exp_tag IS NULL"
+        "   AND detInputExp.include = 1"
+        "   AND detRun.det_id = %s"
+        "   AND detProcessedImfile.exp_tag = '%s'"
+        " GROUP BY"
+        "    detProcessedImfile.class_id,"
+        "    rawImfile.class_id,"
+        "    detRun.det_id"
+        " HAVING"
+        "    COUNT(detProcessedImfile.class_id) = COUNT(rawImfile.class_id)"
+        );
+
+    if (!p_psDBRunQuery(config->dbh, query, det_id, exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+    psFree(output);
+
+    // create a new detProcessedImfile object
+    detProcessedExpRow *detRow = detProcessedExpRowAlloc(
+        (psS32)atol(det_id),
+        exp_tag,
+        recipe,
+        bg,
+        bg_stdev,
+        bg_mean_stdev,
+        path_base,
+        code
+    );
+
+    // insert the new row into the detProcessedImfile table
+    if (!detProcessedExpInsertObject(config->dbh, detRow)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(detRow);
+        return false;
+    }
+
+    psFree(detRow);
+
+    return true;
+}
+
+static bool processedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    bool faulted = psMetadataLookupU64(&status, config->args, "-faulted");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -faulted");
+        return false;
+    }
+
+    psString query = psStringCopy("SELECT * FROM detProcessedExp");
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereSQL(config->where, NULL);
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND detProcessedExp.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND detProcessedExp.fault = 0");
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detProcessedExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool tostackedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    // select detRun.iteration
+    // select detProcessedImfile.det_id
+    // select detProcessedImfile.class_id
+    // by:
+    // find the current iteration bassed on det_id
+    // find all exp_tags in the current det_id/iteration from detInputExp
+    // find all rawImfiles in the current exp_tags
+    // compare to detProcessedImfiles by det_id/exp_tag
+    // found how many imfile there are in each class_id
+    // and:
+    // det_id is not in detStackedImfile;
+    // iteration is not in detStackedImfile;
+    // class_id is not in detStackedImfile;
+
+    psString query = psStringCopy(
+        " SELECT\n"
+        "    detProcessedImfile.det_id,\n"
+        "    detRun.iteration,\n"
+        "    detRun.det_type,\n"
+        "    detRun.workdir,\n"
+        "    detProcessedImfile.class_id,\n"
+        "    rawExp.camera\n"
+        " FROM detRun\n"
+        " JOIN detInputExp\n"
+        "    ON detRun.det_id = detInputExp.det_id\n"
+        "    AND detRun.iteration = detInputExp.iteration\n"
+        " JOIN rawExp\n"
+        "    ON detInputExp.exp_tag = rawExp.exp_tag\n"
+        " JOIN rawImfile\n"
+        "    ON detInputExp.exp_tag = rawImfile.exp_tag\n"
+        " LEFT JOIN detProcessedImfile\n"
+        "    ON detInputExp.det_id = detProcessedImfile.det_id\n"
+        "    AND detInputExp.exp_tag = detProcessedImfile.exp_tag\n"
+        "    AND rawImfile.class_id = detProcessedImfile.class_id\n"
+        " LEFT JOIN detStackedImfile\n"
+        "    ON detInputExp.det_id = detStackedImfile.det_id\n"
+        "    AND detInputExp.iteration = detStackedImfile.iteration\n"
+        "    AND rawImfile.class_id = detStackedImfile.class_id\n"
+        " WHERE\n"
+        "   detRun.state = 'run'\n"
+        "   AND detRun.mode = 'master'\n"
+        "   AND (detProcessedImfile.fault = 0 or detProcessedImfile.fault IS NULL)\n"
+        "   AND detStackedImfile.det_id IS NULL\n"
+        "   AND detStackedImfile.iteration IS NULL\n"
+        "   AND detStackedImfile.class_id IS NULL\n"
+        "   AND detInputExp.include = 1\n"
+        " GROUP BY\n"
+        "    rawImfile.class_id,\n"
+        "    detRun.det_id\n"
+        " HAVING\n"
+        "    COUNT(detProcessedImfile.class_id) = COUNT(rawImfile.class_id)\n"
+        );
+
+    // XXX does it make sense to accept any search params?
+#if 0
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+#endif
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detPendingStackedImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool processedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    bool faulted = psMetadataLookupU64(&status, config->args, "-faulted");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -faulted");
+        return false;
+    }
+
+    psString query = psStringCopy(
+        " SELECT DISTINCT"
+        "   detRun.det_type,"
+        "   detProcessedImfile.*"
+        " FROM detProcessedImfile"
+        " JOIN detRun"
+        "   USING(det_id)"
+        " JOIN detInputExp"
+        "   ON detRun.det_id = detInputExp.det_id"
+        "   AND detRun.iteration = detInputExp.iteration"
+        "   AND detProcessedImfile.exp_tag = detInputExp.exp_tag"
+        " WHERE"
+        "   detRun.state = 'run'"
+        "   AND detRun.mode = 'master'"
+    );
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "detProcessedImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    {
+        bool status = false;
+        bool included = psMetadataLookupBool(&status, config->args, "-included");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+        // restrict search to included imfiles
+        if (included) {
+            psStringAppend(&query, " AND detInputExp.include = 1");
+        }
+    }
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND detProcessedImfile.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND detProcessedImfile.fault = 0");
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "rawImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool addstackedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // det_id, class_id, uri, & recipe are required
+    bool status = false;
+    psString det_id = psMetadataLookupStr(&status, config->args, "-det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -det_id");
+        return false;
+    }
+    if (!det_id) {
+        psError(PS_ERR_UNKNOWN, true, "-det_id is required");
+        return false;
+    }
+    psString class_id = psMetadataLookupStr(&status, config->args, "-class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -class_id");
+        return false;
+    }
+    if (!class_id) {
+        psError(PS_ERR_UNKNOWN, true, "-class_id is required");
+        return false;
+    }
+    psString uri    = psMetadataLookupStr(&status, config->args, "-uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -uri");
+        return false;
+    }
+    if (!uri) {
+        psError(PS_ERR_UNKNOWN, true, "-uri is required");
+        return false;
+    }
+    psString recipe = psMetadataLookupStr(&status, config->args, "-recip");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -recip");
+        return false;
+    }
+    if (!recipe) {
+        psError(PS_ERR_UNKNOWN, true, "-recip is required");
+        return false;
+    }
+    psF64 bg = psMetadataLookupF64(&status, config->args, "-bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg");
+        return false;
+    }
+    if (isnan(bg)) {
+        psError(PS_ERR_UNKNOWN, true, "-bg is required");
+        return false;
+    }
+    psF64 bg_stdev = psMetadataLookupF64(&status, config->args, "-bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_stdev");
+        return false;
+    }
+    if (isnan(bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, true, "-bg_stdev is required");
+        return false;
+    }
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, config->args, "-bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_mean_stdev");
+        return false;
+    }
+
+    // default values
+    psS32 iteration = psMetadataLookupS32(&status, config->args, "-iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -iteration");
+        return false;
+    }
+
+    psS8 code = psMetadataLookupS8(&status, config->args, "-code");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -code");
+        return false;
+    }
+
+    // correlate the class_id against the input exposure(s)
+
+    // we have to generate our own where clause as we want to search only by the
+    // det_id
+    psMetadata *where = psMetadataAlloc();
+    if (!psMetadataAddS32(where, PS_LIST_TAIL, "det_id", 0, "==",
+            (psS64)atoll(det_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(where);
+        return false;
+    }
+
+    psArray *rawImfiles = searchRawImfiles(config, where);
+    psFree(where);
+
+    bool valid_class_id = false;
+    if (rawImfiles) {
+        for (long i = 0; i < psArrayLength(rawImfiles); i++) {
+            if (strcmp(class_id, ((rawImfileRow *)rawImfiles->data[i])->class_id) == 0) {
+                valid_class_id = true;
+                break;
+            }
+        }
+        psFree(rawImfiles);
+    }
+
+    if (!valid_class_id) {
+        psError(PS_ERR_UNKNOWN, true,
+            "class_id can not be correlated with the input exposures");
+        return false;
+    }
+
+    // create a new detStackedImfile object
+    detStackedImfileRow *stackedImfile = detStackedImfileRowAlloc(
+            (psS32)atol(det_id),
+            iteration,
+            class_id,
+            uri,
+            recipe,
+            bg,
+            bg_stdev,
+            bg_mean_stdev,
+            code
+        );
+
+    // insert the new row into the detProcessedImfile table
+    if (!detStackedImfileInsertObject(config->dbh, stackedImfile)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(stackedImfile);
+        return false;
+    }
+
+    psFree(stackedImfile);
+
+    return true;
+}
+
+static bool stackedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    bool faulted = psMetadataLookupU64(&status, config->args, "-faulted");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -faulted");
+        return false;
+    }
+
+    // select detStackedImfile.*
+    // by:
+    // where det_id, iteration, class_id is not in detNormalizedImfile
+
+    psString query = psStringCopy(
+        "SELECT"
+        "   detStackedImfile.*"
+        " FROM detStackedImfile"
+        " JOIN detRun"
+        "   USING(det_id, iteration)"
+        " WHERE"
+        "   detRun.state = 'run'"
+        "   AND detRun.mode = 'master'"
+    );
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "detStackedImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND detStackedImfile.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND detStackedImfile.fault = 0");
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            psFree(output);
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "rawImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool tonormalizedstatMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    // select detRun.det_id (det_id)
+    // select detRun.iteration
+    // by:
+    // find the current iteration bassed on det_id
+    // find all exp_tags in the current det_id/iteration from detInputExp
+    // sort to detInputExp.imfiles to find the largest value per det_id/iter
+    // compare imfiles to the number of detStackedImfiles by class_id
+    // and:
+    // ???
+    // det_id is not in detStackedImfile;
+    // iteration is not in detStackedImfile;
+    // class_id is not in detStackedImfile;
+
+    psString query = psStringCopy(
+        "SELECT"
+        "   det_id,"
+        "   det_type,"
+        "   iteration,"
+        "   camera,"
+        "   workdir"
+        " FROM"
+        "(SELECT DISTINCT"
+        "   detRun.det_id,"
+        "   detRun.det_type,"
+        "   detRun.iteration,"
+        "   detRun.workdir,"
+        "   rawExp.camera,"
+        "   rawExp.imfiles,"
+        "   detStackedImfile.class_id"
+        " FROM detRun"
+        " JOIN detInputExp"
+        "   ON detRun.det_id = detInputExp.det_id"
+        "   AND detRun.iteration = detInputExp.iteration"
+        " JOIN rawExp"
+        "   ON detInputExp.exp_tag = rawExp.exp_tag"
+        " JOIN detStackedImfile"
+        "   ON detInputExp.det_id = detStackedImfile.det_id"
+        "   AND detInputExp.iteration = detStackedImfile.iteration"
+        " LEFT JOIN detNormalizedStatImfile"
+        "   ON detStackedImfile.det_id = detNormalizedStatImfile.det_id"
+        "   AND detStackedImfile.iteration = detNormalizedStatImfile.iteration"
+        "   AND detStackedImfile.class_id = detNormalizedStatImfile.class_id"
+        " WHERE"
+        "   detRun.state = 'run'"
+        "   AND detRun.mode = 'master'"
+        "   AND detStackedImfile.fault = 0"
+        "   AND detNormalizedStatImfile.det_id IS NULL"
+        "   AND detNormalizedStatImfile.iteration IS NULL"
+        "   AND detNormalizedStatImfile.class_id IS NULL"
+        " GROUP BY"
+        "   rawExp.exp_tag,"
+        "   detRun.iteration,"
+        "   detRun.det_id"
+        " HAVING MAX(rawExp.imfiles) = COUNT(detStackedImfile.class_id)"
+        ") as tonormalizedstat"
+        );
+
+    // XXX does it make sense to accept any search params?
+#if 0
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+#endif
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detPendingNormStatImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool addnormalizedstatMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    // select * from detStackedImfile
+    // by det_id, iteration, class_id
+    // where det_id, iteration, class_id is not in detNormalizedStatImfile
+    psString query = psStringCopy(
+        "SELECT DISTINCT"
+        "   detStackedImfile.*"
+        " FROM detStackedImfile"
+        " LEFT JOIN detNormalizedStatImfile"
+        "   USING(det_id, iteration, class_id)"
+        " WHERE"
+        "  detNormalizedStatImfile.det_id IS NULL"
+        "  AND detNormalizedStatImfile.iteration IS NULL"
+        "  AND detNormalizedStatImfile.class_id IS NULL"
+        );
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "detStackedImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // start a transaction so it's all rows or nothing
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(output);
+        return false;
+    }
+
+    for (long i = 0; i < psArrayLength(output); i++) {
+        psMetadata *row = output->data[i];
+        // convert metadata into a detStackedImfile object
+        detStackedImfileRow *stackedImfile = detStackedImfileObjectFromMetadata(row);
+        // convert detStackedImfile object into a detNormalizedStat object
+        detNormalizedStatImfileRow *stat = detStackedToDetNormalizedStatImfile(config, stackedImfile);
+        psFree(stackedImfile);
+        if (!stat) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to convert detStackedImfile to detNormalizedStatImfile");
+            psFree(output);
+            return false;
+        }
+        // insert detNormlized Stat object into the database
+        if (!detNormalizedStatImfileInsertObject(config->dbh, stat)) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(stat);
+            psFree(output);
+        }
+        psFree(stat);
+    }
+
+    psFree(output);
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static detNormalizedStatImfileRow *detStackedToDetNormalizedStatImfile(pxConfig *config, detStackedImfileRow *stackedImfile)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(stackedImfile, NULL);
+
+    bool status = false;
+    psF32 norm = psMetadataLookupF32(&status, config->args, "-norm");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -norm");
+        return false;
+    }
+    if (isnan(norm)) {
+        psError(PS_ERR_UNKNOWN, true, "-norm is required");
+        return false;
+    }
+
+    // default values
+    psS8 code = psMetadataLookupS8(&status, config->args, "-code");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -code");
+        return false;
+    }
+
+    return detNormalizedStatImfileRowAlloc(
+        stackedImfile->det_id,
+        stackedImfile->iteration,
+        stackedImfile->class_id,
+        norm,
+        code
+    );
+}
+
+static bool tonormalizeMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    // select detNormalizedStatImfile.*
+    // by:
+    // where det_id, iteration, class_id is not in detNormalizedImfile
+
+    psString query = psStringCopy(
+        "SELECT DISTINCT"
+        "   detRun.det_type,"
+        "   detRun.workdir,"
+        "   rawExp.camera,"
+        "   detStackedImfile.uri,"
+        "   detNormalizedStatImfile.*"
+        " FROM detRun"
+        " JOIN detStackedImfile"
+        "   USING(det_id, iteration)"
+        " JOIN detInputExp"
+        "   USING(det_id, iteration)"
+        " JOIN rawExp"
+        "   ON detInputExp.exp_tag = rawExp.exp_tag"
+        " JOIN detNormalizedStatImfile"
+        "   ON detStackedImfile.det_id = detNormalizedStatImfile.det_id"
+        "   AND detStackedImfile.iteration = detNormalizedStatImfile.iteration"
+        "   AND detStackedImfile.class_id = detNormalizedStatImfile.class_id"
+        " LEFT JOIN detNormalizedImfile"
+        "   ON detNormalizedStatImfile.det_id = detNormalizedImfile.det_id"
+        "   AND detNormalizedStatImfile.iteration = detNormalizedImfile.iteration"
+        "   AND detNormalizedStatImfile.class_id = detNormalizedImfile.class_id"
+        " WHERE"
+        "   detNormalizedImfile.det_id IS NULL"
+        "   AND detNormalizedImfile.iteration IS NULL"
+        "   AND detNormalizedImfile.class_id IS NULL"
+        "   AND detNormalizedStatImfile.fault = 0"
+        );
+
+    // XXX does it make sense to accept any search params?
+#if 0
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+#endif
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            psFree(output);
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detPendingNormImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+#if 0
+// XXX this function was left in commented as this method may be useful in the
+// future
+static psArray *validDetInputClassIds(pxConfig *config, const char *det_id)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    // det_id is input as a string because the fact that it is an integer
+    // is just a database impliementation detail.
+    PS_ASSERT_PTR_NON_NULL(det_id, NULL);
+
+    psArray *rawImfiles = searchInputImfiles(config, det_id);
+    if (!rawImfiles) {
+        return NULL;
+    }
+
+    psArray *valid_class_ids = NULL;
+    {
+        // All this jumping through hoops is so that we end up with unique
+        // values. PP thinks that making multiple passes through this array and
+        // deleting matched elements would end up being more expensive then a
+        // double sort and stagger scheme. JH thinks it would be cheaper to
+        // just do a unqiue sort and delete.  So this is really just a cheap
+        // hack to avoid implimenting a unique sort function but at least it's
+        // stable.
+        psHash *hash = psHashAlloc(psArrayLength(rawImfiles));
+        for (long i = 0; i < psArrayLength(rawImfiles); i++) {
+            psHashAdd(hash,
+                ((rawImfileRow *)rawImfiles->data[i])->class_id,
+                ((rawImfileRow *)rawImfiles->data[i])->class_id
+            );
+        }
+        valid_class_ids = psHashToArray(hash);
+        psFree(hash);
+    }
+    psFree(rawImfiles);
+
+    return valid_class_ids;
+}
+
+static psArray *searchInputImfiles(pxConfig *config, const char *det_id)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    // det_id is input as a string because the fact that it is an integer
+    // is just a database impliementation detail.
+    PS_ASSERT_PTR_NON_NULL(det_id, NULL);
+
+    psArray *inputExps = NULL;
+    {
+        psMetadata *where = psMetadataAlloc();
+        if (!psMetadataAddS32(where, PS_LIST_TAIL, "det_id", 0, "==",
+                (psS32)atoi(det_id))) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+            psFree(where);
+            return NULL;
+        }
+        inputExps = detInputExpSelectRowObjects(config->dbh, where, 0);
+        psFree(where);
+    }
+    if (!inputExps) {
+        psError(PS_ERR_UNKNOWN, false, "no detInputExp rows found");
+        return NULL;
+    }
+
+    // find rawImfiles associated with detInputExps
+    psArray *rawImfiles = NULL;
+    {
+        psMetadata *where = psMetadataAlloc();
+        for (long i = 0; i < psArrayLength(inputExps); i++) {
+            if (!psMetadataAddStr(where, PS_LIST_TAIL, "exp_tag",
+                    PS_META_DUPLICATE_OK, "==",
+                    ((detInputExpRow *)inputExps->data[i])->exp_tag)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+                psFree(inputExps);
+                psFree(where);
+                return NULL;
+            }
+        }
+        psFree(inputExps);
+        rawImfiles = rawImfileSelectRowObjects(config->dbh, where, 0);
+        // XXX this really should be sorted for uniqueness
+        psFree(where);
+    }
+    if (!rawImfiles) {
+        psError(PS_ERR_UNKNOWN, false, "no rawImfile rows found");
+        return NULL;
+    }
+
+    return rawImfiles;
+}
+#endif
+
+static bool addnormalizedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // make sure that there is a respondoing entry in detNormalizedStatImfile
+    // select * from detNormalizedStatImfile
+    // by det_id, iteration, class_id
+    // where det_id, iteration, class_id is not in detNormalizedImfile
+    psString query = psStringCopy(
+        "SELECT"
+        "   detNormalizedStatImfile.*"
+        " FROM detNormalizedStatImfile"
+        " LEFT JOIN detNormalizedImfile"
+        "   USING(det_id, iteration, class_id)"
+        " WHERE"
+        "  detNormalizedImfile.det_id IS NULL"
+        "  AND detNormalizedImfile.iteration IS NULL"
+        "  AND detNormalizedImfile.class_id IS NULL"
+        );
+
+    {
+        // build a query to search by det_id, iteration, class_id
+        psMetadata *where = psMetadataAlloc();
+        bool status = false;
+        psString det_id = psMetadataLookupStr(&status, config->args, "-det_id");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -det_id");
+            psFree(where);
+            psFree(query);
+            return false;
+        }
+        if (det_id) {
+            if (!psMetadataAddStr(where, PS_LIST_TAIL, "det_id", 0, "==", det_id)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+                psFree(where);
+                psFree(query);
+                return false;
+            }
+        }
+        psS32 iteration = psMetadataLookupS32(&status, config->args, "-iteration");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -iteration");
+            psFree(where);
+            psFree(query);
+            return false;
+        }
+        // always set iteration
+        if (!psMetadataAddS32(where, PS_LIST_TAIL, "iteration", 0, "==", iteration)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+            psFree(where);
+            psFree(query);
+            return false;
+        }
+        psString class_id = psMetadataLookupStr(&status, config->args, "-class_id");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -class_id");
+            psFree(where);
+            psFree(query);
+            return false;
+        }
+        if (class_id) {
+            if (!psMetadataAddStr(where, PS_LIST_TAIL, "class_id", 0, "==", class_id)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+                psFree(where);
+                psFree(query);
+                return false;
+            }
+        }
+
+        // there's not
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "detNormalizedStatImfile");
+        psFree(where);
+        if (whereClause) {
+            psStringAppend(&query, " AND %s", whereClause);
+            psFree(whereClause);
+        }
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // start a transaction so it's all rows or nothing
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(output);
+        return false;
+    }
+
+    for (long i = 0; i < psArrayLength(output); i++) {
+        psMetadata *row = output->data[i];
+        // convert metadata into a detNormalizedStatImfile object
+        detNormalizedStatImfileRow *statImfile = detNormalizedStatImfileObjectFromMetadata(row);
+        // convert detNormalizedStatImfile object into a detNormalizedImfile
+        detNormalizedImfileRow *normalizedImfile  = detNormalizedStatToDetNormalizedmfile(config, statImfile);
+        psFree(statImfile);
+        if (!normalizedImfile) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to convert detStackedImfile to detNormalizedStatImfile");
+            psFree(output);
+            return false;
+        }
+        // insert detNormlized Stat object into the database
+        if (!detNormalizedImfileInsertObject(config->dbh, normalizedImfile)) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(normalizedImfile);
+            psFree(output);
+        }
+        psFree(normalizedImfile);
+    }
+
+    psFree(output);
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool tonormalizedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    psString query = psStringCopy(
+        " SELECT DISTINCT"
+        "    detRun.det_id as det_id,"
+        "    detRun.iteration,"
+        "    detRun.det_type,"
+        "    detRun.workdir,"
+        "    rawExp.camera,"
+        "    rawExp.telescope,"
+        "    rawExp.exp_type,"
+        "    rawExp.imfiles"
+        " FROM detRun"
+        " JOIN detInputExp"
+        "    ON detRun.det_id = detInputExp.det_id"
+        "    AND detRun.iteration = detInputExp.iteration"
+        " JOIN rawExp"
+        "    ON detInputExp.exp_tag = rawExp.exp_tag"
+        " JOIN detNormalizedImfile"
+        "    ON detInputExp.det_id = detNormalizedImfile.det_id"
+        "    AND detInputExp.iteration = detNormalizedImfile.iteration"
+        " LEFT JOIN rawImfile"
+        "    ON detInputExp.exp_tag = rawImfile.exp_tag"
+        "    AND detNormalizedImfile.class_id = rawImfile.class_id"
+        " LEFT JOIN detNormalizedExp"
+        "    ON detInputExp.det_id = detNormalizedExp.det_id"
+        "    AND detInputExp.iteration = detNormalizedExp.iteration"
+        " WHERE"
+        "   detRun.state = 'run'"
+        "   AND detRun.mode = 'master'"
+        "   AND detNormalizedImfile.fault = 0"
+        "   AND detNormalizedExp.det_id IS NULL"
+        "   AND detNormalizedExp.iteration IS NULL"
+        "   AND detInputExp.include = 1"
+        " GROUP BY"
+        "    detNormalizedImfile.iteration,"
+        "    detRun.det_id"
+        " HAVING"
+        "    COUNT(detNormalizedImfile.class_id) = COUNT(rawImfile.class_id)"
+    );
+
+    // XXX does it make sense to accept any search params?
+#if 0
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+#endif
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detPendingNormExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool addnormalizedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // det_id, recip, -bg, -bg_stdev
+    // are required
+    bool status = false;
+    psString det_id = psMetadataLookupStr(&status, config->args, "-det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -det_id");
+        return false;
+    }
+    if (!det_id) {
+        psError(PS_ERR_UNKNOWN, true, "-det_id is required");
+        return false;
+    }
+    psString recipe = psMetadataLookupStr(&status, config->args, "-recip");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -recip");
+        return false;
+    }
+    if (!recipe) {
+        psError(PS_ERR_UNKNOWN, true, "-recip is required");
+        return false;
+    }
+    psF64 bg = psMetadataLookupF64(&status, config->args, "-bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg");
+        return false;
+    }
+    if (isnan(bg)) {
+        psError(PS_ERR_UNKNOWN, true, "-bg is required");
+        return false;
+    }
+    psF64 bg_stdev = psMetadataLookupF64(&status, config->args, "-bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_stdev");
+        return false;
+    }
+    if (isnan(bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, true, "-bg_stdev is required");
+        return false;
+    }
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, config->args, "-bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_mean_stdev");
+        return false;
+    }
+    // iteration has a default value
+    psS32 iteration = psMetadataLookupS32(&status, config->args, "-iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -iteration");        return false;
+    }
+
+    psS8 code = psMetadataLookupS8(&status, config->args, "-code");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -code");
+        return false;
+    }
+
+    // optional
+    psString path_base = psMetadataLookupStr(&status, config->args, "-path_base");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -path_base");
+        return false;
+    }
+
+    psString query = psStringCopy(
+        " SELECT DISTINCT"
+        "    detRun.det_id as det_id,"
+        "    detRun.iteration,"
+        "    detRun.det_type,"
+        "    rawExp.camera,"
+        "    rawExp.telescope,"
+        "    rawExp.exp_type,"
+        "    rawExp.imfiles"
+        " FROM detRun"
+        " JOIN detInputExp"
+        "    ON detRun.det_id = detInputExp.det_id"
+        "    AND detRun.iteration = detInputExp.iteration"
+        " JOIN rawExp"
+        "    ON detInputExp.exp_tag = rawExp.exp_tag"
+        " JOIN detNormalizedImfile"
+        "    ON detInputExp.det_id = detNormalizedImfile.det_id"
+        "    AND detInputExp.iteration = detNormalizedImfile.iteration"
+        " LEFT JOIN rawImfile"
+        "    ON detInputExp.exp_tag = rawImfile.exp_tag"
+        "    AND detNormalizedImfile.class_id = rawImfile.class_id"
+        " LEFT JOIN detNormalizedExp"
+        "    ON detInputExp.det_id = detNormalizedExp.det_id"
+        "    AND detInputExp.iteration = detNormalizedExp.iteration"
+        " WHERE"
+        "   detRun.state = 'run'"
+        "   AND detRun.mode = 'master'"
+        "   AND detNormalizedExp.det_id IS NULL"
+        "   AND detNormalizedExp.iteration IS NULL"
+        "   AND detInputExp.include = 1"
+        "   AND detRun.det_id = %s"
+        "   AND detNormalizedImfile.iteration = %d"
+        " GROUP BY"
+        "    detNormalizedImfile.iteration,"
+        "    detRun.det_id"
+        " HAVING"
+        "    COUNT(detNormalizedImfile.class_id) = COUNT(rawImfile.class_id)"
+        );
+
+    if (!p_psDBRunQuery(config->dbh, query, det_id, iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+    psFree(output);
+
+    // create a new detProcessedImfile object
+    detNormalizedExpRow *detRow = detNormalizedExpRowAlloc(
+        (psS32)atol(det_id),
+        iteration,
+        recipe,
+        bg,
+        bg_stdev,
+        bg_mean_stdev,
+        path_base,
+        code
+    );
+
+    // insert the new row into the detProcessedImfile table
+    if (!detNormalizedExpInsertObject(config->dbh, detRow)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(detRow);
+        return false;
+    }
+
+    psFree(detRow);
+
+    return true;
+}
+
+static bool normalizedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    bool faulted = psMetadataLookupU64(&status, config->args, "-faulted");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -faulted");
+        return false;
+    }
+
+    psString query = psStringCopy(
+        "SELECT"
+        "   detNormalizedExp.*"
+        " FROM detNormalizedExp"
+        " JOIN detRun"
+        "   USING(det_id, iteration)"
+        " WHERE"
+        "   detRun.state = 'run'"
+        "   AND detRun.mode = 'master'"
+    );
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereSQL(config->where, NULL);
+        psStringAppend(&query, " %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND detNormalizedExp.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND detNormalizedExp.fault = 0");
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detNormalizedExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static  detNormalizedImfileRow *detNormalizedStatToDetNormalizedmfile(pxConfig *config, detNormalizedStatImfileRow *statImfile)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(statImfile, NULL);
+
+    bool status = false;
+    psString uri = psMetadataLookupStr(&status, config->args, "-uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -uri");
+        return false;
+    }
+    if (!uri) {
+        psError(PS_ERR_UNKNOWN, true, "-uri is required");
+        return false;
+    }
+    psF64 bg = psMetadataLookupF64(&status, config->args, "-bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg");
+        return false;
+    }
+    if (isnan(bg)) {
+        psError(PS_ERR_UNKNOWN, true, "-bg is required");
+        return false;
+    }
+    psF64 bg_stdev = psMetadataLookupF64(&status, config->args, "-bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_stdev");
+        return false;
+    }
+    if (isnan(bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, true, "-bg_stdev is required");
+        return false;
+    }
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, config->args, "-bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_mean_stdev");
+        return false;
+    }
+    // optional
+    psString path_base = psMetadataLookupStr(&status, config->args, "-path_base");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -path_base");
+        return false;
+    }
+
+    // default values
+    psS8 code = psMetadataLookupS8(&status, config->args, "-code");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -code");
+        return false;
+    }
+
+    return detNormalizedImfileRowAlloc(
+        statImfile->det_id,
+        statImfile->iteration,
+        statImfile->class_id,
+        uri,
+        bg,
+        bg_stdev,
+        bg_mean_stdev,
+        path_base,
+        code
+    );
+}
+
+static bool toresidimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    /*
+which returns a list of processed imfiles (with corresponding detrend
+id, iteration, class id, uri, type) for which the appropriate stacked
+imfile has been normalised, and which have not been masked out by
+detResidImfileAnalysis;
+*/
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    psString query = psStringCopy(
+        "SELECT DISTINCT\n"
+        "   detRun.det_id,\n"
+        "   detRun.iteration,\n"
+        "   detRun.det_type,\n"
+        "   detRun.mode,\n"
+        "   detRun.workdir,\n"
+        "   detProcessedImfile.exp_tag,\n"
+        "   detProcessedImfile.class_id,\n"
+        "   detProcessedImfile.uri,\n"
+        "   detNormalizedImfile.uri AS det_uri,\n"
+        "   rawExp.camera\n"
+        " FROM detRun\n"
+        " JOIN detInputExp\n"
+        "   USING(det_id, iteration)\n"
+        " JOIN rawExp\n"
+        "   ON detInputExp.exp_tag = rawExp.exp_tag\n"
+        " JOIN detProcessedImfile\n"
+        "   ON detRun.det_id = detProcessedImfile.det_id\n"
+        "   AND detInputExp.exp_tag = detProcessedImfile.exp_tag\n"
+        " JOIN detNormalizedImfile\n"
+        "   ON detRun.det_id = detNormalizedImfile.det_id\n"
+        "   AND detRun.iteration = detNormalizedImfile.iteration\n"
+        "   AND detProcessedImfile.class_id = detNormalizedImfile.class_id\n"
+        " LEFT JOIN detResidImfile\n"
+        "   ON detRun.det_id = detResidImfile.det_id\n"
+        "   AND detRun.iteration = detResidImfile.iteration\n"
+        "   AND detProcessedImfile.exp_tag = detResidImfile.exp_tag\n"
+        "   AND detProcessedImfile.class_id = detResidImfile.class_id\n"
+        " WHERE\n"
+        "   detRun.state = 'run'\n"
+        "   AND detRun.mode = 'master'\n"
+        "   AND detNormalizedImfile.fault = 0\n"
+        "   AND detResidImfile.det_id IS NULL\n"
+        "   AND detResidImfile.iteration IS NULL\n"
+        "   AND detResidImfile.exp_tag IS NULL\n"
+        "   AND detResidImfile.class_id IS NULL\n"
+        " UNION"
+        " SELECT\n"
+        "   detRun.det_id,\n"
+        "   detRun.iteration,\n"
+        "   detRun.det_type,\n"
+        "   detRun.mode,\n"
+        "   detRun.workdir,\n"
+        "   rawImfile.exp_tag,\n"
+        "   rawImfile.class_id,\n"
+        "   rawImfile.uri,\n"
+        "   'NULL' AS det_uri,\n"
+        "   rawExp.camera\n"
+        " FROM detRun\n"
+        " JOIN detInputExp\n"
+        "    USING(det_id, iteration)\n"
+        " JOIN rawExp\n"
+        "    ON detInputExp.exp_tag = rawExp.exp_tag\n"
+        " JOIN rawImfile\n"
+        "    ON detInputExp.exp_tag = rawImfile.exp_tag\n"
+        " LEFT JOIN detResidImfile\n"
+        "   ON detRun.det_id = detResidImfile.det_id\n"
+        "   AND detRun.iteration = detResidImfile.iteration\n"
+        "   AND rawImfile.exp_tag = detResidImfile.exp_tag\n"
+        "   AND rawImfile.class_id = detResidImfile.class_id\n"
+        " WHERE\n"
+        "   detRun.state = 'run'\n"
+        "   AND detRun.mode = 'verify'\n"
+        "   AND detResidImfile.det_id IS NULL\n"
+        "   AND detResidImfile.iteration IS NULL\n"
+        "   AND detResidImfile.exp_tag IS NULL\n"
+        "   AND detResidImfile.class_id IS NULL\n"
+        );
+
+    // XXX does it make sense to accept any search params?
+#if 0
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+#endif
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            psFree(output);
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detPendingResidImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool normalizedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    bool faulted = psMetadataLookupU64(&status, config->args, "-faulted");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -faulted");
+        return false;
+    }
+
+    psString query = psStringCopy(
+        "SELECT"
+        " detNormalizedImfile.*"
+        " FROM detNormalizedImfile"
+        " JOIN detRun"
+        "   USING(det_id, iteration)"
+        " WHERE"
+        "   detRun.state = 'run'"
+        "   AND detRun.mode = 'master'"
+    );
+
+    if (config->where) {
+        bool status;
+        int iteration = psMetadataLookupS32 (&status, config->where, "iteration");
+        if (status) {
+            psMetadataRemoveKey (config->where, "iteration");
+            psMetadataAddS32 (config->where, PS_LIST_TAIL, "iteration", 0, "==", iteration);
+        }
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "detNormalizedImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND detNormalizedImfile.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND detNormalizedImfile.fault = 0");
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detNormalizedImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool addresidimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // make sure that there is a coresponding entry in detNormalizedImfile
+    // and the exp_tag specified is valid
+    // select * from detNormalizedImfile
+    // by det_id, iteration, class_id
+    // where det_id, iteration, class_id is not in detResidImfile
+    psString query = psStringCopy(
+        " SELECT * FROM ("
+        "SELECT\n"
+        "   detNormalizedImfile.*\n"
+        " FROM detRun\n"
+        " JOIN detInputExp\n"
+        "   USING(det_id, iteration)\n"
+        " JOIN detNormalizedImfile\n"
+        "   USING(det_id, iteration)\n"
+        " LEFT JOIN detResidImfile\n"
+        "   ON detRun.det_id = detResidImfile.det_id\n"
+        "   AND detRun.iteration = detResidImfile.iteration\n"
+        "   AND detInputExp.exp_tag = detResidImfile.exp_tag\n"
+        "   AND detNormalizedImfile.class_id = detResidImfile.class_id\n"
+        " WHERE\n"
+        "  detRun.state = 'run'\n"
+        "  AND detRun.mode= 'master'\n"
+        "  AND detResidImfile.det_id IS NULL\n"
+        "  AND detResidImfile.iteration IS NULL\n"
+        "  AND detResidImfile.exp_tag IS NULL\n"
+        "  AND detResidImfile.class_id IS NULL\n"
+        " UNION\n"
+        " SELECT\n"
+        "   detRun.det_id,\n"
+        "   detRun.iteration,\n"
+        "   rawImfile.class_id,\n"
+        "   rawImfile.uri,\n"
+        "   0.0 as bg,\n"
+        "   0.0 as bg_stdev,\n"
+        "   0.0 as bg_mean_stdev,\n"
+        "   'NULL' as path_base,\n"
+        "    0 as fault\n"
+        " FROM detRun\n"
+        " JOIN detInputExp\n"
+        "    USING(det_id, iteration)\n"
+        " JOIN rawExp\n"
+        "    ON detInputExp.exp_tag = rawExp.exp_tag\n"
+        " JOIN rawImfile\n"
+        "    ON detInputExp.exp_tag = rawImfile.exp_tag\n"
+        " LEFT JOIN detResidImfile\n"
+        "   ON detRun.det_id = detResidImfile.det_id\n"
+        "   AND detRun.iteration = detResidImfile.iteration\n"
+        "   AND rawImfile.exp_tag = detResidImfile.exp_tag\n"
+        "   AND rawImfile.class_id = detResidImfile.class_id\n"
+        " WHERE\n"
+        "   detRun.state = 'run'\n"
+        "   AND detRun.mode = 'verify'\n"
+        "   AND detResidImfile.det_id IS NULL\n"
+        "   AND detResidImfile.iteration IS NULL\n"
+        "   AND detResidImfile.exp_tag IS NULL\n"
+        "   AND detResidImfile.class_id IS NULL\n"
+        "   AND detInputExp.exp_tag = '%1$s'\n"
+        ") AS foo\n"
+        "  %2$s"
+    );
+
+    // build a query to search by det_id, iteration, class_id
+    psMetadata *where = psMetadataAlloc();
+    bool status = false;
+    psString det_id = psMetadataLookupStr(&status, config->args, "-det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -det_id");
+        psFree(where);
+        psFree(query);
+        return false;
+    }
+    if (det_id) {
+        if (!psMetadataAddStr(where, PS_LIST_TAIL, "det_id", 0, "==", det_id)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+            psFree(where);
+            psFree(query);
+            return false;
+        }
+    }
+
+    psS32 iteration = psMetadataLookupS32(&status, config->args, "-iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -iteration");
+        psFree(where);
+        psFree(query);
+        return false;
+    }
+    // always set iteration
+    if (!psMetadataAddS32(where, PS_LIST_TAIL, "iteration", 0, "==", iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(where);
+        psFree(query);
+        return false;
+    }
+
+    psString class_id = psMetadataLookupStr(&status, config->args, "-class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -class_id");
+        psFree(where);
+        psFree(query);
+        return false;
+    }
+    if (class_id) {
+        if (!psMetadataAddStr(where, PS_LIST_TAIL, "class_id", 0, "==", class_id)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+            psFree(where);
+            psFree(query);
+            return false;
+        }
+    }
+
+    psString whereClause = psDBGenerateWhereSQL(where, NULL);
+    psFree(where);
+
+    // exp_tag is manadatory to cross check that this is a exp_tag for this
+    // detRun
+    psString exp_tag = psMetadataLookupStr(&status, config->args, "-exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_tag");
+        psFree(whereClause);
+        psFree(query);
+        return false;
+    }
+    if (!exp_tag) {
+        psError(PS_ERR_UNKNOWN, true, "-exp_tag is required");
+        psFree(whereClause);
+        psFree(query);
+        return false;
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query, exp_tag, whereClause)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(whereClause);
+        psFree(query);
+        return false;
+    }
+    psFree(whereClause);
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psError(PS_ERR_UNKNOWN, false, "no pending detNormalizedImfile rows found");
+        psFree(output);
+        return false;
+    }
+
+    // start a transaction so it's all rows or nothing
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(output);
+        return false;
+    }
+
+    for (long i = 0; i < psArrayLength(output); i++) {
+        psMetadata *row = output->data[i];
+        // convert metadata into a detNormalizedImfile object
+        detNormalizedImfileRow *normalizedImfile = detNormalizedImfileObjectFromMetadata(row);
+        // convert detNormalizedImfile object into a detResidImfile
+        detResidImfileRow *residImfile  = detNormalizedToDetResidImfile(config, normalizedImfile);
+        psFree(normalizedImfile);
+        if (!residImfile) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to convert detNormalizedImfile to detResidImfile");
+            psFree(output);
+            return false;
+        }
+        // insert detResidImfile object into the database
+        if (!detResidImfileInsertObject(config->dbh, residImfile)) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(residImfile);
+            psFree(output);
+        }
+        psFree(residImfile);
+    }
+
+    psFree(output);
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static  detResidImfileRow *detNormalizedToDetResidImfile(pxConfig *config, detNormalizedImfileRow *normalizedImfile)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(normalizedImfile, NULL);
+
+    bool status = false;
+    psString exp_tag = psMetadataLookupStr(&status, config->args, "-exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_tag");
+        return false;
+    }
+    if (!exp_tag) {
+        psError(PS_ERR_UNKNOWN, true, "-exp_tag is required");
+        return false;
+    }
+    psString uri = psMetadataLookupStr(&status, config->args, "-uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -uri");
+        return false;
+    }
+    if (!uri) {
+        psError(PS_ERR_UNKNOWN, true, "-uri is required");
+        return false;
+    }
+    psString recipe = psMetadataLookupStr(&status, config->args, "-recip");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -recip");
+        return false;
+    }
+    if (!recipe) {
+        psError(PS_ERR_UNKNOWN, true, "-recip is required");
+        return false;
+    }
+    psF64 bg = psMetadataLookupF64(&status, config->args, "-bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg");
+        return false;
+    }
+    if (isnan(bg)) {
+        psError(PS_ERR_UNKNOWN, true, "-bg is required");
+        return false;
+    }
+    psF64 bg_stdev = psMetadataLookupF64(&status, config->args, "-bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_stdev");
+        return false;
+    }
+    if (isnan(bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, true, "-bg_stdev is required");
+        return false;
+    }
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, config->args, "-bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_mean_stdev");
+        return false;
+    }
+    // optional
+    psString path_base = psMetadataLookupStr(&status, config->args, "-path_base");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -path_base");
+        return false;
+    }
+
+    // default values
+    psS8 code = psMetadataLookupS8(&status, config->args, "-code");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -code");
+        return false;
+    }
+
+    // create a new detResidImfileRow and insert it
+    return detResidImfileRowAlloc(
+            normalizedImfile->det_id,
+            normalizedImfile->iteration,
+            exp_tag,
+            normalizedImfile->class_id,
+            uri,
+            recipe,
+            bg,
+            bg_stdev,
+            bg_mean_stdev,
+            path_base,
+            code
+        );
+}
+
+static bool residimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    bool faulted = psMetadataLookupU64(&status, config->args, "-faulted");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -faulted");
+        return false;
+    }
+
+    // select detResidImfile.*
+    // by:
+    // where det_id, iteration, exp_tag is not in detResidExp;
+
+    psString query = psStringCopy(
+        "SELECT"
+        "   detRun.det_type,"
+        "   detRun.mode,"
+        "   detResidImfile.*"
+        " FROM detResidImfile"
+        " JOIN detRun"
+        "   USING(det_id, iteration)"
+        " WHERE"
+        "   detRun.state = 'run'"
+    );
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "detResidImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND detResidImfile.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND detResidImfile.fault = 0");
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            psFree(output);
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "rawResidImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool toresidexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    /*
+which returns a list of exposures for which all component class ids
+have had residuals created.  This list includes the detrend id,
+iteration, exposure id, detrend type, and whether the exposure was
+included in the stack for this iteration.
+    */
+
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    // select detRun.det_id
+    // select detRun.iteration
+    // select detRun.det_type
+    // select detInputExp.exp_tag
+    // select detInputExp.include
+    // by:
+    // find the current iteration bassed on det_id
+    // find all exp_tags in the current det_id/iteration from detInputExp
+    // compare to detInputExp.imfiles to derResidImfile by class_id
+    // and:
+    // detResidImfile.{det_id, iteration, exp_tag} is not in detResidExp
+
+    psString query = psStringCopy(
+        "SELECT DISTINCT\n"
+        "   det_id,\n"
+        "   iteration,\n"
+        "   det_type,\n"
+        "   mode,\n"
+        "   exp_tag,\n"
+        "   include,\n"
+        "   camera,\n"
+        "   workdir\n"
+        " FROM\n"
+        "   (SELECT DISTINCT\n"
+        "       detRun.det_id AS det_id,\n"
+        "       detRun.iteration,\n"
+        "       detRun.det_type,\n"
+        "       detRun.mode,\n"
+        "       detRun.workdir,\n"
+        "       detInputExp.exp_tag,\n"
+        "       detInputExp.include,\n"
+        "       rawExp.imfiles,\n"
+        "       rawExp.camera,\n"
+        "       detResidImfile.class_id\n"
+        "   FROM detRun\n"
+        "   JOIN detInputExp\n"
+        "       USING(det_id, iteration)\n"
+        "   JOIN rawExp\n"
+        "       ON detInputExp.exp_tag = rawExp.exp_tag\n"
+        "   JOIN detResidImfile\n"
+        "       ON detRun.det_id = detResidImfile.det_id\n"
+        "       AND detRun.iteration = detResidImfile.iteration\n"
+        "       AND detInputExp.exp_tag = detResidImfile.exp_tag\n"
+        "   LEFT JOIN detResidExp\n"
+        "       ON detResidImfile.det_id = detResidExp.det_id\n"
+        "       AND detResidImfile.iteration = detResidExp.iteration\n"
+        "       AND detResidImfile.exp_tag = detResidExp.exp_tag\n"
+        "   WHERE\n"
+        "       detRun.state = 'run'\n"
+        "       AND detResidImfile.fault = 0\n"
+        "       AND detResidExp.det_id IS NULL\n"
+        "       AND detResidExp.iteration IS NULL\n"
+        "       AND detResidExp.exp_tag IS NULL\n"
+        "   GROUP BY\n"
+        "       detInputExp.exp_tag,\n"
+        "       detRun.iteration,\n"
+        "       detRun.det_id\n"
+        "   HAVING\n"
+        "       rawExp.imfiles = COUNT(detResidImfile.class_id)\n"
+        " ) AS toresidexp\n"
+        );
+
+    // XXX does it make sense to accept any search params?
+#if 0
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+#endif
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detPendingResidExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool addresidexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    /*
+which returns a list of exposures for which all component class ids
+have had residuals created.  This list includes the detrend id,
+iteration, exposure id, detrend type, and whether the exposure was
+included in the stack for this iteration.
+    */
+
+
+    // select detRun.det_id
+    // select detRun.iteration
+    // select detRun.det_type
+    // select detInputExp.exp_tag
+    // select detInputExp.include
+    // by:
+    // find the current iteration bassed on det_id
+    // find all exp_tags in the current det_id/iteration from detInputExp
+    // compare to detInputExp.imfiles to derResidImfile by class_id
+    // and:
+    // detResidImfile.{det_id, iteration, exp_tag} is not in detResidExp
+
+    psString query = psStringCopy(
+        "SELECT DISTINCT"
+        "   det_id,"
+        "   iteration,"
+        "   exp_tag,"
+        "   include"
+        " FROM"
+        "   (SELECT DISTINCT"
+        "       detRun.det_id AS det_id,"
+        "       detRun.iteration,"
+        "       detRun.det_type,"
+        "       detInputExp.exp_tag,"
+        "       detInputExp.include,"
+        "       rawExp.imfiles"
+        "   FROM detRun"
+        "   JOIN detInputExp"
+        "       USING(det_id, iteration)"
+        "   JOIN rawExp"
+        "       ON detInputExp.exp_tag = rawExp.exp_tag"
+        "   JOIN detResidImfile"
+        "       ON detRun.det_id = detResidImfile.det_id"
+        "       AND detRun.iteration = detResidImfile.iteration"
+        "       AND detInputExp.exp_tag = detResidImfile.exp_tag"
+        "   LEFT JOIN detResidExp"
+        "       ON detResidImfile.det_id = detResidExp.det_id"
+        "       AND detResidImfile.iteration = detResidExp.iteration"
+        "       AND detResidImfile.exp_tag = detResidExp.exp_tag"
+        "   WHERE"
+        "       detRun.state = 'run'"
+        "       AND detResidExp.det_id IS NULL"
+        "       AND detResidExp.iteration IS NULL"
+        "       AND detResidExp.exp_tag IS NULL"
+        "   GROUP BY"
+        "       detInputExp.exp_tag,"
+        "       detRun.iteration,"
+        "       detRun.det_id"
+        "   HAVING"
+        "       rawExp.imfiles = COUNT(detResidImfile.class_id)"
+        " ) AS toresidexp"
+        );
+
+    {
+        // build a query to search by det_id, iteration, exp_tag
+        psMetadata *where = psMetadataAlloc();
+        bool status = false;
+        psString det_id = psMetadataLookupStr(&status, config->args, "-det_id");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -det_id");
+            return false;
+        }
+        if (!det_id) {
+            psError(PS_ERR_UNKNOWN, true, "-det_id is required");
+            return false;
+        }
+        if (!psMetadataAddS64(where, PS_LIST_TAIL, "det_id", 0, "==", (psS64)atoll(det_id))) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+            psFree(where);
+            psFree(query);
+            return false;
+        }
+
+        psS32 iteration = psMetadataLookupS32(&status, config->args, "-iteration");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -iteration");
+            psFree(where);
+            psFree(query);
+            return false;
+        }
+        // always set iteration
+        if (!psMetadataAddS32(where, PS_LIST_TAIL, "iteration", 0, "==", iteration)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+            psFree(where);
+            psFree(query);
+            return false;
+        }
+        psString exp_tag = psMetadataLookupStr(&status, config->args, "-exp_tag");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_tag");
+            psFree(where);
+            psFree(query);
+            return false;
+        }
+        if (exp_tag) {
+            if (!psMetadataAddStr(where, PS_LIST_TAIL, "exp_tag", 0, "==", exp_tag)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+                psFree(where);
+                psFree(query);
+                return false;
+            }
+        }
+
+        // there's not
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psFree(where);
+        if (whereClause) {
+            psStringAppend(&query, " WHERE %s", whereClause);
+            psFree(whereClause);
+        }
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        // XXX check psError here
+        psError(PS_ERR_UNKNOWN, false, "no detResidImfile rows found");
+        psFree(output);
+        return false;
+    }
+
+    // start a transaction so it's all rows or nothing
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(output);
+        return false;
+    }
+
+    for (long i = 0; i < psArrayLength(output); i++) {
+        psMetadata *row = output->data[i];
+        // convert metadata into a detResidExp object
+        detResidExpRow *residExp = mdToDetResidExp(config, row);
+        if (!residExp) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to convert metadata to detResidExp");
+            psFree(output);
+            return false;
+        }
+        // insert detResidExp object into the database
+        if (!detResidExpInsertObject(config->dbh, residExp)) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(residExp);
+            psFree(output);
+        }
+        psFree(residExp);
+    }
+
+    psFree(output);
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static detResidExpRow *mdToDetResidExp(pxConfig *config, psMetadata *row)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(row, NULL);
+
+    bool status = false;
+    // values from row
+    psS32 det_id = psMetadataLookupS32(&status, row, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for det_id");
+        return false;
+    }
+    if (isnan(det_id)) {
+        psError(PS_ERR_UNKNOWN, true, "det_id is required");
+        return false;
+    }
+    psS32 iteration = psMetadataLookupS32(&status, row, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for iteration");
+        return false;
+    }
+    psString exp_tag = psMetadataLookupStr(&status, row, "exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for exp_tag");
+        return false;
+    }
+    if (!exp_tag) {
+        psError(PS_ERR_UNKNOWN, true, "exp_tag is required");
+        return false;
+    }
+    // values from config
+    psString recipe = psMetadataLookupStr(&status, config->args, "-recip");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -recip");
+        return false;
+    }
+    if (!recipe) {
+        psError(PS_ERR_UNKNOWN, true, "-recip is required");
+        return false;
+    }
+    psF64 bg = psMetadataLookupF64(&status, config->args, "-bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg");
+        return false;
+    }
+    if (isnan(bg)) {
+        psError(PS_ERR_UNKNOWN, true, "-bg is required");
+        return false;
+    }
+    psF64 bg_stdev = psMetadataLookupF64(&status, config->args, "-bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_stdev");
+        return false;
+    }
+    if (isnan(bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, true, "-bg_stdev is required");
+        return false;
+    }
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, config->args, "-bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_mean_stdev");
+        return false;
+    }
+    // optional
+    bool reject = psMetadataLookupBool(&status, config->args, "-reject");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -reject");
+        return false;
+    }
+    psString path_base = psMetadataLookupStr(&status, config->args, "-path_base");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -path_base");
+        return false;
+    }
+    if (!path_base) {
+        psError(PS_ERR_UNKNOWN, true, "-path_base is required");
+        return false;
+    }
+
+    // default values
+    psS8 code = psMetadataLookupS8(&status, config->args, "-code");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -code");
+        return false;
+    }
+
+    // create a new detResidImfileRow and insert it
+    return detResidExpRowAlloc(
+            det_id,
+            iteration,
+            exp_tag,
+            recipe,
+            bg,
+            bg_stdev,
+            bg_mean_stdev,
+            path_base,
+            !reject,
+            code
+        );
+}
+
+static bool residexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    bool faulted = psMetadataLookupU64(&status, config->args, "-faulted");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -faulted");
+        return false;
+    }
+
+    psString query = psStringCopy(
+        "SELECT"
+        "   detRun.mode,"
+        "   detResidExp.*,"
+        "   detInputExp.include"
+        " FROM detRun"
+        " JOIN detInputExp"
+        "   USING(det_id, iteration)"
+        " JOIN detResidExp"
+        "   USING(det_id, iteration, exp_tag)"
+        " WHERE"
+        "   detRun.state = 'run'"
+    );
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "detResidExp");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND detResidExp.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND detResidExp.fault = 0");
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detResidExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool residdetrunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    /* which returns a list of detrend runs (with detrend id, iteration and
+     * detrend type) which have completed all residexps.
+     */
+
+    // select detRun.det_id
+    // select detRun.iteration
+    // select detRun.det_type
+    // by:
+    // find the current iteration bassed on det_id
+    // find all exp_tags in the current det_id/iteration from detInputExp
+    // find all exp_tags in the current det_id/iteration from detResidExp
+    // compare the counts of exp_tags
+
+    psString query = psStringCopy(
+        "SELECT DISTINCT\n"
+        "   det_id,\n"
+        "   iteration,\n"
+        "   det_type,\n"
+        "   mode,\n"
+        "   workdir,\n"
+        "   camera\n"
+        " FROM\n"
+        "   (SELECT DISTINCT\n"
+        "       detRun.det_id,\n"
+        "       detRun.iteration,\n"
+        "       detRun.det_type,\n"
+        "       detRun.mode,\n"
+        "       detRun.workdir,\n"
+        "       detInputExp.exp_tag,\n"
+        "       rawExp.camera\n"
+        "   FROM detRun\n"
+        "   JOIN detInputExp\n"
+        "       USING(det_id, iteration)\n"
+        "   JOIN rawExp\n"
+        "       ON detInputExp.exp_tag = rawExp.exp_tag\n"
+        "   LEFT JOIN detResidExp\n"
+        "       ON detRun.det_id = detResidExp.det_id\n"
+        "       AND detRun.iteration = detResidExp.iteration\n"
+        "       AND detInputExp.exp_tag = detResidExp.exp_tag\n"
+        "   WHERE\n"
+        "       detRun.state = 'run'\n"
+        "   GROUP BY\n"
+        "       detRun.det_id,\n"
+        "       detRun.iteration\n"
+        "   HAVING\n"
+        "       COUNT(detResidExp.exp_tag) = COUNT(detInputExp.exp_tag)\n"
+        " ) AS residdetrun\n"
+        );
+
+    // XXX does it make sense to accept any search params?
+#if 0
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+#endif
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "detRejectExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool updateresidexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // build a query to search by det_id, iteration, exp_tag
+    psMetadata *where = psMetadataAlloc();
+    bool status = false;
+    psString det_id = psMetadataLookupStr(&status, config->args, "-det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -det_id");
+        psFree(where);
+        return false;
+    }
+    if (det_id) {
+        if (!psMetadataAddStr(where, PS_LIST_TAIL, "det_id", 0, "==", det_id)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+            psFree(where);
+            return false;
+        }
+    }
+    psS32 iteration = psMetadataLookupS32(&status, config->args, "-iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -iteration");
+        psFree(where);
+        return false;
+    }
+    // always where iteration
+    if (!psMetadataAddS32(where, PS_LIST_TAIL, "iteration", 0, "==", iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(where);
+        return false;
+    }
+    psString exp_tag = psMetadataLookupStr(&status, config->args, "-exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_tag");
+        psFree(where);
+        return false;
+    }
+    if (exp_tag) {
+        if (!psMetadataAddStr(where, PS_LIST_TAIL, "exp_tag", 0, "==", exp_tag)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+            psFree(where);
+            return false;
+        }
+    }
+
+    // find the values we're going to set
+    // copy everything but det_id, iteration, & exp_tag from the args and
+    // remove the '-' prefix
+    psMetadata *set = psMetadataAlloc();
+    psString recipe = psMetadataLookupStr(&status, config->args, "-recip");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -recip");
+        psFree(set);
+        psFree(where);
+        return false;
+    }
+    if (recipe) {
+        if (!psMetadataAddStr(set, PS_LIST_TAIL, "recipe", 0, "==", recipe)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+            psFree(set);
+            psFree(where);
+            return false;
+        }
+    }
+
+    psF64 bg = psMetadataLookupF64(&status, config->args, "-bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg");
+        psFree(set);
+        psFree(where);
+        return false;
+    }
+    if (!isnan(bg)) {
+        if (!psMetadataAddF64(set, PS_LIST_TAIL, "bg", 0, "==", bg)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+            psFree(set);
+            psFree(where);
+            return false;
+        }
+    }
+
+    psF64 bg_stdev = psMetadataLookupF64(&status, config->args, "-bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_stdev");
+        psFree(set);
+        psFree(where);
+        return false;
+    }
+    if (!isnan(bg_stdev)) {
+        if (!psMetadataAddF64(set, PS_LIST_TAIL, "bg_stdev", 0, "==", bg_stdev)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+            psFree(set);
+            psFree(where);
+            return false;
+        }
+    }
+
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, config->args, "-bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_mean_stdev");
+        psFree(set);
+        psFree(where);
+        return false;
+    }
+    if (!isnan(bg_mean_stdev)) {
+        if (!psMetadataAddF64(set, PS_LIST_TAIL, "bg_mean_stdev", 0, "==", bg_mean_stdev)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+            psFree(set);
+            psFree(where);
+            return false;
+        }
+    }
+
+    psString path_base = psMetadataLookupStr(&status, config->args, "-path_base");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -path_base");
+        psFree(set);
+        psFree(where);
+        return false;
+    }
+    if (path_base) {
+        if (!psMetadataAddStr(set, PS_LIST_TAIL, "path_base", 0, "==", path_base)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+            psFree(set);
+            psFree(where);
+            return false;
+        }
+    }
+
+    bool reject = psMetadataLookupBool(&status, config->args, "-reject");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -reject");
+        psFree(set);
+        psFree(where);
+        return false;
+    }
+    if (!psMetadataAddBool(set, PS_LIST_TAIL, "accept", 0, "==", !reject)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item accept");
+        psFree(set);
+        psFree(where);
+        return false;
+    }
+
+    long changed = psDBUpdateRows(config->dbh, "detResidExp", where, set);
+    psFree(set);
+    psFree(where);
+
+    if (changed < 0) {
+        psError(PS_ERR_UNKNOWN, false, "no rows were updated");
+        return false;
+    }
+
+    return true;
+}
+
+static bool adddetrunsummaryMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // select detRun.det_id
+    // select detRun.iteration
+    // by:
+    // find the current iteration bassed on det_id
+    // find all exp_tags in the current det_id/iteration from detInputExp
+    // find all exp_tags in the current det_id/iteration from detResidExp
+    // compare the counts of exp_tags
+
+    psString query = pxDataGet("dettool_find_completed_runs.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    {
+        // build a query to search by det_id, iteration, exp_tag
+        psMetadata *where = psMetadataAlloc();
+        bool status = false;
+        psString det_id = psMetadataLookupStr(&status, config->args, "-det_id");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -det_id");
+            psFree(where);
+            psFree(query);
+            return false;
+        }
+        if (det_id) {
+            if (!psMetadataAddStr(where, PS_LIST_TAIL, "det_id", 0, "==", det_id)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+                psFree(where);
+                psFree(query);
+                return false;
+            }
+        }
+        psS32 iteration = psMetadataLookupS32(&status, config->args, "-iteration");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -iteration");
+            psFree(where);
+            psFree(query);
+            return false;
+        }
+        // always set iteration
+        if (!psMetadataAddS32(where, PS_LIST_TAIL, "iteration", 0, "==", iteration)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+            psFree(where);
+            psFree(query);
+            return false;
+        }
+
+        // there's not
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psFree(where);
+        if (whereClause) {
+            psStringAppend(&query, " WHERE %s", whereClause);
+            psFree(whereClause);
+        }
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // start a transaction so it's all rows or nothing
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(output);
+        return false;
+    }
+
+    for (long i = 0; i < psArrayLength(output); i++) {
+        psMetadata *row = output->data[i];
+        // convert metadata into a detResidExp object
+        detRunSummaryRow *runSummary = mdToDetRunSummary(config, row);
+        if (!runSummary) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to convert metadata to detResidExp");
+            psFree(output);
+            return false;
+        }
+        // insert detResidExp object into the database
+        if (!detRunSummaryInsertObject(config->dbh, runSummary)) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(runSummary);
+            psFree(output);
+            return false;
+        }
+        psFree(runSummary);
+    }
+
+    psFree(output);
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    return true;
+}
+
+static detRunSummaryRow *mdToDetRunSummary(pxConfig *config, psMetadata *row)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    // from row
+    psS32 det_id = psMetadataLookupS32(&status, row, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for det_id");
+        return false;
+    }
+    psS32 iteration = psMetadataLookupS32(&status, row, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for iteration");
+        return false;
+    }
+
+    // from config->args
+    psF64 bg = psMetadataLookupF64(&status, config->args, "-bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg");
+        return NULL;
+    }
+    if (isnan(bg)) {
+        psError(PS_ERR_UNKNOWN, true, "-bg is required");
+        return NULL;
+    }
+    psF64 bg_stdev = psMetadataLookupF64(&status, config->args, "-bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_stdev");
+        return NULL;
+    }
+    if (isnan(bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, true, "-bg_stdev is required");
+        return NULL;
+    }
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, config->args, "-bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_mean_stdev");
+        return NULL;
+    }
+    // optional
+    bool accept = psMetadataLookupBool(&status, config->args, "-accept");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -accept");
+        return NULL;
+    }
+    // default values
+    psS8 code = psMetadataLookupS8(&status, config->args, "-code");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -code");
+        return false;
+    }
+
+    return detRunSummaryRowAlloc(
+            det_id,
+            iteration,
+            bg,
+            bg_stdev,
+            bg_mean_stdev,
+            accept,
+            code
+        );
+}
+
+static bool detrunsummaryMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    bool faulted = psMetadataLookupU64(&status, config->args, "-faulted");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -faulted");
+        return false;
+    }
+
+    psString query = psStringCopy(
+        "SELECT DISTINCT\n"
+        "   detRunSummary.*,\n"
+        "   detRun.det_type,\n"
+        "   detRun.mode\n"
+        " FROM detRun\n"
+        " JOIN detRunSummary\n"
+        "   USING(det_id, iteration)\n"
+        " WHERE\n"
+        "   detRun.state = 'run'\n"
+        );
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, NULL);
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND detRunSummary.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND detRunSummary.fault = 0");
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "rawDetrendImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool updatedetrunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // det_id is required
+    // XXX this isn't strictly nessicary but not having the det_id complicates
+    // incrementing the iteration number
+    bool status = false;
+    psString det_id = psMetadataLookupStr(&status, config->args, "-det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -det_id");
+        return false;
+    }
+    if (!det_id) {
+        psError(PS_ERR_UNKNOWN, true, "-det_id is required");
+        return false;
+    }
+    // either -rerun or -state must be specified
+    bool again = psMetadataLookupBool(&status, config->args, "-again");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -again");
+        return false;
+    }
+    psString state = psMetadataLookupStr(&status, config->args, "-state");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -state");
+        return false;
+    }
+    if (!(again || state)) {
+        psError(PS_ERR_UNKNOWN, true, "either -again or -state must be specified");
+        return false;
+    }
+    if (again && state) {
+        psError(PS_ERR_UNKNOWN, true, "either -again or -state must be specified");
+        return false;
+    }
+
+    if (state) {
+        // set detRun.state to state
+        return setDetRunState(config, det_id, state);
+    }
+
+    // else
+    // -again
+
+    // select detRun.det_id
+    // select detRun.iteration
+    // select detInputExp.exp_tag
+    // select detResidExp.accept
+    // by:
+    // find the current iteration bassed on det_id
+    // find all exp_tags in the current det_id/iteration from detInputExp
+    // find all exp_tags in the current det_id/iteration from detResidExp
+    // compare the counts of exp_tags
+
+    psString query = psStringCopy(
+        "SELECT DISTINCT"
+        "   detRun.det_id AS det_id,"
+        "   detRun.iteration,"
+        "   detInputExp.exp_tag,"
+        "   detResidExp.accept"
+        " FROM detRun"
+        " JOIN detInputExp"
+        "   ON detRun.det_id = detInputExp.det_id"
+        "   AND detRun.iteration = detInputExp.iteration"
+        " JOIN detResidExp"
+        "   ON detRun.det_id = detResidExp.det_id"
+        "   AND detRun.iteration = detResidExp.iteration"
+        "   AND detInputExp.exp_tag = detResidExp.exp_tag"
+        " WHERE"
+        "   detRun.state = 'run'"
+        "   AND detRun.mode = 'master'"
+    );
+
+    // XXX this query was not restricted by det_id, resulting
+    // in an inconsistent UPDATE below.  I added this AND clause
+    // though there may be a cleaner method (EAM 2006.10.08)
+    psStringAppend (
+        &query,
+        "  AND detRun.det_id = '%s'", det_id);
+
+    psStringAppend (
+        &query,
+        " GROUP BY"
+        "   detRun.det_id,"
+        "   detRun.iteration,"
+        "   detInputExp.exp_tag"
+        " HAVING"
+        "   COUNT(detResidExp.exp_tag) = COUNT(detInputExp.exp_tag)"
+        );
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("dettool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // start a transaction so we don't end up with an incremented iteration
+    // count but no detInputExps
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(output);
+        return false;
+    }
+
+    // up the detRuns iteration count for for the single det_id we are
+    // operating on
+    // XXX this will have to changed in order to support multiple det_ids with
+    // a single invocation of this functions
+    psS32 newIteration = incrementIteration(config, det_id);
+    if (!newIteration) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psFree(output);
+        return false;
+    }
+
+    for (long i = 0; i < psArrayLength(output); i++) {
+        psMetadata *row = output->data[i];
+        psString exp_tag = psMetadataLookupStr(&status, row, "exp_tag");
+        if (!status) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for exp_tag");
+            psFree(output);
+            return false;
+        }
+        bool accept = psMetadataLookupBool(&status, row, "accept");
+        if (!status) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for accept");
+            psFree(output);
+            return false;
+        }
+
+        // detResidExp.include is used to set detInputExp.include
+        if (!detInputExpInsert(
+                    config->dbh,
+                    (psS32)atol(det_id),
+                    newIteration,
+                    exp_tag,
+                    accept
+                )
+        ) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    // point of no return for det_id creation
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool rerunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // det_id is required
+    bool status = false;
+    psString det_id = psMetadataLookupStr(&status, config->args, "-det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -det_id");
+        return false;
+    }
+    if (!det_id) {
+        psError(PS_ERR_UNKNOWN, true, "-det_id is required");
+        return false;
+    }
+
+    // we have to support multipe exp_tags
+    psMetadataItem *item = psMetadataLookup(config->args, "-exp_tag");
+    if (!item) {
+        // this shouldn't actually happen when using psArgs
+        psError(PS_ERR_UNKNOWN, true, "-exp_tag is required");
+        return false;
+    }
+
+    psList *exp_tag_list = item->data.list;
+    psMetadata *where = psMetadataAlloc();
+    // make sure that -exp_tag was parsed correctly
+    // XXX this can be removed someday
+    if (item->type == PS_DATA_METADATA_MULTI) {
+        psListIterator *iter = psListIteratorAlloc(item->data.list, 0, false);
+        psMetadataItem *mItem = NULL;
+        while ((mItem = psListGetAndIncrement(iter))) {
+            psString exp_tag = mItem->data.V;
+            // if exp_tag is NULL then it means that -exp_tag has not been
+            // specified
+            if (!exp_tag) {
+                psError(PS_ERR_UNKNOWN, true,
+                        "at least one -exp_tag is required");
+                psFree(where);
+                return false;
+            }
+
+            if (!psMetadataAddStr(where, PS_LIST_TAIL, "exp_tag",
+                        PS_META_DUPLICATE_OK, "==", exp_tag)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+                psFree(iter);
+                psFree(where);
+                return false;
+            }
+        }
+        psFree(iter);
+    } else {
+        psAbort(                "-exp_tag was not parsed correctly (this should not happen");
+    }
+
+    // check that the specified exp_tags actually exist in the iteration zero
+    // detInputExp set
+
+    // add the det_id & iteration == 0 to the where clause
+    if (!psMetadataAddS32(where, PS_LIST_TAIL, "det_id", 0, "==",
+                (psS32)atol(det_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(where);
+        return false;
+    }
+    if (!psMetadataAddS32(where, PS_LIST_TAIL, "iteration", 0, "==", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(where);
+        return false;
+    }
+
+    psArray *detrendExps = detInputExpSelectRowObjects(config->dbh, where, 0);
+    psFree(where);
+    if (!detrendExps) {
+        psError(PS_ERR_UNKNOWN, false, "no rawExp rows found");
+        psFree(where);
+        return false;
+    }
+
+    // build a hash for the valid exp_tags
+    psHash *valid_exp_tags = psHashAlloc(psArrayLength(detrendExps));
+    for (long i = 0; i < psArrayLength(detrendExps); i++) {
+        psHashAdd(valid_exp_tags,
+            ((detInputExpRow *)detrendExps->data[i])->exp_tag,
+            detrendExps->data[i]
+        );
+    }
+    psFree(detrendExps);
+
+    // start a transaction so we don't end up with an incremented iteration
+    // count but no detInputExps
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(valid_exp_tags);
+        return false;
+    }
+
+    // up the detRuns iteration count
+    psS32 newIteration = incrementIteration(config, det_id);
+    if (!newIteration) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psFree(valid_exp_tags);
+        return false;
+    }
+
+    // check exp_tags and build up an array of new detInputExp rows at the same
+    // time
+    psListIterator *iter = psListIteratorAlloc(exp_tag_list, 0, false);
+    psMetadataItem *mItem = NULL;
+    psArray *newInputExps = psArrayAllocEmpty(psListLength(exp_tag_list));
+    while ((mItem = psListGetAndIncrement(iter))) {
+        detInputExpRow *inputExp = psHashLookup(valid_exp_tags,
+                (char *)mItem->data.V);
+        if (!inputExp) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            // invalid exp_tag
+            psError(PS_ERR_UNKNOWN, false, "exp_tag %s is invalid for det_id %s",
+                    (char *)mItem->data.V, det_id);
+            psFree(iter);
+            psFree(valid_exp_tags);
+            return false;
+        }
+        detInputExpRow *newInputExp = detInputExpRowAlloc(
+            (psS32)atol(det_id),
+            newIteration,
+            inputExp->exp_tag,
+            true   // use
+        );
+        psArrayAdd(newInputExps, 0, newInputExp);
+        psFree(newInputExp);
+    }
+    psFree(iter);
+    psFree(valid_exp_tags);
+
+    for (long i = 0; i < psArrayLength(newInputExps); i++) {
+        if (!detInputExpInsertObject(config->dbh, newInputExps->data[i])) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psFree(newInputExps);
+            return false;
+        }
+    }
+    psFree(newInputExps);
+
+    // point of no return for det_id creation
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool register_detrendMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required options
+    bool status = false;
+    psString det_type = psMetadataLookupStr(&status, config->args, "-det_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -det_type");
+        return false;
+    }
+    if (!det_type) {
+        psError(PS_ERR_UNKNOWN, true, "-det_type is required");
+        return false;
+    }
+
+    psString filelevel = psMetadataLookupStr(&status, config->args, "-filelevel");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -filelevel");
+        return false;
+    }
+    if (!filelevel) {
+        psError(PS_ERR_UNKNOWN, true, "-filelevel is required");
+        return false;
+    }
+
+    psString workdir = psMetadataLookupStr(&status, config->args, "-workdir");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -workdir");
+        return false;
+    }
+    if (!workdir) {
+        psError(PS_ERR_UNKNOWN, true, "-workdir is required");
+        return false;
+    }
+
+    // everything else is optional
+    psString mode = psMetadataLookupStr(&status, config->args, "-mode");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -mode");
+        return false;
+    }
+    // check mode
+    if (mode && !isValidMode(config, mode)) {
+        psError(PS_ERR_UNKNOWN, false, "invalud mode");
+        return false;
+    }
+
+    psString camera = psMetadataLookupStr(&status, config->args, "-inst");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -inst");
+        return false;
+    }
+
+    psString telescope = psMetadataLookupStr(&status, config->args, "-telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -telescope");
+        return false;
+    }
+
+    psString exp_type = psMetadataLookupStr(&status, config->args, "-exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_type");
+        return false;
+    }
+
+    psString filter = psMetadataLookupStr(&status, config->args, "-filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -filter");
+        return false;
+    }
+
+    psF32 airmass_min = psMetadataLookupF32(&status, config->args, "-airmass_min");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -airmass_min");
+        return false;
+    }
+
+    psF32 airmass_max = psMetadataLookupF32(&status, config->args, "-airmass_max");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -airmass_max");
+        return false;
+    }
+
+    psF32 exp_time_min = psMetadataLookupF32(&status, config->args, "-exp_time_min");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_time_min");
+        return false;
+    }
+
+    psF32 exp_time_max = psMetadataLookupF32(&status, config->args, "-exp_time_max");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_time_max");
+        return false;
+    }
+
+    psF32 ccd_temp_min = psMetadataLookupF32(&status, config->args, "-ccd_temp_min");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -ccd_temp_min");
+        return false;
+    }
+
+    psF32 ccd_temp_max = psMetadataLookupF32(&status, config->args, "-ccd_temp_max");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -ccd_temp_max");
+        return false;
+    }
+
+    psF64 posang_min = psMetadataLookupF32(&status, config->args, "-posang_min");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -posang_min");
+        return false;
+    }
+
+    psF64 posang_max = psMetadataLookupF32(&status, config->args, "-posang_max");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -posang_max");
+        return false;
+    }
+
+    psTime *registered = NULL;
+    {
+        psString registeredStr = psMetadataLookupStr(&status, config->args, "-registered");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -registered");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (registeredStr) {
+            registered = psTimeFromISO(registeredStr, PS_TIME_UTC);
+        } else {
+            registered = NULL;
+        }
+    }
+
+    psTime *time_begin = NULL;
+    {
+        psString time_beginStr = psMetadataLookupStr(&status, config->args, "-time_begin");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -time_begin");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (time_beginStr) {
+            time_begin = psTimeFromISO(time_beginStr, PS_TIME_UTC);
+        } else {
+            time_begin = NULL;
+        }
+    }
+
+    psTime *time_end = NULL;
+    {
+        psString time_endStr = psMetadataLookupStr(&status, config->args, "-time_end");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -time_end");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (time_endStr) {
+            time_end = psTimeFromISO(time_endStr, PS_TIME_UTC);
+        } else {
+            time_end = NULL;
+        }
+    }
+
+    psTime *use_begin = NULL;
+    {
+        psString use_beginStr = psMetadataLookupStr(&status, config->args, "-use_begin");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -use_begin");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (use_beginStr) {
+            use_begin = psTimeFromISO(use_beginStr, PS_TIME_UTC);
+        } else {
+            use_begin = NULL;
+        }
+    }
+
+    psTime *use_end = NULL;
+    {
+        psString use_endStr = psMetadataLookupStr(&status, config->args, "-use_end");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -use_end");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (use_endStr) {
+            use_end = psTimeFromISO(use_endStr, PS_TIME_UTC);
+        } else {
+            use_end = NULL;
+        }
+    }
+
+    psF64 bg = psMetadataLookupF64(&status, config->args, "-bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg");
+        return false;
+    }
+
+    psF64 bg_stdev = psMetadataLookupF64(&status, config->args, "-bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_stdev");
+        return false;
+    }
+
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, config->args, "-bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_mean_stdev");
+        return false;
+    }
+
+    psString parent = psMetadataLookupStr(&status, config->args, "-parent");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -parent");
+        return false;
+    }
+
+    psString label = psMetadataLookupStr(&status, config->args, "-label");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -label");
+        return false;
+    }
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!detRunInsert(config->dbh,
+            0,          // det_id
+            0,          // the iteration is fixed at 0
+            det_type,
+            mode,
+            "reg",      // state
+            filelevel,
+            workdir,
+            camera,
+            telescope,
+            exp_type,
+            filter,
+            airmass_min,
+            airmass_max,
+            exp_time_min,
+            exp_time_max,
+            ccd_temp_min,
+            ccd_temp_max,
+            posang_min,
+            posang_max,
+            registered,
+            time_begin,
+            time_end,
+            use_begin,
+            use_end,
+            0.0,        // solang min
+            0.0,        // solang max
+            label,      // label
+            (psS64)atoll(parent)
+    )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psFree(registered);
+        return false;
+    }
+    psFree(registered);
+
+    if (!detRunSummaryInsert(config->dbh,
+            psDBLastInsertID(config->dbh),
+            0,  // the iteration is fixed at 0
+            bg,
+            bg_stdev,
+            bg_mean_stdev,
+            true,   // accept
+            0       // fault code
+    )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        return false;
+    }
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool register_detrend_imfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required options
+    bool status = false;
+    psString det_id = psMetadataLookupStr(&status, config->args, "-det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -det_id");
+        return false;
+    }
+    if (!det_id) {
+        psError(PS_ERR_UNKNOWN, true, "-det_id is required");
+        return false;
+    }
+
+    psString class_id = psMetadataLookupStr(&status, config->args, "-class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -class_id");
+        return false;
+    }
+    if (!class_id) {
+        psError(PS_ERR_UNKNOWN, true, "-class_id is required");
+        return false;
+    }
+
+    psString uri    = psMetadataLookupStr(&status, config->args, "-uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -uri");
+        return false;
+    }
+    if (!uri) {
+        psError(PS_ERR_UNKNOWN, true, "-uri is required");
+        return false;
+    }
+
+    // everything else is optional
+    psF64 bg = psMetadataLookupF64(&status, config->args, "-bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg");
+        return false;
+    }
+
+    psF64 bg_stdev = psMetadataLookupF64(&status, config->args, "-bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_stdev");
+        return false;
+    }
+
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, config->args, "-bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_mean_stdev");
+        return false;
+    }
+
+    psString path_base = psMetadataLookupStr(&status, config->args, "-path_base");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -path_base");
+        return false;
+    }
+
+    // XXX this is going in without checking that the det_id is valid.
+    // it seems like it be better to be using foreign key constraints rather
+    // then having to resort to a seperate query (and locking) to check that
+    // the det_id is valid
+    if (!detNormalizedImfileInsert(config->dbh,
+            (psS64)atoll(det_id),
+            0,  // the iteration is fixed at 0
+            class_id,
+            uri,
+            bg,
+            bg_stdev,
+            bg_mean_stdev,
+            path_base,
+            0       // fault code
+    )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static psS32 incrementIteration(pxConfig *config, const char *det_id)
+{
+    // this function returns zero on error
+    PS_ASSERT_PTR_NON_NULL(config, 0);
+    PS_ASSERT_PTR_NON_NULL(det_id, 0);
+
+    char *query = "UPDATE detRun SET iteration = iteration + 1 WHERE det_id = '%s'";
+    if (!p_psDBRunQuery(config->dbh, query, det_id)) {
+        psError(PS_ERR_UNKNOWN, false,
+                "failed to increment iteration for det_id %s", det_id);
+        return 0;
+    }
+
+    psMetadata *where = psMetadataAlloc();
+    if (!psMetadataAddS32(where, PS_LIST_TAIL, "det_id", 0, "==",
+                (psS32)atol(det_id))) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(where);
+        return 0;
+    }
+
+    psArray *detRuns = detRunSelectRowObjects(config->dbh, where, 0);
+    psFree(where);
+    if (!detRuns) {
+        psError(PS_ERR_UNKNOWN, false, "no detRun rows found");
+        return 0;
+    }
+
+    // sanity check the database
+    if (psArrayLength(detRuns) != 1) {
+        // this should no happen
+        psAbort(                "database query return too many rows (this should not happen");
+    }
+
+    psS32 newIteration = ((detRunRow *)detRuns->data[0])->iteration;
+    psFree(detRuns);
+
+    return newIteration;
+}
+
+static bool setDetRunState(pxConfig *config, const char *det_id, const char *state)
+{
+    PS_ASSERT_PTR_NON_NULL(det_id, false);
+    PS_ASSERT_PTR_NON_NULL(state, false);
+
+    // check that state is a valid string value
+    if (!(
+            (strncmp(state, "run", 4) == 0)
+            || (strncmp(state, "stop", 5) == 0)
+            || (strncmp(state, "reg", 4) == 0)
+        )
+    ) {
+        psError(PS_ERR_UNKNOWN, false,
+                "invalid detRun state: %s", state);
+        return false;
+    }
+
+    char *query = "UPDATE detRun SET state = '%s' WHERE det_id = '%s'";
+    if (!p_psDBRunQuery(config->dbh, query, state, det_id)) {
+        psError(PS_ERR_UNKNOWN, false,
+                "failed to change state for det_id %s", det_id);
+        return false;
+    }
+
+    return true;
+}
+
+static bool isValidMode(pxConfig *config, const char *mode)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(mode, false);
+
+    // check that state is a valid string value
+    if (!(
+            (strncmp(mode, "master", 7) == 0)
+            || (strncmp(mode, "verify", 7) == 0)
+        )
+    ) {
+        psError(PS_ERR_UNKNOWN, false,
+                "invalid detRun mode: %s", mode);
+        return false;
+    }
+
+    return true;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/dettool.h
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/dettool.h	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/dettool.h	(revision 22073)
@@ -0,0 +1,70 @@
+/*
+ * dettool.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef DETTOOL_H
+#define DETTOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    DETTOOL_MODE_NONE           = 0x0,
+    DETTOOL_MODE_PENDING,
+    DETTOOL_MODE_DEFINEBYTAG,
+    DETTOOL_MODE_DEFINEBYQUERY,
+    DETTOOL_MODE_DEFINEBYDETRUN,
+    DETTOOL_MODE_RUNS,
+    DETTOOL_MODE_CHILDLESSRUN,
+    DETTOOL_MODE_INPUT,
+    DETTOOL_MODE_RAW,
+    DETTOOL_MODE_TOPROCESSEDIMFILE,
+    DETTOOL_MODE_ADDPROCESSEDIMFILE,
+    DETTOOL_MODE_PROCESSEDIMFILE,
+    DETTOOL_MODE_TOPROCESSEDEXP,
+    DETTOOL_MODE_ADDPROCESSEDEXP,
+    DETTOOL_MODE_PROCESSEDEXP,
+    DETTOOL_MODE_TOSTACKED,
+    DETTOOL_MODE_ADDSTACKED,
+    DETTOOL_MODE_STACKED,
+    DETTOOL_MODE_TONORMALIZE,
+    DETTOOL_MODE_ADDNORMALIZEDSTAT,
+    DETTOOL_MODE_TONORMALIZEDSTAT,
+    DETTOOL_MODE_ADDNORMALIZEDIMFILE,
+    DETTOOL_MODE_NORMALIZEDIMFILE,
+    DETTOOL_MODE_TONORMALIZEDEXP,
+    DETTOOL_MODE_ADDNORMALIZEDEXP,
+    DETTOOL_MODE_NORMALIZEDEXP,
+    DETTOOL_MODE_TORESIDIMFILE,
+    DETTOOL_MODE_ADDRESIDIMFILE,
+    DETTOOL_MODE_RESIDIMFILE,
+    DETTOOL_MODE_TORESIDEXP,
+    DETTOOL_MODE_RESIDEXP,
+    DETTOOL_MODE_ADDRESIDEXP,
+    DETTOOL_MODE_RESIDDETRUN,
+    DETTOOL_MODE_UPDATERESIDEXP,
+    DETTOOL_MODE_ADDDETRUNSUMMARY,
+    DETTOOL_MODE_DETRUNSUMMARY,
+    DETTOOL_MODE_UPDATEDETRUN,
+    DETTOOL_MODE_RERUN,
+    DETTOOL_MODE_REGISTER_DETREND,
+    DETTOOL_MODE_REGISTER_DETREND_IMFILE
+} dettoolMode;
+
+pxConfig *dettoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // DETTOOL_H
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/dettoolConfig.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/dettoolConfig.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/dettoolConfig.c	(revision 22073)
@@ -0,0 +1,992 @@
+/*
+ * dettoolConfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "dettool.h"
+
+pxConfig *dettoolConfig(pxConfig *config, int argc, char **argv) {
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    psTime *time = psTimeGetNow(PS_TIME_TAI);
+    psString now = psTimeToISO(time);
+    psFree(time);
+
+    // -pending
+    psMetadata *pendingArgs = psMetadataAlloc();
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-exp_tag",  0,
+            "search by exposure ID", NULL);
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-exp_type",  0,
+            "search by exposure type", NULL);
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-inst",  0,
+            "search by camera", NULL);
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-telescope",  0,
+            "search by telescope", NULL);
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-filter",  0,
+            "search by filter", NULL);
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-uri",  0,
+            "search by URL", NULL);
+    psMetadataAddBool(pendingArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -definebytag 
+    psMetadata *definebytagArgs = psMetadataAlloc();
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-exp_tag",
+            PS_META_DUPLICATE_OK,
+            "include this exposure (multiple OK, required)", NULL);
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-det_type",  0,
+            "define the type of detrend run (required)", NULL);
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-mode",  0,
+            "define the mode of this detrend run", "master");
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-filelevel",  0,
+            "define filelevel (required)", NULL);
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-workdir",  0,
+            "define workdir (required)", NULL);
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-inst",  0,
+            "define camera", NULL);
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-telescope",  0,
+            "define telescope", NULL);
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-exp_type",  0,
+            "define exposure type", NULL);
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-filter",  0,
+            "define filter ", NULL);
+    psMetadataAddF32(definebytagArgs, PS_LIST_TAIL, "-airmass_min",  0,
+            "define min airmass", NAN);
+    psMetadataAddF32(definebytagArgs, PS_LIST_TAIL, "-airmass_max",  0,
+            "define max airmass", NAN);
+    psMetadataAddF32(definebytagArgs, PS_LIST_TAIL, "-exp_time_min",  0,
+            "define min exposure time", NAN);
+    psMetadataAddF32(definebytagArgs, PS_LIST_TAIL, "-exp_time_max",  0,
+            "define max exposure time", NAN);
+    psMetadataAddF64(definebytagArgs, PS_LIST_TAIL, "-ccd_temp_min",  0,
+            "define min ccd tempature", NAN);
+    psMetadataAddF64(definebytagArgs, PS_LIST_TAIL, "-ccd_temp_max",  0,
+            "define max ccd tempature", NAN);
+    psMetadataAddF64(definebytagArgs, PS_LIST_TAIL, "-posang_min",  0,
+            "define min rotator position angle", NAN);
+    psMetadataAddF64(definebytagArgs, PS_LIST_TAIL, "-posang_max",  0,
+            "define max rotator position angle", NAN);
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-registered",  0,
+            "time detrend run was registered", now);
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-time_begin",  0,
+            "detrend applyes to exposures taken during this peroid", NULL);
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-time_end",  0,
+            "detrend applyes to exposures taken during this peroid", NULL);
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-use_begin",  0,
+            "start of detrend run applicable peroid", NULL);
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-use_end",  0,
+            "end of detrend run applicable peroid", NULL);
+    psMetadataAddStr(definebytagArgs, PS_LIST_TAIL, "-label",  0,
+            "define detrun label", NULL);
+    psMetadataAddBool(definebytagArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+   
+    // -definebyquery
+    psMetadata *definebyqueryArgs = psMetadataAlloc();
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-det_type",  0,
+            "define the type of detrend run (required)", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-mode",  0,
+            "define the mode of this detrend run", "master");
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-filelevel",  0,
+            "define filelevel (required)", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-workdir",  0,
+            "define workdir (required)", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-inst",  0,
+            "define camera", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-telescope",  0,
+            "define telescope", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-filter",  0,
+            "define filter ", NULL);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-airmass_min",  0,
+            "define min airmass", NAN);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-airmass_max",  0,
+            "define max airmass", NAN);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-exp_time_min",  0,
+            "define min exposure time", NAN);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-exp_time_max",  0,
+            "define max exposure time", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-ccd_temp_min",  0,
+            "define min ccd tempature", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-ccd_temp_max",  0,
+            "define max ccd tempature", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-posang_min",  0,
+            "define min rotator position angle", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-posang_max",  0,
+            "define max rotator position angle", NAN);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-registered",  0,
+            "time detrend run was registered", now);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-time_begin",  0,
+            "detrend applyes to exposures taken during this peroid", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-time_end",  0,
+            "detrend applyes to exposures taken during this peroid", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-use_begin",  0,
+            "start of detrend run applicable peroid", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-use_end",  0,
+            "end of detrend run applicable peroid", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-select_exp_type",  0,
+            "search for exp_type", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-select_inst",  0,
+            "search for camera", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-select_telescope",  0,
+            "search for telescope", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-select_filter",  0,
+            "search for filter", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-select_uri",  0,
+            "search for uri", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-select_dateobs_begin", 0,
+            "search for exposures by time (>=)", NULL);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-select_dateobs_end", 0,
+            "search for exposures by time (<)", NULL);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-select_airmass_min",  0,
+            "define min airmass", NAN);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-select_airmass_max",  0,
+            "define max airmass", NAN);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-select_exp_time_min",  0,
+            "define min exposure time", NAN);
+    psMetadataAddF32(definebyqueryArgs, PS_LIST_TAIL, "-select_exp_time_max",  0,
+            "define max exposure time", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-select_ccd_temp_min",  0,
+            "define min ccd tempature", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-select_ccd_temp_max",  0,
+            "define max ccd tempature", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-select_posang_min",  0,
+            "define min rotator position angle", NAN);
+    psMetadataAddF64(definebyqueryArgs, PS_LIST_TAIL, "-select_posang_max",  0,
+            "define max rotator position angle", NAN);
+    psMetadataAddBool(definebyqueryArgs, PS_LIST_TAIL, "-pretend",  0,
+            "print the exposures that would be included in the detrend run and exit", false);
+    psMetadataAddStr(definebyqueryArgs, PS_LIST_TAIL, "-label",  0,
+            "define detrun label", NULL);
+    psMetadataAddBool(definebyqueryArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+ 
+    // -definebydetrun
+    psMetadata *definebydetrunArgs = psMetadataAlloc();
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-det_id",  0,
+            "det ID to base a new detRun on (required)", NULL);
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-set_det_type",  0,
+            "define the type of detrend run (required)", NULL);
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-set_mode",  0,
+            "define the mode of this detrend run", "master");
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-set_exp_type",  0,
+            "define exposure type", NULL);
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-set_filelevel",  0,
+            "define filelevel", NULL);
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-set_workdir",  0,
+            "define workdir", NULL);
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-set_filter",  0,
+            "define filter ", NULL);
+    psMetadataAddF32(definebydetrunArgs, PS_LIST_TAIL, "-set_airmass_min",  0,
+            "define airmass", NAN);
+    psMetadataAddF32(definebydetrunArgs, PS_LIST_TAIL, "-set_airmass_max",  0,
+            "define airmass", NAN);
+    psMetadataAddF32(definebydetrunArgs, PS_LIST_TAIL, "-set_exp_time_min",  0,
+            "define exposure time", NAN);
+    psMetadataAddF32(definebydetrunArgs, PS_LIST_TAIL, "-set_exp_time_max",  0,
+            "define exposure time", NAN);
+    psMetadataAddF64(definebydetrunArgs, PS_LIST_TAIL, "-set_ccd_temp_min",  0,
+            "define ccd tempature", NAN);
+    psMetadataAddF64(definebydetrunArgs, PS_LIST_TAIL, "-set_ccd_temp_max",  0,
+            "define ccd tempature", NAN);
+    psMetadataAddF64(definebydetrunArgs, PS_LIST_TAIL, "-set_posang_min",  0,
+            "define rotator position angle", NAN);
+    psMetadataAddF64(definebydetrunArgs, PS_LIST_TAIL, "-set_posang_max",  0,
+            "define rotator position angle", NAN);
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-set_registered",  0,
+            "time detrend run was registered", now);
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-set_time_begin",  0,
+            "start of peroid to apply detrend too", NULL);
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-set_time_end",  0,
+            "end of peroid to apply detrend too", NULL);
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-set_use_begin",  0,
+            "start of detrend run applicable peroid", NULL);
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-set_use_end",  0,
+            "end of detrend run applicable peroid", NULL);
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-filter_input_begin", 0,
+            "filter input detrun exp to be in this peroid", NULL);
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-filter_input_end", 0,
+            "filter input detrun exp to be in this peroid", NULL);
+    psMetadataAddStr(definebydetrunArgs, PS_LIST_TAIL, "-set_label",  0,
+            "define detrun label", NULL);
+    psMetadataAddBool(definebydetrunArgs, PS_LIST_TAIL, "-simple", 0,
+            "use the simple output format", false);
+
+    // -runs
+    psMetadata *runsArgs = psMetadataAlloc();
+    psMetadataAddStr(runsArgs, PS_LIST_TAIL, "-det_type",  0,
+            "search for type of detrend run", NULL);
+    psMetadataAddBool(runsArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -childlessrun
+    psMetadata *childlessrunArgs = psMetadataAlloc();
+    psMetadataAddStr(childlessrunArgs, PS_LIST_TAIL, "-det_type",  0,
+            "search for type of detrend run", NULL);
+    psMetadataAddU64(childlessrunArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(childlessrunArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -input
+    psMetadata *inputArgs = psMetadataAlloc();
+    psMetadataAddStr(inputArgs, PS_LIST_TAIL, "-det_id", 0,
+            "search for detrend ID", NULL);
+    psMetadataAddS32(inputArgs, PS_LIST_TAIL, "-iteration",  0,
+            "define iteration number", 0);
+    psMetadataAddStr(inputArgs, PS_LIST_TAIL, "-exp_tag",  0,
+            "search for exp ID", NULL);
+    psMetadataAddBool(inputArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+ 
+    // -raw
+    psMetadata *rawArgs = psMetadataAlloc();
+    psMetadataAddBool(rawArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -toprocessedimfile
+    psMetadata *toprocessedimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(toprocessedimfileArgs, PS_LIST_TAIL, "-det_id",  0,
+            "search for detrend ID", NULL);
+    psMetadataAddStr(toprocessedimfileArgs, PS_LIST_TAIL, "-exp_tag",  0,
+            "search for exp ID", NULL);
+    psMetadataAddStr(toprocessedimfileArgs, PS_LIST_TAIL, "-class_id",  0,
+            "search for class ID", NULL);
+    psMetadataAddU64(toprocessedimfileArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(toprocessedimfileArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+    
+    // -addprocessedimfile
+    psMetadata *addprocessedimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-det_id",  0,
+            "define detrend ID (required)", NULL);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-exp_tag",  0,
+            "define exp ID (required)", NULL);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-class_id",  0,
+            "define class ID (required)", NULL);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-uri",  0,
+            "define URI (required)", NULL);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-recip",  0,
+            "define recipe (required)", NULL);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-bg",  0,
+            "define exposue background", NAN);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-bg_stdev",  0,
+            "define exposue background stdev", NAN);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,
+            "define exposue background mean stdev", NAN);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-path_base",  0,
+            "define banana 1", NULL);
+    psMetadataAddS8(addprocessedimfileArgs, PS_LIST_TAIL, "-code",  0,
+            "set fault code (required)", 0);
+    
+    // -processedimfile
+    psMetadata *processedimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(processedimfileArgs, PS_LIST_TAIL, "-det_id",  0,
+            "search for detrend ID", NULL);
+    psMetadataAddStr(processedimfileArgs, PS_LIST_TAIL, "-exp_tag",  0,
+            "search for exp ID", NULL);
+    psMetadataAddStr(processedimfileArgs, PS_LIST_TAIL, "-class_id",  0,
+            "search for class ID", NULL);
+    psMetadataAddBool(processedimfileArgs, PS_LIST_TAIL, "-chip",  0,
+            "restrict results to completed 'chip' sets", false);
+    psMetadataAddBool(processedimfileArgs, PS_LIST_TAIL, "-exp",  0,
+            "restrict results to complete 'exposures'", false);
+    psMetadataAddBool(processedimfileArgs, PS_LIST_TAIL, "-included",  0,
+            "restrict results to exposures 'includeded' in the current iteration", false);
+    psMetadataAddU64(processedimfileArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(processedimfileArgs, PS_LIST_TAIL, "-faulted",  0,
+            "only return imfiles with a fault status set", false);
+    psMetadataAddBool(processedimfileArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -toprocessedexp
+    psMetadata *toprocessedexpArgs = psMetadataAlloc();
+    psMetadataAddU64(toprocessedexpArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(toprocessedexpArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -addproccessedexp
+    psMetadata *addprocessedexpArgs = psMetadataAlloc();
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-det_id",  0,
+            "define detrend ID (required)", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-exp_tag",  0,
+            "define detrend ID (required)", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-recip",  0,
+            "define recipe (required)", NULL);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-bg",  0,
+            "define exposue background", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-bg_stdev",  0,
+            "define exposue background stdev", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,
+            "define exposue background mean stdev", NAN);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-path_base",  0,
+            "define banana 1", NULL);
+    psMetadataAddS8(addprocessedexpArgs, PS_LIST_TAIL, "-code",  0,
+            "set fault code (required)", 0);
+
+    // -proccessedexp
+    psMetadata *processedexpArgs = psMetadataAlloc();
+    psMetadataAddStr(processedexpArgs, PS_LIST_TAIL, "-det_id",  0,
+            "define detrend ID (required)", NULL);
+    psMetadataAddStr(processedexpArgs, PS_LIST_TAIL, "-exp_tag",  0,
+            "define detrend ID (required)", NULL);
+    psMetadataAddU64(processedexpArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(processedexpArgs, PS_LIST_TAIL, "-faulted",  0,
+            "only return imfiles with a fault status set", false);
+    psMetadataAddBool(processedexpArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -tostacked
+    psMetadata *tostackedArgs = psMetadataAlloc();
+    psMetadataAddU64(tostackedArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(tostackedArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -addstacked
+    psMetadata *addstackedArgs = psMetadataAlloc();
+    psMetadataAddStr(addstackedArgs, PS_LIST_TAIL, "-det_id",  0,
+            "define detrend ID (required)", NULL);
+    psMetadataAddS32(addstackedArgs, PS_LIST_TAIL, "-iteration",  0,
+            "define iteration number", 0);
+    psMetadataAddStr(addstackedArgs, PS_LIST_TAIL, "-class_id",  0,
+            "define class ID (required)", NULL);
+    psMetadataAddStr(addstackedArgs, PS_LIST_TAIL, "-recip",  0,
+            "define recipe (required)", NULL);
+    psMetadataAddF64(addstackedArgs, PS_LIST_TAIL, "-bg",  0,
+            "define exposue background", NAN);
+    psMetadataAddF64(addstackedArgs, PS_LIST_TAIL, "-bg_stdev",  0,
+            "define exposue background stdev", NAN);
+    psMetadataAddF64(addstackedArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,
+            "define exposue background mean stdev", NAN);
+    psMetadataAddStr(addstackedArgs, PS_LIST_TAIL, "-uri",  0,
+            "define URI (required)", NULL);
+    psMetadataAddS8(addstackedArgs, PS_LIST_TAIL, "-code",  0,
+            "set fault code (required)", 0);
+    
+    // -stacked
+    psMetadata *stackedArgs = psMetadataAlloc();
+    psMetadataAddStr(stackedArgs, PS_LIST_TAIL, "-det_id",  0,
+            "search for detrend ID", NULL);
+    psMetadataAddS32(stackedArgs, PS_LIST_TAIL, "-iteration",  0,
+            "search for iteration number", 0);
+    psMetadataAddStr(stackedArgs, PS_LIST_TAIL, "-class_id",  0,
+            "search for class ID", NULL);
+    psMetadataAddStr(stackedArgs, PS_LIST_TAIL, "-recip",  0,
+            "search for recipe", NULL);
+    psMetadataAddU64(stackedArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(stackedArgs, PS_LIST_TAIL, "-faulted",  0,
+            "only return imfiles with a fault status set", false);
+    psMetadataAddBool(stackedArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -tonormalize
+    psMetadata *tonormalizeArgs = psMetadataAlloc();
+    psMetadataAddU64(tonormalizeArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(tonormalizeArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -addnormstat
+    psMetadata *addnormstatArgs = psMetadataAlloc();
+    psMetadataAddStr(addnormstatArgs, PS_LIST_TAIL, "-det_id",  0,
+            "define detrend ID (required)", NULL);
+    psMetadataAddS32(addnormstatArgs, PS_LIST_TAIL, "-iteration",  0,
+            "define iteration number", 0);
+    psMetadataAddStr(addnormstatArgs, PS_LIST_TAIL, "-class_id",  0,
+            "define class ID", NULL);
+    psMetadataAddF32(addnormstatArgs, PS_LIST_TAIL, "-norm",  0,
+            "define normal value (required)", NAN);
+    psMetadataAddS8(addnormstatArgs, PS_LIST_TAIL, "-code",  0,
+            "set fault code (required)", 0);
+ 
+    // -tonormstat
+    psMetadata *tonormstatArgs = psMetadataAlloc();
+    psMetadataAddU64(tonormstatArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(tonormstatArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -addnormalizedimfile
+    psMetadata *addnormalizedimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(addnormalizedimfileArgs, PS_LIST_TAIL, "-det_id", 0,
+            "define detrend ID (required)", NULL);
+    psMetadataAddS32(addnormalizedimfileArgs, PS_LIST_TAIL, "-iteration", 0,
+            "define iteration number", 0);
+    psMetadataAddStr(addnormalizedimfileArgs, PS_LIST_TAIL, "-class_id", 0,
+            "define class ID (required)", NULL);
+    psMetadataAddStr(addnormalizedimfileArgs, PS_LIST_TAIL, "-uri", 0,
+            "define URI (required)", NULL);
+    psMetadataAddF64(addnormalizedimfileArgs, PS_LIST_TAIL, "-bg", 0,
+            "define exposue background", NAN);
+    psMetadataAddF64(addnormalizedimfileArgs, PS_LIST_TAIL, "-bg_stdev", 0,
+            "define exposue background stdev", NAN);
+    psMetadataAddF64(addnormalizedimfileArgs, PS_LIST_TAIL, "-bg_mean_stdev", 0,
+            "define exposue background mean stdev", NAN);
+    psMetadataAddStr(addnormalizedimfileArgs, PS_LIST_TAIL, "-path_base", 0,
+            "define banana 1", NULL);
+    psMetadataAddS8(addnormalizedimfileArgs, PS_LIST_TAIL, "-code",  0,
+            "set fault code (required)", 0);
+
+    // -tonormalizedexp
+    psMetadata *tonormalizedexpArgs = psMetadataAlloc();
+    psMetadataAddU64(tonormalizedexpArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(tonormalizedexpArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -addnormalizedexp
+    psMetadata *addnormalizedexpArgs = psMetadataAlloc();
+    psMetadataAddStr(addnormalizedexpArgs, PS_LIST_TAIL, "-det_id",  0,
+            "define detrend ID (required)", NULL);
+    psMetadataAddS32(addnormalizedexpArgs, PS_LIST_TAIL, "-iteration",  0,
+            "define iteration number", 0);
+    psMetadataAddStr(addnormalizedexpArgs, PS_LIST_TAIL, "-recip",  0,
+            "search for recipe", NULL);
+    psMetadataAddF64(addnormalizedexpArgs, PS_LIST_TAIL, "-bg",  0,
+            "define exposue background", NAN);
+    psMetadataAddF64(addnormalizedexpArgs, PS_LIST_TAIL, "-bg_stdev",  0,
+            "define exposue background stdev", NAN);
+    psMetadataAddF64(addnormalizedexpArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,
+            "define exposue background mean stdev", NAN);
+    psMetadataAddStr(addnormalizedexpArgs, PS_LIST_TAIL, "-path_base",  0,
+            "define banana 1", NULL);
+    psMetadataAddS8(addnormalizedexpArgs, PS_LIST_TAIL, "-code",  0,
+            "set fault code (required)", 0);
+
+    // -normalizedexp
+    psMetadata *normalizedexpArgs = psMetadataAlloc();
+    psMetadataAddStr(normalizedexpArgs, PS_LIST_TAIL, "-det_id",  0,
+            "define detrend ID (required)", NULL);
+    psMetadataAddS32(normalizedexpArgs, PS_LIST_TAIL, "-iteration",  0,
+            "define iteration number", 0);
+    psMetadataAddStr(normalizedexpArgs, PS_LIST_TAIL, "-recip",  0,
+            "search for recipe", NULL);
+    psMetadataAddF64(normalizedexpArgs, PS_LIST_TAIL, "-bg",  0,
+            "define exposue background", NAN);
+    psMetadataAddF64(normalizedexpArgs, PS_LIST_TAIL, "-bg_stdev",  0,
+            "define exposue background stdev", NAN);
+    psMetadataAddF64(normalizedexpArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,
+            "define exposue background mean stdev", NAN);
+    psMetadataAddStr(normalizedexpArgs, PS_LIST_TAIL, "-path_base",  0,
+            "define banana 1", NULL);
+    psMetadataAddU64(normalizedexpArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(normalizedexpArgs, PS_LIST_TAIL, "-faulted",  0,
+            "only return imfiles with a fault status set", false);
+    psMetadataAddBool(normalizedexpArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+ 
+    // -toresidimfile
+    psMetadata *toresidimfileArgs = psMetadataAlloc();
+    psMetadataAddU64(toresidimfileArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(toresidimfileArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -normalizedimfile
+    psMetadata *normalizedimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(normalizedimfileArgs, PS_LIST_TAIL, "-det_id",  0,
+            "search for detrend ID (required)", NULL);
+    psMetadataAddS32(normalizedimfileArgs, PS_LIST_TAIL, "-iteration",  0,
+            "search for iteration number", 0);
+    psMetadataAddStr(normalizedimfileArgs, PS_LIST_TAIL, "-class_id",  0,
+            "search for class ID", NULL);
+    psMetadataAddStr(normalizedimfileArgs, PS_LIST_TAIL, "-recip",  0,
+            "search for recipe", NULL);
+    psMetadataAddU64(normalizedimfileArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(normalizedimfileArgs, PS_LIST_TAIL, "-faulted",  0,
+            "only return imfiles with a fault status set", false);
+    psMetadataAddBool(normalizedimfileArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+ 
+    // -toresidexp
+    psMetadata *toresidexpArgs = psMetadataAlloc();
+    psMetadataAddU64(toresidexpArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(toresidexpArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -addresidimfile
+    psMetadata *addresidimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(addresidimfileArgs, PS_LIST_TAIL, "-det_id",  0,
+            "define detrend ID (required)", NULL);
+    psMetadataAddS32(addresidimfileArgs, PS_LIST_TAIL, "-iteration",  0,
+            "define iteration number", 0);
+    psMetadataAddStr(addresidimfileArgs, PS_LIST_TAIL, "-exp_tag",  0,
+            "define detrend ID (required)", NULL);
+    psMetadataAddStr(addresidimfileArgs, PS_LIST_TAIL, "-class_id",  0,
+            "define class ID (required)", NULL);
+    psMetadataAddStr(addresidimfileArgs, PS_LIST_TAIL, "-uri",  0,
+            "define resid file URI (required)", NULL);
+    psMetadataAddStr(addresidimfileArgs, PS_LIST_TAIL, "-recip",  0,
+            "define recipe (required)", NULL);
+    psMetadataAddF64(addresidimfileArgs, PS_LIST_TAIL, "-bg",  0,
+            "define exposue background", NAN);
+    psMetadataAddF64(addresidimfileArgs, PS_LIST_TAIL, "-bg_stdev",  0,
+            "define exposue background stdev", NAN);
+    psMetadataAddF64(addresidimfileArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,
+            "define exposue background mean stdev", NAN);
+    psMetadataAddStr(addresidimfileArgs, PS_LIST_TAIL, "-path_base",  0,
+            "define banana 1", NULL);
+    psMetadataAddS8(addresidimfileArgs, PS_LIST_TAIL, "-code",  0,
+            "set fault code (required)", 0);
+
+    // -residimfile
+    psMetadata *residimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(residimfileArgs, PS_LIST_TAIL, "-det_id",  0,
+            "search for detrend ID (required)", NULL);
+    psMetadataAddS32(residimfileArgs, PS_LIST_TAIL, "-iteration",  0,
+            "search for iteration number", 0);
+    psMetadataAddStr(residimfileArgs, PS_LIST_TAIL, "-exp_tag",  0,
+            "define detrend ID (required)", NULL);
+    psMetadataAddStr(residimfileArgs, PS_LIST_TAIL, "-class_id",  0,
+            "search for class ID", NULL);
+    psMetadataAddStr(residimfileArgs, PS_LIST_TAIL, "-recip",  0,
+            "search for recipe", NULL);
+    psMetadataAddU64(residimfileArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(residimfileArgs, PS_LIST_TAIL, "-faulted",  0,
+            "only return imfiles with a fault status set", false);
+    psMetadataAddBool(residimfileArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -addresidexp
+    psMetadata *addresidexpArgs = psMetadataAlloc();
+    psMetadataAddStr(addresidexpArgs, PS_LIST_TAIL, "-det_id",  0,
+            "define detrend ID (required)", NULL);
+    psMetadataAddS32(addresidexpArgs, PS_LIST_TAIL, "-iteration",  0,
+            "define iteration number", 0);
+    psMetadataAddStr(addresidexpArgs, PS_LIST_TAIL, "-exp_tag",  0,
+            "define detrend ID (required)", NULL);
+    psMetadataAddStr(addresidexpArgs, PS_LIST_TAIL, "-recip",  0,
+            "define recipe (required)", NULL);
+    psMetadataAddF64(addresidexpArgs, PS_LIST_TAIL, "-bg",  0,
+            "define exposue background", NAN);
+    psMetadataAddF64(addresidexpArgs, PS_LIST_TAIL, "-bg_stdev",  0,
+            "define exposue background stdev", NAN);
+    psMetadataAddF64(addresidexpArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,
+            "define exposue background mean stdev", NAN);
+    psMetadataAddStr(addresidexpArgs, PS_LIST_TAIL, "-path_base",  0,
+            "define banana 1", NULL);
+    psMetadataAddS8(addresidexpArgs, PS_LIST_TAIL, "-code",  0,
+            "set fault code (required)", 0);
+    psMetadataAddBool(addresidexpArgs, PS_LIST_TAIL, "-reject",  0,
+            "exposure is not to be stacked in the next iteration", false);
+
+    // -residexp
+    psMetadata *residexpArgs = psMetadataAlloc();
+    psMetadataAddStr(residexpArgs, PS_LIST_TAIL, "-det_id",  0,
+            "search for detrend ID (required)", NULL);
+    psMetadataAddS32(residexpArgs, PS_LIST_TAIL, "-iteration",  0,
+            "search for iteration number", 0);
+    psMetadataAddStr(residexpArgs, PS_LIST_TAIL, "-exp_tag",  0,
+            "search for exp ID", NULL);
+    psMetadataAddStr(residexpArgs, PS_LIST_TAIL, "-recip",  0,
+            "search for recipe", NULL);
+    psMetadataAddU64(residexpArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(residexpArgs, PS_LIST_TAIL, "-faulted",  0,
+            "only return imfiles with a fault status set", false);
+    psMetadataAddBool(residexpArgs, PS_LIST_TAIL, "-reject",  0,
+            "search for acceptable residuals", false);
+    psMetadataAddBool(residexpArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -residdetrun
+    psMetadata *residdetrunArgs = psMetadataAlloc();
+    psMetadataAddBool(residdetrunArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+    psMetadataAddU64(residdetrunArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+
+    // -updateresidexp
+    psMetadata *updateresidexpArgs = psMetadataAlloc();
+    psMetadataAddStr(updateresidexpArgs, PS_LIST_TAIL, "-det_id",  0,
+            "define detrend ID", NULL);
+    psMetadataAddS32(updateresidexpArgs, PS_LIST_TAIL, "-iteration",  0,
+            "define iteration number", 0);
+    psMetadataAddStr(updateresidexpArgs, PS_LIST_TAIL, "-exp_tag",  0,
+            "define exp ID", NULL);
+    psMetadataAddStr(updateresidexpArgs, PS_LIST_TAIL, "-recip",  0,
+            "define recipe", NULL);
+    psMetadataAddF64(updateresidexpArgs, PS_LIST_TAIL, "-bg",  0,
+            "define exposue background", NAN);
+    psMetadataAddF64(updateresidexpArgs, PS_LIST_TAIL, "-bg_stdev",  0,
+            "define exposue background stdev", NAN);
+    psMetadataAddF64(updateresidexpArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,
+            "define exposue background mean stdev", NAN);
+    psMetadataAddStr(updateresidexpArgs, PS_LIST_TAIL, "-path_base",  0,
+            "define banana 1", NULL);
+    psMetadataAddBool(updateresidexpArgs, PS_LIST_TAIL, "-reject",  0,
+            "exposure is not to be stacked in the next iteration", false);
+
+    // -adddetrunsummary
+    psMetadata *adddetrunsummaryArgs = psMetadataAlloc();
+    psMetadataAddStr(adddetrunsummaryArgs, PS_LIST_TAIL, "-det_id",  0,
+            "define detrend ID (required)", NULL);
+    psMetadataAddS32(adddetrunsummaryArgs, PS_LIST_TAIL, "-iteration",  0,
+            "define iteration number", 0);
+    psMetadataAddF64(adddetrunsummaryArgs, PS_LIST_TAIL, "-bg",  0,
+            "define exposue background", NAN);
+    psMetadataAddF64(adddetrunsummaryArgs, PS_LIST_TAIL, "-bg_stdev",  0,
+            "define exposue background stdev", NAN);
+    psMetadataAddF64(adddetrunsummaryArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,
+            "define exposue background mean stdev", NAN);
+    psMetadataAddS8(adddetrunsummaryArgs, PS_LIST_TAIL, "-code",  0,
+            "set fault code (required)", 0);
+    psMetadataAddBool(adddetrunsummaryArgs, PS_LIST_TAIL, "-accept",  0,
+            "declare that this detrun iteration is accepted as a master", false);
+ 
+    // -detrunsummary
+    psMetadata *detrunsummaryArgs = psMetadataAlloc();
+    psMetadataAddStr(detrunsummaryArgs, PS_LIST_TAIL, "-det_id",  0,
+            "search for detrend ID", NULL);
+    psMetadataAddS32(detrunsummaryArgs, PS_LIST_TAIL, "-iteration",  0,
+            "search for iteration number", 0);
+    psMetadataAddBool(detrunsummaryArgs, PS_LIST_TAIL, "-faulted",  0,
+            "only return imfiles with a fault status set", false);
+    psMetadataAddBool(detrunsummaryArgs, PS_LIST_TAIL, "-reject",  0,
+            "search for acceptable residuals", false);
+    psMetadataAddBool(detrunsummaryArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -updatedetrun
+    psMetadata *updatedetrunArgs = psMetadataAlloc();
+    psMetadataAddStr(updatedetrunArgs, PS_LIST_TAIL, "-det_id",  0,
+            "search for detrend master for detrend ID (required)", NULL);
+    psMetadataAddBool(updatedetrunArgs, PS_LIST_TAIL, "-again",  0,
+            "start a new iteration of this detrend run", false);
+    psMetadataAddStr(updatedetrunArgs, PS_LIST_TAIL, "-state",  0,
+            "set the state of this detrend run", false);
+
+    // -rerun
+    psMetadata *rerunArgs = psMetadataAlloc();
+    psMetadataAddStr(rerunArgs, PS_LIST_TAIL, "-det_id",  0,
+            "search for detrend master for detrend ID (required)", NULL);
+    psMetadataAddStr(rerunArgs, PS_LIST_TAIL, "-exp_tag",  PS_META_DUPLICATE_OK,
+            "include this exposure (multiple OK, required)", NULL);
+
+    // -register_detrend
+    psMetadata *register_detrendArgs = psMetadataAlloc();
+    psMetadataAddStr(register_detrendArgs, PS_LIST_TAIL, "-det_type",  0,
+            "define the type of detrend run (required)", NULL);
+    psMetadataAddStr(register_detrendArgs, PS_LIST_TAIL, "-mode",  0,
+            "define the mode of this detrend run", "master");
+    psMetadataAddStr(register_detrendArgs, PS_LIST_TAIL, "-filelvel",  0,
+            "define filelevel (required)", NULL);
+    psMetadataAddStr(register_detrendArgs, PS_LIST_TAIL, "-workdir",  0,
+            "define workdir (required)", NULL);
+    psMetadataAddStr(register_detrendArgs, PS_LIST_TAIL, "-inst",  0,
+            "define camera", NULL);
+    psMetadataAddStr(register_detrendArgs, PS_LIST_TAIL, "-telescope",  0,
+            "define telescope", NULL);
+    psMetadataAddStr(register_detrendArgs, PS_LIST_TAIL, "-exp_type",  0,
+            "define exposure type", NULL);
+    psMetadataAddStr(register_detrendArgs, PS_LIST_TAIL, "-filter",  0,
+            "define filter ", NULL);
+    psMetadataAddF32(register_detrendArgs, PS_LIST_TAIL, "-airmass_min",  0,
+            "define min airmass", NAN);
+    psMetadataAddF32(register_detrendArgs, PS_LIST_TAIL, "-airmass_max",  0,
+            "define max airmass", NAN);
+    psMetadataAddF32(register_detrendArgs, PS_LIST_TAIL, "-exp_time_min",  0,
+            "define min exposure time", NAN);
+    psMetadataAddF32(register_detrendArgs, PS_LIST_TAIL, "-exp_time_max",  0,
+            "define max exposure time", NAN);
+    psMetadataAddF64(register_detrendArgs, PS_LIST_TAIL, "-ccd_temp_min",  0,
+            "define min ccd tempature", NAN);
+    psMetadataAddF64(register_detrendArgs, PS_LIST_TAIL, "-ccd_temp_max",  0,
+            "define max ccd tempature", NAN);
+    psMetadataAddF64(register_detrendArgs, PS_LIST_TAIL, "-posang_min",  0,
+            "define min rotator position angle", NAN);
+    psMetadataAddF64(register_detrendArgs, PS_LIST_TAIL, "-posang_max",  0,
+            "define max rotator position angle", NAN);
+    psMetadataAddStr(register_detrendArgs, PS_LIST_TAIL, "-registered",  0,
+            "time detrend run was registered", now);
+    psMetadataAddStr(register_detrendArgs, PS_LIST_TAIL, "-time_begin",  0,
+            "detrend applyes to exposures taken during this peroid", NULL);
+    psMetadataAddStr(register_detrendArgs, PS_LIST_TAIL, "-time_end",  0,
+            "detrend applyes to exposures taken during this peroid", NULL);
+    psMetadataAddStr(register_detrendArgs, PS_LIST_TAIL, "-use_begin",  0,
+            "start of detrend run applicable peroid", NULL);
+    psMetadataAddStr(register_detrendArgs, PS_LIST_TAIL, "-use_end",  0,
+            "end of detrend run applicable peroid", NULL);
+    psMetadataAddF64(register_detrendArgs, PS_LIST_TAIL, "-bg",  0,
+            "define exposue background", NAN);
+    psMetadataAddF64(register_detrendArgs, PS_LIST_TAIL, "-bg_stdev",  0,
+            "define exposue background stdev", NAN);
+    psMetadataAddF64(register_detrendArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,
+            "define exposue background mean stdev", NAN);
+    psMetadataAddStr(register_detrendArgs, PS_LIST_TAIL, "-parent",  0,
+            "define parent det_id", NULL);
+    psMetadataAddBool(register_detrendArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+ 
+    // -register_detrend_imfile
+    psMetadata *register_detrend_imfileArgs = psMetadataAlloc();
+    psMetadataAddStr(register_detrend_imfileArgs, PS_LIST_TAIL, "-det_id",  0,
+            "define detrend ID (required)", NULL);
+    psMetadataAddStr(register_detrend_imfileArgs, PS_LIST_TAIL, "-class_id",  0,
+            "search for class ID (required)", NULL);
+    psMetadataAddStr(register_detrend_imfileArgs, PS_LIST_TAIL, "-uri",  0,
+            "define resid file URI (required)", NULL);
+    psMetadataAddF64(register_detrend_imfileArgs, PS_LIST_TAIL, "-bg",  0,
+            "define exposue background", NAN);
+    psMetadataAddF64(register_detrend_imfileArgs, PS_LIST_TAIL, "-bg_stdev",  0,
+            "define exposue background stdev", NAN);
+    psMetadataAddF64(register_detrend_imfileArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,
+            "define exposue background mean stdev", NAN);
+    psMetadataAddStr(register_detrend_imfileArgs, PS_LIST_TAIL, "-path_base",  0,
+            "define banana 1", NULL);
+
+    psFree(now);
+
+    
+#define PXTOOL_MODE(option, modeval, argset) \
+{ \
+    int N = 0; \
+    if ((N = psArgumentGet (argc, argv, option))) { \
+        psArgumentRemove (N, &argc, argv); \
+        if (config->mode) { \
+            psError(PS_ERR_UNKNOWN, true, "only one mode selection is allowed"); \
+            psFree(argset); \
+            psFree(argSets); \
+            psFree(config); \
+            return NULL; \
+        } \
+        config->mode = modeval; \
+        config->args = psMemIncrRefCounter(argset); \
+    } \
+    if (!psMetadataAddMetadata(argSets, PS_LIST_TAIL, option, 0, NULL, argset)) {;\
+        psError(PS_ERR_UNKNOWN, false, "failed to add argset for %s", option); \
+    } \
+    psFree(argset); \
+}
+
+    psMetadata *argSets = psMetadataAlloc();
+    // find which mode we're running under
+    PXTOOL_MODE("-pending",         DETTOOL_MODE_PENDING,       pendingArgs);
+    PXTOOL_MODE("-definebytag",     DETTOOL_MODE_DEFINEBYTAG,   definebytagArgs);
+    PXTOOL_MODE("-definebyquery",   DETTOOL_MODE_DEFINEBYQUERY, definebyqueryArgs);
+    PXTOOL_MODE("-definebydetrun",  DETTOOL_MODE_DEFINEBYDETRUN, definebydetrunArgs);
+    PXTOOL_MODE("-raw",             DETTOOL_MODE_RAW,           rawArgs);
+    PXTOOL_MODE("-runs",            DETTOOL_MODE_RUNS,          runsArgs);
+    PXTOOL_MODE("-childlessrun",    DETTOOL_MODE_CHILDLESSRUN,  childlessrunArgs);
+    PXTOOL_MODE("-input",           DETTOOL_MODE_INPUT,         inputArgs);
+    PXTOOL_MODE("-toprocessedimfile", DETTOOL_MODE_TOPROCESSEDIMFILE, toprocessedimfileArgs);
+    PXTOOL_MODE("-addprocessedimfile", DETTOOL_MODE_ADDPROCESSEDIMFILE,  addprocessedimfileArgs);
+    PXTOOL_MODE("-processedimfile", DETTOOL_MODE_PROCESSEDIMFILE, processedimfileArgs);
+    PXTOOL_MODE("-toprocessedexp",  DETTOOL_MODE_TOPROCESSEDEXP,  toprocessedexpArgs);
+    PXTOOL_MODE("-addprocessedexp", DETTOOL_MODE_ADDPROCESSEDEXP, addprocessedexpArgs);
+    PXTOOL_MODE("-processedexp",    DETTOOL_MODE_PROCESSEDEXP, processedexpArgs);
+    PXTOOL_MODE("-tostacked",       DETTOOL_MODE_TOSTACKED,     tostackedArgs);
+    PXTOOL_MODE("-addstacked",      DETTOOL_MODE_ADDSTACKED,    addstackedArgs);
+    PXTOOL_MODE("-stacked",         DETTOOL_MODE_STACKED,       stackedArgs);
+    PXTOOL_MODE("-tonormalize",     DETTOOL_MODE_TONORMALIZE,   tonormalizeArgs);
+    PXTOOL_MODE("-addnormalizedstat",     DETTOOL_MODE_ADDNORMALIZEDSTAT,   addnormstatArgs);
+    PXTOOL_MODE("-tonormalizedstat",      DETTOOL_MODE_TONORMALIZEDSTAT,    tonormstatArgs);
+    PXTOOL_MODE("-addnormalizedimfile", DETTOOL_MODE_ADDNORMALIZEDIMFILE,addnormalizedimfileArgs);
+    PXTOOL_MODE("-normalizedimfile",DETTOOL_MODE_NORMALIZEDIMFILE, normalizedimfileArgs);
+    PXTOOL_MODE("-tonormalizedexp", DETTOOL_MODE_TONORMALIZEDEXP, tonormalizedexpArgs);
+    PXTOOL_MODE("-addnormalizedexp", DETTOOL_MODE_ADDNORMALIZEDEXP, addnormalizedexpArgs);
+    PXTOOL_MODE("-normalizedexp",   DETTOOL_MODE_NORMALIZEDEXP, normalizedexpArgs);
+    PXTOOL_MODE("-toresidimfile",   DETTOOL_MODE_TORESIDIMFILE, toresidimfileArgs);
+    PXTOOL_MODE("-addresidimfile",  DETTOOL_MODE_ADDRESIDIMFILE,  addresidimfileArgs);
+    PXTOOL_MODE("-residimfile",     DETTOOL_MODE_RESIDIMFILE,    residimfileArgs);
+    PXTOOL_MODE("-toresidexp",      DETTOOL_MODE_TORESIDEXP,    toresidexpArgs);
+    PXTOOL_MODE("-addresidexp",     DETTOOL_MODE_ADDRESIDEXP,  addresidexpArgs);
+    PXTOOL_MODE("-residexp",        DETTOOL_MODE_RESIDEXP,     residexpArgs);
+    PXTOOL_MODE("-residdetrun",     DETTOOL_MODE_RESIDDETRUN,  residdetrunArgs);
+    PXTOOL_MODE("-updateresidexp", DETTOOL_MODE_UPDATERESIDEXP,updateresidexpArgs);
+    PXTOOL_MODE("-adddetrunsummary", DETTOOL_MODE_ADDDETRUNSUMMARY,adddetrunsummaryArgs);
+    PXTOOL_MODE("-detrunsummary", DETTOOL_MODE_DETRUNSUMMARY,detrunsummaryArgs);
+    PXTOOL_MODE("-updatedetrun", DETTOOL_MODE_UPDATEDETRUN, updatedetrunArgs);
+    PXTOOL_MODE("-rerun",           DETTOOL_MODE_RERUN,         rerunArgs);
+    PXTOOL_MODE("-register_detrend", DETTOOL_MODE_REGISTER_DETREND, register_detrendArgs);
+    PXTOOL_MODE("-register_detrend_imfile", DETTOOL_MODE_REGISTER_DETREND_IMFILE, register_detrend_imfileArgs);
+
+    bool argErr = false;
+    if (config->mode == DETTOOL_MODE_NONE) {
+        argErr = true;
+        fprintf (stderr, "mode argument is required\n");
+    } else { 
+	if (! psArgumentParse(config->args, &argc, argv)) {
+	    argErr = true;
+	    fprintf (stderr, "error parsing arguments\n");
+	} 
+	if (argc != 1) {
+	    fprintf (stderr, "extra arguments: ");
+	    for (int i = 1; i < argc; i++) {
+		fprintf (stderr, "%s ", argv[i]);
+	    }		
+	    fprintf (stderr, "\n");
+	    argErr = true;
+	}
+    }
+
+    if (argErr) {
+        printf("\nPan-STARRS Detrend Tool\n");
+        printf("Usage: %s <mode> [<options>]\n\n", argv[0]);
+        printf(" <mode> :\n\n"); 
+
+        psMetadataIterator *iter = psMetadataIteratorAlloc(argSets, 0, NULL);
+        psMetadataItem *item = NULL;
+        while ((item = psMetadataGetAndIncrement(iter))) {
+            if (!item->type == PS_DATA_METADATA) {
+                psAbort("all options must be specified as a metadata");
+            }
+
+            fprintf(stdout, "%s ", item->name);
+            psArgumentHelp(item->data.md);
+        }
+        psFree(iter);
+        psFree(argSets);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+
+    // setup search criterion
+#define addWhereStr(name) \
+{ \
+    psString str = NULL; \
+    bool status = false; \
+    if ((str = psMetadataLookupStr(&status, config->args, "-" #name))) { \
+        if (!psMetadataAddStr(config->where, PS_LIST_TAIL, #name, 0, "==", str)) {\
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+            psFree(config); \
+            return NULL; \
+        } \
+    } \
+}
+
+    // generate SQL where clause
+    config->where = psMetadataAlloc();
+
+    addWhereStr(det_id);
+    {
+        int n = 0;
+        bool status = false;
+        if ((n = psMetadataLookupS32(&status, config->args, "-iteration"))) {
+            if (!psMetadataAddS32(config->where, PS_LIST_TAIL, "iteration", 0, "==", n)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+                psFree(config);
+                return NULL;
+            }
+        }
+    }
+    addWhereStr(det_type);
+    addWhereStr(exp_tag);
+    addWhereStr(class_id);
+    // convert '-inst' to 'camera'
+    {
+        psString str = NULL;
+        bool status = false;
+        if ((str = psMetadataLookupStr(&status, config->args, "-inst"))) {
+            if (!psMetadataAddStr(config->where, PS_LIST_TAIL, "camera", 0, "==", str)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+                psFree(config);
+                return NULL;
+            }
+        }
+    }
+    addWhereStr(telescope);
+    addWhereStr(exp_type);
+    {
+        int n = 0;
+        bool status = false;
+        if ((n = psMetadataLookupS32(&status, config->args, "-imfiles"))) {
+            if (!psMetadataAddS32(config->where, PS_LIST_TAIL, "imfiles", 0, "==", n)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+                psFree(config);
+                return NULL;
+            }
+        }
+    }
+    addWhereStr(filter);
+    addWhereStr(recipe);
+    {
+        int n = 0;
+        bool status = false;
+        if ((n = psMetadataLookupS32(&status, config->args, "-guide_version"))) {
+            if (!psMetadataAddS32(config->where, PS_LIST_TAIL, "guide_version", 0, "==", n)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item guide_version");
+                psFree(config);
+                return NULL;
+            }
+        }
+    }
+    {
+        bool boolean = false;
+        bool status = false;
+
+        // map reject -> !accept
+        if ((boolean = psMetadataLookupBool(&status, config->args, "-reject"))) {
+            if (!psMetadataAddBool(config->where, PS_LIST_TAIL, "accept", 0, "==", !boolean)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item reject");
+                psFree(config);
+                return NULL;
+            }
+        }
+    }
+
+    if (config->where->list->n < 1) {
+        psFree(config->where);
+        config->where = NULL;
+    }
+
+    // define Database handle, if used
+    // do this last so we don't setup a connection before CLI options are
+    // validated
+    config->dbh = pmConfigDB(config->modules);
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    // save argv/argc
+    config->argv = argv;
+    config->argc = argc;
+
+    return config;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/difftool.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/difftool.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/difftool.c	(revision 22073)
@@ -0,0 +1,661 @@
+/*
+ * difftool.c
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVB_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include <ippdb.h>
+
+#include "pxtools.h"
+#include "difftool.h"
+
+static bool definerunMode(pxConfig *config);
+static bool updaterunMode(pxConfig *config);
+static bool addinputskyfileMode(pxConfig *config);
+static bool inputskyfileMode(pxConfig *config);
+static bool todiffskyfileMode(pxConfig *config);
+static bool adddiffskyfileMode(pxConfig *config);
+static bool diffskyfileMode(pxConfig *config);
+
+static bool setdiffRunState(pxConfig *config, const char *warp_id, const char *state);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = difftoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(DIFFTOOL_MODE_DEFINERUN,         definerunMode);
+        MODECASE(DIFFTOOL_MODE_UPDATERUN,         updaterunMode);
+        MODECASE(DIFFTOOL_MODE_ADDINPUTSKYFILE,    addinputskyfileMode);
+        MODECASE(DIFFTOOL_MODE_INPUTSKYFILE,       inputskyfileMode);
+        MODECASE(DIFFTOOL_MODE_TODIFFSKYFILE,      todiffskyfileMode);
+        MODECASE(DIFFTOOL_MODE_ADDDIFFSKYFILE,     adddiffskyfileMode);
+        MODECASE(DIFFTOOL_MODE_DIFFSKYFILE,        diffskyfileMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+
+static bool definerunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required options
+    bool status = false;
+    psString workdir = psMetadataLookupStr(&status, config->args, "-workdir");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -workdir");
+        return false;
+    }
+    if (!workdir) {
+        psError(PS_ERR_UNKNOWN, true, "-workdir is required");
+        return false;
+    }
+
+    psString skycell_id = psMetadataLookupStr(&status, config->args, "-skycell_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -skycell_id");
+        return false;
+    }
+    if (!skycell_id) {
+        psError(PS_ERR_UNKNOWN, true, "-skycell_id is required");
+        return false;
+    }
+
+    psString tess_id = psMetadataLookupStr(&status, config->args, "-tess_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -tess_id");
+        return false;
+    }
+    if (!tess_id) {
+        psError(PS_ERR_UNKNOWN, true, "-tess_id is required");
+        return false;
+    }
+
+    psTime *registered = NULL;
+    {
+        psString registeredStr = psMetadataLookupStr(&status, config->args, "-registered");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -registered");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (registeredStr) {
+            registered = psTimeFromISO(registeredStr, PS_TIME_UTC);
+        } else {
+            registered = NULL;
+        }
+    }
+
+    diffRunRow *run = diffRunRowAlloc(
+            0,          // ID
+            "reg",      // state
+            workdir,
+            NULL,       // dvodb
+            registered,
+            skycell_id,
+            tess_id
+    );
+    psFree(registered);
+    if (!run) {
+        psError(PS_ERR_UNKNOWN, false, "failed to alloc diffRun object");
+        return true;
+    }
+    if (!diffRunInsertObject(config->dbh, run)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(run);
+        return true;
+    }
+
+    // get the assigned diff_id
+    run->diff_id = psDBLastInsertID(config->dbh);
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            psFree(run);
+            return false;
+        }
+    }
+
+    if (!diffRunPrintObject(stdout, run, !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print object");
+            psFree(run);
+            return false;
+    }
+
+    psFree(run);
+
+    return true;
+}
+
+
+static bool updaterunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psString diff_id = psMetadataLookupStr(&status, config->args, "-diff_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -diff_id");
+        return false;
+    }
+    if (!diff_id) {
+        psError(PS_ERR_UNKNOWN, true, "-diff_id is required");
+        return false;
+    }
+
+    psString state = psMetadataLookupStr(&status, config->args, "-state");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -state");
+        return false;
+    }
+    if (!state) {
+        psError(PS_ERR_UNKNOWN, true, "-state is required");
+        return false;
+    }
+
+    if (state) {
+        // set detRun.state to state
+        return setdiffRunState(config, diff_id, state);
+    }
+
+    return true;
+}
+
+
+static bool addinputskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psString diff_id = psMetadataLookupStr(&status, config->args, "-diff_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -diff_id");
+        return false;
+    }
+    if (!diff_id) {
+        psError(PS_ERR_UNKNOWN, true, "-diff_id is required");
+        return false;
+    }
+
+    psString warp_id = psMetadataLookupStr(&status, config->args, "-warp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -warp_id");
+        return false;
+    }
+    if (!diff_id) {
+        psError(PS_ERR_UNKNOWN, true, "-diff_id is required");
+        return false;
+    }
+
+    psString skycell_id = psMetadataLookupStr(&status, config->args, "-skycell_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -skycell_id");
+        return false;
+    }
+    if (!skycell_id) {
+        psError(PS_ERR_UNKNOWN, true, "-skycell_id is required");
+        return false;
+    }
+
+    psString tess_id = psMetadataLookupStr(&status, config->args, "-tess_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -tess_id");
+        return false;
+    }
+    if (!tess_id) {
+        psError(PS_ERR_UNKNOWN, true, "-tess_id is required");
+        return false;
+    }
+
+    psString kind = psMetadataLookupStr(&status, config->args, "-kind");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -kind");
+        return false;
+    }
+
+    // defaults to false
+    bool template = psMetadataLookupBool(&status, config->args, "-template");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -template");
+        return false;
+    }
+
+    // XXX need to validate the warp_id here
+    // XXX instead of validiting it here we should just use forgein key
+    // constrants
+    if (!diffInputSkyfileInsert(config->dbh,
+            (psS64)atoll(diff_id),
+            (psS64)atoll(warp_id),
+            skycell_id,
+            tess_id,
+            kind,
+            template
+        )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool inputskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    // find all rawImfiles matching the default query
+    psString query = pxDataGet("difftool_inputskyfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "diffInputSkyfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("difftool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (psArrayLength(output)) {
+        if (!convertIdToStr(output)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
+            psFree(output);
+            return false;
+        }
+
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "diffInputSkyfile", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool todiffskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    psString query = pxDataGet("difftool_todiffskyfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "diffSkyfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("difftool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (psArrayLength(output)) {
+        if (!convertIdToStr(output)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
+            psFree(output);
+            return false;
+        }
+
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "diffSkyfile", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool adddiffskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psString diff_id = psMetadataLookupStr(&status, config->args, "-diff_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -diff_id");
+        return false;
+    }
+    if (!diff_id) {
+        psError(PS_ERR_UNKNOWN, true, "-diff_id is required");
+        return false;
+    }
+
+    psString uri = psMetadataLookupStr(&status, config->args, "-uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -uri");
+        return false;
+    }
+    if (!uri) {
+        psError(PS_ERR_UNKNOWN, true, "-uri is required");
+        return false;
+    }
+
+    // optional
+    psF64 bg = psMetadataLookupF64(&status, config->args, "-bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg");
+        return false;
+    }
+
+    psF64 bg_stdev = psMetadataLookupF64(&status, config->args, "-bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_stdev");
+        return false;
+    }
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!diffSkyfileInsert(config->dbh,
+            (psS64)atoll(diff_id),
+            uri,
+            bg,
+            bg_stdev
+        )) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!setdiffRunState(config, diff_id, "stop")) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // point of no return
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool diffskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    psString query = pxDataGet("difftool_skyfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "diffSkyfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("difftool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (psArrayLength(output)) {
+        if (!convertIdToStr(output)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
+            psFree(output);
+            return false;
+        }
+
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "diffSkyfile", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool setdiffRunState(pxConfig *config, const char *diff_id, const char *state)
+{
+    PS_ASSERT_PTR_NON_NULL(diff_id, false);
+    PS_ASSERT_PTR_NON_NULL(state, false);
+
+    // check that state is a valid string value
+    if (!(
+            (strncmp(state, "run", 4) == 0)
+            || (strncmp(state, "stop", 5) == 0)
+            || (strncmp(state, "reg", 4) == 0)
+        )
+    ) {
+        psError(PS_ERR_UNKNOWN, false,
+                "invalid diffRun state: %s", state);
+        return false;
+    }
+
+    char *query = "UPDATE diffRun SET state = '%s' WHERE diff_id = '%s'";
+    if (!p_psDBRunQuery(config->dbh, query, state, diff_id)) {
+        psError(PS_ERR_UNKNOWN, false,
+                "failed to change state for diff_id %s", diff_id);
+        return false;
+    }
+
+    return true;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/difftool.h
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/difftool.h	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/difftool.h	(revision 22073)
@@ -0,0 +1,38 @@
+/*
+ * difftool.h
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef DIFFTOOL_H
+#define DIFFTOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    DIFFTOOL_MODE_NONE           = 0x0,
+    DIFFTOOL_MODE_DEFINERUN,
+    DIFFTOOL_MODE_UPDATERUN,
+    DIFFTOOL_MODE_ADDINPUTSKYFILE,
+    DIFFTOOL_MODE_INPUTSKYFILE,
+    DIFFTOOL_MODE_TODIFFSKYFILE,
+    DIFFTOOL_MODE_ADDDIFFSKYFILE,
+    DIFFTOOL_MODE_DIFFSKYFILE,
+} difftoolMode;
+
+pxConfig *difftoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // DIFFTOOL_H
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/difftoolConfig.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/difftoolConfig.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/difftoolConfig.c	(revision 22073)
@@ -0,0 +1,282 @@
+/*
+ * difftoolConfig.c
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "difftool.h"
+
+pxConfig *difftoolConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    psTime *time = psTimeGetNow(PS_TIME_TAI);
+    psString now = psTimeToISO(time);
+    psFree(time);
+
+    // -definerun
+    psMetadata *definerunArgs = psMetadataAlloc();
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-workdir", 0,
+            "define workdir (required)", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-registered",  0,
+            "time detrend run was registered", now);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-skycell_id",  0,
+            "define skycell ID (required)", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-tess_id",  0,
+            "define tessellation ID (required)", NULL);
+    psMetadataAddBool(definerunArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+ 
+    // -updaterun
+    psMetadata *updaterunArgs = psMetadataAlloc();
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-diff_id", 0,
+            "define diff ID (required)", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-state", 0,
+            "set state (required)", NULL);
+#if 0
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-workdir", 0,
+            "define workdir (required)", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-registered",  0,
+            "time detrend run was registered", now);
+#endif
+
+    // -addinputskyfile
+    psMetadata *addinputskyfileArgs = psMetadataAlloc();
+    psMetadataAddStr(addinputskyfileArgs, PS_LIST_TAIL, "-diff_id", 0,
+            "define diff ID (required)", NULL);
+    psMetadataAddStr(addinputskyfileArgs, PS_LIST_TAIL, "-warp_id", 0,
+            "define warp ID (required)", NULL);
+    psMetadataAddStr(addinputskyfileArgs, PS_LIST_TAIL, "-skycell_id", 0,
+            "define by skycell ID", NULL);
+    psMetadataAddStr(addinputskyfileArgs, PS_LIST_TAIL, "-tess_id", 0,
+            "dfine tess ID", NULL);
+    psMetadataAddStr(addinputskyfileArgs, PS_LIST_TAIL, "-kind", 0,
+            "define kind", NULL);
+    psMetadataAddBool(addinputskyfileArgs, PS_LIST_TAIL, "-template",  0,
+            "this sky cell file is the subtrahend", false);
+
+    // -inputskyfile
+    psMetadata *inputskyfileArgs = psMetadataAlloc();
+    psMetadataAddStr(inputskyfileArgs, PS_LIST_TAIL, "-diff_id", 0,
+            "search by diff ID (required)", NULL);
+    psMetadataAddStr(inputskyfileArgs, PS_LIST_TAIL, "-warp_id", 0,
+            "search by warp ID (required)", NULL);
+    psMetadataAddStr(inputskyfileArgs, PS_LIST_TAIL, "-skycell_id", 0,
+            "search by skycell ID", NULL);
+    psMetadataAddStr(inputskyfileArgs, PS_LIST_TAIL, "-tess_id", 0,
+            "search by tess ID", NULL);
+    psMetadataAddStr(inputskyfileArgs, PS_LIST_TAIL, "-kind", 0,
+            "search by kind", NULL);
+    psMetadataAddBool(inputskyfileArgs, PS_LIST_TAIL, "-template",  0,
+            "search by subtrahend", false);
+    psMetadataAddU64(inputskyfileArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(inputskyfileArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -todiffskyfile
+    psMetadata *todiffskyfileArgs = psMetadataAlloc();
+    psMetadataAddStr(todiffskyfileArgs, PS_LIST_TAIL, "-diff_id", 0,
+            "search by diff ID", NULL);
+    psMetadataAddU64(todiffskyfileArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(todiffskyfileArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -adddiffskyfile
+    psMetadata *adddiffskyfileArgs = psMetadataAlloc();
+    psMetadataAddStr(adddiffskyfileArgs, PS_LIST_TAIL, "-diff_id", 0,
+            "define warp ID (required)", NULL);
+    psMetadataAddStr(adddiffskyfileArgs, PS_LIST_TAIL, "-uri", 0,
+            "define URI of file (required)", 0);
+    psMetadataAddF64(adddiffskyfileArgs, PS_LIST_TAIL, "-bg",  0,
+            "define exposue background", NAN);
+    psMetadataAddF64(adddiffskyfileArgs, PS_LIST_TAIL, "-bg_stdev",  0,
+            "define exposue background mean stdev", NAN);
+ 
+    // -diffskyfile
+    psMetadata *diffskyfileArgs = psMetadataAlloc();
+    psMetadataAddStr(diffskyfileArgs, PS_LIST_TAIL, "-diff_id", 0,
+            "search by warp ID", NULL);
+    psMetadataAddStr(diffskyfileArgs , PS_LIST_TAIL, "-skycell_id",  0,
+            "define skycell ID (required)", NULL);
+    psMetadataAddStr(diffskyfileArgs, PS_LIST_TAIL, "-tess_id",  0,
+            "define tessellation ID (required)", NULL);
+    psMetadataAddU64(diffskyfileArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(diffskyfileArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    psFree(now);
+
+#define PXTOOL_MODE(option, modeval, argset) \
+{ \
+    int N = 0; \
+    if ((N = psArgumentGet (argc, argv, option))) { \
+        psArgumentRemove (N, &argc, argv); \
+        if (config->mode) { \
+            psError(PS_ERR_UNKNOWN, true, "only one mode selection is allowed"); \
+            psFree(config); \
+            return NULL; \
+        } \
+        config->mode = modeval; \
+        config->args = psMemIncrRefCounter(argset); \
+    } \
+    if (!psMetadataAddMetadata(argSets, PS_LIST_TAIL, option, 0, NULL, argset)) {;\
+        psError(PS_ERR_UNKNOWN, false, "failed to add argset for %s", option); \
+    } \
+    psFree(argset); \
+}
+
+    psMetadata *argSets = psMetadataAlloc();
+    // find which mode we're running under
+    PXTOOL_MODE("-definerun",       DIFFTOOL_MODE_DEFINERUN,      definerunArgs);
+    PXTOOL_MODE("-updaterun",       DIFFTOOL_MODE_UPDATERUN,      updaterunArgs);
+    PXTOOL_MODE("-addinputskyfile",  DIFFTOOL_MODE_ADDINPUTSKYFILE, addinputskyfileArgs);
+    PXTOOL_MODE("-inputskyfile",     DIFFTOOL_MODE_INPUTSKYFILE,    inputskyfileArgs);
+    PXTOOL_MODE("-todiffskyfile",    DIFFTOOL_MODE_TODIFFSKYFILE,   todiffskyfileArgs);
+    PXTOOL_MODE("-adddiffskyfile",   DIFFTOOL_MODE_ADDDIFFSKYFILE,  adddiffskyfileArgs);
+    PXTOOL_MODE("-diffskyfile",      DIFFTOOL_MODE_DIFFSKYFILE,     diffskyfileArgs);
+
+    bool argErr = false;
+    if (config->mode == DIFFTOOL_MODE_NONE) {
+        argErr = true;
+        fprintf (stderr, "mode argument is required\n");
+    } else if (! psArgumentParse(config->args, &argc, argv) || argc != 1) {
+        argErr = true;
+        fprintf (stderr, "error parsing arguments\n");
+    }
+
+    if (argErr) {
+        printf("\nPan-STARRS Difference Tool\n");
+        printf("Usage: %s <mode> [<options>]\n\n", argv[0]);
+        printf(" <mode> :\n\n"); 
+
+        psMetadataIterator *iter = psMetadataIteratorAlloc(argSets, 0, NULL);
+        psMetadataItem *item = NULL;
+        while ((item = psMetadataGetAndIncrement(iter))) {
+            if (!item->type == PS_DATA_METADATA) {
+                psAbort("all options must be specified as a metadata");
+            }
+
+            fprintf(stdout, "%s ", item->name);
+            psArgumentHelp(item->data.md);
+        }
+        psFree(iter);
+
+        psFree(argSets);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+
+    // setup search criterion
+#define addWhereStr(name) \
+{ \
+    psString str = NULL; \
+    bool status = false; \
+    if ((str = psMetadataLookupStr(&status, config->args, "-" #name))) { \
+        if (!psMetadataAddStr(config->where, PS_LIST_TAIL, #name, 0, "==", str)) {\
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+            psFree(config); \
+            return NULL; \
+        } \
+    } \
+}
+
+#define addWhereS32(name) \
+{ \
+    psS32 s32 = 0; \
+    bool status = false; \
+    if ((s32= psMetadataLookupS32(&status, config->args, "-" #name))) { \
+        if (!psMetadataAddS32(config->where, PS_LIST_TAIL, #name, 0, "==", s32)) { \
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+            psFree(config); \
+            return NULL; \
+        } \
+    } \
+}
+
+
+    // generate SQL where clause
+    config->where = psMetadataAlloc();
+
+{
+    psString str = NULL;
+    bool status = false;
+    if ((str = psMetadataLookupStr(&status, config->args, "-diff_id"))) {
+        if (!psMetadataAddS64(config->where, PS_LIST_TAIL, "diff_id", 0, "==", (psS64)atoll(str))) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item diff_id");
+            psFree(config);
+            return NULL;
+        } 
+    } 
+}
+{
+    psString str = NULL;
+    bool status = false;
+    if ((str = psMetadataLookupStr(&status, config->args, "-warp_id"))) {
+        if (!psMetadataAddS64(config->where, PS_LIST_TAIL, "warp_id", 0, "==", (psS64)atoll(str))) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
+            psFree(config);
+            return NULL;
+        } 
+    } 
+}
+    addWhereStr(skycell_id);
+    addWhereStr(tess_id);
+
+    if (config->where->list->n < 1) {
+        psFree(config->where);
+        config->where = NULL;
+    }
+
+    // define Database handle, if used
+    // do this last so we don't setup a connection before CLI options are
+    // validated
+    config->dbh = pmConfigDB(config->modules);
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    // save argv/argc
+    config->argv = argv;
+    config->argc = argc;
+
+    return config;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/guidetool.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/guidetool.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/guidetool.c	(revision 22073)
@@ -0,0 +1,201 @@
+/*
+ * guidetool.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include "pxtools.h"
+#include "guidetool.h"
+
+static bool pendingMode(pxConfig *config);
+static bool defineMode(pxConfig *config);
+static guidePendingExpRow *rawScienceToguidePendingExp(pxConfig *config, rawScienceExpRow *exp);
+
+int main(int argc, char **argv)
+{
+    pxConfig *config = guidetoolConfig(NULL, argc, argv);
+
+    switch (config->mode) {
+        case PX_MODE_PENDING:
+            if (!pendingMode(config)) {
+                goto FAIL;
+            }
+            break;
+        case PX_MODE_DEFINE:
+            if (!defineMode(config)) {
+                goto FAIL;
+            }
+            break;
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psFree(config);
+    exit(EXIT_FAILURE);
+}
+
+static bool pendingMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // return all guidePendingExp rows unless there CLI search options
+    psArray *guidePendingExp = guidePendingExpSelectRowObjects(
+        config->dbh,
+        config->where ? config->where : NULL,
+        0
+    );
+    if (!guidePendingExp) {
+        psError(PS_ERR_UNKNOWN, false, "no guidePendingExp found");
+        return false;
+    }
+
+    psMetadata *output = psMetadataAlloc();
+
+    for (long i = 0; i < psArrayLength(guidePendingExp); i++) {
+        psMetadata *md = guidePendingExpMetadataFromObject(guidePendingExp->data[i]);
+        psMetadataAddMetadata(output, PS_LIST_TAIL, "guidePendingExp",
+            PS_META_DUPLICATE_OK, NULL, md);
+        psFree(md);
+    }
+
+    psString str = psMetadataConfigFormat(output);
+    psFree(output);
+
+    fprintf(stdout, "%s\n", str);
+    psFree(str);
+
+    return true;
+}
+
+static bool defineMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    
+    // return all rawScienceExp rows unless there CLI search options
+    psArray *rawScienceExps = rawScienceExpSelectRowObjects(
+        config->dbh,
+        config->where ? config->where : NULL,
+        0
+    );
+
+    if (!rawScienceExps) {
+        psError(PS_ERR_UNKNOWN, false, "no rawScienceExp rows found");
+        return true;
+    }
+
+    // insert the rawScienceExps into guidePendingExp
+    // XXX for the being we're going to skip checking for duplicate entries
+    // XXX this will have to implimented unless duplicate key insertion 
+    // XXX support is implimented in psDB
+    for (long i = 0; i < psArrayLength(rawScienceExps); i++) {
+        guidePendingExpRow * new = rawScienceToguidePendingExp(
+                                            config, rawScienceExps->data[i]);
+        if (!guidePendingExpInsertObject(config->dbh, new)) {
+            psError(PS_ERR_UNKNOWN, false, "dbh access failed");
+            return false; 
+        }
+        psFree(new);
+    }
+
+    return true;
+}
+
+static guidePendingExpRow *rawScienceToguidePendingExp(pxConfig *config, rawScienceExpRow *exp)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(exp, NULL);
+
+    bool status = false;
+    psString filter = psMetadataLookupStr(&status, config->args, "-filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -filter");
+        return false;
+    }
+    if (!filter) {
+        psError(PS_ERR_UNKNOWN, true, "-filter is required");
+        return false;
+    }
+    psF32 airmass = psMetadataLookupF32(&status, config->args, "-airmass");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -airmass");
+        return false;
+    }
+    if (!airmass) {
+        psError(PS_ERR_UNKNOWN, true, "-airmass is required");
+        return false;
+    }
+    psF64 ra = psMetadataLookupF64(&status, config->args, "-ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -ra");
+        return false;
+    }
+    if (!ra) {
+        psError(PS_ERR_UNKNOWN, true, "-airmass is required");
+        return false;
+    }
+    psF64 dec = psMetadataLookupF64(&status, config->args, "-dec");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -dec");
+        return false;
+    }
+    if (!dec) {
+        psError(PS_ERR_UNKNOWN, true, "-dec is required");
+        return false;
+    }
+    psF32 exp_time = psMetadataLookupF32(&status, config->args, "-exp_time");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_time");
+        return false;
+    }
+    if (!exp_time) {
+        psError(PS_ERR_UNKNOWN, true, "-exp_time is required");
+        return false;
+    }
+    psF64 background = psMetadataLookupF64(&status, config->args, "-background");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -background");
+        return false;
+    }
+    if (!exp_time) {
+        psError(PS_ERR_UNKNOWN, true, "-background is required");
+        return false;
+    }
+
+    return guidePendingExpRowAlloc(
+        exp->exp_tag,
+        exp->camera,
+        exp->telescope,
+        exp->exp_type,
+        exp->imfiles,
+        filter,
+        airmass,
+        ra,
+        dec,
+        exp_time,
+        background,
+        "my recipe",
+        0xff // XXX calc version number
+    );
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/guidetool.h
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/guidetool.h	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/guidetool.h	(revision 22073)
@@ -0,0 +1,27 @@
+/*
+ * guidetool.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef GUIDETOOL_H
+#define GUIDETOOL_H 1
+
+#include "pxtools.h"
+
+pxConfig *guidetoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // GUIDETOOL_H
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/guidetoolConfig.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/guidetoolConfig.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/guidetoolConfig.c	(revision 22073)
@@ -0,0 +1,205 @@
+/*
+ * guidetoolConfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+
+// this function can not fail -- exits on error
+
+pxConfig *guidetoolConfig(pxConfig *config, int argc, char **argv) {
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv);
+    if (!config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        exit(EXIT_FAILURE);
+    }
+
+    // CLI options
+    // modes
+    psMetadata *args = psMetadataAlloc();
+    psMetadataAddStr(args , PS_LIST_TAIL, "-pending", 0,
+        "examine pending image table, write ppImage output", NULL);
+    psMetadataAddStr(args , PS_LIST_TAIL, "-define",  0,
+        "update pending image table", NULL);
+
+    // -pending search
+    psMetadata *pendingArgs = psMetadataAlloc();
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-exp_tag",  0,
+        "define exposure ID", NULL);
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-inst",  0,
+        "define camera of interest", NULL);
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-telescope",  0,
+        "define telescope of interest", NULL);
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-exp_type",  0,
+        "define exposure type", NULL);
+    psMetadataAddS32(pendingArgs, PS_LIST_TAIL, "-imfiles",  0,
+        "define number of imfiles", 0);
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-filter",  0,
+        "define filter of interest", NULL);
+    psMetadataAddStr(pendingArgs, PS_LIST_TAIL, "-recipe",  0,
+        "define recipe of interest", NULL);
+    psMetadataAddS32(pendingArgs, PS_LIST_TAIL, "-guide_version",  0,
+        "define guide_version of interest", 0);
+
+    // -define inputs
+    psMetadata *defineArgs = psMetadataAlloc();
+    psMetadataAddStr(defineArgs, PS_LIST_TAIL, "-exp_tag",  0,
+        "define class", NULL);
+    psMetadataAddStr(defineArgs, PS_LIST_TAIL, "-inst",  0,
+        "define camera of interest", NULL);
+    psMetadataAddStr(defineArgs, PS_LIST_TAIL, "-telescope",  0,
+        "define camera of interest", NULL);
+    psMetadataAddStr(defineArgs, PS_LIST_TAIL, "-exp_type",  0,
+        "define class", NULL);
+    psMetadataAddS32(defineArgs, PS_LIST_TAIL, "-imfiles",  0,
+        "define number of imfiles", 0);
+    psMetadataAddStr(defineArgs, PS_LIST_TAIL, "-filter",  0,
+        "define filter of interest", NULL);
+    // these two are inputs NOT search parameters
+    psMetadataAddStr(defineArgs, PS_LIST_TAIL, "-recipe",  0,
+        "define recipe of interest", NULL);
+    psMetadataAddS32(defineArgs, PS_LIST_TAIL, "-guide_version",  0,
+        "define guide_version of interest", 0);
+
+    // find which mode we're running under
+    int N = 0;
+    config->mode = PX_MODE_NONE;
+    if ((N = psArgumentGet (argc, argv, "-pending"))) {
+        psArgumentRemove (N, &argc, argv);
+        if (config->mode) {
+            psError(PS_ERR_UNKNOWN, true, "only one mode selection is allowed");
+        }
+        config->mode = PX_MODE_PENDING;
+    }
+    if ((N = psArgumentGet (argc, argv, "-define"))) {
+        psArgumentRemove (N, &argc, argv);
+        if (config->mode) {
+            psError(PS_ERR_UNKNOWN, true, "only one mode selection is allowed");
+        }
+        config->mode = PX_MODE_DEFINE;
+    }
+
+    // parse CLI for these options
+    switch (config->mode) {
+        case PX_MODE_PENDING:
+            config->args = psMemIncrRefCounter(pendingArgs);
+            break;
+        case PX_MODE_DEFINE:
+            config->args = psMemIncrRefCounter(defineArgs);
+            break;
+        default:
+            config->args = psMemIncrRefCounter(args);
+    }
+
+    bool argErr = false;
+    if (config->mode == PX_MODE_NONE) {
+        argErr = true;
+        fprintf (stderr, "mode argument is required\n");
+    } else if (! psArgumentParse(config->args, &argc, argv) || argc != 1) {
+        argErr = true;
+        fprintf (stderr, "error parsing arguments\n");
+    }
+
+    if (argErr) {
+        printf("\nPan-STARRS Guide Star Tool\n");
+        printf("Usage: %s <mode> [<options>]\n\n", argv[0]);
+        printf(" <mode> : -pending | -define\n\n");
+        fprintf (stdout, "-pending ");
+        psArgumentHelp(pendingArgs);
+        fprintf (stdout, "-define ");
+        psArgumentHelp(defineArgs);
+        psFree(args);
+        psFree(pendingArgs);
+        psFree(defineArgs);
+        exit(EXIT_FAILURE);
+    }
+
+    psFree(args);
+    psFree(pendingArgs);
+    psFree(defineArgs);
+
+    // setup search criterion
+#define addWhereStr(name) \
+    if ((str = psMetadataLookupStr(&status, config->args, "-" #name))) { \
+        if (!psMetadataAddStr(config->where, PS_LIST_TAIL, #name, 0, "==", str)) {\
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+            exit(EXIT_FAILURE); \
+        } \
+    }
+
+    // generate SQL where clause
+    config->where = psMetadataAlloc();
+
+{
+    psString str = NULL;
+    int n = 0;
+    bool status = false;
+
+    addWhereStr(exp_tag);
+    // convert '-inst' to 'camera'
+    if ((str = psMetadataLookupStr(&status, config->args, "-inst"))) {
+        if (!psMetadataAddStr(config->where, PS_LIST_TAIL, "camera", 0, "==", str)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+            exit(EXIT_FAILURE);
+        }
+    }
+    addWhereStr(telescope);
+    addWhereStr(exp_type);
+    if ((n = psMetadataLookupS32(&status, config->args, "-imfiles"))) {
+        if (!psMetadataAddS32(config->where, PS_LIST_TAIL, "imfiles", 0, "==", n)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+            exit(EXIT_FAILURE);
+        }
+    }
+    addWhereStr(filter);
+    addWhereStr(stats);
+    addWhereStr(recipe);
+    if ((n = psMetadataLookupS32(&status, config->args, "-guide_version"))) {
+        if (!psMetadataAddS32(config->where, PS_LIST_TAIL, "guide_version", 0, "==", n)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item guide_version");
+            exit(EXIT_FAILURE);
+        }
+    }
+}
+
+    if (config->where->list->n < 1) {
+        psFree(config->where);
+        config->where = NULL;
+    }
+
+    // define Database handle, if used
+    config->dbh = pmConfigDB(config->modules);
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        exit(EXIT_FAILURE);
+    }
+
+    return config;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxadmin.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxadmin.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxadmin.c	(revision 22073)
@@ -0,0 +1,75 @@
+/*
+ * pxadmin.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include "pxtools.h"
+#include "pxadmin.h"
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = pxAdminConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        case PXADMIN_MODE_RECREATE:
+            if (!pxDeleteTables(config)) {
+                goto FAIL;
+            }
+            // fall through
+        case PXADMIN_MODE_CREATE:
+            if (!pxCreateTables(config)) {
+                goto FAIL;
+            }
+            break;
+        case PXADMIN_MODE_DELETE:
+            if (!pxDeleteTables(config)) {
+                goto FAIL;
+            }
+            break;
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxadmin.h
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxadmin.h	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxadmin.h	(revision 22073)
@@ -0,0 +1,34 @@
+/*
+ * pxadmin.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PXADMIN_H
+#define PXADMIN_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    PXADMIN_MODE_NONE      = 0x0,
+    PXADMIN_MODE_CREATE,
+    PXADMIN_MODE_DELETE,
+    PXADMIN_MODE_RECREATE
+} pxAdminMode;
+
+pxConfig *pxAdminConfig (pxConfig *config, int argc, char **argv);
+
+#endif // PXADMIN_H
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxadminConfig.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxadminConfig.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxadminConfig.c	(revision 22073)
@@ -0,0 +1,119 @@
+/*
+ * pxadminConfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "pxadmin.h"
+
+pxConfig *pxAdminConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    // Parse other command-line arguments
+    psMetadata *arguments = psMetadataAlloc(); // The arguments, with default values
+
+    int N;
+    config->mode = PXADMIN_MODE_NONE;
+    if ((N = psArgumentGet(argc, argv, "-create"))) {
+        psArgumentRemove(N, &argc, argv);
+        if (config->mode) {
+            psAbort("only one mode selection is allowed");
+        }
+        config->mode = PXADMIN_MODE_CREATE;
+    }
+    if ((N = psArgumentGet(argc, argv, "-delete"))) {
+        psArgumentRemove(N, &argc, argv);
+        if (config->mode) {
+            psAbort("only one mode selection is allowed");
+        }
+        config->mode = PXADMIN_MODE_DELETE;
+    }
+    if ((N = psArgumentGet(argc, argv, "-recreate"))) {
+        psArgumentRemove(N, &argc, argv);
+        if (config->mode) {
+            psAbort("only one mode selection is allowed");
+        }
+        config->mode = PXADMIN_MODE_RECREATE;
+    }
+
+    // paul's argument parsing convention requires: -key value
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-create", 0,
+            "create the P2 tables", "");
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-delete", 0,
+            "delete the P2 tables", "");
+    psMetadataAddStr(arguments, PS_LIST_TAIL, "-recreate", 0,
+            "delete and recreate the P2 tables", "");
+
+    if (config->mode == PXADMIN_MODE_NONE) {
+        fprintf (stderr, "admin mode not specified\n");
+        psArgumentHelp(arguments);
+        psFree(arguments);
+        psFree(config);
+        return NULL;
+    }
+
+    if ((N = psArgumentGet (argc, argv, "-help"))) {
+        psArgumentHelp(arguments);
+        psFree(arguments);
+        psFree(config);
+        return NULL;
+    }
+
+    if (! psArgumentParse(arguments, &argc, argv) || argc != 1) {
+        printf("\nPan-STARRS Phase 2 Admin Tool\n\n");
+        printf("Usage: %s [mode]\n", argv[0]);
+        printf(" [mode] : -create | -delete\n\n");
+        psArgumentHelp(arguments);
+        psFree(arguments);
+        psFree(config);
+        return NULL;
+    }
+
+   psFree(arguments);
+
+    // define Database handle, if used
+    config->dbh = pmConfigDB(config->modules);
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    // save argv/argc
+    config->argv = argv;
+    config->argc = argc;
+
+    return config;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxconfig.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxconfig.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxconfig.c	(revision 22073)
@@ -0,0 +1,50 @@
+/*
+ * pxconfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "pxtools.h"
+
+static void pxConfigFree(pxConfig *ptr);
+
+pxConfig *pxConfigAlloc(void)
+{
+    pxConfig *config;
+
+    config = psAlloc(sizeof(pxConfig));
+    psMemSetDeallocator(config, (psFreeFunc)pxConfigFree);
+
+    config->mode            = 0;
+    config->dbh             = NULL;
+    config->modules         = NULL;
+    config->where           = NULL;
+    config->args            = NULL;
+
+    return config;
+}
+
+static void pxConfigFree(pxConfig *config)
+{
+    psDBCleanup(config->dbh);
+    psFree(config->modules);
+    psFree(config->where);
+    psFree(config->args);
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxdata.c.template
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxdata.c.template	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxdata.c.template	(revision 22073)
@@ -0,0 +1,94 @@
+/*
+ * pxdata.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVB_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h> // getenv
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <pslib.h>
+
+#include "pxtools.h"
+#include "pxdata.h"
+
+int test_f(const char *path);
+
+psString pxDataGet(const char *filename)
+{
+    PS_ASSERT_PTR_NON_NULL(filename, NULL);
+
+    psString filepath = NULL;
+
+    // look to see if PXDATA is set
+    char *PXDATA = getenv("PXDATA");
+    if (PXDATA) {
+        // if it is, then prepend filename to PXDATA
+        psStringAppend(&filepath, "%s/%s", PXDATA, filename);
+    }
+
+    // see if we have a valid filename
+    if (test_f(filepath)) {
+        // if we do, slurp the file
+        psString text = psSlurpFilename(filepath);
+        if (!text) {
+            psError(PS_ERR_IO, false, "failed to slurp %s", filepath);
+            psFree(filepath);
+            return NULL;
+        }
+        psFree(filepath);
+
+        return text;
+    }
+    psFree(filepath);
+
+    // look under our share path
+    psStringAppend(&filepath, "%s/%s", "PKGDATADIR", filename);
+
+    // see if we have a valid filename
+    if (test_f(filepath)) {
+        // if we do, slurp the file
+        psString text = psSlurpFilename(filepath);
+        if (!text) {
+            psError(PS_ERR_IO, false, "failed to slurp %s", filepath);
+            psFree(filepath);
+            return NULL;
+        }
+        psFree(filepath);
+
+        return text;
+    }
+    psFree(filepath);
+
+    // couldn't find a matching filename
+    psError(PS_ERR_IO, true, "can't find a file in search path(s) to match : %s", filename);
+
+    return NULL;
+}
+
+int test_f(const char *path)
+{
+        // copied from coreutils 6.4 test.c licenced under GPL v2 
+        struct stat stat_buf;
+        return (stat (path, &stat_buf) == 0
+                   && S_ISREG (stat_buf.st_mode));
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxdata.h
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxdata.h	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxdata.h	(revision 22073)
@@ -0,0 +1,27 @@
+/*
+ * pxdata.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PXDATA_H
+#define PXDATA_H 1
+
+#include <pslib.h>
+
+psString pxDataGet(const char *filename);
+
+#endif // PXDATA_H
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxerrors.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxerrors.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxerrors.c	(revision 22073)
@@ -0,0 +1,42 @@
+/*
+ * pxerrors.c
+ *
+ * Copyright (C) 2006  Eugene Magnier
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "pxtools.h"
+
+psExit pxerrorGetExitStatus () {
+
+    psErrorCode err = psErrorCodeLast ();
+    switch (err) {
+      case PXTOOLS_ERR_SYS:
+	return PS_EXIT_SYS_ERROR;
+      case PXTOOLS_ERR_CONFIG:
+	return PS_EXIT_CONFIG_ERROR;
+      case PXTOOLS_ERR_PROG:
+	return PS_EXIT_PROG_ERROR;
+      case PXTOOLS_ERR_DATA:
+	return PS_EXIT_DATA_ERROR;
+      default:
+	return PS_EXIT_UNKNOWN_ERROR;
+    }	
+    return PS_EXIT_UNKNOWN_ERROR;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxfault.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxfault.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxfault.c	(revision 22073)
@@ -0,0 +1,65 @@
+/*
+ * pxfault.c
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pslib.h>
+
+#include "pxtools.h"
+
+bool pxSetFaultCode(psDB *dbh, const char *tableName, psMetadata *where, psS8 code)
+{
+    PS_ASSERT_PTR_NON_NULL(dbh, false);
+    PS_ASSERT_PTR_NON_NULL(tableName, false);
+    PS_ASSERT_PTR_NON_NULL(where, false);
+
+#if 0
+    // map code string to numeric fault code
+    psU32 code = mapCodeStrToInt(codeStr);
+    if (code == (psU32)-1) {
+        psError(PS_ERR_UNKNOWN, false, "error resolving error code");
+        return false;
+    }
+#endif
+
+    // update the database
+    psMetadata *values = psMetadataAlloc();
+    if (!psMetadataAddS8(values, PS_LIST_HEAD, "fault", 0, NULL, code)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add metadata item fault");
+        psFree(values);
+        return false;
+    }
+
+    long rowsAffected = psDBUpdateRows(dbh, tableName, where, values); 
+    psFree(values);
+    if (rowsAffected < 0) {
+        // database error
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (rowsAffected < 1) {
+        // we didn't do anything
+        psError(PS_ERR_UNKNOWN, false, "zero rows were affected - either the search criteria didn't match any rows or the field already had the value being set.");
+        return false;
+    }
+
+    return true;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxinject.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxinject.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxinject.c	(revision 22073)
@@ -0,0 +1,228 @@
+/*
+ * pxinject.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <ippdb.h>
+
+#include "pxtools.h"
+#include "pxtag.h"
+#include "pxinject.h"
+
+static bool newExpMode(pxConfig *config);
+static bool newImfileMode(pxConfig *config);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = pxinjectConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(PXINJECT_MODE_NEWEXP, newExpMode);
+        MODECASE(PXINJECT_MODE_NEWIMFILE, newImfileMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint (stderr, "failure\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+static bool newExpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psString exp_id = psMetadataLookupStr(&status, config->args, "-exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_id");
+        return false;
+    }
+    if (!exp_id) {
+        psError(PS_ERR_UNKNOWN, true, "-exp_id is required");
+        return false;
+    }
+    psString camera = psMetadataLookupStr(&status, config->args, "-inst");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -inst");
+        return false;
+    }
+    if (!camera) {
+        psError(PS_ERR_UNKNOWN, true, "-inst is required");
+        return false;
+    }
+    psString telescope = psMetadataLookupStr(&status, config->args, "-telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -telescope");
+        return false;
+    }
+    if (!telescope) {
+        psError(PS_ERR_UNKNOWN, true, "-telescope is required");
+        return false;
+    }
+    psS32 imfiles = psMetadataLookupS32(&status, config->args, "-imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -imfiles");
+        return false;
+    }
+    if (!imfiles) {
+        psError(PS_ERR_UNKNOWN, true, "-imfiles is required");
+        return false;
+    }
+
+    psString workdir = psMetadataLookupStr(&status, config->args, "-workdir");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -workdir");
+        return false;
+    }
+    if (!workdir) {
+        psError(PS_ERR_UNKNOWN, true, "-workdir is required");
+        return false;
+    }
+
+    psString exp_tag = pxGenExpTag(config, exp_id);
+    if (!exp_tag) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!newExpInsert(config->dbh,
+                exp_tag,
+                exp_id,
+                camera,
+                telescope,
+                imfiles,
+                workdir
+            )
+        ) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(exp_tag);
+        return false;
+    }
+
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_tag", 0, NULL, exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        psFree(exp_tag);
+        psFree(md);
+    }
+    psFree(exp_tag);
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            psFree(md);
+            return false;
+        }
+    }
+
+    // negate simple so the default is true
+    if (!ippdbPrintMetadata(stdout, md, !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(md);
+        return false;
+    }
+
+    psFree(md);
+
+    return true;
+}
+
+static bool newImfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psString exp_tag = psMetadataLookupStr(&status, config->args, "-exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_tag");
+        return false;
+    }
+    if (!exp_tag) {
+        psError(PS_ERR_UNKNOWN, true, "-exp_tag is required");
+        return false;
+    }
+    psString class = psMetadataLookupStr(&status, config->args, "-class");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -class");
+        return false;
+    }
+    if (!class) {
+        psError(PS_ERR_UNKNOWN, true, "-class is required");
+        return false;
+    }
+    psString class_id = psMetadataLookupStr(&status, config->args, "-class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -class_id");
+        return false;
+    }
+    if (!class_id) {
+        psError(PS_ERR_UNKNOWN, true, "-class_id is required");
+        return false;
+    }
+    psString uri = psMetadataLookupStr(&status, config->args, "-uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -uri");
+        return false;
+    }
+    if (!uri) {
+        psError(PS_ERR_UNKNOWN, true, "-uri is required");
+        return false;
+    }
+
+    // insert with error flag state set to 0 (no errors)
+    if (!newImfileInsert(config->dbh, exp_tag, class, class_id, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxinject.h
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxinject.h	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxinject.h	(revision 22073)
@@ -0,0 +1,33 @@
+/*
+ * pxinject.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PXINJECT_H
+#define PXINJECT_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    PXINJECT_MODE_NONE           = 0x0,
+    PXINJECT_MODE_NEWEXP,
+    PXINJECT_MODE_NEWIMFILE
+} pxinjectMode;
+
+pxConfig *pxinjectConfig(pxConfig *config, int argc, char **argv);
+
+#endif // PXINJECT_H
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxinjectConfig.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxinjectConfig.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxinjectConfig.c	(revision 22073)
@@ -0,0 +1,218 @@
+/*
+ * pxinjectConfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "pxinject.h"
+
+pxConfig *pxinjectConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    // -newExp
+    psMetadata *newExpArgs = psMetadataAlloc();
+    psMetadataAddStr(newExpArgs, PS_LIST_TAIL, "-exp_id",  0,
+        "define the exp_id (required)", NULL);
+    psMetadataAddStr(newExpArgs, PS_LIST_TAIL, "-inst",  0,
+        "define the camera name (required)", NULL);
+    psMetadataAddStr(newExpArgs, PS_LIST_TAIL, "-telescope",  0,
+        "define the telescope name (required)", NULL);
+    psMetadataAddS32(newExpArgs, PS_LIST_TAIL, "-imfiles",  0,
+        "define the number of imfiles in this exp (required)", 0);
+    psMetadataAddStr(newExpArgs, PS_LIST_TAIL, "-workdir",  0,
+        "define workdir (required)", 0);
+    psMetadataAddBool(newExpArgs, PS_LIST_TAIL, "-simple",  0,
+        "use the simple output format", false);
+
+    // -newImfile
+    psMetadata *newImfileArgs = psMetadataAlloc();
+    psMetadataAddStr(newImfileArgs, PS_LIST_TAIL, "-exp_tag",  0,
+        "define the exp_tag (required)", NULL);
+    psMetadataAddStr(newImfileArgs, PS_LIST_TAIL, "-class",  0,
+        "define the class (required)", NULL);
+    psMetadataAddStr(newImfileArgs, PS_LIST_TAIL, "-class_id",  0,
+        "define the class ID (required)", NULL);
+    psMetadataAddStr(newImfileArgs, PS_LIST_TAIL, "-uri",  0,
+        "define the URI (required)", NULL);
+
+#define PXTOOL_MODE(option, modeval, argset) \
+{ \
+    int N = 0; \
+    if ((N = psArgumentGet (argc, argv, option))) { \
+        psArgumentRemove (N, &argc, argv); \
+        if (config->mode) { \
+            psError(PS_ERR_UNKNOWN, true, "only one mode selection is allowed"); \
+        } \
+        config->mode = modeval; \
+        config->args = psMemIncrRefCounter(argset); \
+    } \
+}
+
+    // find which mode we're running under
+    PXTOOL_MODE("-newExp",          PXINJECT_MODE_NEWEXP,       newExpArgs);
+    PXTOOL_MODE("-newImfile",       PXINJECT_MODE_NEWIMFILE,    newImfileArgs);
+
+    bool argErr = false;
+    if (config->mode == PXINJECT_MODE_NONE) {
+        argErr = true;
+        fprintf (stderr, "mode argument is required\n");
+    } else if (! psArgumentParse(config->args, &argc, argv) || argc != 1) {
+        argErr = true;
+        fprintf (stderr, "error parsing arguments\n");
+    }
+
+    if (argErr) {
+        printf("\nPan-STARRS Detrend Tool\n");
+        printf("Usage: %s <mode> [<options>]\n\n", argv[0]);
+        printf(" <mode> : [-newExp|-newImfile]\n\n");
+        fprintf (stdout, "-newExp ");
+        psArgumentHelp(newExpArgs);
+        psFree(newExpArgs);
+        fprintf (stdout, "-newImfile ");
+        psArgumentHelp(newImfileArgs);
+        psFree(newImfileArgs);
+
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(newExpArgs);
+    psFree(newImfileArgs);
+
+    // setup search criterion
+#define addWhereStr(name) \
+{ \
+    psString str = NULL; \
+    bool status = false; \
+    if ((str = psMetadataLookupStr(&status, config->args, "-" #name))) { \
+        if (!psMetadataAddStr(config->where, PS_LIST_TAIL, #name, 0, "==", str)) {\
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+            psFree(config); \
+            return NULL; \
+        } \
+    } \
+}
+
+    // generate SQL where clause
+    config->where = psMetadataAlloc();
+
+    addWhereStr(det_id);
+    {
+        int n = 0;
+        bool status = false;
+        if ((n = psMetadataLookupS32(&status, config->args, "-iteration"))) {
+            if (!psMetadataAddS32(config->where, PS_LIST_TAIL, "iteration", 0, "==", n)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+                psFree(config);
+                return NULL;
+            }
+        }
+    }
+    addWhereStr(det_type);
+    addWhereStr(exp_tag);
+    addWhereStr(class_id);
+    // convert '-inst' to 'camera'
+    {
+        psString str = NULL;
+        bool status = false;
+        if ((str = psMetadataLookupStr(&status, config->args, "-inst"))) {
+            if (!psMetadataAddStr(config->where, PS_LIST_TAIL, "camera", 0, "==", str)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+                psFree(config);
+                return NULL;
+            }
+        }
+    }
+    addWhereStr(telescope);
+    {
+        int n = 0;
+        bool status = false;
+        if ((n = psMetadataLookupS32(&status, config->args, "-imfiles"))) {
+            if (!psMetadataAddS32(config->where, PS_LIST_TAIL, "imfiles", 0, "==", n)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+                psFree(config);
+                return NULL;
+            }
+        }
+    }
+    addWhereStr(filter);
+    addWhereStr(stats);
+    addWhereStr(recipe);
+    {
+        int n = 0;
+        bool status = false;
+        if ((n = psMetadataLookupS32(&status, config->args, "-guide_version"))) {
+            if (!psMetadataAddS32(config->where, PS_LIST_TAIL, "guide_version", 0, "==", n)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item guide_version");
+                psFree(config);
+                return NULL;
+            }
+        }
+    }
+    {
+        bool boolean = false;
+        bool status = false;
+
+        if ((boolean = psMetadataLookupBool(&status, config->args, "-accept"))) {
+            if (!psMetadataAddBool(config->where, PS_LIST_TAIL, "accept", 0, "==", boolean)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item accept");
+                psFree(config);
+                return NULL;
+            }
+        }
+    }
+
+    if (config->where->list->n < 1) {
+        psFree(config->where);
+        config->where = NULL;
+    }
+
+    // define Database handle, if used
+    // do this last so we don't setup a connection before CLI options are
+    // validated
+    config->dbh = pmConfigDB(config->modules);
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    // save argv/argc
+    config->argv = argv;
+    config->argc = argc;
+
+    return config;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxio.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxio.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxio.c	(revision 22073)
@@ -0,0 +1,59 @@
+/*
+ * pxio.c
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVB_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdbool.h>
+
+#include <pslib.h>
+
+#include "pxtools.h"
+#include "pxio.h"
+
+bool convertIdToStr(psArray *mds)
+{
+    PS_ASSERT_PTR_NON_NULL(mds, false);
+
+    char *ids[] = {"guide_id", "chip_id", "cam_id", "det_id", "warp_id", "diff_id", "stack_id", NULL};
+
+    // loop over array of metadatas
+    for (long i = 0; i < psArrayLength(mds); i++) {
+        psMetadata *md = mds->data[i];
+        // loop over array of ID fields
+        for (int j = 0; ids[j] != NULL; j++) {
+            char *name = ids[j];
+            bool status = false;
+            psS64 numeric_id = psMetadataLookupS64(&status, md, name);
+            if (!status) {
+                continue;
+//                psError(PS_ERR_UNKNOWN, false, "failed to lookup value for %s",
+//                        name);
+ //               return false;
+            }
+            psMetadataRemoveKey(md, name);
+            psString string_id = psDBIntToString((psU64)numeric_id);
+            psMetadataAddStr(md, PS_LIST_HEAD, name, 0, NULL, string_id);
+            psFree(string_id);
+        }
+    }
+
+    return true;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxio.h
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxio.h	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxio.h	(revision 22073)
@@ -0,0 +1,27 @@
+/*
+ * pxio.h
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PXIO_H
+#define PXIO_H 1
+
+#include <pslib.h>
+
+bool convertIdToStr(psArray *mds);
+
+#endif // PXIO_H
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxtables.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxtables.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxtables.c	(revision 22073)
@@ -0,0 +1,181 @@
+/*
+ * pxtables.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include "pxtools.h"
+
+#define CREATE_TABLE(createFunc) \
+    if (!createFunc(config->dbh)) { \
+        psError(PS_ERR_UNKNOWN, false, "dbh access failed"); \
+        status = false; \
+    }
+
+#define DROP_TABLE(dropFunc) \
+    if (!dropFunc(config->dbh)) { \
+        psError(PS_ERR_UNKNOWN, false, "dbh access failed"); \
+        status = false; \
+    }
+
+bool pxCreateTables(pxConfig *config) {
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool            status = true;
+    CREATE_TABLE(expTagCounterCreateTable);
+    // the counter must be initialized to 0
+    if (!p_psDBRunQuery(config->dbh, "INSERT INTO expTagCounter VALUES (0)")) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        status = false; 
+    }
+    CREATE_TABLE(summitExpCreateTable);
+    CREATE_TABLE(summitImfileCreateTable);
+    CREATE_TABLE(pzPendingExpCreateTable);
+    CREATE_TABLE(pzPendingImfileCreateTable);
+    CREATE_TABLE(pzDoneExpCreateTable);
+    CREATE_TABLE(pzDoneImfileCreateTable);
+    CREATE_TABLE(newExpCreateTable);
+    CREATE_TABLE(newImfileCreateTable);
+    CREATE_TABLE(rawExpCreateTable);
+    CREATE_TABLE(rawImfileCreateTable);
+    CREATE_TABLE(guidePendingExpCreateTable);
+    CREATE_TABLE(chipPendingExpCreateTable);
+    CREATE_TABLE(chipPendingImfileCreateTable);
+    CREATE_TABLE(chipProcessedExpCreateTable);
+    CREATE_TABLE(chipProcessedImfileCreateTable);
+    CREATE_TABLE(chipMaskCreateTable);
+    CREATE_TABLE(camPendingExpCreateTable);
+    CREATE_TABLE(camProcessedExpCreateTable);
+    CREATE_TABLE(camMaskCreateTable);
+    CREATE_TABLE(detRunCreateTable);
+    CREATE_TABLE(detInputExpCreateTable);
+    CREATE_TABLE(detProcessedImfileCreateTable);
+    CREATE_TABLE(detProcessedExpCreateTable);
+    CREATE_TABLE(detStackedImfileCreateTable);
+    CREATE_TABLE(detNormalizedStatImfileCreateTable);
+    CREATE_TABLE(detNormalizedImfileCreateTable);
+    CREATE_TABLE(detNormalizedExpCreateTable);
+    CREATE_TABLE(detResidImfileCreateTable);
+    CREATE_TABLE(detResidExpCreateTable);
+    CREATE_TABLE(detRunSummaryCreateTable);
+    CREATE_TABLE(warpRunCreateTable);
+    CREATE_TABLE(warpInputExpCreateTable);
+    CREATE_TABLE(warpSkyCellMapCreateTable);
+    CREATE_TABLE(warpSkyfileCreateTable);
+    CREATE_TABLE(diffRunCreateTable);
+    CREATE_TABLE(diffInputSkyfileCreateTable);
+    CREATE_TABLE(diffSkyfileCreateTable);
+    CREATE_TABLE(stackRunCreateTable);
+    CREATE_TABLE(stackInputSkyfileCreateTable);
+    CREATE_TABLE(stackSumSkyfileCreateTable);
+
+    return true;
+}
+
+bool pxDeleteTables(pxConfig *config) {
+    bool            status = true;
+    char line[128], answer[128];
+
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    fprintf (stdout, "*** delete the chip tables? ***\n");
+    fprintf (stdout, "*** to delete the tables, answer YES, and give password ***\n");
+    fprintf (stdout, "*** WARNING: this action is permanent ***\n\n");
+
+    fprintf (stdout, "delete the chip tables (YES/[n]): ");
+    fgets (line, 128, stdin);
+    sscanf (line, "%s", answer);
+    if (strcmp (answer, "YES"))  {
+        psError(PS_ERR_UNKNOWN, true, "tables NOT deleleted");
+        return false;
+    }
+
+    fprintf (stdout, "enter dbh connection password: ");
+    fgets (line, 128, stdin);
+    sscanf (line, "%s", answer);
+
+    {
+        bool status;
+        psString dbPassword = psMetadataLookupStr(&status, config->modules->site, "DBPASSWORD");
+        if (strcmp (answer, dbPassword)) {
+            psError(PS_ERR_UNKNOWN, true, "tables NOT deleleted");
+            return false;
+        }
+    }
+
+    // disable foreign key constrants
+    if (!p_psDBRunQuery(config->dbh, "SET FOREIGN_KEY_CHECKS=0")) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        status = false; 
+    }
+
+    DROP_TABLE(expTagCounterDropTable);
+    DROP_TABLE(summitExpDropTable);
+    DROP_TABLE(summitImfileDropTable);
+    DROP_TABLE(pzPendingExpDropTable);
+    DROP_TABLE(pzPendingImfileDropTable);
+    DROP_TABLE(pzDoneExpDropTable);
+    DROP_TABLE(pzDoneImfileDropTable);
+    DROP_TABLE(newExpDropTable);
+    DROP_TABLE(newImfileDropTable);
+    DROP_TABLE(rawExpDropTable);
+    DROP_TABLE(rawImfileDropTable);
+    DROP_TABLE(guidePendingExpDropTable);
+    DROP_TABLE(chipPendingExpDropTable);
+    DROP_TABLE(chipPendingImfileDropTable);
+    DROP_TABLE(chipProcessedExpDropTable);
+    DROP_TABLE(chipProcessedImfileDropTable);
+    DROP_TABLE(chipMaskDropTable);
+    DROP_TABLE(camPendingExpDropTable);
+    DROP_TABLE(camProcessedExpDropTable);
+    DROP_TABLE(camMaskDropTable);
+    DROP_TABLE(detRunDropTable);
+    DROP_TABLE(detInputExpDropTable);
+    DROP_TABLE(detProcessedImfileDropTable);
+    DROP_TABLE(detProcessedExpDropTable);
+    DROP_TABLE(detStackedImfileDropTable);
+    DROP_TABLE(detNormalizedStatImfileDropTable);
+    DROP_TABLE(detNormalizedImfileDropTable);
+    DROP_TABLE(detNormalizedExpDropTable);
+    DROP_TABLE(detResidImfileDropTable);
+    DROP_TABLE(detResidExpDropTable);
+    DROP_TABLE(detRunSummaryDropTable);
+    DROP_TABLE(warpRunDropTable);
+    DROP_TABLE(warpInputExpDropTable);
+    DROP_TABLE(warpSkyCellMapDropTable);
+    DROP_TABLE(warpSkyfileDropTable);
+    DROP_TABLE(diffRunDropTable);
+    DROP_TABLE(diffInputSkyfileDropTable);
+    DROP_TABLE(diffSkyfileDropTable);
+    DROP_TABLE(stackRunDropTable);
+    DROP_TABLE(stackInputSkyfileDropTable);
+    DROP_TABLE(stackSumSkyfileDropTable);
+
+    // re-enable foreign key constrants
+    if (!p_psDBRunQuery(config->dbh, "SET FOREIGN_KEY_CHECKS=1")) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        status = false; 
+    }
+
+    return status;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxtag.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxtag.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxtag.c	(revision 22073)
@@ -0,0 +1,102 @@
+/*
+ * pxtag.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVB_CONFIG_H
+#include <config.h>
+#endif
+
+#include <inttypes.h>
+
+#include "pxtools.h"
+#include "pxtag.h"
+
+psString pxGenExpTag(pxConfig *config, const char *exp_id)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    // start a transaction so we don't increment the expTag counter unless we
+    // can successfully retreive it's value
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return NULL;
+    }
+
+    if (!p_psDBRunQuery(config->dbh,
+        "UPDATE expTagCounter SET counter = LAST_INSERT_ID(counter + 1)")) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return NULL;
+    }
+
+    // psDBLastInsertID() can't be used here as it called mysql_insert_id()
+    // which doesn't work with this trick.  See:
+    // http://dev.mysql.com/doc/refman/4.1/en/information-functions.html
+    if (!p_psDBRunQuery(config->dbh, "SELECT LAST_INSERT_ID() as counter")) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return NULL;
+    }
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return NULL;
+    }
+    // sanity check that we only got one row
+    if (psArrayLength(output) != 1) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "should have gotten 1 row but %lu rows were returned", psArrayLength(output));
+        psFree(output);
+        return NULL;
+    }
+
+    // point of no return 
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(output);
+        return NULL;
+    }
+
+    psMetadata *row = output->data[0];
+    bool status = false;
+    psU64 counter = psMetadataLookupU64(&status, row, "counter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for counter");
+        psFree(output);
+        return NULL;
+    }
+    psString exp_tag = NULL;
+    psStringAppend(&exp_tag, "%s.%" PRIu64, exp_id, counter);
+    psFree(output);
+
+    return exp_tag;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxtag.h
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxtag.h	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxtag.h	(revision 22073)
@@ -0,0 +1,27 @@
+/*
+ * pxtag.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PXTAG_H
+#define PXTAG_H 1
+
+#include "pxtools.h"
+
+psString pxGenExpTag(pxConfig *config, const char *exp_id);
+
+#endif // PXTAG_H
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxtools.h
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxtools.h	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxtools.h	(revision 22073)
@@ -0,0 +1,58 @@
+/*
+ * pxtools.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PXTOOLS_H
+#define PXTOOLS_H 1
+
+# include <stdio.h>
+# include <strings.h>  // for strcasecmp
+# include <unistd.h>   // for unlink
+# include <pslib.h>
+# include <psmodules.h>
+# include <ippdb.h>
+
+# include "pxtoolsErrorCodes.h"
+# include "pxio.h"
+# include "pxdata.h"
+
+// load these values from the db in the init stage
+# define P2_TYPE_OBJECT 1
+# define P2_STATE_READY 2
+# define RECIPE "PHASE2"
+# define MAX_ROWS 10e9
+
+typedef struct {
+    int mode;
+    pmConfig *modules;
+    psDB *dbh;
+    psMetadata *args;
+    psMetadata *where;
+    int argc;
+    char **argv;
+} pxConfig;
+
+pxConfig *pxConfigAlloc(void);
+bool pxCreateTables (pxConfig *config);
+bool pxDeleteTables (pxConfig *config);
+
+bool pxSetFaultCode(psDB *dbh, const char *tableName, psMetadata *where, psS8 code);
+
+psExit pxerrorGetExitStatus ();
+
+#endif // PXTOOLS_H
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxtoolsErrorCodes.c.in
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxtoolsErrorCodes.c.in	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxtoolsErrorCodes.c.in	(revision 22073)
@@ -0,0 +1,25 @@
+/*
+ * The line
+    { PXTOOLS_ERR_$X{ErrorCode}, "$X{ErrorDescription}"},
+ * (without the Xs)
+ * will be replaced by values from errorCodes.dat
+ */
+#include "pslib.h"
+#include "pxtoolsErrorCodes.h"
+
+void pxtoolsErrorRegister(void)
+{
+    static psErrorDescription errors[] = {
+       { PXTOOLS_ERR_BASE, "First value we use; lower values belong to psLib" },
+       { PXTOOLS_ERR_${ErrorCode}, "${ErrorDescription}"},
+    };
+    static int nerror = PXTOOLS_ERR_NERROR - PXTOOLS_ERR_BASE; // number of values in enum
+
+    for (int i = 0; i < nerror; i++) {
+       psErrorDescription *tmp = psAlloc(sizeof(psErrorDescription));
+       *tmp = errors[i];
+       psErrorRegister(tmp, 1);
+       psFree(tmp);			/* it's on the internal list */
+    }
+    nerror = 0;			                // don't register more than once
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxtoolsErrorCodes.dat
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxtoolsErrorCodes.dat	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxtoolsErrorCodes.dat	(revision 22073)
@@ -0,0 +1,10 @@
+#
+# This file is used to generate pxtoolsErrorCodes.h
+#
+BASE = 1100		First value we use; lower values belong to psLib
+UNKNOWN			Unknown PM error code
+ARGUMENTS		Incorrect arguments
+SYS			System error
+CONFIG			Problem in configure files
+PROG			Programming error
+DATA   			invalid data
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxtoolsErrorCodes.h.in
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxtoolsErrorCodes.h.in	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pxtoolsErrorCodes.h.in	(revision 22073)
@@ -0,0 +1,18 @@
+#if !defined(PXTOOLS_ERROR_CODES_H)
+#define PXTOOLS_ERROR_CODES_H
+/*
+ * The line
+ *  PXTOOLS_ERR_$X{ErrorCode},
+ * (without the X)
+ *
+ * will be replaced by values from errorCodes.dat
+ */
+typedef enum {
+    PXTOOLS_ERR_BASE = 512,
+    PXTOOLS_ERR_${ErrorCode},
+    PXTOOLS_ERR_NERROR
+} pxtoolsErrorCode;
+
+void pxtoolsErrorRegister(void);
+
+#endif
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pzgetexp.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pzgetexp.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pzgetexp.c	(revision 22073)
@@ -0,0 +1,372 @@
+/*
+ * pzgetexp.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "pxtools.h"
+#include "pzgetexp.h"
+
+#define PRODUCT_LS_CMD "dsproductls"
+
+static bool go (pxConfig *config);
+static psArray *parseFileSets(pxConfig *config, const char *str);
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = pzgetexpConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    if (!go(config)) {
+        goto FAIL;
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+    
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+static bool go(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    // find last fileset/exp_id (if we have one)
+    bool haveLastFileSet = false;
+    psString lastFileSet = NULL;
+    {
+        char *query = "SELECT * from summitExp ORDER BY dateobs DESC LIMIT 1";
+        if (!p_psDBRunQuery(config->dbh, query)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+
+        psArray *output = p_psDBFetchResult(config->dbh);
+        if (!output) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+        if (!psArrayLength(output)) {
+            psError(PS_ERR_UNKNOWN, false, "no summitExp rows found");
+            haveLastFileSet = false;
+            psFree(output);
+        } else {
+            haveLastFileSet = true;
+            bool status = false;
+            lastFileSet = psStringCopy(psMetadataLookupStr(&status, output->data[0], "exp_id"));
+            psFree(output);
+        }
+    }
+
+    // invoke dsproductls
+    // dsproductls --uri <> --last_fileset <>
+    bool status = false;
+    psString uri = psMetadataLookupStr(&status, config->args, "-uri");
+    psString cmd = NULL;
+    if (haveLastFileSet) {
+        psStringAppend(&cmd, "%s --uri %s --last_fileset %s",
+            PRODUCT_LS_CMD, uri, lastFileSet);
+        psFree(lastFileSet);
+    } else {
+        psStringAppend(&cmd, "%s --uri %s", PRODUCT_LS_CMD, uri);
+    }
+
+    psTrace("pzgetexp", PS_LOG_INFO, "cmd is: %s\n", cmd);
+
+    FILE *output = popen(cmd, "r");
+    psFree(cmd);
+
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, true, "popen() failed");
+        return false;
+    }
+    psString cmdOutput = psSlurpFile(output);
+    pclose(output);
+
+    psArray *newSummitExps = parseFileSets(config, cmdOutput);
+    psFree(cmdOutput);
+    if (!newSummitExps) {
+        // XXX not nessicarily an error
+        psError(PS_ERR_UNKNOWN, true, "no new fileSet/exp IDs");
+        return false;
+    }
+    if (!psArrayLength(newSummitExps)) {
+        psTrace("pzgetexp", PS_LOG_INFO, "no new fileSet/exp IDs");
+        psFree(newSummitExps);
+        return true;
+    }
+
+    // start a transaction so it's all rows or nothing
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // create a temporry table
+    {
+        char *query = "CREATE TEMPORARY TABLE incoming" 
+            " (exp_id VARCHAR(64), camera VARCHAR(64), telescope VARCHAR(64), dateobs DATETIME, exp_type VARCHAR(64), uri VARCHAR(255), PRIMARY KEY(exp_id, camera, telescope))"
+           " ENGINE=MEMORY";
+        if (!p_psDBRunQuery(config->dbh, query)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(newSummitExps);
+            return false;
+        }
+    }
+
+    {
+        char *query = "INSERT INTO incoming (exp_id, camera, telescope, dateobs, exp_type, uri) VALUES (?, ?, ?, ?, ?, ?)";
+
+        long inserted = p_psDBRunQueryPrepared(config->dbh, newSummitExps, query);
+        if (inserted < 0) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(newSummitExps);
+            return false;
+        }
+        // sanity check that we actually inserted something
+        if (inserted == 0) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error -- we should have inserted at least one row");
+            psFree(newSummitExps);
+            return false;
+        }
+    }
+
+    psFree(newSummitExps);
+
+    // add new exps to pzPendingExp -- must be done before the new exps are
+    // added to summitExp because of the SQL logic
+    {
+        char *query = 
+            "INSERT INTO pzPendingExp" 
+            "   SElECT"
+            "       incoming.exp_id,"
+            "       incoming.camera,"
+            "       incoming.telescope"
+            "   FROM incoming"
+            "   LEFT JOIN summitExp"
+            "       USING(exp_id, camera, telescope)"
+            "   WHERE"
+            "       summitExp.exp_id is NULL"
+            "       AND summitExp.camera is NULL"
+            "       AND summitExp.telescope is NULL";
+
+        if (!p_psDBRunQuery(config->dbh, query)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+    }
+
+    // add new exps to summitExp
+    {
+        char *query = 
+            "INSERT INTO summitExp" 
+            "   SElECT"
+            "       incoming.*,"
+            "       NULL"
+            "   FROM incoming"
+            "   LEFT JOIN summitExp"
+            "       USING(exp_id, camera, telescope)"
+            "   WHERE"
+            "       summitExp.exp_id is NULL"
+            "       AND summitExp.camera is NULL"
+            "       AND summitExp.telescope is NULL";
+
+        if (!p_psDBRunQuery(config->dbh, query)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+    }
+    
+    // point of no return
+    if (!psDBCommit(config->dbh)) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static psArray *parseFileSets(pxConfig *config, const char *str)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(str, NULL);
+    
+    // these are constants for all records parsed -- look them up before we do
+    // any work
+    bool status = false;
+    char *camera = psMetadataLookupStr(&status, config->args, "-inst");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup item '-inst'");
+        return NULL;
+    }
+    char *telescope = psMetadataLookupStr(&status, config->args, "-telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup item '-telescope'");
+        return NULL;
+    }
+
+    // split the string into lines
+    psList *doc = psStringSplit(str, "\n", false);
+
+    psListIterator *lineCursor = psListIteratorAlloc(doc, 0, false);
+
+    psArray *summitExps = psArrayAllocEmpty(psListLength(doc));
+    psString line;
+    while ((line = psListGetAndIncrement(lineCursor))) {
+        psTrace("pzgetimfile", PS_LOG_INFO, "parsing line: %s\n", line);
+
+        // split line into tokens
+        psList *tokens = psStringSplit(line, " ", false);
+
+        // check to see if this line is a comment (or if the first token is
+        // NULL)
+        if (!psListGet(tokens, 0) || *((char *)psListGet(tokens, 0)) == '#') {
+            psFree(tokens);
+            continue;
+        }
+
+        // check that we have the right number of tokens
+        // print "# uri fileset datetime type\n";
+        if (psListLength(tokens) != 4) {
+            // error
+            return false;
+        }
+
+        // find the values of interest
+        psListIterator *tokenCursor = psListIteratorAlloc(tokens, 0, false);
+        char *uri       = psListGetAndIncrement(tokenCursor);
+        char *exp_id    = psListGetAndIncrement(tokenCursor); // fileset
+        char *dateobsStr= psListGetAndIncrement(tokenCursor); // datetime
+        char *exp_type  = psListGetAndIncrement(tokenCursor); // type
+
+        // create a new metadata to represent this line and it's values
+        psMetadata *md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, exp_id)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, camera)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, telescope)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        psTime *dateobs = psTimeFromISO(dateobsStr, PS_TIME_UTC);
+        if (!psMetadataAddTime(md, PS_LIST_TAIL, "dateobs", 0, NULL, dateobs)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+            psFree(dateobs);
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        psFree(dateobs);
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, exp_type)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, uri)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+
+        // must be freed after the new metadata is built -- holds the strings
+        psFree(tokenCursor);
+        psFree(tokens);
+
+        // add the new metadata to the result set
+        psArrayAdd(summitExps, 0, md);
+
+        // debugging
+        if (psTraceGetLevel("pzgetexp") == PS_LOG_INFO) {
+            psString doc = psMetadataConfigFormat(md);
+            psTrace("pzgetexp", PS_LOG_INFO, "parsed line as:\n %s\n", doc);
+            psFree(doc);
+        }
+
+        psFree(md);
+
+    }
+
+    psFree(lineCursor);
+    psFree(doc);
+
+    return summitExps;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pzgetexp.h
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pzgetexp.h	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pzgetexp.h	(revision 22073)
@@ -0,0 +1,27 @@
+/*
+ * pzgetexp.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PZGETEXP_H
+#define PZGETEXP_H 1
+
+#include "pxtools.h"
+
+pxConfig *pzgetexpConfig(pxConfig *config, int argc, char **argv);
+
+#endif // PZGETEXP_H
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pzgetexpConfig.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pzgetexpConfig.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pzgetexpConfig.c	(revision 22073)
@@ -0,0 +1,83 @@
+/*
+ * pzgetexpConfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "pxtools.h"
+
+pxConfig *pzgetexpConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (! config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration!\n");
+        psFree(config);
+        return NULL;
+    }
+
+    psMetadata *args = psMetadataAlloc();
+    psMetadataAddStr(args , PS_LIST_TAIL, "-uri", 0,
+        "DataStore product URI", "");
+    psMetadataAddStr(args , PS_LIST_TAIL, "-inst", 0,
+        "camera name", "");
+    psMetadataAddStr(args , PS_LIST_TAIL, "-telescope",  0,
+        "telescope name", "");
+
+    bool status = false;
+    if (!psArgumentParse(args, &argc, argv)
+        || argc != 1
+        || strcmp(psMetadataLookupStr(&status, args, "-uri"), "") == 0
+        || strcmp(psMetadataLookupStr(&status, args, "-inst"), "") == 0
+        || strcmp(psMetadataLookupStr(&status, args, "-telescope"), "") == 0
+    ) {
+        fprintf(stderr, "error parsing arguments\n");
+        printf("\nPan-STARRS Phase Z Search Tool\n");
+        printf("Usage: %s -uri <uri> -inst <camera> -telescope <telescope>\n\n",
+            argv[0]);
+        psArgumentHelp(args);
+        psFree(config);
+        return NULL;
+    }
+
+    config->args = args;
+    // don't free args here as it's silly to increment the ref count then
+    // "free" it
+
+    // define Database handle, if used
+    config->dbh = pmConfigDB(config->modules);
+    if(!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't connect to db\n");
+        psFree(config);
+        return NULL;
+    }
+
+    return config;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pzgetimfiles.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pzgetimfiles.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pzgetimfiles.c	(revision 22073)
@@ -0,0 +1,682 @@
+/*
+ * pzgetimfiles.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "pxtag.h"
+#include "pxtools.h"
+#include "pzgetimfiles.h"
+
+#define FILESET_LS_CMD "dsfilesetls"
+
+static bool go (pxConfig *config);
+static psArray *parseFiles(pxConfig *config, const char *str);
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = pzgetimfilesConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    if (!go(config)) {
+        goto FAIL;
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+static bool go(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    bool status = false;
+    psString uri = psMetadataLookupStr(&status, config->args, "-uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -uri");
+        return false;
+    }
+    if (!uri) {
+        psError(PS_ERR_UNKNOWN, true, "-uri is required");
+        return false;
+    }
+
+    psString filesetid = psMetadataLookupStr(&status, config->args, "-filesetid");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -filesetid");
+        return false;
+    }
+    if (!filesetid) {
+        psError(PS_ERR_UNKNOWN, true, "-filesetid is required");
+        return false;
+    }
+
+    psString camera = psMetadataLookupStr(&status, config->args, "-inst");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -inst");
+        return NULL;
+    }
+    if (!camera) {
+        psError(PS_ERR_UNKNOWN, true, "-inst is required");
+        return NULL;
+    }
+
+    psString telescope = psMetadataLookupStr(&status, config->args, "-telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -telescope");
+        return NULL;
+    }
+    if (!telescope) {
+        psError(PS_ERR_UNKNOWN, true, "-telescope is required");
+        return NULL;
+    }
+
+    // invoke dsfilesetls
+    psString cmd = NULL;
+    psStringAppend(&cmd, "%s --uri %s", FILESET_LS_CMD, uri);
+
+    psTrace("pzgetimfiles", PS_LOG_INFO, "cmd is: %s\n", cmd);
+
+    FILE *output = popen(cmd, "r");
+    psFree(cmd);
+
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, true, "popen() failed");
+        return false;
+    }
+
+    psString cmdOutput = psSlurpFile(output);
+    int exitStatus = pclose(output);
+
+    if (exitStatus != 0) {
+        psError(PS_ERR_UNKNOWN, true, "%s failed with exit status %d",
+            FILESET_LS_CMD, exitStatus);
+        psFree(cmdOutput);
+        return false;
+    }
+
+    // prase output of dsfilesetls
+    psArray *newImfiles = parseFiles(config, cmdOutput);
+    if (!newImfiles) {
+        // XXX not nessicarily an error
+        psError(PS_ERR_UNKNOWN, true, "no new files/imfiles");
+        psFree(cmdOutput);
+        return false;
+    }
+    psFree(cmdOutput);
+    
+    // save the number of new Imfiles;
+    long imfiles = psArrayLength(newImfiles);
+
+    // start a transaction so it's all rows or nothing
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // if the fileset was empty (no files) then we can bail out early
+    if (imfiles == 0) {
+        psFree(newImfiles);
+
+        char *query = 
+            "UPDATE summitExp"
+            " SET imfiles = %d"
+            " WHERE exp_id = '%s'"
+            " AND camera = '%s'"
+            " AND telescope = '%s'";
+        if (!p_psDBRunQuery(config->dbh, query, imfiles, filesetid, camera, telescope)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+
+        // remove the pzPendingExp entry for this exp (fileset)
+        {
+            char *query = 
+                "DELETE FROM pzPendingExp"
+                " WHERE"
+                "   exp_id = '%s'"
+                "   AND camera = '%s'"
+                "   AND telescope = '%s'";
+            if (!p_psDBRunQuery(config->dbh, query, filesetid, camera, telescope)) {
+                // rollback
+                if (!psDBRollback(config->dbh)) {
+                    psError(PS_ERR_UNKNOWN, false, "database error");
+                }
+                psError(PS_ERR_UNKNOWN, false, "database error");
+                return false;
+            }
+
+            // sanity check: we should have removed only one row
+            psU64 affected = psDBAffectedRows(config->dbh);
+            if (psDBAffectedRows(config->dbh) != 1) {
+                // rollback
+                if (!psDBRollback(config->dbh)) {
+                    psError(PS_ERR_UNKNOWN, false, "database error");
+                }
+                psError(PS_ERR_UNKNOWN, false, "should have affected 1 row but %" PRIu64 " rows were modified", affected);
+                return false;
+            }
+        }
+
+        // point of no return
+        if (!psDBCommit(config->dbh)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+
+        return true;
+    } 
+
+    // try not to insert duplicate pzPendingExp/pzPendingImfile entries
+
+    // create a temp table
+    {
+        char *query = 
+            "CREATE TEMPORARY TABLE incoming (exp_id VARCHAR(64), camera VARCHAR(64), telescope VARCHAR(64), file_id VARCHAR(64), bytes INT, md5sum VARCHAR(32), class VARCHAR(64), class_id VARCHAR(64), uri VARCHAR(255), PRIMARY KEY(exp_id, camera, telescope, class, class_id)) ENGINE=MEMORY";
+
+        if (!p_psDBRunQuery(config->dbh, query)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(newImfiles);
+            return false;
+        }
+    }
+
+    // load the imfiles (files) into the temp table
+    {
+        char *query = "INSERT INTO incoming (exp_id, camera, telescope, file_id, bytes, md5sum, class, class_id, uri) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
+
+        long inserted = p_psDBRunQueryPrepared(config->dbh, newImfiles, query);
+        if (inserted < 0) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(newImfiles);
+            return false;
+        }
+        // sanity check that we actually inserted something
+        if (inserted == 0) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error -- we should have inserted at least one row");
+            psFree(newImfiles);
+            return false;
+        }
+    }
+
+    psFree(newImfiles);
+
+    // queue the imfiles (files) in pzPendingImfile -- must be done before the
+    // imfile is insterted into summitImfile (after that happens when don't
+    // know which imfiles are new anymore (well thats not exactly true but it
+    // make be someday if we have to beable to resync to modified datastore
+    // entries)
+    {
+        char *query = 
+            "INSERT INTO pzPendingImfile" 
+            "   SElECT"
+            "       incoming.exp_id,"
+            "       incoming.camera,"
+            "       incoming.telescope,"
+            "       incoming.class,"
+            "       incoming.class_id,"
+            "       NULL" // exp_tag, will be assigned later
+            "   FROM incoming"
+            "   JOIN pzPendingExp"
+            "       USING(exp_id, camera, telescope)"
+            "   LEFT JOIN summitImfile"
+            "       USING(exp_id, camera, telescope, class, class_id)"
+            "   WHERE"
+            "       summitImfile.exp_id is NULL"
+            "       AND summitImfile.camera is NULL"
+            "       AND summitImfile.telescope is NULL"
+            "       AND summitImfile.class is NULL"
+            "       AND summitImfile.class_id is NULL";
+
+        if (!p_psDBRunQuery(config->dbh, query)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+    }
+
+    // copy imfiles (files) from the temp table into summitImfiles
+    {
+        char *query = 
+            "INSERT INTO summitImfile" 
+            "   SElECT"
+            "       incoming.exp_id,"
+            "       incoming.camera,"
+            "       incoming.telescope,"
+            "       incoming.file_id,"
+            "       incoming.bytes,"
+            "       incoming.md5sum,"
+            "       incoming.class,"
+            "       incoming.class_id,"
+            "       incoming.uri"
+            "   FROM incoming"
+            "   LEFT JOIN summitImfile"
+            "       USING(exp_id, camera, telescope, class, class_id)"
+            "   WHERE"
+            "       summitImfile.exp_id is NULL"
+            "       AND summitImfile.camera is NULL"
+            "       AND summitImfile.telescope is NULL"
+            "       AND summitImfile.class is NULL"
+            "       AND summitImfile.class_id is NULL";
+
+        if (!p_psDBRunQuery(config->dbh, query)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+    }
+
+    // update summitExp.imfiles (should have been NULL)
+    {
+        char *query = 
+            "UPDATE summitExp"
+            " SET imfiles = (SELECT COUNT(*) FROM summitImfile"
+            "   WHERE"
+            "       exp_id = '%s'"
+            "       AND camera = '%s'"
+            "       AND telescope = '%s'"
+            ")" 
+            " WHERE"
+            "   exp_id = '%s'"
+            "   AND camera = '%s'"
+            "   AND telescope = '%s'";
+        if (!p_psDBRunQuery(config->dbh, query, filesetid, camera, telescope, filesetid, camera, telescope)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+
+        // sanity check: we should have updated only one row
+        psU64 affected = psDBAffectedRows(config->dbh);
+        if (psDBAffectedRows(config->dbh) != 1) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "should have affected 1 row but %" PRIu64 " rows were modified", affected);
+            return false;
+        }
+    }
+
+    // copy the summitExp row into newExp in order to create a new exp_tag
+    {
+        psString exp_tag = pxGenExpTag(config, filesetid);
+        if (!exp_tag) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+
+        char *query = 
+            "INSERT INTO newExp" 
+            "   SElECT"
+            "       '%s'," // exp_tag
+            "       summitExp.exp_id,"
+            "       summitExp.camera,"
+            "       summitExp.telescope,"
+            "       summitExp.dateobs,"
+            "       summitExp.exp_type,"
+            "       summitExp.imfiles,"
+            "       0"      // error flags 
+            "   FROM summitExp"
+            "   WHERE"
+            "       summitExp.exp_id = '%s'"
+            "       AND summitExp.camera = '%s'"
+            "       AND summitExp.telescope = '%s'";
+
+        if (!p_psDBRunQuery(config->dbh, query, exp_tag, filesetid, camera, telescope)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(exp_tag);
+            return false;
+        }
+        psFree(exp_tag);
+
+        // sanity check: we should have inserted only one row
+        psU64 affected = psDBAffectedRows(config->dbh);
+        if (psDBAffectedRows(config->dbh) != 1) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "should have affected 1 row but %" PRIu64 " rows were modified", affected);
+            return false;
+        }
+    }
+ 
+    // set the exp_tag for the imfiles (files) in the exposure (fileset) that
+    // we are investigating
+    {
+        char *query = 
+            "UPDATE pzPendingImfile"
+            " SET exp_tag = (SELECT exp_tag from newExp"
+            "                WHERE exp_id = '%s'"
+            "                AND camera = '%s'"
+            "                AND telescope = '%s')"
+            " WHERE"
+            "   exp_id = '%s'"
+            "   AND camera = '%s'"
+            "   AND telescope = '%s'";
+        if (!p_psDBRunQuery(config->dbh, query, filesetid, camera, telescope, filesetid, camera, telescope)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+    }
+
+    // cp the pzPendingExp entry to pzDoneExp
+    {
+        char *query = 
+            "INSERT INTO pzDoneExp"
+            "   SELECT"
+            "       pzPendingExp.*"
+            "   FROM pzPendingExp"
+            "   WHERE"
+            "       pzPendingExp.exp_id = '%s'"
+            "       AND pzPendingExp.camera = '%s'"
+            "       AND pzPendingExp.telescope = '%s'";
+        if (!p_psDBRunQuery(config->dbh, query, filesetid, camera, telescope)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+ 
+        // sanity check: we should have inserted only one row
+        psU64 affected = psDBAffectedRows(config->dbh);
+        if (psDBAffectedRows(config->dbh) != 1) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "should have affected 1 row but %" PRIu64 " rows were modified", affected);
+            return false;
+        }
+    }
+ 
+    // remove the pzPendingExp entry for this exp (fileset)
+    {
+        char *query = 
+            "DELETE FROM pzPendingExp"
+            " WHERE"
+            "   exp_id = '%s'"
+            "   AND camera = '%s'"
+            "   AND telescope = '%s'";
+        if (!p_psDBRunQuery(config->dbh, query, filesetid, camera, telescope)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+ 
+        // sanity check: we should have removed only one row
+        psU64 affected = psDBAffectedRows(config->dbh);
+        if (psDBAffectedRows(config->dbh) != 1) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "should have affected 1 row but %" PRIu64 " rows were modified", affected);
+            return false;
+        }
+    }
+
+    // point of no return
+    if (!psDBCommit(config->dbh)) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static psArray *parseFiles(pxConfig *config, const char *str)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(str, NULL);
+
+    // these are constants for all records parsed -- look them up before we do
+    // any work
+    bool status = false;
+    psString exp_id = psMetadataLookupStr(&status, config->args, "-filesetid");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for '-filesetid'");
+        return NULL;
+    }
+    if (!exp_id) {
+        psError(PS_ERR_UNKNOWN, true, "-filesetid is required");
+        return NULL;
+    }
+
+    psString camera = psMetadataLookupStr(&status, config->args, "-inst");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -inst");
+        return NULL;
+    }
+    if (!camera) {
+        psError(PS_ERR_UNKNOWN, true, "-inst is required");
+        return NULL;
+    }
+
+    psString telescope = psMetadataLookupStr(&status, config->args, "-telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -telescope");
+        return NULL;
+    }
+    if (!telescope) {
+        psError(PS_ERR_UNKNOWN, true, "-telescope is required");
+        return NULL;
+    }
+
+    // split the string into lines
+    psList *doc = psStringSplit(str, "\n", false);
+
+    psListIterator *lineCursor = psListIteratorAlloc(doc, 0, false);
+
+    psArray *pzPendingImfiles = psArrayAllocEmpty(psListLength(doc));
+    psString line;
+    while ((line = psListGetAndIncrement(lineCursor))) {
+        psTrace("pzgetimfiles", PS_LOG_INFO, "parsing line: %s\n", line);
+
+        // split line into tokens
+        psList *tokens = psStringSplit(line, " ", false);
+
+        // check to see if this line is a comment (or if the first token is
+        // NULL)
+        if (!psListGet(tokens, 0) || *((char *)psListGet(tokens, 0)) == '#') {
+            psFree(tokens);
+            continue;
+        }
+
+        // check that we have the right number of tokens
+        // print "# uri fileid bytes md5sum type \n";
+        if (!psListLength(tokens) > 5) {
+            psError(PS_ERR_UNKNOWN, true, "invalid line format: %s", line);
+            psFree(tokens);
+            psFree(pzPendingImfiles);                
+            psFree(lineCursor);
+            psFree(doc);
+            return false;
+        }
+
+        // find the values of interest
+        psListIterator *tokenCursor = psListIteratorAlloc(tokens, 0, false);
+        char *uri       = psListGetAndIncrement(tokenCursor);
+        char *file_id   = psListGetAndIncrement(tokenCursor); // fileid
+        char *bytes     = psListGetAndIncrement(tokenCursor); // bytes
+        char *md5sum    = psListGetAndIncrement(tokenCursor); // md5sum
+        char *class     = psListGetAndIncrement(tokenCursor); // type
+        char *class_id  = psListGetAndIncrement(tokenCursor); // chipname
+
+        // create a new metadata to represent this line and it's values
+        psMetadata *md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, exp_id)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, camera)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, telescope)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "file_id", 0, NULL, file_id)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item bytes");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        if (!psMetadataAddS64(md, PS_LIST_TAIL, "bytes", 0, NULL, (psS64)atoll(bytes))) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item bytes");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "md5sum", 0, NULL, md5sum)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item md5sum");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, NULL, class)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, class_id)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, uri)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+            psFree(md);
+            psFree(tokenCursor);
+            psFree(tokens);
+            return NULL;
+        }
+
+        // must be freed after the new metadata is built -- holds the strings
+        psFree(tokenCursor);
+        psFree(tokens);
+
+        // debugging
+        if (psTraceGetLevel("pzgetimfiles") >= PS_LOG_INFO) {
+            psString doc = psMetadataConfigFormat(md);
+            psTrace("pzgetimfiles", PS_LOG_INFO, "parsed line as:\n %s\n", doc);
+            psFree(doc);
+        }
+
+        psArrayAdd(pzPendingImfiles, 0, md);
+
+        psFree(md);
+    }
+
+    psFree(lineCursor);
+    psFree(doc);
+
+    return pzPendingImfiles;;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pzgetimfiles.h
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pzgetimfiles.h	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pzgetimfiles.h	(revision 22073)
@@ -0,0 +1,27 @@
+/*
+ * pzgetimfiles.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PZGETIMFILES_H
+#define PZGETIMFILES 1
+
+#include "pxtools.h"
+
+pxConfig *pzgetimfilesConfig(pxConfig *config, int argc, char **argv);
+
+#endif // PZGETIMFILES_H
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pzgetimfilesConfig.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pzgetimfilesConfig.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pzgetimfilesConfig.c	(revision 22073)
@@ -0,0 +1,85 @@
+/*
+ * pzgetimfilesConfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "pxtools.h"
+
+pxConfig *pzgetimfilesConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (! config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration!\n");
+        psFree(config);
+        return NULL;
+    }
+
+    psMetadata *args = psMetadataAlloc();
+    psMetadataAddStr(args , PS_LIST_TAIL, "-uri", 0,
+            "DataStore FileSet URI", "");
+    psMetadataAddStr(args , PS_LIST_TAIL, "-filesetid", 0,
+            "FileSet ID", "");
+    psMetadataAddStr(args, PS_LIST_TAIL, "-inst",  0,
+            "search by camera", NULL);
+    psMetadataAddStr(args, PS_LIST_TAIL, "-telescope",  0,
+            "search by telescope", NULL);
+
+
+    bool status = false;
+    if (!psArgumentParse(args, &argc, argv)
+        || argc != 1
+        || strcmp(psMetadataLookupStr(&status, args, "-uri"), "") == 0
+        || strcmp(psMetadataLookupStr(&status, args, "-filesetid"), "") == 0
+    ) {
+        fprintf(stderr, "error parsing arguments\n");
+        printf("\nPan-STARRS Phase Z Search Tool\n");
+        printf("Usage: %s -uri <uri> -filesetid <id>\n\n", argv[0]);
+        psArgumentHelp(args);
+        psFree(args);
+        psFree(config);
+        return NULL;
+    }
+
+    config->args = args;
+    // don't free args here as it's silly to increment the ref count then
+    // "free" it
+
+    // define Database handle, if used
+    config->dbh = pmConfigDB(config->modules);
+    if(!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't connect to db\n");
+        psFree(config);
+        return NULL;
+    }
+
+    return config;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pztool.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pztool.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pztool.c	(revision 22073)
@@ -0,0 +1,444 @@
+/*
+ * pztool.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <inttypes.h>
+
+#include "pxtools.h"
+#include "pztool.h"
+
+static bool seenMode(pxConfig *config);
+static bool pendingExpMode(pxConfig *config);
+static bool pendingImfileMode(pxConfig *config);
+static bool copydoneMode(pxConfig *config);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+                goto FAIL; \
+            } \
+    break;
+
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = pztoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(PZTOOL_MODE_SEEN, seenMode);
+        MODECASE(PZTOOL_MODE_PENDINGEXP, pendingExpMode);
+        MODECASE(PZTOOL_MODE_PENDINGIMFILE, pendingImfileMode);
+        MODECASE(PZTOOL_MODE_COPYDONE, copydoneMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+static bool seenMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psString query = psStringCopy("SELECT * FROM summitExp");
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "summitExp");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("pztool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "summitExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool pendingExpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psString query = psStringCopy(
+            "SELECT"
+            "   summitExp.*"
+            " FROM pzPendingExp"
+            " JOIN summitExp"
+            "   USING(exp_id, camera, telescope)"
+        );
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "pzPendingExp");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("pztool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            psFree(output);
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "summitExp", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool pendingImfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psString query = psStringCopy(
+            "SELECT"
+            "   summitImfile.*,"
+            "   pzPendingImfile.exp_tag"
+            " FROM pzPendingImfile"
+            " JOIN summitImfile"
+            "   USING(exp_id, camera, telescope, class, class_id)"
+        );
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "pzPendingImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("pztool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            psFree(output);
+            return false;
+        }
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "pzPendingImfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool copydoneMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psString exp_id = psMetadataLookupStr(&status, config->args, "-exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_id");
+        return false;
+    }
+    if (!exp_id) {
+        psError(PS_ERR_UNKNOWN, true, "-exp_id is required");
+        return false;
+    }
+
+    psString camera = psMetadataLookupStr(&status, config->args, "-inst");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -inst");
+        return NULL;
+    }
+    if (!camera) {
+        psError(PS_ERR_UNKNOWN, true, "-inst is required");
+        return NULL;
+    }
+
+    psString telescope = psMetadataLookupStr(&status, config->args, "-telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -telescope");
+        return NULL;
+    }
+    if (!telescope) {
+        psError(PS_ERR_UNKNOWN, true, "-telescope is required");
+        return NULL;
+    }
+
+    psString class = psMetadataLookupStr(&status, config->args, "-class");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -class");
+        return NULL;
+    }
+    if (!class) {
+        psError(PS_ERR_UNKNOWN, true, "-class is required");
+        return NULL;
+    }
+
+    psString class_id = psMetadataLookupStr(&status, config->args, "-class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -class_id");
+        return NULL;
+    }
+    if (!class_id) {
+        psError(PS_ERR_UNKNOWN, true, "-class_id is required");
+        return NULL;
+    }
+
+    psString uri = psMetadataLookupStr(&status, config->args, "-uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -uri");
+        return false;
+    }
+    if (!uri) {
+        psError(PS_ERR_UNKNOWN, true, "-uri is required");
+        return false;
+    }
+
+    // need to know exp_id, camera, telescope, class, class_id (to find the
+    // pzPendingImfile entry and the URI for the newImfile entry.
+
+    // start a transaction so it's all rows or nothing
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+  
+    // insert new imfile into newImfile
+    {
+        char *query =
+            "INSERT INTO newImfile"
+            "   SElECT"
+            "       pzPendingImfile.exp_tag,"
+            "       pzPendingImfile.class,"
+            "       pzPendingImfile.class_id,"
+            "       '%s'," // uri of downloaded file
+            "       0"     // error flags
+            "   FROM pzPendingImfile"
+            "   WHERE"
+            "       pzPendingImfile.exp_id = '%s'"
+            "       AND pzPendingImfile.camera = '%s'"
+            "       AND pzPendingImfile.telescope = '%s'"
+            "       AND pzPendingImfile.class = '%s'"
+            "       AND pzPendingImfile.class_id = '%s'";
+
+        if (!p_psDBRunQuery(config->dbh, query, uri, exp_id, camera, telescope, class, class_id)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        } 
+
+        // sanity check: we should have inserted only one row
+        psU64 affected = psDBAffectedRows(config->dbh);
+        if (psDBAffectedRows(config->dbh) != 1) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "should have affected 1 row but %" PRIu64 " rows were modified", affected);
+            return false;
+        }
+    }
+
+    // cp the imfile into pzDoneImfile
+    {
+        char *query =
+            "INSERT INTO pzDoneImfile"
+            "   SElECT"
+            "       pzPendingImfile.*,"
+            "       '%s'" // uri of downloaded file
+            "   FROM pzPendingImfile"
+            "   WHERE"
+            "       pzPendingImfile.exp_id = '%s'"
+            "       AND pzPendingImfile.camera = '%s'"
+            "       AND pzPendingImfile.telescope = '%s'"
+            "       AND pzPendingImfile.class = '%s'"
+            "       AND pzPendingImfile.class_id = '%s'";
+
+        if (!p_psDBRunQuery(config->dbh, query, uri, exp_id, camera, telescope, class, class_id)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        } 
+
+        // sanity check: we should have inserted only one row
+        psU64 affected = psDBAffectedRows(config->dbh);
+        if (psDBAffectedRows(config->dbh) != 1) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "should have affected 1 row but %" PRIu64 " rows were modified", affected);
+            return false;
+        }
+    }
+
+    // removve the entry from pzPendingImfile
+    {
+        char *query =
+            "DELETE FROM pzPendingImfile"
+            "   WHERE"
+            "       pzPendingImfile.exp_id = '%s'"
+            "       AND pzPendingImfile.camera = '%s'"
+            "       AND pzPendingImfile.telescope = '%s'"
+            "       AND pzPendingImfile.class = '%s'"
+            "       AND pzPendingImfile.class_id = '%s'";
+
+        if (!p_psDBRunQuery(config->dbh, query, exp_id, camera, telescope, class, class_id)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        } 
+
+        // sanity check: we should have removed only one row
+        psU64 affected = psDBAffectedRows(config->dbh);
+        if (psDBAffectedRows(config->dbh) != 1) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "should have affected 1 row but %" PRIu64 " rows were modified", affected);
+            return false;
+        }
+    }
+
+    // point of no return
+    if (!psDBCommit(config->dbh)) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pztool.h
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pztool.h	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pztool.h	(revision 22073)
@@ -0,0 +1,35 @@
+/*
+ * pztool.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PZTOOL_H
+#define PZTOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    PZTOOL_MODE_NONE      = 0x0,
+    PZTOOL_MODE_SEEN,
+    PZTOOL_MODE_PENDINGEXP,
+    PZTOOL_MODE_PENDINGIMFILE,
+    PZTOOL_MODE_COPYDONE
+} pztoolMode;
+
+pxConfig *pztoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // PZTOOL_H
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/pztoolConfig.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/pztoolConfig.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/pztoolConfig.c	(revision 22073)
@@ -0,0 +1,213 @@
+/*
+ * pztoolConfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "pztool.h"
+
+pxConfig *pztoolConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (! config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration!\n");
+        psFree(config);
+        return NULL;
+    }
+
+    // -seen
+    psMetadata *seenArgs = psMetadataAlloc();
+    psMetadataAddStr(seenArgs, PS_LIST_TAIL, "-exp_tag", 0,
+            "define exposure ID", NULL); 
+    psMetadataAddStr(seenArgs, PS_LIST_TAIL, "-inst", 0,
+            "define camera ID", NULL); 
+    psMetadataAddStr(seenArgs, PS_LIST_TAIL, "-telescope", 0,
+            "define telescope ID", NULL); 
+    psMetadataAddStr(seenArgs, PS_LIST_TAIL, "-exp_type", 0,
+            "define exposure type", NULL); 
+    psMetadataAddBool(seenArgs, PS_LIST_TAIL, "-simple", 0,
+            "use the simple output format", false);
+    
+    // -pendingexp
+    psMetadata *pendingexpArgs = psMetadataAlloc();
+    psMetadataAddStr(pendingexpArgs, PS_LIST_TAIL, "-exp_tag", 0,
+            "define exposure ID", NULL); 
+    psMetadataAddStr(pendingexpArgs, PS_LIST_TAIL, "-inst", 0,
+            "define camera ID", NULL); 
+    psMetadataAddStr(pendingexpArgs, PS_LIST_TAIL, "-telescope", 0,
+            "define telescope ID", NULL); 
+    psMetadataAddStr(pendingexpArgs, PS_LIST_TAIL, "-exp_type", 0,
+            "define exposure type", NULL); 
+    psMetadataAddBool(pendingexpArgs, PS_LIST_TAIL, "-simple", 0,
+            "use the simple output format", false);
+
+    // -pendingimfile
+    psMetadata *pendingimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-exp_tag", 0,
+            "define exposure ID", NULL); 
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-inst", 0,
+            "define camera ID", NULL); 
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-telescope", 0,
+            "define telescope ID", NULL); 
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-exp_type", 0,
+            "define exposure type", NULL); 
+    psMetadataAddBool(pendingimfileArgs, PS_LIST_TAIL, "-simple", 0,
+            "use the simple output format", false);
+
+    // -copydone
+    psMetadata *copydoneArgs = psMetadataAlloc();
+    psMetadataAddStr(copydoneArgs, PS_LIST_TAIL, "-exp_id", 0,
+            "define exposure ID", NULL); 
+    psMetadataAddStr(copydoneArgs, PS_LIST_TAIL, "-inst", 0,
+            "define camera ID", NULL); 
+    psMetadataAddStr(copydoneArgs, PS_LIST_TAIL, "-telescope", 0,
+            "define telescope ID", NULL); 
+    psMetadataAddStr(copydoneArgs, PS_LIST_TAIL, "-class", 0,
+            "define class", NULL);
+    psMetadataAddStr(copydoneArgs, PS_LIST_TAIL, "-class_id", 0,
+            "define class_id", NULL);
+    psMetadataAddStr(copydoneArgs, PS_LIST_TAIL, "-uri", 0,
+            "define storage uri", NULL);
+
+#define PXTOOL_MODE(option, modeval, argset) \
+{ \
+    int N = 0; \
+    if ((N = psArgumentGet (argc, argv, option))) { \
+        psArgumentRemove (N, &argc, argv); \
+        if (config->mode) { \
+            psError(PS_ERR_UNKNOWN, true, "only one mode selection is allowed"); \
+            psFree(config); \
+            return NULL; \
+        } \
+        config->mode = modeval; \
+        config->args = psMemIncrRefCounter(argset); \
+    } \
+    if (!psMetadataAddMetadata(argSets, PS_LIST_TAIL, option, 0, NULL, argset)) {;\
+        psError(PS_ERR_UNKNOWN, false, "failed to add argset for %s", option); \
+    } \
+    psFree(argset); \
+}
+
+    psMetadata *argSets = psMetadataAlloc();
+    // find which mode we're running under
+    PXTOOL_MODE("-seen",            PZTOOL_MODE_SEEN,         seenArgs);
+    PXTOOL_MODE("-pendingexp",      PZTOOL_MODE_PENDINGEXP,   pendingexpArgs);
+    PXTOOL_MODE("-pendingimfile",   PZTOOL_MODE_PENDINGIMFILE,pendingimfileArgs);
+    PXTOOL_MODE("-copydone",        PZTOOL_MODE_COPYDONE,     copydoneArgs);
+
+    bool argErr = false;
+    if (config->mode == PZTOOL_MODE_NONE) {
+        argErr = true;
+        fprintf (stderr, "mode argument is required\n");
+    } else if (! psArgumentParse(config->args, &argc, argv) || argc != 1) {
+        argErr = true;
+        fprintf (stderr, "error parsing arguments\n");
+    }
+
+    if (argErr) {
+        printf("\nPan-STARRS Detrend Tool\n");
+        printf("Usage: %s <mode> [<options>]\n\n", argv[0]);
+        printf(" <mode> :\n\n"); 
+
+        psMetadataIterator *iter = psMetadataIteratorAlloc(argSets, 0, NULL);
+        psMetadataItem *item = NULL;
+        while ((item = psMetadataGetAndIncrement(iter))) {
+            if (!item->type == PS_DATA_METADATA) {
+                psAbort("all options must be specified as a metadata");
+            }
+
+            fprintf(stdout, "%s ", item->name);
+            psArgumentHelp(item->data.md);
+        }
+        psFree(iter);
+
+        psFree(argSets);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+
+    // setup search criterion
+#define addWhereStr(name) \
+{ \
+    psString str = NULL; \
+    bool status = false; \
+    if ((str = psMetadataLookupStr(&status, config->args, "-" #name))) { \
+        if (!psMetadataAddStr(config->where, PS_LIST_TAIL, #name, 0, "==", str)) {\
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+            psFree(config); \
+            return NULL; \
+        } \
+    } \
+}
+
+    // generate SQL where clause
+    config->where = psMetadataAlloc();
+
+    addWhereStr(exp_tag); 
+    addWhereStr(telescope); 
+    // convert '-inst' to 'camera'
+    {
+        psString str = NULL;
+        bool status = false;
+        if ((str = psMetadataLookupStr(&status, config->args, "-inst"))) {
+            if (!psMetadataAddStr(config->where, PS_LIST_TAIL, "camera", 0, "==", str)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+                psFree(config);
+                return NULL;
+            }
+        }
+    }
+    addWhereStr(exp_type); 
+    addWhereStr(class); 
+    addWhereStr(class_id); 
+
+    if (config->where->list->n < 1) {
+        psFree(config->where);
+        config->where = NULL;
+    }
+
+    // define Database handle, if used
+    // do this last so we don't setup a connection before CLI options are
+    // validated
+    config->dbh = pmConfigDB(config->modules);
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    // save argv/argc
+    config->argv = argv;
+    config->argc = argc;
+
+    return config;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/regtool.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/regtool.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/regtool.c	(revision 22073)
@@ -0,0 +1,1133 @@
+/*
+ * regtool.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+
+#include "pxtools.h"
+#include "pxdata.h"
+#include "regtool.h"
+#include "chiptool.h"
+
+static bool pendingimfileMode(pxConfig *config);
+static bool addprocessedimfileMode(pxConfig *config);
+static bool processedimfileMode(pxConfig *config);
+static bool updateprocessedimfileMode(pxConfig *config);
+
+static bool pendingexpMode(pxConfig *config);
+static bool addprocessedexpMode(pxConfig *config);
+static bool processedexpMode(pxConfig *config);
+static bool updateprocessedexpMode(pxConfig *config);
+
+
+// static guidePendingExpRow *newToP1PendingExp(newExpRow *newExp);
+//static chipPendingExpRow *newTochipPendingExp(pxConfig *config, newExpRow *newExp);
+//static chipPendingImfileRow *rawImfileTochipPendingImfile(pxConfig *config, psS64 chip_id, rawImfileRow *rawImfile);
+
+static rawExpRow *newToRawExp(pxConfig *config, newExpRow *exp);
+static rawImfileRow *newToRawImfile(pxConfig *config, newImfileRow *exp);
+//static psU32 mapCodeStrToInt(const char *codeStr);
+
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = regtoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(REGTOOL_MODE_PENDINGIMFILE,         pendingimfileMode);
+        MODECASE(REGTOOL_MODE_ADDPROCESSEDIMFILE,    addprocessedimfileMode);
+        MODECASE(REGTOOL_MODE_PROCESSEDIMFILE,       processedimfileMode);
+        MODECASE(REGTOOL_MODE_UPDATEPROCESSEDIMFILE, updateprocessedimfileMode);
+        MODECASE(REGTOOL_MODE_PENDINGEXP,            pendingexpMode);
+        MODECASE(REGTOOL_MODE_ADDPROCESSEDEXP,       addprocessedexpMode);
+        MODECASE(REGTOOL_MODE_PROCESSEDEXP,          processedexpMode);
+        MODECASE(REGTOOL_MODE_UPDATEPROCESSEDEXP,    updateprocessedexpMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+
+static bool pendingimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    // select newImfiles that:
+    // exp_tag is in newExp
+    // don't have their exp_tag in rawExp 
+    // XXX having the same exp_tag in newExp and raw*Exp is probably an error
+    // that should be checked for
+
+    psString query = pxDataGet("regtool_pendingimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+	// XXX PS_EXIT_PROG_ERROR (incorrect SQL) or SYS_ERROR (database comms)
+        psError(PXTOOLS_ERR_PROG, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+        
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("regtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    simple = psMetadataLookupBool(&status, config->args, "-simple");
+    if (!status) {
+	psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -simple");
+	return false;
+    }
+
+    // negate simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "regPendingImfile", !simple)) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool addprocessedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psString query = pxDataGet("regtool_find_unprocessed_imfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    {
+        // build a query to search by exp_tag, class, class_id
+        psMetadata *where = psMetadataAlloc();
+        bool status = false;
+        psString exp_tag = psMetadataLookupStr(&status, config->args, "-exp_tag");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_tag");
+            psFree(query);
+            return false;
+        }
+        if (exp_tag) {
+            if (!psMetadataAddStr(where, PS_LIST_TAIL, "exp_tag", 0, "==", exp_tag)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+                psFree(where);
+                psFree(query);
+                return false;
+            }
+        }
+
+        psString class_id = psMetadataLookupStr(&status, config->args, "-class_id");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -class_id");
+            psFree(query);
+            return false;
+        }
+        if (class_id) {
+            if (!psMetadataAddStr(where, PS_LIST_TAIL, "class_id", 0, "==", class_id)) {
+                psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+                psFree(where);
+                psFree(query);
+                return false;
+            }
+        }
+
+        // there's not
+        psString whereClaus = psDBGenerateWhereSQL(where, NULL);
+        psFree(where);
+        if (whereClaus) {
+            psStringAppend(&query, " %s", whereClaus);
+            psFree(whereClaus);
+        }
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psError(PS_ERR_UNKNOWN, false, "no pending newImfile rows found");
+        psFree(output);
+        return false;
+    }
+
+    // insert 'newImfile's into rawImfile
+    if (psArrayLength(output) > 0) {
+        // start a transaction so we don't end up half of the imfiles we were
+        // trying to update uninserted
+        if (!psDBTransaction(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(output);
+            return false;
+        }
+
+        for (long i = 0; i < psArrayLength(output); i++) {
+            // convert newImfile metadata -> newImfile object
+            newImfileRow *object = newImfileObjectFromMetadata(output->data[i]);
+            // convert newImfile object -> rawImfile object
+            rawImfileRow *imfile = newToRawImfile(config, object); 
+            if (!imfile) {
+                // rollback
+                if (!psDBRollback(config->dbh)) {
+                    psError(PS_ERR_UNKNOWN, false, "database error");
+                }
+                psError(PS_ERR_UNKNOWN, false, "failed to create a new rawImfile row");
+                psFree(object);
+                psFree(output);
+                return false;
+            }
+            // insert the rawImfile object into the database
+            if (!rawImfileInsertObject(config->dbh, imfile)) {
+                // rollback
+                if (!psDBRollback(config->dbh)) {
+                    psError(PS_ERR_UNKNOWN, false, "database error");
+                }
+                psError(PS_ERR_UNKNOWN, false, "failed to insert row into the database");
+                psFree(imfile);
+                psFree(object);
+                psFree(output);
+                return false;
+            }
+            psFree(imfile);
+            // remove the neImfile object from the database
+            if (!newImfileDeleteObject(config->dbh, object)) {
+                // rollback
+                if (!psDBRollback(config->dbh)) {
+                    psError(PS_ERR_UNKNOWN, false, "database error");
+                }
+                psError(PS_ERR_UNKNOWN, false, "failed to delete row from the database");
+                psFree(object);
+                psFree(output);
+                return false;
+            }
+            psFree(object);
+        }
+
+        // point of no return for rawImfile
+        if (!psDBCommit(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool processedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    bool faulted = psMetadataLookupU64(&status, config->args, "-faulted");
+    if (!status) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -faulted");
+        return false;
+    }
+
+    psString query = pxDataGet("regtool_processedimfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "rawImfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND rawImfile.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND rawImfile.fault = 0");
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("regtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "rawImfile", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool updateprocessedimfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psS8 code = psMetadataLookupS8(&status, config->args, "-code");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -code");
+        return false;
+    }
+
+    if (!pxSetFaultCode(config->dbh, "rawImfile", config->where, code)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to set set fault flag");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool pendingexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    // return only exps that:
+    // are not in rawExp 
+    // have ALL of their imfiles in rawImfile (by count)
+    // and have no associated imfiles left in newImfile
+    psString query = pxDataGet("regtool_pendingexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+	// XXX PS_EXIT_PROG_ERROR (incorrect SQL) or SYS_ERROR (database comms)
+        psError(PXTOOLS_ERR_PROG, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("regtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    simple = psMetadataLookupBool(&status, config->args, "-simple");
+    if (!status) {
+	psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -simple");
+	return false;
+    }
+
+    // negate simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "regPendingExp", !simple)) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool addprocessedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // make sure that the exp_tag(s) are ready to be updated based on:
+    // exp_tag is not in rawExp
+    // exp_tag is not in newImfile
+    // that the correct count of imfiles is in rawImfile
+   
+    bool status = false;
+    psString exp_tag = psMetadataLookupStr(&status, config->args, "-exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_tag");
+        return false;
+    }
+    if (!exp_tag) {
+        psError(PS_ERR_UNKNOWN, true, "-exp_tag is required");
+        return false;
+    }
+
+    bool detrend = psMetadataLookupBool(&status, config->args, "-detrend");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -detrend");
+        return false;
+    }
+
+    psString query = pxDataGet("regtool_find_unprocessed_exp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query, exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("regtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // start a transaction so we don't end up with an exp in both rawExp &
+    // newExp
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(output);
+        return false;
+    }
+
+    // insert the exp into rawExp
+    for (long i = 0; psArrayLength(output) > i; i++) {
+        psMetadata *row = output->data[i];
+        // convert metadata into a newExp object
+        newExpRow *newExp = newExpObjectFromMetadata(row);
+        // convert newExp object into a rawExp object
+        rawExpRow *rawExp = newToRawExp(config, newExp);
+        if (!rawExp) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to convert newExp to rawExp");
+            psFree(newExp);
+            psFree(output);
+            return false;
+        }
+
+        // insert the rawExp object into the database
+        if (!rawExpInsertObject(config->dbh, rawExp)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(rawExp);
+            psFree(newExp);
+            psFree(output);
+            return false;
+        }
+        psFree(rawExp);
+
+        // delete the newExp object from the database
+        if (!newExpDeleteObject(config->dbh, newExp)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(newExp);
+            psFree(output);
+            return false;
+        }
+
+        psFree(newExp);
+
+        // if this is a detrend image don't put it in the chip queue (and we're
+        // done)
+        if (detrend) {
+            continue;
+        }
+
+        // insert an entry into the chipPendingExp table
+        if (!chipQueueExpTag(config, exp_tag, NULL, NULL, NULL, NULL, NULL)) {
+            // rollback
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to queue chipPendingExp");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool processedexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    bool faulted = psMetadataLookupU64(&status, config->args, "-faulted");
+    if (!status) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -faulted");
+        return false;
+    }
+
+    psString query = pxDataGet("regtool_processedexp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "rawExp");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (faulted) {
+        // list only faulted rows
+        psStringAppend(&query, " %s", "AND rawExp.fault != 0");
+    } else {
+        // don't list faulted rows
+        psStringAppend(&query, " %s", "AND rawExp.fault = 0");
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("regtool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "rawExp", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool updateprocessedexpMode(pxConfig *config)
+{
+    bool status = false;
+    psS8 code = psMetadataLookupS8(&status, config->args, "-code");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -code");
+        return false;
+    }
+
+    if (!pxSetFaultCode(config->dbh, "rawExp", config->where, code)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to set set fault flag");
+        return false;
+    }
+
+    return true;
+}
+
+
+# if 0
+static guidePendingExpRow *newToP1PendingExp(newExpRow *newExp)
+{
+    return guidePendingExpRowAlloc(
+        newExp->exp_tag,
+        newExp->camera,
+        newExp->telescope,
+        newExp->exp_type,
+        newExp->imfiles,
+        "my filter",
+        0.1, // airmass
+        0.2, // ra
+        0.3, // dec
+        0.4, // exp time
+        0.5, // background
+        "my recipe",
+        0xff // XXX calc version number
+    );
+}
+#endif
+
+
+#if 0
+static chipPendingExpRow *newTochipPendingExp(pxConfig *config, newExpRow *exp)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(exp, NULL);
+
+    // optional
+    bool status = false;
+    psString label = psMetadataLookupStr(&status, config->args, "-label");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -label");
+        return false;
+    }
+
+    chipPendingExpRow *chipExp = chipPendingExpRowAlloc(
+            0,              // chip ID, assigned by db
+            exp->exp_tag,
+            0xdeadbeef,     // guide version
+            label,
+            "my recipe",    // recipe
+            NULL,           // expgroup
+            NULL            // dvodb
+    );
+
+    return chipExp;
+}
+
+
+static chipPendingImfileRow *rawImfileTochipPendingImfile(pxConfig *config, psS64 chip_id, rawImfileRow *rawImfile)
+{
+    return chipPendingImfileRowAlloc(
+            chip_id,
+            rawImfile->class_id,
+            rawImfile->uri
+    );
+}
+#endif
+
+
+static rawExpRow *newToRawExp(pxConfig *config, newExpRow *exp)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(exp, NULL);
+
+    bool status = false;
+    // optional
+    psString exp_type = psMetadataLookupStr(&status, config->args, "-exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_type");
+        return false;
+    }
+
+    psString filelevel = psMetadataLookupStr(&status, config->args, "-filelevel");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -filelevel");
+        return false;
+    }
+    if (!filelevel) {
+        psError(PS_ERR_UNKNOWN, true, "-filelevel is required");
+        return false;
+    }
+
+    psString filter = psMetadataLookupStr(&status, config->args, "-filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -filter");
+        return false;
+    }
+
+    psF32 airmass = psMetadataLookupF32(&status, config->args, "-airmass");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -airmass");
+        return false;
+    }
+
+    psF64 ra = psMetadataLookupF64(&status, config->args, "-ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -ra");
+        return false;
+    }
+
+    psF64 decl = psMetadataLookupF64(&status, config->args, "-decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -decl");
+        return false;
+    }
+
+    psF32 exp_time = psMetadataLookupF32(&status, config->args, "-exp_time");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_time");
+        return false;
+    }
+
+    psF64 bg = psMetadataLookupF64(&status, config->args, "-bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg");
+        return false;
+    }
+
+    psF64 bg_stdev = psMetadataLookupF64(&status, config->args, "-bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_stdev");
+        return false;
+    }
+
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, config->args, "-bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_mean_stdev");
+        return false;
+    }
+
+    psF64 alt = psMetadataLookupF64(&status, config->args, "-alt");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -alt");
+        return false;
+    }
+
+    psF64 az = psMetadataLookupF64(&status, config->args, "-az");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -az");
+        return false;
+    }
+
+    psF32 ccd_temp = psMetadataLookupF32(&status, config->args, "-ccd_temp");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -ccd_temp");
+        return false;
+    }
+
+    psF64 posang = psMetadataLookupF32(&status, config->args, "-posang");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -posang");
+        return false;
+    }
+
+    psString object = psMetadataLookupStr(&status, config->args, "-object");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -object");
+        return false;
+    }
+
+    psTime *dateobs = NULL;
+    {
+        psString dateobsStr = psMetadataLookupStr(&status, config->args, "-dateobs");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -dateobs");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (dateobsStr) {
+            dateobs = psTimeFromISO(dateobsStr, PS_TIME_UTC);
+        } else {
+            dateobs = NULL;
+        }
+    }
+
+    // default
+    psS8 code = psMetadataLookupS8(&status, config->args, "-code");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -code");
+        return false;
+    }
+
+    rawExpRow *raw = rawExpRowAlloc(
+        exp->exp_tag,
+        exp->exp_id,
+        exp->camera,
+        exp->telescope,
+        dateobs,
+        exp_type,
+        exp->imfiles,
+        filelevel,
+        exp->workdir,
+        filter,
+        airmass,
+        ra,
+        decl,
+        exp_time,
+        bg,
+        bg_stdev,
+        bg_mean_stdev,
+        alt,
+        az,
+        ccd_temp,
+        posang,
+        object,
+        0.0,
+        code
+    );
+
+    if (dateobs) {
+        psFree(dateobs);
+    }
+
+    return raw;
+}
+
+
+static rawImfileRow *newToRawImfile(pxConfig *config, newImfileRow *imfile)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(exp, NULL);
+
+    bool status = false;
+    // optional
+    psString exp_type = psMetadataLookupStr(&status, config->args, "-exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_type");
+        return false;
+    }
+
+    psString filelevel = psMetadataLookupStr(&status, config->args, "-filelevel");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -filelevel");
+        return false;
+    }
+
+    psString filter = psMetadataLookupStr(&status, config->args, "-filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -filter");
+        return false;
+    }
+
+    psF32 airmass = psMetadataLookupF32(&status, config->args, "-airmass");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -airmass");
+        return false;
+    }
+
+    psF64 ra = psMetadataLookupF64(&status, config->args, "-ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -ra");
+        return false;
+    }
+
+    psF64 decl = psMetadataLookupF64(&status, config->args, "-decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -decl");
+        return false;
+    }
+
+    psF32 exp_time = psMetadataLookupF32(&status, config->args, "-exp_time");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -exp_time");
+        return false;
+    }
+
+    psF64 bg = psMetadataLookupF64(&status, config->args, "-bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg");
+        return false;
+    }
+
+    psF64 bg_stdev = psMetadataLookupF64(&status, config->args, "-bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_stdev");
+        return false;
+    }
+
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, config->args, "-bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_mean_stdev");
+        return false;
+    }
+
+    psF64 alt = psMetadataLookupF64(&status, config->args, "-alt");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -alt");
+        return false;
+    }
+
+    psF64 az = psMetadataLookupF64(&status, config->args, "-az");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -az");
+        return false;
+    }
+
+    psF32 ccd_temp = psMetadataLookupF32(&status, config->args, "-ccd_temp");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -ccd_temp");
+        return false;
+    }
+
+    psF64 posang = psMetadataLookupF32(&status, config->args, "-posang");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -posang");
+        return false;
+    }
+
+    psString object = psMetadataLookupStr(&status, config->args, "-object");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -object");
+        return false;
+    }
+
+    psTime *dateobs = NULL;
+    {
+        psString dateobsStr = psMetadataLookupStr(&status, config->args, "-dateobs");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -dateobs");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (dateobsStr) {
+            dateobs = psTimeFromISO(dateobsStr, PS_TIME_UTC);
+        } else {
+            dateobs = NULL;
+        }
+    }
+
+    psS8 code = psMetadataLookupS8(&status, config->args, "-code");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -code");
+        return false;
+    }
+
+    rawImfileRow *raw = rawImfileRowAlloc(
+        imfile->exp_tag,
+        imfile->class_id,
+        imfile->uri,
+        exp_type,
+        filelevel,
+        filter,
+        airmass,
+        ra,
+        decl,
+        exp_time,
+        bg,
+        bg_stdev,
+        bg_mean_stdev,
+        alt,
+        az,
+        ccd_temp,
+        posang,
+        object,
+        dateobs,
+        code
+    );
+
+    psFree(dateobs);
+
+    return raw;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/regtool.h
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/regtool.h	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/regtool.h	(revision 22073)
@@ -0,0 +1,39 @@
+/*
+ * regtool.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef REGTOOL_H
+#define REGTOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    REGTOOL_MODE_NONE      = 0x0,
+    REGTOOL_MODE_PENDINGIMFILE,
+    REGTOOL_MODE_ADDPROCESSEDIMFILE,
+    REGTOOL_MODE_PROCESSEDIMFILE,
+    REGTOOL_MODE_UPDATEPROCESSEDIMFILE,
+    REGTOOL_MODE_PENDINGEXP,
+    REGTOOL_MODE_ADDPROCESSEDEXP,
+    REGTOOL_MODE_PROCESSEDEXP,
+    REGTOOL_MODE_UPDATEPROCESSEDEXP,
+} regtoolMode;
+
+pxConfig *regtoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // REGTOOL_H
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/regtoolConfig.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/regtoolConfig.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/regtoolConfig.c	(revision 22073)
@@ -0,0 +1,340 @@
+/*
+ * regtoolConfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+#include <math.h>
+
+#include "pxtools.h"
+#include "regtool.h"
+
+pxConfig *regtoolConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(PXTOOLS_ERR_CONFIG, false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    // -pendingimfile
+    psMetadata *pendingimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-exp_tag",  0,
+        "search by exposure ID", NULL);
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-class",  0,
+        "search by class", NULL);
+    psMetadataAddStr(pendingimfileArgs, PS_LIST_TAIL, "-class_id",  0,
+        "search by class ID", NULL);
+    psMetadataAddU64(pendingimfileArgs, PS_LIST_TAIL, "-limit",  0,
+        "limit result set to N items", 0);
+    psMetadataAddBool(pendingimfileArgs, PS_LIST_TAIL, "-simple",  0,
+        "use the simple output format", false);
+
+    // -addprocessedimfile
+    psMetadata *addprocessedimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-exp_tag",  0,
+        "define exposure ID", NULL);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-class_id",  0,
+        "define class ID", NULL);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-exp_type",  0,
+        "define exposure type", NULL);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-filelevel",  0,
+        "define filelevel", NULL);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-filter",  0,
+        "define filter ", NULL);
+    psMetadataAddF32(addprocessedimfileArgs, PS_LIST_TAIL, "-airmass",  0,
+        "define airmass", NAN);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-ra",  0,
+        "define RA", NAN);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-decl",  0,
+        "define DEC", NAN);
+    psMetadataAddF32(addprocessedimfileArgs, PS_LIST_TAIL, "-exp_time",  0,
+        "define exposure time", NAN);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-bg",  0,
+        "define exposue background", NAN);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-bg_stdev",  0,
+        "define exposue background stdev", NAN);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,
+        "define exposue background mean stdev", NAN);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-alt",  0,
+        "define altitute", NAN);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-az",  0,
+        "define azimuth", NAN);
+    psMetadataAddF32(addprocessedimfileArgs, PS_LIST_TAIL, "-ccd_temp",  0,
+        "define ccd tempature", NAN);
+    psMetadataAddF64(addprocessedimfileArgs, PS_LIST_TAIL, "-posang",  0,
+        "define rotator position angle", NAN);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-object",  0,
+        "define exposure object", NULL);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-dateobs",  0,
+        "define observation time", NULL);
+    psMetadataAddS8(addprocessedimfileArgs, PS_LIST_TAIL, "-code",  0,
+        "set fault code (required)", 0);
+
+    // -processedimfile
+    psMetadata *processedimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(processedimfileArgs, PS_LIST_TAIL, "-exp_tag",  0,
+        "search by exposure ID", NULL);
+    psMetadataAddStr(processedimfileArgs, PS_LIST_TAIL, "-class_id",  0,
+        "search by class ID", NULL);
+    psMetadataAddU64(processedimfileArgs, PS_LIST_TAIL, "-limit",  0,
+        "limit result set to N items", 0);
+    psMetadataAddBool(processedimfileArgs, PS_LIST_TAIL, "-faulted",  0,
+        "only return imfiles with a fault status set", false);
+    psMetadataAddBool(processedimfileArgs, PS_LIST_TAIL, "-simple",  0,
+        "use the simple output format", false);
+
+    // -updateprocessedimfile
+    psMetadata *updateprocessedimfileArgs = psMetadataAlloc();
+    psMetadataAddStr(updateprocessedimfileArgs, PS_LIST_TAIL, "-exp_tag",  0,
+        "search by exposure ID", NULL);
+    psMetadataAddStr(updateprocessedimfileArgs, PS_LIST_TAIL, "-class_id",  0,
+        "search by class ID", NULL);
+    psMetadataAddS8(updateprocessedimfileArgs, PS_LIST_TAIL, "-code",  0,
+        "set fault code (required)", 0);
+    
+
+    // -pendingexp
+    psMetadata *pendingexpArgs = psMetadataAlloc();
+    psMetadataAddStr(pendingexpArgs, PS_LIST_TAIL, "-exp_tag",  0,
+        "search by exposure ID", NULL);
+    psMetadataAddStr(pendingexpArgs, PS_LIST_TAIL, "-inst",  0,
+        "search by camera", NULL);
+    psMetadataAddStr(pendingexpArgs, PS_LIST_TAIL, "-telescope",  0,
+        "search by telescope", NULL);
+    psMetadataAddStr(pendingexpArgs, PS_LIST_TAIL, "-exp_type",  0,
+        "search by exposure type", NULL);
+    psMetadataAddStr(pendingexpArgs, PS_LIST_TAIL, "-imfiles",  0,
+        "search for exps with N imfiles", NULL);
+    psMetadataAddU64(pendingexpArgs, PS_LIST_TAIL, "-limit",  0,
+        "limit result set to N items", 0);
+    psMetadataAddBool(pendingexpArgs, PS_LIST_TAIL, "-simple",  0,
+        "use the simple output format", false);
+
+    // -addprocessedexp
+    psMetadata *addprocessedexpArgs = psMetadataAlloc();
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-exp_tag",  0,
+        "exp_tag to operate on (required)", NULL); 
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-exp_type",  0,
+        "define exposure type", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-filelevel",  0,
+        "define the data partitioning level of this file", NULL);
+    psMetadataAddStr(addprocessedimfileArgs, PS_LIST_TAIL, "-workdir",  0,
+        "define the \"default\" workdir for this exposure", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-filter",  0,
+        "define filter ", NULL);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-airmass",  0,
+        "define airmass", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-ra",  0,
+        "define RA", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-decl",  0,
+        "define DEC", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-exp_time",  0,
+        "define exposure time", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-bg",  0,
+        "define exposue background", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-bg_stdev",  0,
+        "define exposue background stdev", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-bg_mean_stdev",  0,
+        "define exposue background mean stdev", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-alt",  0,
+        "define altitute", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-az",  0,
+        "define azimuth", NAN);
+    psMetadataAddF32(addprocessedexpArgs, PS_LIST_TAIL, "-ccd_temp",  0,
+        "define ccd tempature", NAN);
+    psMetadataAddF64(addprocessedexpArgs, PS_LIST_TAIL, "-posang",  0,
+        "define rotator position angle", NAN);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-object",  0,
+        "define exposure object", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-dateobs",  0,
+        "define observation time", NULL);
+    psMetadataAddStr(addprocessedexpArgs, PS_LIST_TAIL, "-label",  0,
+        "define label for phase 2 processing (non-detrend data only)", NULL);
+    psMetadataAddS8(addprocessedexpArgs, PS_LIST_TAIL, "-code",  0,
+        "set fault code (required)", 0);
+    /*
+    psMetadataAddStr(updateArgs, PS_LIST_TAIL, "-recip",  0,
+        "define URL", NULL);
+    psMetadataAddStr(updateArgs, PS_LIST_TAIL, "-mosiac",  0,
+        "define URL", NULL);
+    */
+    psMetadataAddBool(addprocessedexpArgs, PS_LIST_TAIL, "-detrend",  0,
+        "declare this as detrend data", false);
+
+    // -processedexp
+    psMetadata *processedexpArgs = psMetadataAlloc();
+    psMetadataAddStr(processedexpArgs, PS_LIST_TAIL, "-exp_tag",  0,
+        "search by exposure ID", NULL);
+    psMetadataAddU64(processedexpArgs, PS_LIST_TAIL, "-limit",  0,
+        "limit result set to N items", 0);
+    psMetadataAddBool(processedexpArgs, PS_LIST_TAIL, "-faulted",  0,
+        "only return imfiles with a fault status set", false);
+    psMetadataAddBool(processedexpArgs, PS_LIST_TAIL, "-simple",  0,
+        "use the simple output format", false);
+
+    // -updatedprocessedexp
+    psMetadata *updatedprocessedexpArgs = psMetadataAlloc();
+    psMetadataAddStr(updatedprocessedexpArgs, PS_LIST_TAIL, "-exp_tag",  0,
+        "search by exposure ID", NULL);
+    psMetadataAddS8(updatedprocessedexpArgs, PS_LIST_TAIL, "-code",  0,
+        "set fault code (required)", 0);
+    
+
+#define PXTOOL_MODE(option, modeval, argset) \
+{ \
+    int N = 0; \
+    if ((N = psArgumentGet (argc, argv, option))) { \
+        psArgumentRemove (N, &argc, argv); \
+        if (config->mode) { \
+            psError(PXTOOLS_ERR_CONFIG, true, "only one mode selection is allowed"); \
+            psFree(config); \
+            return NULL; \
+        } \
+        config->mode = modeval; \
+        config->args = psMemIncrRefCounter(argset); \
+    } \
+    if (!psMetadataAddMetadata(argSets, PS_LIST_TAIL, option, 0, NULL, argset)) {;\
+        psError(PXTOOLS_ERR_PROG, false, "failed to add argset for %s", option);\
+    } \
+    psFree(argset); \
+}
+
+    psMetadata *argSets = psMetadataAlloc();
+    // find which mode we're running under
+    PXTOOL_MODE("-pendingimfile",   REGTOOL_MODE_PENDINGIMFILE, pendingimfileArgs);
+    PXTOOL_MODE("-addprocessedimfile", REGTOOL_MODE_ADDPROCESSEDIMFILE, addprocessedimfileArgs);
+    PXTOOL_MODE("-processedimfile", REGTOOL_MODE_PROCESSEDIMFILE, processedimfileArgs);
+    PXTOOL_MODE("-updateprocessedimfile",  REGTOOL_MODE_UPDATEPROCESSEDIMFILE, updateprocessedimfileArgs);
+    PXTOOL_MODE("-pendingexp",      REGTOOL_MODE_PENDINGEXP,pendingexpArgs);
+    PXTOOL_MODE("-addprocessedexp", REGTOOL_MODE_ADDPROCESSEDEXP, addprocessedexpArgs);
+    PXTOOL_MODE("-processedexp",    REGTOOL_MODE_PROCESSEDEXP, processedexpArgs);
+    PXTOOL_MODE("-updateprocessedexp", REGTOOL_MODE_UPDATEPROCESSEDEXP,      updatedprocessedexpArgs);
+
+    bool argErr = false;
+    if (config->mode == REGTOOL_MODE_NONE) {
+	psError(PXTOOLS_ERR_CONFIG, false, "missing mode argument");
+        fprintf (stderr, "mode argument is required\n");
+        argErr = true;
+    } else if (! psArgumentParse(config->args, &argc, argv) || argc != 1) {
+	psError(PXTOOLS_ERR_CONFIG, false, "unknown argument");
+        fprintf (stderr, "error parsing arguments\n");
+        argErr = true;
+    }
+
+    if (argErr) {
+        printf("\nPan-STARRS Registration Tool\n");
+        printf("Usage: %s <mode> [<options>]\n\n", argv[0]);
+        printf(" <mode> :\n\n");
+
+        psMetadataIterator *iter = psMetadataIteratorAlloc(argSets, 0, NULL);
+        psMetadataItem *item = NULL;
+        while ((item = psMetadataGetAndIncrement(iter))) {
+            if (!item->type == PS_DATA_METADATA) {
+                psAbort("all options must be specified as a metadata");            }
+
+            fprintf(stdout, "%s ", item->name);
+            psArgumentHelp(item->data.md);
+        }
+        psFree(iter);
+
+        psFree(argSets);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+
+    // setup search criterion
+#define addWhereStr(name) \
+{ \
+    psString str = NULL; \
+    bool status = false; \
+    if ((str = psMetadataLookupStr(&status, config->args, "-" #name))) { \
+        if (!psMetadataAddStr(config->where, PS_LIST_TAIL, #name, 0, "==", str)) {\
+            psError(PXTOOLS_ERR_PROG, false, "failed to add item " #name); \
+            psFree(config); \
+            return NULL; \
+        } \
+    } \
+}
+
+    // generate SQL where clause
+    config->where = psMetadataAlloc();
+
+    addWhereStr(exp_tag);
+    // convert '-inst' to 'camera'
+    {
+        psString str = NULL; 
+        bool status = false;
+        if ((str = psMetadataLookupStr(&status, config->args, "-inst"))) {
+            if (!psMetadataAddStr(config->where, PS_LIST_TAIL, "camera", 0, "==", str)) {
+                psError(PXTOOLS_ERR_PROG, false, "failed to add item camera");
+                psFree(config);
+                return NULL;
+            }
+        }
+    }
+    addWhereStr(telescope);
+    addWhereStr(exp_type);
+    {
+        int imfiles = 0; 
+        bool status = false;
+        if ((imfiles = psMetadataLookupS32(&status, config->args, "-imfiles"))) {
+            if (!psMetadataAddS32(config->where, PS_LIST_TAIL, "imfiles", 0, "==", imfiles)) {
+                psError(PXTOOLS_ERR_PROG, false, "failed to add item imfiles");
+                psFree(config);
+                return NULL;
+            }
+        }
+    }
+    addWhereStr(class);
+    addWhereStr(class_id);
+    addWhereStr(filter);
+
+    if (config->where->list->n < 1) {
+        psFree(config->where);
+        config->where = NULL;
+    }
+
+
+    // define Database handle, if used
+    config->dbh = pmConfigDB(config->modules);
+    if (!config->dbh) {
+        psError(PXTOOLS_ERR_SYS, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    // save argv/argc
+    config->argv = argv;
+    config->argc = argc;
+
+    return config;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/stacktool.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/stacktool.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/stacktool.c	(revision 22073)
@@ -0,0 +1,675 @@
+/*
+ * stacktool.c
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVB_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include <ippdb.h>
+
+#include "pxtools.h"
+#include "stacktool.h"
+
+static bool definerunMode(pxConfig *config);
+static bool updaterunMode(pxConfig *config);
+static bool addinputskyfileMode(pxConfig *config);
+static bool inputskyfileMode(pxConfig *config);
+static bool tosumMode(pxConfig *config);
+static bool addsumskyfileMode(pxConfig *config);
+static bool sumskyfileMode(pxConfig *config);
+
+static bool setstackRunState(pxConfig *config, const char *stack_id, const char *state);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = stacktoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(STACKTOOL_MODE_DEFINERUN,         definerunMode);
+        MODECASE(STACKTOOL_MODE_UPDATERUN,         updaterunMode);
+        MODECASE(STACKTOOL_MODE_ADDINPUTSKYFILE,    addinputskyfileMode);
+        MODECASE(STACKTOOL_MODE_INPUTSKYFILE,       inputskyfileMode);
+        MODECASE(STACKTOOL_MODE_TOSUM,             tosumMode);
+        MODECASE(STACKTOOL_MODE_ADDSUMSKYFILE,      addsumskyfileMode);
+        MODECASE(STACKTOOL_MODE_SUMSKYFILE,         sumskyfileMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+
+static bool definerunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required options
+    bool status = false;
+    psString workdir = psMetadataLookupStr(&status, config->args, "-workdir");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -workdir");
+        return false;
+    }
+    if (!workdir) {
+        psError(PS_ERR_UNKNOWN, true, "-workdir is required");
+        return false;
+    }
+
+    psString skycell_id = psMetadataLookupStr(&status, config->args, "-skycell_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -skycell_id");
+        return false;
+    }
+    if (!skycell_id) {
+        psError(PS_ERR_UNKNOWN, true, "-skycell_id is required");
+        return false;
+    }
+
+    psString tess_id = psMetadataLookupStr(&status, config->args, "-tess_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -tess_id");
+        return false;
+    }
+    if (!tess_id) {
+        psError(PS_ERR_UNKNOWN, true, "-tess_id is required");
+        return false;
+    }
+
+    // we have to support multipe exp_tags
+    psMetadataItem *warp_ids = psMetadataLookup(config->args, "-warp_id");
+    if (!warp_ids) {
+        // this shouldn't actually happen when using psArgs
+        psError(PS_ERR_UNKNOWN, true, "-warp_id is required");
+        return false;
+    }
+
+    psTime *registered = NULL;
+    {
+        psString registeredStr = psMetadataLookupStr(&status, config->args, "-registered");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -registered");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (registeredStr) {
+            registered = psTimeFromISO(registeredStr, PS_TIME_UTC);
+        } else {
+            registered = NULL;
+        }
+    }
+
+    stackRunRow *run = stackRunRowAlloc(
+            0,          // ID
+            "run",      // state
+            workdir,
+            NULL,       // dvodb
+            registered,
+            skycell_id,
+            tess_id
+    );
+    psFree(registered);
+    if (!run) {
+        psError(PS_ERR_UNKNOWN, false, "failed to alloc stackRun object");
+        return true;
+    }
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!stackRunInsertObject(config->dbh, run)) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(run);
+        return true;
+    }
+
+    // get the assigned warp_id
+    run->stack_id = psDBLastInsertID(config->dbh);
+
+    // insert the stackInputSkyfile rows
+    psListIterator *iter = psListIteratorAlloc(warp_ids->data.list, 0, false);
+    psMetadataItem *item = NULL;
+    while ((item = psListGetAndIncrement(iter))) {
+        // if the value is NULL this is probably the first pass through the
+        // loop and -warp_id was not specified at all
+        if (!item->data.V) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -warp_id");
+            return false;
+        }
+        if (!stackInputSkyfileInsert(config->dbh, run->stack_id, (psS64)atoll((char *)item->data.V))) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to insert stackInputSkyfile rows");
+            return false;
+        }
+    }
+    psFree(iter);
+
+    // point of no return
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            psFree(run);
+            return false;
+        }
+    }
+
+    if (!stackRunPrintObject(stdout, run, !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print object");
+            psFree(run);
+            return false;
+    }
+
+    psFree(run);
+
+    return true;
+}
+
+
+static bool updaterunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psString stack_id = psMetadataLookupStr(&status, config->args, "-stack_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -stack_id");
+        return false;
+    }
+    if (!stack_id) {
+        psError(PS_ERR_UNKNOWN, true, "-stack_id is required");
+        return false;
+    }
+
+    psString state = psMetadataLookupStr(&status, config->args, "-state");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -state");
+        return false;
+    }
+    if (!state) {
+        psError(PS_ERR_UNKNOWN, true, "-state is required");
+        return false;
+    }
+
+    if (state) {
+        // set detRun.state to state
+        return setstackRunState(config, stack_id, state);
+    }
+
+    return true;
+}
+
+
+static bool addinputskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psString stack_id = psMetadataLookupStr(&status, config->args, "-stack_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -stack_id");
+        return false;
+    }
+    if (!stack_id) {
+        psError(PS_ERR_UNKNOWN, true, "-stack_id is required");
+        return false;
+
+    }
+
+    psString warp_id = psMetadataLookupStr(&status, config->args, "-warp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -warp_id");
+        return false;
+    }
+    if (!warp_id) {
+        psError(PS_ERR_UNKNOWN, true, "-warp_id is required");
+        return false;
+    }
+
+
+    // XXX need to validate the warp_id here
+    // XXX instead of validiting it here we should just use forgein key
+    // constrants
+    if (!stackInputSkyfileInsert(config->dbh,
+            (psS64)atoll(stack_id),
+            (psS64)atoll(warp_id)
+        )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool inputskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    psString query = pxDataGet("stacktool_inputskyfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "stackInputSkyfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("stacktool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (psArrayLength(output)) {
+        if (!convertIdToStr(output)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
+            psFree(output);
+            return false;
+        }
+
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "stackInputSkyfile", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool tosumMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    psString query = pxDataGet("stacktool_tosum.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "stackSumSkyfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("stacktool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (psArrayLength(output)) {
+        if (!convertIdToStr(output)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
+            psFree(output);
+            return false;
+        }
+
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "stackSumSkyfile", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool addsumskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psString stack_id = psMetadataLookupStr(&status, config->args, "-stack_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -stack_id");
+        return false;
+    }
+    if (!stack_id) {
+        psError(PS_ERR_UNKNOWN, true, "-stack_id is required");
+        return false;
+
+    }
+
+    psString uri = psMetadataLookupStr(&status, config->args, "-uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -uri");
+        return false;
+    }
+    if (!uri) {
+        psError(PS_ERR_UNKNOWN, true, "-uri is required");
+        return false;
+    }
+
+    // optional
+    psF64 bg = psMetadataLookupF64(&status, config->args, "-bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg");
+        return false;
+    }
+
+    psF64 bg_stdev = psMetadataLookupF64(&status, config->args, "-bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_stdev");
+        return false;
+    }
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // XXX need to validate the stack_id here
+    // XXX instead of validiting it here we should just use forgein key
+    // constrants
+    if (!stackSumSkyfileInsert(config->dbh,
+            (psS64)atoll(stack_id),
+            uri,
+            bg,
+            bg_stdev
+        )) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!setstackRunState(config, stack_id, "stop")) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "failed to change stackRun's state");
+        return false;
+    }
+
+    // point of no return
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool sumskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    psString query = pxDataGet("stacktool_sumskyfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "stackSumSkyfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("stacktool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (psArrayLength(output)) {
+        if (!convertIdToStr(output)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
+            psFree(output);
+            return false;
+        }
+
+        if (!ippdbPrintMetadatas(stdout, output, "stackSumSkyfile", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool setstackRunState(pxConfig *config, const char *stack_id, const char *state)
+{
+    PS_ASSERT_PTR_NON_NULL(stack_id, false);
+    PS_ASSERT_PTR_NON_NULL(state, false);
+
+    // check that state is a valid string value
+    if (!(
+            (strncmp(state, "run", 4) == 0)
+            || (strncmp(state, "stop", 5) == 0)
+            || (strncmp(state, "reg", 4) == 0)
+        )
+    ) {
+        psError(PS_ERR_UNKNOWN, false,
+                "invalid warpRun state: %s", state);
+        return false;
+    }
+
+    char *query = "UPDATE stackRun SET state = '%s' WHERE stack_id = '%s'";
+    if (!p_psDBRunQuery(config->dbh, query, state, stack_id)) {
+        psError(PS_ERR_UNKNOWN, false,
+                "failed to change state for stack_id %s", stack_id);
+        return false;
+    }
+
+    return true;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/stacktool.h
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/stacktool.h	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/stacktool.h	(revision 22073)
@@ -0,0 +1,38 @@
+/*
+ * stacktool.h
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef STACKTOOL_H
+#define STACKTOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    STACKTOOL_MODE_NONE           = 0x0,
+    STACKTOOL_MODE_DEFINERUN,
+    STACKTOOL_MODE_UPDATERUN,
+    STACKTOOL_MODE_ADDINPUTSKYFILE,
+    STACKTOOL_MODE_INPUTSKYFILE,
+    STACKTOOL_MODE_TOSUM,
+    STACKTOOL_MODE_ADDSUMSKYFILE,
+    STACKTOOL_MODE_SUMSKYFILE,
+} stacktoolMode;
+
+pxConfig *stacktoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // STACKTOOL_H
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/stacktoolConfig.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/stacktoolConfig.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/stacktoolConfig.c	(revision 22073)
@@ -0,0 +1,259 @@
+/*
+ * stacktoolConfig.c
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "stacktool.h"
+
+pxConfig *stacktoolConfig(pxConfig *config, int argc, char **argv) {
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    psTime *time = psTimeGetNow(PS_TIME_TAI);
+    psString now = psTimeToISO(time);
+    psFree(time);
+
+    // -definerun
+    psMetadata *definerunArgs = psMetadataAlloc();
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-workdir", 0,
+            "define workdir (required)", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-registered",  0,
+            "time detrend run was registered", now);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-skycell_id",  0,
+            "define skycell ID (required)", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-tess_id",  0,
+            "define tessellation ID (required)", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-warp_id",
+             PS_META_DUPLICATE_OK,
+             "include this warp ID (multiple OK, required)", NULL);
+
+    psMetadataAddBool(definerunArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+ 
+    // -updaterun
+    psMetadata *updaterunArgs = psMetadataAlloc();
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-stack_id", 0,
+            "define stack ID (required)", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-state", 0,
+            "set state (required)", NULL);
+#if 0
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-workdir", 0,
+            "define workdir (required)", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-registered",  0,
+            "time detrend run was registered", now);
+#endif
+
+    // -addinputskyfile
+    psMetadata *addinputskyfileArgs = psMetadataAlloc();
+    psMetadataAddStr(addinputskyfileArgs, PS_LIST_TAIL, "-stack_id", 0,
+            "define stack ID (required)", NULL);
+    psMetadataAddStr(addinputskyfileArgs, PS_LIST_TAIL, "-warp_id", 0,
+            "define warp ID (required)", NULL);
+
+    // -inputskyfile
+    psMetadata *inputskyfileArgs = psMetadataAlloc();
+    psMetadataAddStr(inputskyfileArgs, PS_LIST_TAIL, "-stack_id", 0,
+            "search by stack ID (required)", NULL);
+    psMetadataAddStr(inputskyfileArgs, PS_LIST_TAIL, "-warp_id", 0,
+            "search by warp ID (required)", NULL);
+    psMetadataAddU64(inputskyfileArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(inputskyfileArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -tosum
+    psMetadata *tosumArgs = psMetadataAlloc();
+    psMetadataAddStr(tosumArgs, PS_LIST_TAIL, "-stack_id", 0,
+            "search by stack ID (required)", NULL);
+    psMetadataAddStr(tosumArgs, PS_LIST_TAIL, "-warp_id", 0,
+            "search by warp ID (required)", NULL);
+    psMetadataAddU64(tosumArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(tosumArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -addsumskyfile
+    psMetadata *addsumskyfileArgs = psMetadataAlloc();
+    psMetadataAddStr(addsumskyfileArgs, PS_LIST_TAIL, "-stack_id", 0,
+            "define warp ID (required)", NULL);
+    psMetadataAddStr(addsumskyfileArgs, PS_LIST_TAIL, "-uri", 0,
+            "define URI of file (required)", 0);
+    psMetadataAddF64(addsumskyfileArgs, PS_LIST_TAIL, "-bg",  0,
+            "define exposue background", NAN);
+    psMetadataAddF64(addsumskyfileArgs, PS_LIST_TAIL, "-bg_stdev",  0,
+            "define exposue background mean stdev", NAN);
+
+    // -sumskyfile
+    psMetadata *sumskyfileArgs= psMetadataAlloc();
+    psMetadataAddStr(sumskyfileArgs, PS_LIST_TAIL, "-stack_id", 0,
+            "search by stack ID (required)", NULL);
+    psMetadataAddStr(sumskyfileArgs, PS_LIST_TAIL, "-warp_id", 0,
+            "search by warp ID (required)", NULL);
+    psMetadataAddU64(sumskyfileArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(sumskyfileArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    psFree(now);
+
+#define PXTOOL_MODE(option, modeval, argset) \
+{ \
+    int N = 0; \
+    if ((N = psArgumentGet (argc, argv, option))) { \
+        psArgumentRemove (N, &argc, argv); \
+        if (config->mode) { \
+            psError(PS_ERR_UNKNOWN, true, "only one mode selection is allowed"); \
+            psFree(config); \
+            return NULL; \
+        } \
+        config->mode = modeval; \
+        config->args = psMemIncrRefCounter(argset); \
+    } \
+    if (!psMetadataAddMetadata(argSets, PS_LIST_TAIL, option, 0, NULL, argset)) {;\
+        psError(PS_ERR_UNKNOWN, false, "failed to add argset for %s", option); \
+    } \
+    psFree(argset); \
+}
+
+    psMetadata *argSets = psMetadataAlloc();
+    // find which mode we're running under
+    PXTOOL_MODE("-definerun",       STACKTOOL_MODE_DEFINERUN,      definerunArgs);
+    PXTOOL_MODE("-updaterun",       STACKTOOL_MODE_UPDATERUN,      updaterunArgs);
+    PXTOOL_MODE("-addinputskyfile",  STACKTOOL_MODE_ADDINPUTSKYFILE, addinputskyfileArgs);
+    PXTOOL_MODE("-inputskyfile",     STACKTOOL_MODE_INPUTSKYFILE,    inputskyfileArgs);
+    PXTOOL_MODE("-tosum",           STACKTOOL_MODE_TOSUM,          tosumArgs);
+    PXTOOL_MODE("-addsumskyfile",    STACKTOOL_MODE_ADDSUMSKYFILE,   addsumskyfileArgs);
+    PXTOOL_MODE("-sumskyfile",       STACKTOOL_MODE_SUMSKYFILE,      sumskyfileArgs);
+
+    bool argErr = false;
+    if (config->mode == STACKTOOL_MODE_NONE) {
+        argErr = true;
+        fprintf (stderr, "mode argument is required\n");
+    } else if (! psArgumentParse(config->args, &argc, argv) || argc != 1) {
+        argErr = true;
+        fprintf (stderr, "error parsing arguments\n");
+    }
+
+    if (argErr) {
+        printf("\nPan-STARRS Stack Tool\n");
+        printf("Usage: %s <mode> [<options>]\n\n", argv[0]);
+        printf(" <mode> :\n\n"); 
+
+        psMetadataIterator *iter = psMetadataIteratorAlloc(argSets, 0, NULL);
+        psMetadataItem *item = NULL;
+        while ((item = psMetadataGetAndIncrement(iter))) {
+            if (!item->type == PS_DATA_METADATA) {
+                psAbort("all options must be specified as a metadata");
+            }
+
+            fprintf(stdout, "%s ", item->name);
+            psArgumentHelp(item->data.md);
+        }
+        psFree(iter);
+
+        psFree(argSets);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+
+    // setup search criterion
+#define addWhereStr(name) \
+{ \
+    psString str = NULL; \
+    bool status = false; \
+    if ((str = psMetadataLookupStr(&status, config->args, "-" #name))) { \
+        if (!psMetadataAddStr(config->where, PS_LIST_TAIL, #name, 0, "==", str)) {\
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+            psFree(config); \
+            return NULL; \
+        } \
+    } \
+}
+
+#define addWhereS32(name) \
+{ \
+    psS32 s32 = 0; \
+    bool status = false; \
+    if ((s32= psMetadataLookupS32(&status, config->args, "-" #name))) { \
+        if (!psMetadataAddS32(config->where, PS_LIST_TAIL, #name, 0, "==", s32)) { \
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+            psFree(config); \
+            return NULL; \
+        } \
+    } \
+}
+
+
+    // generate SQL where clause
+    config->where = psMetadataAlloc();
+
+{
+    psString str = NULL;
+    bool status = false;
+    if ((str = psMetadataLookupStr(&status, config->args, "-warp_id"))) {
+        if (!psMetadataAddS64(config->where, PS_LIST_TAIL, "warp_id", 0, "==", (psS64)atoll(str))) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
+            psFree(config);
+            return NULL;
+        } 
+    } 
+}
+    addWhereStr(skycell_id);
+    addWhereStr(tess_id);
+    addWhereStr(exp_tag);
+
+    if (config->where->list->n < 1) {
+        psFree(config->where);
+        config->where = NULL;
+    }
+
+    // define Database handle, if used
+    // do this last so we don't setup a connection before CLI options are
+    // validated
+    config->dbh = pmConfigDB(config->modules);
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    // save argv/argc
+    config->argv = argv;
+    config->argc = argc;
+
+    return config;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/warptool.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/warptool.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/warptool.c	(revision 22073)
@@ -0,0 +1,1157 @@
+/*
+ * warptool.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVB_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include <ippdb.h>
+
+#include "pxtools.h"
+#include "warptool.h"
+
+static bool definerunMode(pxConfig *config);
+static bool updaterunMode(pxConfig *config);
+static bool addinputexpMode(pxConfig *config);
+static bool expMode(pxConfig *config);
+static bool imfileMode(pxConfig *config);
+static bool tooverlapMode(pxConfig *config);
+static bool addoverlapMode(pxConfig *config);
+static bool scmapMode(pxConfig *config);
+static bool towarpedMode(pxConfig *config);
+static bool addwarpedMode(pxConfig *config);
+static bool warpedMode(pxConfig *config);
+
+static bool parseAndInsertSkyCellMap(pxConfig *config, const char *mapfile);
+static bool setwarpRunState(pxConfig *config, const char *warp_id, const char *state);
+static bool isValidMode(pxConfig *config, const char *mode);
+bool warpCompletedRuns(pxConfig *config);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = warptoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(WARPTOOL_MODE_DEFINERUN,         definerunMode);
+        MODECASE(WARPTOOL_MODE_UPDATERUN,         updaterunMode);
+        MODECASE(WARPTOOL_MODE_ADDINPUTEXP,       addinputexpMode);
+        MODECASE(WARPTOOL_MODE_EXP,               expMode);
+        MODECASE(WARPTOOL_MODE_IMFILE,            imfileMode);
+        MODECASE(WARPTOOL_MODE_TOOVERLAP,         tooverlapMode);
+        MODECASE(WARPTOOL_MODE_ADDOVERLAP,        addoverlapMode);
+        MODECASE(WARPTOOL_MODE_SCMAP,             scmapMode);
+        MODECASE(WARPTOOL_MODE_TOWARPED,          towarpedMode);
+        MODECASE(WARPTOOL_MODE_ADDWARPED,         addwarpedMode);
+        MODECASE(WARPTOOL_MODE_WARPED,            warpedMode);
+        default:
+            psAbort("invalid option (this should not happen)");
+    }
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(EXIT_SUCCESS);
+
+FAIL:
+    psErrorStackPrint(stderr, "\n");
+    int exit_status = pxerrorGetExitStatus();
+
+    psFree(config);
+    pmConfigDone();
+    psLibFinalize();
+
+    exit(exit_status);
+}
+
+
+static bool definerunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required options
+    bool status = false;
+    psString mode = psMetadataLookupStr(&status, config->args, "-mode");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -survey_mode");
+        return false;
+    }
+    if (!mode) {
+        psError(PS_ERR_UNKNOWN, true, "-mode is required");
+        return false;
+    }
+    // check mode
+    if (mode && !isValidMode(config, mode)) {
+        psError(PS_ERR_UNKNOWN, false, "invalud mode");
+        return false;
+    }
+
+    psString workdir = psMetadataLookupStr(&status, config->args, "-workdir");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -workdir");
+        return false;
+    }
+    if (!workdir) {
+        psError(PS_ERR_UNKNOWN, true, "-workdir is required");
+        return false;
+    }
+
+    psTime *registered = NULL;
+    {
+        psString registeredStr = psMetadataLookupStr(&status, config->args, "-registered");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -registered");
+            return false;
+        }
+        // pass through NULL as this is an optional field
+        if (registeredStr) {
+            registered = psTimeFromISO(registeredStr, PS_TIME_UTC);
+        } else {
+            registered = NULL;
+        }
+    }
+
+    warpRunRow *warpRun = warpRunRowAlloc(
+            0,          // ID
+            mode,
+            "reg",      // state
+            workdir,
+            NULL,       // dvodb
+            registered
+    );
+    psFree(registered);
+    if (!warpRun) {
+        psError(PS_ERR_UNKNOWN, false, "failed to alloc warpRun object");
+        return true;
+    }
+    if (!warpRunInsertObject(config->dbh, warpRun)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(warpRun);
+        return true;
+    }
+
+    // get the assigned warp_id
+    warpRun->warp_id = psDBLastInsertID(config->dbh);
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            psFree(warpRun);
+            return false;
+        }
+    }
+
+    if (!warpRunPrintObject(stdout, warpRun, !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print object");
+            psFree(warpRun);
+            return false;
+    }
+
+    psFree(warpRun);
+
+    return true;
+}
+
+
+static bool updaterunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psString warp_id = psMetadataLookupStr(&status, config->args, "-warp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -warpt_id");
+        return false;
+    }
+    if (!warp_id) {
+        psError(PS_ERR_UNKNOWN, true, "-warp_id is required");
+        return false;
+    }
+
+    psString state = psMetadataLookupStr(&status, config->args, "-state");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -state");
+        return false;
+    }
+    if (!state) {
+        psError(PS_ERR_UNKNOWN, true, "-state is required");
+        return false;
+    }
+
+    if (state) {
+        // set detRun.state to state
+        return setwarpRunState(config, warp_id, state);
+    }
+
+    return true;
+}
+
+
+static bool addinputexpMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psString warp_id = psMetadataLookupStr(&status, config->args, "-warp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -warp_id");
+        return false;
+    }
+    if (!warp_id) {
+        psError(PS_ERR_UNKNOWN, true, "-warp_id is required");
+        return false;
+    }
+
+    psString cam_id = psMetadataLookupStr(&status, config->args, "-cam_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -cam_id");
+        return false;
+    }
+    if (!cam_id) {
+        psError(PS_ERR_UNKNOWN, true, "-cam_id is required");
+        return false;
+    }
+
+    // defaults to false
+    bool magiced = psMetadataLookupBool(&status, config->args, "-magiced");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -magiced");
+        return false;
+    }
+
+    // XXX need to validate the warp_id here
+    // XXX instead of validiting it here we should just use forgein key
+    // constrants
+    if (!warpInputExpInsert(config->dbh,
+            (psS64)atoll(warp_id),
+            (psS64)atoll(cam_id),
+            magiced
+        )) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool expMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    // find all rawImfiles matching the default query
+    psString query = pxDataGet("warptool_exp.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "warpInputExp");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("warptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (psArrayLength(output)) {
+        if (!convertIdToStr(output)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
+            psFree(output);
+            return false;
+        }
+
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "warpInputExp", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool imfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    // find all rawImfiles matching the default query
+    psString query = pxDataGet("warptool_imfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "warpInputExp");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("warptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (psArrayLength(output)) {
+        if (!convertIdToStr(output)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
+            psFree(output);
+            return false;
+        }
+
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "warpInputImfile", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool tooverlapMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    // find all rawImfiles matching the default query
+    psString query = pxDataGet("warptool_tooverlap.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "warpInputExp");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("warptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (psArrayLength(output)) {
+        if (!convertIdToStr(output)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
+            psFree(output);
+            return false;
+        }
+
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "warpInputExp", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool addoverlapMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psString mapfile = psMetadataLookupStr(&status, config->args, "-mapfile");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -mapfile");
+        return false;
+    }
+    if (!mapfile) {
+        psError(PS_ERR_UNKNOWN, true, "-mapfile is required");
+        return false;
+    }
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!parseAndInsertSkyCellMap(config, mapfile)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to inject mapfile: %s into the database", mapfile);
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        return false;
+    }
+
+    // point of no return
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool parseAndInsertSkyCellMap(pxConfig *config, const char *mapfile)
+{
+    unsigned int nFail = 0;
+    psMetadata *skycells = psMetadataConfigRead(NULL, &nFail, mapfile, false);
+    if (!skycells) {
+        psError(PS_ERR_UNKNOWN, false, "failed to parse mapfile: %s", mapfile);
+        return false;
+    }
+    if (nFail) {
+        psError(PS_ERR_UNKNOWN, false, "there were %d errors parsing mapfile: %s", nFail, mapfile);
+        psFree(skycells);
+        return false;
+    }
+
+    psMetadataItem *item = NULL;
+    psMetadataIterator *iter = psMetadataIteratorAlloc(skycells, 0, NULL);
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        if (item->type != PS_DATA_METADATA) {
+            psError(PS_ERR_UNKNOWN, false, "mapfile: %s is in the wrong format", mapfile);
+            psFree(iter);
+            psFree(skycells);
+            return false;
+        }
+
+        psMetadata *sc = item->data.md;
+        // this conversion isn't strictly nessicary but it's an easy way of
+        // validating the format
+        warpSkyCellMapRow *row = warpSkyCellMapObjectFromMetadata(sc);
+        if (!row) {
+            psError(PS_ERR_UNKNOWN, false, "failed to convert mapfile: %s metdata entry into a warpSkyCellMap object", mapfile);
+            psFree(iter);
+            psFree(skycells);
+            return false;
+        }
+
+        if (!warpSkyCellMapInsertObject(config->dbh, row)) {
+            psErrorCode err = psErrorCodeLast();
+            switch (err) {
+                case PS_ERR_DB_CLIENT:
+                    psError(PXTOOLS_ERR_SYS, false, "database error");
+                case PS_ERR_DB_SERVER:
+                    psError(PXTOOLS_ERR_PROG, false, "database error");
+                default:
+                    psError(PXTOOLS_ERR_PROG, false, "unknown error");
+            }
+            psFree(row);
+            psFree(iter);
+            psFree(skycells);
+            return false;
+        }
+
+        psFree(row);
+    }
+    psFree(iter);
+    psFree(skycells);
+
+    return true;
+}
+
+
+static bool scmapMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    // find all rawImfiles matching the default query
+    psString query = pxDataGet("warptool_scmap.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "warpSkyCellMap");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("warptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (psArrayLength(output)) {
+        if (!convertIdToStr(output)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
+            psFree(output);
+            return false;
+        }
+
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "warpSkyCellMap", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool towarpedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    // find all rawImfiles matching the default query
+    psString query = pxDataGet("warptool_towarped.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "warpSkyCellMap");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("warptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (psArrayLength(output)) {
+        if (!convertIdToStr(output)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
+            psFree(output);
+            return false;
+        }
+
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "warpPendingSkyCell", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool addwarpedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psString warp_id = psMetadataLookupStr(&status, config->args, "-warp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -warp_id");
+        return false;
+    }
+    if (!warp_id) {
+        psError(PS_ERR_UNKNOWN, true, "-warp_id is required");
+        return false;
+    }
+
+    psString skycell_id = psMetadataLookupStr(&status, config->args, "-skycell_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -skycell_id");
+        return false;
+    }
+    if (!skycell_id) {
+        psError(PS_ERR_UNKNOWN, true, "-skycell_id is required");
+        return false;
+    }
+
+    psString tess_id = psMetadataLookupStr(&status, config->args, "-tess_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -tess_id");
+        return false;
+    }
+    if (!tess_id) {
+        psError(PS_ERR_UNKNOWN, true, "-tess_id is required");
+        return false;
+    }
+
+    psString uri = psMetadataLookupStr(&status, config->args, "-uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -uri");
+        return false;
+    }
+    if (!uri) {
+        psError(PS_ERR_UNKNOWN, true, "-uri is required");
+        return false;
+    }
+
+    // optional
+    psF64 bg = psMetadataLookupF64(&status, config->args, "-bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg");
+        return false;
+    }
+
+    psF64 bg_stdev = psMetadataLookupF64(&status, config->args, "-bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -bg_stdev");
+        return false;
+    }
+
+    // we don't want to insert the last skyfile in a run but then not mark the
+    // run as 'stop'
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // XXX need to validate that this coresponds to an warpInputImfile
+    if (!warpSkyfileInsert(config->dbh,
+            (psS64)atoll(warp_id),
+            skycell_id,
+            tess_id,
+            uri,
+            bg,
+            bg_stdev
+        )) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!warpCompletedRuns(config)) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    // point of no return
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+bool warpCompletedRuns(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // XXX this SQL has not been broken out to into seperate files as the MYSQL
+    // < 5 & MYSQL 5 versions need to be kept in sync
+#if MYSQL5
+    // XXX at MySQL 4.1.21 (probably all of 4.1.x) chokes and dies on this
+    // statement as it thinks it is trying to select from the table being
+    // updated. The 4.1 manual says that nested sub-queries are explicited
+    // allowed to do this with update statements as a temporary table is
+    // created so that you are not actually selecting from the table you are
+    // modifying.
+    char *query =
+        "UPDATE warpRun\n"
+        "   SET warpRun.state = 'stop'\n"
+        " WHERE\n"
+        "   warpRun.warp_id =\n"
+        "   (SELECT DISTINCT\n"
+        "       warp_id\n"
+        "   FROM\n"
+        "       (SELECT DISTINCT\n"
+        "           warpRun.warp_id,\n"
+        "           warpSkyCellMap.warp_id as foo,\n"
+        "           warpSkyfile.warp_id as bar\n"
+        "       FROM warpRun\n"
+        "       JOIN warpSkyCellMap\n"
+        "           USING(warp_id)\n"
+        "       LEFT JOIN warpSkyfile\n"
+        "           USING(warp_id, skycell_id, tess_id)\n"
+        "       WHERE\n"
+        "           warpRun.state = 'run'\n"
+        "       GROUP BY\n"
+        "           warpRun.warp_id\n"
+        "       HAVING\n"
+        "       COUNT(warpSkyCellMap.warp_id) = COUNT(warpSkyfile.warp_id)\n"
+        "       ) as Foo\n"
+        "   )\n";
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+#else // if MYSQL5
+{
+    char *query =
+        "CREATE TEMPORARY TABLE finished\n"
+        " (warp_id INT, PRIMARY KEY(warp_id)) ENGINE=MEMORY\n";
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+}
+
+{
+    char *query =
+        "INSERT INTO finished\n"
+        " SELECT\n"
+        "   warp_id\n"
+        " FROM\n"
+        "   (SELECT DISTINCT\n"
+        "       warpRun.warp_id,\n"
+        "       warpSkyCellMap.warp_id as foo,\n"
+        "       warpSkyfile.warp_id as bar\n"
+        "   FROM warpRun\n"
+        "   JOIN warpSkyCellMap\n"
+        "       USING(warp_id)\n"
+        "   LEFT JOIN warpSkyfile\n"
+        "       USING(warp_id, skycell_id, tess_id)\n"
+        "   WHERE\n"
+        "       warpRun.state = 'run'\n"
+        "   GROUP BY\n"
+        "       warpRun.warp_id\n"
+        "   HAVING\n"
+        "       COUNT(warpSkyCellMap.warp_id) = COUNT(warpSkyfile.warp_id)\n"
+        " ) as Foo \n";
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+}
+
+{
+    char *query =
+        "UPDATE warpRun\n"
+        "   SET warpRun.state = 'stop'\n"
+        " WHERE\n"
+        "   warpRun.warp_id =\n"
+        "   (SELECT DISTINCT\n"
+        "       warp_id\n"
+        "   FROM finished\n"
+        "   )\n";
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+}
+#endif // if MYSQL5
+
+    return true;
+}
+
+static bool warpedMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    bool status = false;
+    psU64 limit = psMetadataLookupU64(&status, config->args, "-limit");
+    if (!status) {
+        psError(PXTOOLS_ERR_PROG, false, "failed to lookup value for -limit");
+        return false;
+    }
+
+    // find all rawImfiles matching the default query
+    psString query = pxDataGet("warptool_warped.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (config->where) {
+        psString whereClause = psDBGenerateWhereConditionSQL(config->where, "warpSkyfile");
+        psStringAppend(&query, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("warptool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    bool simple = false;
+    {
+        bool status = false;
+        simple = psMetadataLookupBool(&status, config->args, "-simple");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup value for -simple");
+            return false;
+        }
+    }
+
+    if (psArrayLength(output)) {
+        if (!convertIdToStr(output)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to convert id fields into a strings");
+            psFree(output);
+            return false;
+        }
+
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "warpSkyfile", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+static bool setwarpRunState(pxConfig *config, const char *warp_id, const char *state)
+{
+    PS_ASSERT_PTR_NON_NULL(warp_id, false);
+    PS_ASSERT_PTR_NON_NULL(state, false);
+
+    // check that state is a valid string value
+    if (!(
+            (strncmp(state, "run", 4) == 0)
+            || (strncmp(state, "stop", 5) == 0)
+            || (strncmp(state, "reg", 4) == 0)
+        )
+    ) {
+        psError(PS_ERR_UNKNOWN, false,
+                "invalid warpRun state: %s", state);
+        return false;
+    }
+
+    char *query = "UPDATE warpRun SET state = '%s' WHERE warp_id = '%s'";
+    if (!p_psDBRunQuery(config->dbh, query, state, warp_id)) {
+        psError(PS_ERR_UNKNOWN, false,
+                "failed to change state for warp_id %s", warp_id);
+        return false;
+    }
+
+    return true;
+}
+
+
+static bool isValidMode(pxConfig *config, const char *mode)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(mode, false);
+
+    // check that state is a valid string value
+    if (!(
+            (strncmp(mode, "warp", 5) == 0)
+            || (strncmp(mode, "diff", 5) == 0)
+            || (strncmp(mode, "stack", 6) == 0)
+            || (strncmp(mode, "magic", 6) == 0)
+        )
+    ) {
+        psError(PS_ERR_UNKNOWN, false,
+                "invalid detRun mode: %s", mode);
+        return false;
+    }
+
+    return true;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/warptool.h
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/warptool.h	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/warptool.h	(revision 22073)
@@ -0,0 +1,42 @@
+/*
+ * warptool.h
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef WARPTOOL_H
+#define WARPTOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    WARPTOOL_MODE_NONE           = 0x0,
+    WARPTOOL_MODE_DEFINERUN,
+    WARPTOOL_MODE_UPDATERUN,
+    WARPTOOL_MODE_ADDINPUTEXP,
+    WARPTOOL_MODE_EXP,
+    WARPTOOL_MODE_IMFILE,
+    WARPTOOL_MODE_TOOVERLAP,
+    WARPTOOL_MODE_ADDOVERLAP,
+    WARPTOOL_MODE_SCMAP,
+    WARPTOOL_MODE_TOWARPED,
+    WARPTOOL_MODE_ADDWARPED,
+    WARPTOOL_MODE_WARPED,
+} warptoolMode;
+
+pxConfig *warptoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // WARPTOOL_H
Index: /tags/ipp-1-X/rel-1_1_19/ippTools/src/warptoolConfig.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippTools/src/warptoolConfig.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippTools/src/warptoolConfig.c	(revision 22073)
@@ -0,0 +1,313 @@
+/*
+ * warptoolConfig.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "warptool.h"
+
+pxConfig *warptoolConfig(pxConfig *config, int argc, char **argv) {
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    psTime *time = psTimeGetNow(PS_TIME_TAI);
+    psString now = psTimeToISO(time);
+    psFree(time);
+
+    // -definerun
+    psMetadata *definerunArgs = psMetadataAlloc();
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-mode", 0,
+            "define mode (required)", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-workdir", 0,
+            "define workdir (required)", NULL);
+    psMetadataAddStr(definerunArgs, PS_LIST_TAIL, "-registered",  0,
+            "time detrend run was registered", now);
+    psMetadataAddBool(definerunArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+ 
+    // -updaterun
+    psMetadata *updaterunArgs = psMetadataAlloc();
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-warp_id", 0,
+            "define warptool ID (required)", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-state", 0,
+            "set state (required)", NULL);
+#if 0
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-workdir", 0,
+            "define workdir (required)", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-registered",  0,
+            "time detrend run was registered", now);
+#endif
+
+    // -addinputexp
+    psMetadata *addinputexpArgs = psMetadataAlloc();
+    psMetadataAddStr(addinputexpArgs, PS_LIST_TAIL, "-warp_id", 0,
+            "define warptool ID (required)", NULL);
+    psMetadataAddStr(addinputexpArgs, PS_LIST_TAIL, "-cam_id", 0,
+            "define camtool ID (required)", NULL);
+    psMetadataAddBool(addinputexpArgs, PS_LIST_TAIL, "-magiced",  0,
+            "has this exposure been magiced", false);
+
+    // -exp
+    psMetadata *expArgs = psMetadataAlloc();
+    psMetadataAddStr(expArgs, PS_LIST_TAIL, "-warp_id", 0,
+            "search by warptool ID", NULL);
+    psMetadataAddStr(expArgs, PS_LIST_TAIL, "-cam_id", 0,
+            "search by camtool ID", NULL);
+    psMetadataAddU64(expArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(expArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -imfile
+    psMetadata *imfileArgs = psMetadataAlloc();
+    psMetadataAddStr(imfileArgs, PS_LIST_TAIL, "-warp_id", 0,
+            "search by warptool ID", NULL);
+    psMetadataAddStr(imfileArgs, PS_LIST_TAIL, "-cam_id", 0,
+            "search by camtool ID", NULL);
+    psMetadataAddU64(imfileArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(imfileArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -tooverlap
+    psMetadata *tooverlapArgs = psMetadataAlloc();
+    psMetadataAddStr(tooverlapArgs, PS_LIST_TAIL, "-warp_id", 0,
+            "search by warp ID", NULL);
+    psMetadataAddU64(tooverlapArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(tooverlapArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -addoverlap
+    psMetadata *addoverlapArgs = psMetadataAlloc();
+    psMetadataAddStr(addoverlapArgs, PS_LIST_TAIL, "-mapfile", 0,
+            "path to skycell <-> imfile mapping file", NULL);
+
+    // -scmap
+    psMetadata *scmapArgs = psMetadataAlloc();
+    psMetadataAddStr(scmapArgs, PS_LIST_TAIL, "-warp_id", 0,
+            "search by warptool ID", NULL);
+    psMetadataAddStr(scmapArgs, PS_LIST_TAIL, "-skycell_id", 0,
+            "searcy by skycell ID", NULL);
+    psMetadataAddStr(scmapArgs, PS_LIST_TAIL, "-tess_id", 0,
+            "searcy by tess ID", NULL);
+    psMetadataAddU64(scmapArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(scmapArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    // -towarped
+    psMetadata *towarpedArgs = psMetadataAlloc();
+    psMetadataAddStr(towarpedArgs, PS_LIST_TAIL, "-warp_id", 0,
+            "search by warptool ID", NULL);
+    psMetadataAddU64(towarpedArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(towarpedArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+ 
+    // -addwarped
+    psMetadata *addwarpedArgs = psMetadataAlloc();
+    psMetadataAddStr(addwarpedArgs, PS_LIST_TAIL, "-warp_id", 0,
+            "define warptool ID (required)", NULL);
+    psMetadataAddStr(addwarpedArgs, PS_LIST_TAIL, "-skycell_id",  0,
+            "define skycell ID (required)", NULL);
+    psMetadataAddStr(addwarpedArgs, PS_LIST_TAIL, "-tess_id",  0,
+            "define tessellation ID (required)", NULL);
+    psMetadataAddStr(addwarpedArgs, PS_LIST_TAIL, "-uri", 0,
+            "define URI of file (required)", 0);
+    psMetadataAddF64(addwarpedArgs, PS_LIST_TAIL, "-bg",  0,
+            "define exposue background", NAN);
+    psMetadataAddF64(addwarpedArgs, PS_LIST_TAIL, "-bg_stdev",  0,
+            "define exposue background stdev", NAN);
+ 
+    // -warped
+    psMetadata *warpedArgs = psMetadataAlloc();
+    psMetadataAddStr(warpedArgs, PS_LIST_TAIL, "-warp_id", 0,
+            "search by warptool ID", NULL);
+    psMetadataAddStr(warpedArgs, PS_LIST_TAIL, "-skycell_id",  0,
+            "define skycell ID (required)", NULL);
+    psMetadataAddStr(warpedArgs, PS_LIST_TAIL, "-tess_id",  0,
+            "define tessellation ID (required)", NULL);
+    psMetadataAddStr(warpedArgs, PS_LIST_TAIL, "-exp_tag", 0,
+            "define exposure tag (required)", NULL);
+    psMetadataAddStr(warpedArgs, PS_LIST_TAIL, "-cam_id", 0,
+            "define phase 3 version of exposure tag (required)", NULL);
+    psMetadataAddU64(warpedArgs, PS_LIST_TAIL, "-limit",  0,
+            "limit result set to N items", 0);
+    psMetadataAddBool(warpedArgs, PS_LIST_TAIL, "-simple",  0,
+            "use the simple output format", false);
+
+    psFree(now);
+
+#define PXTOOL_MODE(option, modeval, argset) \
+{ \
+    int N = 0; \
+    if ((N = psArgumentGet (argc, argv, option))) { \
+        psArgumentRemove (N, &argc, argv); \
+        if (config->mode) { \
+            psError(PS_ERR_UNKNOWN, true, "only one mode selection is allowed"); \
+            psFree(config); \
+            return NULL; \
+        } \
+        config->mode = modeval; \
+        config->args = psMemIncrRefCounter(argset); \
+    } \
+    if (!psMetadataAddMetadata(argSets, PS_LIST_TAIL, option, 0, NULL, argset)) {;\
+        psError(PS_ERR_UNKNOWN, false, "failed to add argset for %s", option); \
+    } \
+    psFree(argset); \
+}
+
+    psMetadata *argSets = psMetadataAlloc();
+    // find which mode we're running under
+    PXTOOL_MODE("-definerun",       WARPTOOL_MODE_DEFINERUN,      definerunArgs);
+    PXTOOL_MODE("-updaterun",       WARPTOOL_MODE_UPDATERUN,      updaterunArgs);
+    PXTOOL_MODE("-addinputexp",     WARPTOOL_MODE_ADDINPUTEXP,    addinputexpArgs);
+    PXTOOL_MODE("-exp",             WARPTOOL_MODE_EXP,            expArgs);
+    PXTOOL_MODE("-imfile",          WARPTOOL_MODE_IMFILE,         imfileArgs);
+    PXTOOL_MODE("-tooverlap",       WARPTOOL_MODE_TOOVERLAP,      tooverlapArgs);
+    PXTOOL_MODE("-addoverlap",      WARPTOOL_MODE_ADDOVERLAP,     addoverlapArgs);
+    PXTOOL_MODE("-scmap",           WARPTOOL_MODE_SCMAP,          scmapArgs);
+    PXTOOL_MODE("-towarped",        WARPTOOL_MODE_TOWARPED,       towarpedArgs);
+    PXTOOL_MODE("-addwarped",       WARPTOOL_MODE_ADDWARPED,      addwarpedArgs);
+    PXTOOL_MODE("-warped",          WARPTOOL_MODE_WARPED,         warpedArgs);
+#if 0
+    PXTOOL_MODE("-tostackedimfile", WARPTOOL_MODE_TOSTACKEDIMFILE, tostackedimfileArgs);
+    PXTOOL_MODE("-addstackedimfile", WARPTOOL_MODE_ADDSTACKEDIMFILE, addstackedimfileArgs);
+    PXTOOL_MODE("-stackedimfile",   WARPTOOL_MODE_STACKEDIMFILE, stackedimfileArgs);
+    PXTOOL_MODE("-todiffimfile", WARPTOOL_MODE_TODIFFIMFILE, todiffimfileArgs);
+    PXTOOL_MODE("-adddiffimfile", WARPTOOL_MODE_ADDDIFFIMFILE, adddiffimfileArgs);
+    PXTOOL_MODE("-diffimfile",   WARPTOOL_MODE_DIFFIMFILE, diffimfileArgs);
+#endif
+
+    bool argErr = false;
+    if (config->mode == WARPTOOL_MODE_NONE) {
+        argErr = true;
+        fprintf (stderr, "mode argument is required\n");
+    } else if (! psArgumentParse(config->args, &argc, argv) || argc != 1) {
+        argErr = true;
+        fprintf (stderr, "error parsing arguments\n");
+    }
+
+    if (argErr) {
+        printf("\nPan-STARRS Warp Tool\n");
+        printf("Usage: %s <mode> [<options>]\n\n", argv[0]);
+        printf(" <mode> :\n\n"); 
+
+        psMetadataIterator *iter = psMetadataIteratorAlloc(argSets, 0, NULL);
+        psMetadataItem *item = NULL;
+        while ((item = psMetadataGetAndIncrement(iter))) {
+            if (!item->type == PS_DATA_METADATA) {
+                psAbort("all options must be specified as a metadata");
+            }
+
+            fprintf(stdout, "%s ", item->name);
+            psArgumentHelp(item->data.md);
+        }
+        psFree(iter);
+
+        psFree(argSets);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+
+    // setup search criterion
+#define addWhereStr(name) \
+{ \
+    psString str = NULL; \
+    bool status = false; \
+    if ((str = psMetadataLookupStr(&status, config->args, "-" #name))) { \
+        if (!psMetadataAddStr(config->where, PS_LIST_TAIL, #name, 0, "==", str)) {\
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+            psFree(config); \
+            return NULL; \
+        } \
+    } \
+}
+
+#define addWhereS32(name) \
+{ \
+    psS32 s32 = 0; \
+    bool status = false; \
+    if ((s32= psMetadataLookupS32(&status, config->args, "-" #name))) { \
+        if (!psMetadataAddS32(config->where, PS_LIST_TAIL, #name, 0, "==", s32)) { \
+            psError(PS_ERR_UNKNOWN, false, "failed to add item " #name); \
+            psFree(config); \
+            return NULL; \
+        } \
+    } \
+}
+
+
+    // generate SQL where clause
+    config->where = psMetadataAlloc();
+
+{
+    psString str = NULL;
+    bool status = false;
+    if ((str = psMetadataLookupStr(&status, config->args, "-warp_id"))) {
+        if (!psMetadataAddS64(config->where, PS_LIST_TAIL, "warp_id", 0, "==", (psS64)atoll(str))) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
+            psFree(config);
+            return NULL;
+        } 
+    } 
+}
+    addWhereStr(skycell_id);
+    addWhereStr(tess_id);
+    addWhereStr(exp_tag);
+
+    if (config->where->list->n < 1) {
+        psFree(config->where);
+        config->where = NULL;
+    }
+
+    // define Database handle, if used
+    // do this last so we don't setup a connection before CLI options are
+    // validated
+    config->dbh = pmConfigDB(config->modules);
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    // save argv/argc
+    config->argv = argv;
+    config->argc = argc;
+
+    return config;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/.cvsignore
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/.cvsignore	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/.cvsignore	(revision 22073)
@@ -0,0 +1,21 @@
+Doxyfile
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+compile
+config.guess
+config.h
+config.h.in
+config.log
+config.status
+config.sub
+configure
+depcomp
+install-sh
+ippdb.pc
+libtool
+ltmain.sh
+missing
+stamp-h1
+docs
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/COPYING
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/COPYING	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/COPYING	(revision 22073)
@@ -0,0 +1,345 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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 of the License, 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
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/Doxyfile.in
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/Doxyfile.in	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/Doxyfile.in	(revision 22073)
@@ -0,0 +1,1219 @@
+# Doxyfile 1.4.2
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded 
+# by quotes) that should identify the project.
+
+PROJECT_NAME           = @PACKAGE_NAME@
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. 
+# This could be handy for archiving the generated documentation or 
+# if some version control system is used.
+
+PROJECT_NUMBER         = @PACKAGE_VERSION@
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) 
+# base path where the generated documentation will be put. 
+# If a relative path is entered, it will be relative to the location 
+# where doxygen was started. If left blank the current directory will be used.
+
+# @top_builddir@ doesn't work for some reason
+OUTPUT_DIRECTORY       = @builddir@/docs
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 
+# 4096 sub-directories (in 2 levels) under the output directory of each output 
+# format and will distribute the generated files over these directories. 
+# Enabling this option can be useful when feeding doxygen a huge amount of 
+# source files, where putting all generated files in the same directory would 
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all 
+# documentation generated by doxygen is written. Doxygen will use this 
+# information to generate all constant output in the proper language. 
+# The default language is English, other supported languages are: 
+# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, 
+# Dutch, Finnish, French, German, Greek, Hungarian, Italian, Japanese, 
+# Japanese-en (Japanese with English messages), Korean, Korean-en, Norwegian, 
+# Polish, Portuguese, Romanian, Russian, Serbian, Slovak, Slovene, Spanish, 
+# Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE        = English
+
+# This tag can be used to specify the encoding used in the generated output. 
+# The encoding is not always determined by the language that is chosen, 
+# but also whether or not the output is meant for Windows or non-Windows users. 
+# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES 
+# forces the Windows encoding (this is the default for the Windows binary), 
+# whereas setting the tag to NO uses a Unix-style encoding (the default for 
+# all platforms other than Windows).
+
+USE_WINDOWS_ENCODING   = NO
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will 
+# include brief member descriptions after the members that are listed in 
+# the file and class documentation (similar to JavaDoc). 
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend 
+# the brief description of a member or function before the detailed description. 
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the 
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator 
+# that is used to form the text in various listings. Each string 
+# in this list, if found as the leading text of the brief description, will be 
+# stripped from the text and the result after processing the whole list, is 
+# used as the annotated text. Otherwise, the brief description is used as-is. 
+# If left blank, the following values are used ("$name" is automatically 
+# replaced with the name of the entity): "The $name class" "The $name widget" 
+# "The $name file" "is" "provides" "specifies" "contains" 
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       = 
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then 
+# Doxygen will generate a detailed section even if there is only a brief 
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all 
+# inherited members of a class in the documentation of that class as if those 
+# members were ordinary class members. Constructors, destructors and assignment 
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full 
+# path before files name in the file list and in the header files. If set 
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag 
+# can be used to strip a user-defined part of the path. Stripping is 
+# only done if one of the specified strings matches the left-hand part of 
+# the path. The tag can be used to show relative paths in the file list. 
+# If left blank the directory from which doxygen is run is used as the 
+# path to strip.
+
+STRIP_FROM_PATH        = 
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of 
+# the path mentioned in the documentation of a class, which tells 
+# the reader which header file to include in order to use a class. 
+# If left blank only the name of the header file containing the class 
+# definition is used. Otherwise one should specify the include paths that 
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    = 
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter 
+# (but less readable) file names. This can be useful is your file systems 
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen 
+# will interpret the first line (until the first dot) of a JavaDoc-style 
+# comment as the brief description. If set to NO, the JavaDoc 
+# comments will behave just like the Qt-style comments (thus requiring an 
+# explicit @brief command for a brief description.
+
+JAVADOC_AUTOBRIEF      = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen 
+# treat a multi-line C++ special comment block (i.e. a block of //! or /// 
+# comments) as a brief description. This used to be the default behaviour. 
+# The new default is to treat a multi-line C++ comment block as a detailed 
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the DETAILS_AT_TOP tag is set to YES then Doxygen 
+# will output the detailed description near the top, like JavaDoc.
+# If set to NO, the detailed description appears after the member 
+# documentation.
+
+DETAILS_AT_TOP         = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented 
+# member inherits the documentation from any documented member that it 
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC 
+# tag is set to YES, then doxygen will reuse the documentation of the first 
+# member in the group (if any) for the other members of the group. By default 
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce 
+# a new page for each member. If set to NO, the documentation of a member will 
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. 
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 4
+
+# This tag can be used to specify a number of aliases that acts 
+# as commands in the documentation. An alias has the form "name=value". 
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to 
+# put the command \sideeffect (or @sideeffect) in the documentation, which 
+# will result in a user-defined paragraph with heading "Side Effects:". 
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                = 
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C 
+# sources only. Doxygen will then generate output that is more tailored for C. 
+# For instance, some of the names that are used will be different. The list 
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java sources 
+# only. Doxygen will then generate output that is more tailored for Java. 
+# For instance, namespaces will be presented as packages, qualified scopes 
+# will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of 
+# the same type (for instance a group of public functions) to be put as a 
+# subgroup of that type (e.g. under the Public Functions section). Set it to 
+# NO to prevent subgrouping. Alternatively, this can be done per class using 
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in 
+# documentation are documented, even if no documentation was available. 
+# Private class members and static file members will be hidden unless 
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class 
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file 
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) 
+# defined locally in source files will be included in the documentation. 
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local 
+# methods, which are defined in the implementation section but not in 
+# the interface are included in the documentation. 
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all 
+# undocumented members of documented classes, files or namespaces. 
+# If set to NO (the default) these members will be included in the 
+# various overviews, but no documentation section is generated. 
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all 
+# undocumented classes that are normally visible in the class hierarchy. 
+# If set to NO (the default) these classes will be included in the various 
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all 
+# friend (class|struct|union) declarations. 
+# If set to NO (the default) these declarations will be included in the 
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any 
+# documentation blocks found inside the body of a function. 
+# If set to NO (the default) these blocks will be appended to the 
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation 
+# that is typed after a \internal command is included. If the tag is set 
+# to NO (the default) then the documentation will be excluded. 
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate 
+# file names in lower-case letters. If set to YES upper-case letters are also 
+# allowed. This is useful if you have classes or files whose names only differ 
+# in case and if your file system supports case sensitive file names. Windows 
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen 
+# will show members with their full class and namespace scopes in the 
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen 
+# will put a list of the files that are included by a file in the documentation 
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] 
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen 
+# will sort the (detailed) documentation of file and class members 
+# alphabetically by member name. If set to NO the members will appear in 
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the 
+# brief documentation of file, namespace and class members alphabetically 
+# by member name. If set to NO (the default) the members will appear in 
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be 
+# sorted by fully-qualified names, including namespaces. If set to 
+# NO (the default), the class list will be sorted only by class name, 
+# not including the namespace part. 
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the 
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or 
+# disable (NO) the todo list. This list is created by putting \todo 
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or 
+# disable (NO) the test list. This list is created by putting \test 
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or 
+# disable (NO) the bug list. This list is created by putting \bug 
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or 
+# disable (NO) the deprecated list. This list is created by putting 
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional 
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       = 
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines 
+# the initial value of a variable or define consists of for it to appear in 
+# the documentation. If the initializer consists of more lines than specified 
+# here it will be hidden. Use a value of 0 to hide initializers completely. 
+# The appearance of the initializer of individual variables and defines in the 
+# documentation can be controlled using \showinitializer or \hideinitializer 
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated 
+# at the bottom of the documentation of classes and structs. If set to YES the 
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# If the sources in your project are distributed over multiple directories 
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy 
+# in the documentation.
+
+SHOW_DIRECTORIES       = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that 
+# doxygen should invoke to get the current version for each file (typically from the 
+# version control system). Doxygen will invoke the program by executing (via 
+# popen()) the command <command> <input-file>, where <command> is the value of 
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file 
+# provided by doxygen. Whatever the progam writes to standard output 
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated 
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are 
+# generated by doxygen. Possible values are YES and NO. If left blank 
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings 
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will 
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for 
+# potential errors in the documentation, such as not documenting some 
+# parameters in a documented function, or documenting parameters that 
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for 
+# functions that are documented, but have no documentation for their parameters 
+# or return value. If set to NO (the default) doxygen will only warn about 
+# wrong or incomplete parameter documentation, but not about the absence of 
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that 
+# doxygen can produce. The string should contain the $file, $line, and $text 
+# tags, which will be replaced by the file and line number from which the 
+# warning originated and the warning text. Optionally the format may contain 
+# $version, which will be replaced by the version of the file (if it could 
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning 
+# and error messages should be written. If left blank the output is written 
+# to stderr.
+
+WARN_LOGFILE           = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain 
+# documented source files. You may enter file names like "myfile.cpp" or 
+# directories like "/usr/src/myproject". Separate the files or directories 
+# with spaces.
+
+INPUT                  = @top_srcdir@/src
+
+# If the value of the INPUT tag contains directories, you can use the 
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank the following patterns are tested: 
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx 
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm
+
+FILE_PATTERNS          = *.h
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories 
+# should be searched for input files as well. Possible values are YES and NO. 
+# If left blank NO is used.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should 
+# excluded from the INPUT source files. This way you can easily exclude a 
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                = 
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or 
+# directories that are symbolic links (a Unix filesystem feature) are excluded 
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the 
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude 
+# certain files from those directories.
+
+EXCLUDE_PATTERNS       = 
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or 
+# directories that contain example code fragments that are included (see 
+# the \include command).
+
+EXAMPLE_PATH           = 
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the 
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank all files are included.
+
+EXAMPLE_PATTERNS       = 
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be 
+# searched for input files to be used with the \include or \dontinclude 
+# commands irrespective of the value of the RECURSIVE tag. 
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or 
+# directories that contain image that are included in the documentation (see 
+# the \image command).
+
+IMAGE_PATH             = 
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should 
+# invoke to filter for each input file. Doxygen will invoke the filter program 
+# by executing (via popen()) the command <filter> <input-file>, where <filter> 
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an 
+# input file. Doxygen will then use the output that the filter program writes 
+# to standard output.  If FILTER_PATTERNS is specified, this tag will be 
+# ignored.
+
+INPUT_FILTER           = 
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern 
+# basis.  Doxygen will compare the file name with each pattern and apply the 
+# filter if there is a match.  The filters are a list of the form: 
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further 
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER 
+# is applied to all files.
+
+FILTER_PATTERNS        = 
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using 
+# INPUT_FILTER) will be used to filter the input files when producing source 
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will 
+# be generated. Documented entities will be cross-referenced with these sources. 
+# Note: To get rid of all source code in the generated output, make sure also 
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body 
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct 
+# doxygen to hide any special comment blocks from generated source code 
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES (the default) 
+# then for each documented function all documented 
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES (the default) 
+# then for each documented function all documented entities 
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = YES
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen 
+# will generate a verbatim copy of the header file for each class for 
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index 
+# of all compounds will be generated. Enable this if the project 
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then 
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns 
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all 
+# classes will be put under the same header in the alphabetical index. 
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that 
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will 
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for 
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank 
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard header.
+
+HTML_HEADER            = 
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard footer.
+
+HTML_FOOTER            = 
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading 
+# style sheet that is used by each HTML page. It can be used to 
+# fine-tune the look of the HTML output. If the tag is left blank doxygen 
+# will generate a default style sheet. Note that doxygen will try to copy 
+# the style sheet file to the HTML output directory, so don't put your own 
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        = 
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, 
+# files or namespaces will be aligned in HTML using tables. If set to 
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files 
+# will be generated that can be used as input for tools like the 
+# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) 
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can 
+# be used to specify the file name of the resulting .chm file. You 
+# can add a path in front of the file if the result should not be 
+# written to the html output directory.
+
+CHM_FILE               = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can 
+# be used to specify the location (absolute path including file name) of 
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run 
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag 
+# controls if a separate .chi index file is generated (YES) or that 
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag 
+# controls whether a binary table of contents is generated (YES) or a 
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members 
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at 
+# top of each HTML page. The value NO (the default) enables the index and 
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# This tag can be used to set the number of enum values (range [1..20]) 
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
+# generated containing a tree-like index structure (just like the one that 
+# is generated for HTML Help). For this to work a browser that supports 
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, 
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are 
+# probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW      = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be 
+# used to set the initial width (in pixels) of the frame in which the tree 
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will 
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be 
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to 
+# generate index for LaTeX. If left blank `makeindex' will be used as the 
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact 
+# LaTeX documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used 
+# by the printer. Possible values are: a4, a4wide, letter, legal and 
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX 
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         = 
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for 
+# the generated latex document. The header should contain everything until 
+# the first chapter. If it is left blank doxygen will generate a 
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           = 
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated 
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will 
+# contain links (just like the HTML output) instead of page references 
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of 
+# plain latex in the generated Makefile. Set this option to YES to get a 
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. 
+# command to the generated LaTeX files. This will instruct LaTeX to keep 
+# running if errors occur, instead of asking the user for help. 
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not 
+# include the index chapters (such as File Index, Compound Index, etc.) 
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output 
+# The RTF output is optimized for Word 97 and may not look very pretty with 
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact 
+# RTF documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated 
+# will contain hyperlink fields. The RTF file will 
+# contain links (just like the HTML output) instead of page references. 
+# This makes the output suitable for online browsing using WORD or other 
+# programs which support those fields. 
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's 
+# config file, i.e. a series of assignments. You only have to provide 
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    = 
+
+# Set optional variables used in the generation of an rtf document. 
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will 
+# generate man pages
+
+GENERATE_MAN           = YES
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to 
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output, 
+# then it will generate one additional man file for each entity 
+# documented in the real man page(s). These additional files 
+# only source the real man page, but without them the man command 
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will 
+# generate an XML file that captures the structure of 
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_SCHEMA             = 
+
+# The XML_DTD tag can be used to specify an XML DTD, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_DTD                = 
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will 
+# dump the program listings (including syntax highlighting 
+# and cross-referencing information) to the XML output. Note that 
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will 
+# generate an AutoGen Definitions (see autogen.sf.net) file 
+# that captures the structure of the code including all 
+# documentation. Note that this feature is still experimental 
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will 
+# generate a Perl module file that captures the structure of 
+# the code including all documentation. Note that this 
+# feature is still experimental and incomplete at the 
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate 
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able 
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be 
+# nicely formatted so it can be parsed by a human reader.  This is useful 
+# if you want to understand what is going on.  On the other hand, if this 
+# tag is set to NO the size of the Perl module output will be much smaller 
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file 
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. 
+# This is useful so different doxyrules.make files included by the same 
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor   
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will 
+# evaluate all C-preprocessor directives found in the sources and include 
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro 
+# names in the source code. If set to NO (the default) only conditional 
+# compilation will be performed. Macro expansion can be done in a controlled 
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES 
+# then the macro expansion is limited to the macros specified with the 
+# PREDEFINED and EXPAND_AS_PREDEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files 
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that 
+# contain include files that are not input files but should be processed by 
+# the preprocessor.
+
+INCLUDE_PATH           = 
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard 
+# patterns (like *.h and *.hpp) to filter out the header-files in the 
+# directories. If left blank, the patterns specified with FILE_PATTERNS will 
+# be used.
+
+INCLUDE_FILE_PATTERNS  = 
+
+# The PREDEFINED tag can be used to specify one or more macro names that 
+# are defined before the preprocessor is started (similar to the -D option of 
+# gcc). The argument of the tag is a list of macros of the form: name 
+# or name=definition (no spaces). If the definition and the = are 
+# omitted =1 is assumed. To prevent a macro definition from being 
+# undefined via #undef or recursively expanded use the := operator 
+# instead of the = operator.
+
+PREDEFINED             = 
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then 
+# this tag can be used to specify a list of macro names that should be expanded. 
+# The macro definition that is found in the sources will be used. 
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED      = 
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then 
+# doxygen's preprocessor will remove all function-like macros that are alone 
+# on a line, have an all uppercase name, and do not end with a semicolon. Such 
+# function macros are typically used for boiler-plate code, and will confuse 
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references   
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles. 
+# Optionally an initial location of the external documentation 
+# can be added for each tagfile. The format of a tag file without 
+# this location is as follows: 
+#   TAGFILES = file1 file2 ... 
+# Adding location for the tag files is done as follows: 
+#   TAGFILES = file1=loc1 "file2 = loc2" ... 
+# where "loc1" and "loc2" can be relative or absolute paths or 
+# URLs. If a location is present for each tag, the installdox tool 
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen 
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               = 
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create 
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       = 
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed 
+# in the class index. If set to NO only the inherited external classes 
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed 
+# in the modules index. If set to NO, only the current project's groups will 
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script 
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = @PERL@
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool   
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will 
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base 
+# or super classes. Setting the tag to NO turns the diagrams off. Note that 
+# this option is superseded by the HAVE_DOT option below. This is only a 
+# fallback. It is recommended to install and use dot, since it yields more 
+# powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# If set to YES, the inheritance and collaboration graphs will hide 
+# inheritance and usage relations if the target is undocumented 
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is 
+# available from the path. This tool is part of Graphviz, a graph visualization 
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section 
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect inheritance relations. Setting this tag to YES will force the 
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect implementation dependencies (inheritance, containment, and 
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and 
+# collaboration diagrams in a style similar to the OMG's Unified Modeling 
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the 
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT 
+# tags are set to YES then doxygen will generate a graph for each documented 
+# file showing the direct and indirect include dependencies of the file with 
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and 
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each 
+# documented header file showing the documented files that directly or 
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will 
+# generate a call dependency graph for every global function or class method. 
+# Note that enabling this option will significantly increase the time of a run. 
+# So in most cases it will be better to enable call graphs for selected 
+# functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen 
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES 
+# then doxygen will show the dependencies a directory has on other directories 
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images 
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be 
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               = 
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that 
+# contain dot files that are included in the documentation (see the 
+# \dotfile command).
+
+DOTFILE_DIRS           = 
+
+# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width 
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than 
+# this value, doxygen will try to truncate the graph, so that it fits within 
+# the specified constraint. Beware that most browsers cannot cope with very 
+# large images.
+
+MAX_DOT_GRAPH_WIDTH    = 1024
+
+# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height 
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than 
+# this value, doxygen will try to truncate the graph, so that it fits within 
+# the specified constraint. Beware that most browsers cannot cope with very 
+# large images.
+
+MAX_DOT_GRAPH_HEIGHT   = 1024
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the 
+# graphs generated by dot. A depth value of 3 means that only nodes reachable 
+# from the root by following a path via at most 3 edges will be shown. Nodes 
+# that lay further from the root node will be omitted. Note that setting this 
+# option to 1 or 2 may greatly reduce the computation time needed for large 
+# code bases. Also note that a graph may be further truncated if the graph's 
+# image dimensions are not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH 
+# and MAX_DOT_GRAPH_HEIGHT). If 0 is used for the depth value (the default), 
+# the graph is not depth-constrained.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent 
+# background. This is disabled by default, which results in a white background. 
+# Warning: Depending on the platform used, enabling this option may lead to 
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to 
+# read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output 
+# files in one run (i.e. multiple -o and -T options on the command line). This 
+# makes dot run faster, but since only newer versions of dot (>1.8.10) 
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will 
+# generate a legend page explaining the meaning of the various boxes and 
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will 
+# remove the intermediate dot files that are used to generate 
+# the various graphs.
+
+DOT_CLEANUP            = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine   
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be 
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE           = NO
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/Makefile.am
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/Makefile.am	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/Makefile.am	(revision 22073)
@@ -0,0 +1,61 @@
+SUBDIRS = src tests
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA= ippdb.pc
+
+EXTRA_DIST = ippdb.pc.in Doxyfile.in
+
+if HAVE_DOXYGEN
+
+man_MANS = \
+    $(top_builddir)/docs/man/man3/ippdb.3 \
+    $(top_builddir)/docs/man/man3/expTagCounterRow.3 \
+    $(top_builddir)/docs/man/man3/summitExpRow.3 \
+    $(top_builddir)/docs/man/man3/summitImfileRow.3 \
+    $(top_builddir)/docs/man/man3/pzPendingExpRow.3 \
+    $(top_builddir)/docs/man/man3/pzPendingImfileRow.3 \
+    $(top_builddir)/docs/man/man3/pzDoneExpRow.3 \
+    $(top_builddir)/docs/man/man3/pzDoneImfileRow.3 \
+    $(top_builddir)/docs/man/man3/newExpRow.3 \
+    $(top_builddir)/docs/man/man3/newImfileRow.3 \
+    $(top_builddir)/docs/man/man3/rawExpRow.3 \
+    $(top_builddir)/docs/man/man3/rawImfileRow.3 \
+    $(top_builddir)/docs/man/man3/guidePendingExpRow.3 \
+    $(top_builddir)/docs/man/man3/chipPendingExpRow.3 \
+    $(top_builddir)/docs/man/man3/chipPendingImfileRow.3 \
+    $(top_builddir)/docs/man/man3/chipProcessedExpRow.3 \
+    $(top_builddir)/docs/man/man3/chipMaskRow.3 \
+    $(top_builddir)/docs/man/man3/chipProcessedImfileRow.3 \
+    $(top_builddir)/docs/man/man3/camPendingExpRow.3 \
+    $(top_builddir)/docs/man/man3/camProcessedExpRow.3 \
+    $(top_builddir)/docs/man/man3/camMaskRow.3 \
+    $(top_builddir)/docs/man/man3/warpRunRow.3 \
+    $(top_builddir)/docs/man/man3/warpInputExpRow.3 \
+    $(top_builddir)/docs/man/man3/warpSkyCellMapRow.3 \
+    $(top_builddir)/docs/man/man3/warpSkyfileRow.3 \
+    $(top_builddir)/docs/man/man3/diffRunRow.3 \
+    $(top_builddir)/docs/man/man3/diffInputSkyfileRow.3 \
+    $(top_builddir)/docs/man/man3/diffSkyfileRow.3 \
+    $(top_builddir)/docs/man/man3/stackRunRow.3 \
+    $(top_builddir)/docs/man/man3/stackInputSkyfileRow.3 \
+    $(top_builddir)/docs/man/man3/stackSumSkyfileRow.3 \
+    $(top_builddir)/docs/man/man3/detRunRow.3 \
+    $(top_builddir)/docs/man/man3/detInputExpRow.3 \
+    $(top_builddir)/docs/man/man3/detProcessedImfileRow.3 \
+    $(top_builddir)/docs/man/man3/detProcessedExpRow.3 \
+    $(top_builddir)/docs/man/man3/detStackedImfileRow.3 \
+    $(top_builddir)/docs/man/man3/detNormalizedStatImfileRow.3 \
+    $(top_builddir)/docs/man/man3/detNormalizedImfileRow.3 \
+    $(top_builddir)/docs/man/man3/detNormalizedExpRow.3 \
+    $(top_builddir)/docs/man/man3/detResidImfileRow.3 \
+    $(top_builddir)/docs/man/man3/detResidExpRow.3 \
+    $(top_builddir)/docs/man/man3/detRunSummaryRow.3 
+
+
+docs/man/man3/ippdb.3 docs/man/man3/expTagCounterRow.3 docs/man/man3/summitExpRow.3 docs/man/man3/summitImfileRow.3 docs/man/man3/pzPendingExpRow.3 docs/man/man3/pzPendingImfileRow.3 docs/man/man3/pzDoneExpRow.3 docs/man/man3/pzDoneImfileRow.3 docs/man/man3/newExpRow.3 docs/man/man3/newImfileRow.3 docs/man/man3/rawExpRow.3 docs/man/man3/rawImfileRow.3 docs/man/man3/guidePendingExpRow.3 docs/man/man3/chipPendingExpRow.3 docs/man/man3/chipPendingImfileRow.3 docs/man/man3/chipProcessedExpRow.3 docs/man/man3/chipMaskRow.3 docs/man/man3/chipProcessedImfileRow.3 docs/man/man3/camPendingExpRow.3 docs/man/man3/camProcessedExpRow.3 docs/man/man3/camMaskRow.3 docs/man/man3/warpRunRow.3 docs/man/man3/warpInputExpRow.3 docs/man/man3/warpSkyCellMapRow.3 docs/man/man3/warpSkyfileRow.3 docs/man/man3/diffRunRow.3 docs/man/man3/diffInputSkyfileRow.3 docs/man/man3/diffSkyfileRow.3 docs/man/man3/stackRunRow.3 docs/man/man3/stackInputSkyfileRow.3 docs/man/man3/stackSumSkyfileRow.3 docs/man/man3/detRunRow.3 docs/man/man3/detInputExpRow.3 docs/man/man3/detProcessedImfileRow.3 docs/man/man3/detProcessedExpRow.3 docs/man/man3/detStackedImfileRow.3 docs/man/man3/detNormalizedStatImfileRow.3 docs/man/man3/detNormalizedImfileRow.3 docs/man/man3/detNormalizedExpRow.3 docs/man/man3/detResidImfileRow.3 docs/man/man3/detResidExpRow.3 docs/man/man3/detRunSummaryRow.3:
+	$(DOXYGEN)
+
+endif
+
+clean-local:
+	-rm -rf docs
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/autogen.sh
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/autogen.sh	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/autogen.sh	(revision 22073)
@@ -0,0 +1,103 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+ORIGDIR=`pwd`
+cd $srcdir
+
+PROJECT=ippdb
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL="aclocal $ACLOCAL_FLAGS"
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $LIBTOOlIZE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+        DIE=1
+}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+$LIBTOOLIZE --copy --force || echo "$LIBTOOlIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/configure.ac
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/configure.ac	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/configure.ac	(revision 22073)
@@ -0,0 +1,46 @@
+dnl
+dnl This file was generated by glueforge 1.01
+dnl
+dnl Do NOT directly edit this file.
+dnl
+
+AC_PREREQ(2.59)
+
+AC_INIT([ippdb], [1.1.19], [pan-starrs.ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([ippdb.pc.in])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_CONFIG_HEADER([config.h])
+AM_MAINTAINER_MODE
+
+AC_CONFIG_TESTDIR([tests])
+AC_CONFIG_FILES([tests/Makefile])
+AM_MISSING_PROG([AUTOM4TE], [autom4te])
+
+AC_LANG(C)
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+
+PKG_CHECK_MODULES([PSLIB], [pslib >= 0.9.0]) 
+
+AC_PATH_PROG([PERL], [perl], [missing])
+if test "$PERL" = "missing" ; then
+  AC_MSG_ERROR([perl is required])
+fi
+
+AC_PATH_PROG([DOXYGEN], [doxygen], [])
+AM_CONDITIONAL([HAVE_DOXYGEN], [test -n "$DOXYGEN"])
+
+dnl is this the best was to setup recursive CFLAGS?
+IPP_STDOPTS
+ippdb_CFLAGS="-Wall -Werror -pedantic -std=c99 -fno-strict-aliasing"
+AC_SUBST([ippdb_CFLAGS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+  ippdb.pc
+  Doxyfile
+])
+AC_OUTPUT
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/ippdb.pc.in
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/ippdb.pc.in	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/ippdb.pc.in	(revision 22073)
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: @PACKAGE@
+Description: some library
+Version: @VERSION@
+Requires: pslib
+Libs: -L${libdir} -l@PACKAGE@
+Cflags: -I${includedir}
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/src/.cvsignore
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/src/.cvsignore	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/src/.cvsignore	(revision 22073)
@@ -0,0 +1,6 @@
+.deps
+Makefile
+Makefile.in
+.libs
+libippdb.la
+libippdb_la-ippdb.lo
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/src/Makefile.am
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/src/Makefile.am	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/src/Makefile.am	(revision 22073)
@@ -0,0 +1,10 @@
+AM_CFLAGS = @ippdb_CFLAGS@
+
+include_HEADERS = ippdb.h
+lib_LTLIBRARIES = libippdb.la
+libippdb_la_CFLAGS  = $(PSLIB_CFLAGS) $(AM_CFLAGS)
+libippdb_la_LIBS    = $(PSLIB_LIBS) $(AM_LIBS)
+libippdb_la_LDFLAGS = -release $(PACKAGE_VERSION)
+libippdb_la_SOURCES = \
+    ippdb.h \
+    ippdb.c
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/src/ippdb.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/src/ippdb.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/src/ippdb.c	(revision 22073)
@@ -0,0 +1,17190 @@
+/*
+ * code.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ *
+ * This file was generated by glueforge 1.01
+ *
+ * Do NOT directly edit this file.
+ *
+ */
+
+#include <stdio.h>
+#include <inttypes.h>
+
+#include "ippdb.h"
+
+#define EXPTAGCOUNTER_TABLE_NAME "expTagCounter"
+#define SUMMITEXP_TABLE_NAME "summitExp"
+#define SUMMITIMFILE_TABLE_NAME "summitImfile"
+#define PZPENDINGEXP_TABLE_NAME "pzPendingExp"
+#define PZPENDINGIMFILE_TABLE_NAME "pzPendingImfile"
+#define PZDONEEXP_TABLE_NAME "pzDoneExp"
+#define PZDONEIMFILE_TABLE_NAME "pzDoneImfile"
+#define NEWEXP_TABLE_NAME "newExp"
+#define NEWIMFILE_TABLE_NAME "newImfile"
+#define RAWEXP_TABLE_NAME "rawExp"
+#define RAWIMFILE_TABLE_NAME "rawImfile"
+#define GUIDEPENDINGEXP_TABLE_NAME "guidePendingExp"
+#define CHIPPENDINGEXP_TABLE_NAME "chipPendingExp"
+#define CHIPPENDINGIMFILE_TABLE_NAME "chipPendingImfile"
+#define CHIPPROCESSEDEXP_TABLE_NAME "chipProcessedExp"
+#define CHIPMASK_TABLE_NAME "chipMask"
+#define CHIPPROCESSEDIMFILE_TABLE_NAME "chipProcessedImfile"
+#define CAMPENDINGEXP_TABLE_NAME "camPendingExp"
+#define CAMPROCESSEDEXP_TABLE_NAME "camProcessedExp"
+#define CAMMASK_TABLE_NAME "camMask"
+#define WARPRUN_TABLE_NAME "warpRun"
+#define WARPINPUTEXP_TABLE_NAME "warpInputExp"
+#define WARPSKYCELLMAP_TABLE_NAME "warpSkyCellMap"
+#define WARPSKYFILE_TABLE_NAME "warpSkyfile"
+#define DIFFRUN_TABLE_NAME "diffRun"
+#define DIFFINPUTSKYFILE_TABLE_NAME "diffInputSkyfile"
+#define DIFFSKYFILE_TABLE_NAME "diffSkyfile"
+#define STACKRUN_TABLE_NAME "stackRun"
+#define STACKINPUTSKYFILE_TABLE_NAME "stackInputSkyfile"
+#define STACKSUMSKYFILE_TABLE_NAME "stackSumSkyfile"
+#define DETRUN_TABLE_NAME "detRun"
+#define DETINPUTEXP_TABLE_NAME "detInputExp"
+#define DETPROCESSEDIMFILE_TABLE_NAME "detProcessedImfile"
+#define DETPROCESSEDEXP_TABLE_NAME "detProcessedExp"
+#define DETSTACKEDIMFILE_TABLE_NAME "detStackedImfile"
+#define DETNORMALIZEDSTATIMFILE_TABLE_NAME "detNormalizedStatImfile"
+#define DETNORMALIZEDIMFILE_TABLE_NAME "detNormalizedImfile"
+#define DETNORMALIZEDEXP_TABLE_NAME "detNormalizedExp"
+#define DETRESIDIMFILE_TABLE_NAME "detResidImfile"
+#define DETRESIDEXP_TABLE_NAME "detResidExp"
+#define DETRUNSUMMARY_TABLE_NAME "detRunSummary"
+#define MAX_STRING_LENGTH 1024
+
+psDB *ippdbInit(const char *host, const char *user, const char *passwd, const char *dbname, unsigned int port)
+{
+    return psDBInit(host, user, passwd, dbname, port);
+}
+
+void ippdbCleanup(psDB *dbh)
+{
+    psDBCleanup(dbh);
+}
+
+bool ippdbPrintMetadata(FILE *stream, psMetadata *md, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(md, false);
+
+    psMetadata *clean = psMetadataCopy(NULL, md);
+
+    if (!ippdbPrintMetadataRaw(stream, clean, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(clean);
+        return false;
+    }
+    psFree(clean);
+
+    return true;
+}
+
+bool ippdbPrintMetadataRaw(FILE *stream, psMetadata *md, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(md, false);
+
+    if (mdcf) {
+        psString str = psMetadataConfigFormat(md);
+        if (!str) {
+            psError(PS_ERR_UNKNOWN, false, "failed to format data into a string");
+            psFree(str);
+            return false;
+        }
+        fprintf(stream, "%s\n", str);
+        psFree(str);
+
+        return true;
+    }
+
+#define METADATAITEM_STRIFY_CASE(ptype, format, type) \
+case ptype: \
+    psStringAppend(&str, format, item->data.type); \
+    break;
+
+    // else
+    // flatten the metadata into | separated values
+    psString str = NULL;
+    psMetadataItem *item = NULL;
+    psMetadataIterator *iter = psMetadataIteratorAlloc(md, 0, NULL);
+    while ((item = psMetadataGetAndIncrement(iter))) {
+        switch (item->type) {
+            METADATAITEM_STRIFY_CASE(PS_DATA_S8, "%hhd", S8);
+            METADATAITEM_STRIFY_CASE(PS_DATA_S16, "%hd", S16);
+            METADATAITEM_STRIFY_CASE(PS_DATA_S32, "%d", S32);
+            METADATAITEM_STRIFY_CASE(PS_DATA_S64, "%" PRId64, S64);
+            METADATAITEM_STRIFY_CASE(PS_DATA_U8, "%hhu", U8);
+            METADATAITEM_STRIFY_CASE(PS_DATA_U16, "%hu", U16);
+            METADATAITEM_STRIFY_CASE(PS_DATA_U32, "%u", U32);
+            METADATAITEM_STRIFY_CASE(PS_DATA_U64, "%" PRIu64, U64);
+            METADATAITEM_STRIFY_CASE(PS_DATA_F32, "%f", F32);
+            METADATAITEM_STRIFY_CASE(PS_DATA_F64, "%f", F64);
+            case PS_DATA_STRING:
+                if (item->data.str) {
+                    psString tmpStr = psStringCopy(item->data.str);
+                    psStringSubstitute(&tmpStr, "_", " ");
+                    psStringAppend(&str, "%s", tmpStr);
+                    psFree(tmpStr);
+                }
+                break;
+            case PS_DATA_BOOL:
+                if (item->data.B) {
+                    psStringAppend(&str, "T");
+                } else {
+                    psStringAppend(&str, "F");
+                }
+                break;
+            case PS_DATA_METADATA:
+                if (!ippdbPrintMetadataRaw(stream, item->data.md, mdcf)) {
+                    psError(PS_ERR_UNKNOWN, false ,"failed to print nested metadata");
+                }
+                // a metadata is a special case. We don't want a | seperating
+                // the metadata from any other output so we need to skip the
+                // !->offEnd test
+                continue;
+            case PS_DATA_TIME:
+               // pass through NULLs as "NULL"
+                if (item->data.V) {
+                    psString time = psTimeToISO(item->data.V);
+                    psStringAppend(&str, "%s", time);
+                psFree(time);
+                } else {
+                    psStringAppend(&str, "NULL");
+                }
+                break;
+            default:
+                psError(PS_ERR_UNKNOWN, true,"unsupported psMetadataItem type");
+                psFree(iter);
+                psFree(str);
+                return false;
+        }
+        if (!iter->iter->offEnd) {
+            psStringAppend(&str, " ");
+        }
+    }
+    psFree(iter);
+
+    // if we did nothing but handle recursive metadatas str will be NULL
+    if (str) {
+        fprintf(stream, "%s\n", str);
+        psFree(str);
+    }
+
+    return true;
+}
+
+bool ippdbPrintMetadatas(FILE *stream, psArray *mds, const char *mdname, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(mds, false);
+    PS_ASSERT_PTR_NON_NULL(mdname, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(mds); i++) {
+        psMetadata *md = psMetadataCopy(NULL, mds->data[i]);
+        if (!psMetadataAddMetadata(output, PS_LIST_TAIL, mdname, PS_META_DUPLICATE_OK, NULL, md)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add a metadata item");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadata(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+bool ippdbPrintMetadatasRaw(FILE *stream, psArray *mds, const char *mdname, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(mds, false);
+    PS_ASSERT_PTR_NON_NULL(mdname, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(mds); i++) {
+        psMetadata *md = mds->data[i];
+        if (!psMetadataAddMetadata(output, PS_LIST_TAIL, mdname, PS_META_DUPLICATE_OK, NULL, md)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add a metadata item");
+            psFree(output);
+            return false;
+        }
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static void expTagCounterRowFree(expTagCounterRow *object);
+
+expTagCounterRow *expTagCounterRowAlloc(psU64 counter)
+{
+    expTagCounterRow *_object;
+
+    _object = psAlloc(sizeof(expTagCounterRow));
+    psMemSetDeallocator(_object, (psFreeFunc)expTagCounterRowFree);
+
+    _object->counter = counter;
+
+    return _object;
+}
+
+static void expTagCounterRowFree(expTagCounterRow *object)
+{
+}
+
+bool expTagCounterCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "counter", PS_DATA_U64, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item counter");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, EXPTAGCOUNTER_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool expTagCounterDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, EXPTAGCOUNTER_TABLE_NAME);
+}
+
+bool expTagCounterInsert(psDB * dbh, psU64 counter)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "counter", PS_DATA_U64, NULL, counter)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item counter");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, EXPTAGCOUNTER_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long expTagCounterDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, EXPTAGCOUNTER_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from expTagCounter");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool expTagCounterInsertObject(psDB *dbh, expTagCounterRow *object)
+{
+    return expTagCounterInsert(dbh, object->counter);
+}
+
+bool expTagCounterInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!expTagCounterInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool expTagCounterInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  EXPTAGCOUNTER_TABLE_NAME
+    if (!psFitsMoveExtName(fits, EXPTAGCOUNTER_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", EXPTAGCOUNTER_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, EXPTAGCOUNTER_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool expTagCounterSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, EXPTAGCOUNTER_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, EXPTAGCOUNTER_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *expTagCounterMetadataFromObject(const expTagCounterRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "counter", PS_DATA_U64, NULL, object->counter)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item counter");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+expTagCounterRow *expTagCounterObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psU64 counter = psMetadataLookupU64(&status, md, "counter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item counter");
+        return false;
+    }
+
+    return expTagCounterRowAlloc(counter);
+}
+psArray *expTagCounterSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, EXPTAGCOUNTER_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        expTagCounterRow *object = expTagCounterObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool expTagCounterDeleteObject(psDB *dbh, const expTagCounterRow *object)
+{
+    psMetadata *where = expTagCounterMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, EXPTAGCOUNTER_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from expTagCounter");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "expTagCounterRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long expTagCounterDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        expTagCounterRow *object = objects->data[i];
+        psMetadata *where = expTagCounterMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, EXPTAGCOUNTER_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from expTagCounter");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool expTagCounterPrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = expTagCounterMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            EXPTAGCOUNTER_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool expTagCounterPrintObject(FILE *stream, expTagCounterRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = expTagCounterMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void summitExpRowFree(summitExpRow *object);
+
+summitExpRow *summitExpRowAlloc(const char *exp_id, const char *camera, const char *telescope, psTime* dateobs, const char *exp_type, const char *uri, psS32 imfiles)
+{
+    summitExpRow    *_object;
+
+    _object = psAlloc(sizeof(summitExpRow));
+    psMemSetDeallocator(_object, (psFreeFunc)summitExpRowFree);
+
+    _object->exp_id = psStringCopy(exp_id);
+    _object->camera = psStringCopy(camera);
+    _object->telescope = psStringCopy(telescope);
+    _object->dateobs = psTimeCopy(dateobs);
+    _object->exp_type = psStringCopy(exp_type);
+    _object->uri = psStringCopy(uri);
+    _object->imfiles = imfiles;
+
+    return _object;
+}
+
+static void summitExpRowFree(summitExpRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->dateobs);
+    psFree(object->exp_type);
+    psFree(object->uri);
+}
+
+bool summitExpCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dateobs", PS_DATA_TIME, NULL, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "imfiles", PS_DATA_S32, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, SUMMITEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool summitExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, SUMMITEXP_TABLE_NAME);
+}
+
+bool summitExpInsert(psDB * dbh, const char *exp_id, const char *camera, const char *telescope, psTime* dateobs, const char *exp_type, const char *uri, psS32 imfiles)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dateobs", PS_DATA_TIME, NULL, dateobs)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "imfiles", PS_DATA_S32, NULL, imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, SUMMITEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long summitExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, SUMMITEXP_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from summitExp");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool summitExpInsertObject(psDB *dbh, summitExpRow *object)
+{
+    return summitExpInsert(dbh, object->exp_id, object->camera, object->telescope, object->dateobs, object->exp_type, object->uri, object->imfiles);
+}
+
+bool summitExpInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!summitExpInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool summitExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  SUMMITEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, SUMMITEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", SUMMITEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, SUMMITEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool summitExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, SUMMITEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, SUMMITEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *summitExpMetadataFromObject(const summitExpRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, object->camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, object->telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dateobs", PS_DATA_TIME, NULL, object->dateobs)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, object->exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "imfiles", PS_DATA_S32, NULL, object->imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+summitExpRow *summitExpObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    char* exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    char* camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    char* telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    psTime* dateobs = psMetadataLookupPtr(&status, md, "dateobs");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dateobs");
+        return false;
+    }
+    char* exp_type = psMetadataLookupPtr(&status, md, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        return false;
+    }
+    char* uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    psS32 imfiles = psMetadataLookupS32(&status, md, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        return false;
+    }
+
+    return summitExpRowAlloc(exp_id, camera, telescope, dateobs, exp_type, uri, imfiles);
+}
+psArray *summitExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, SUMMITEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        summitExpRow *object = summitExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool summitExpDeleteObject(psDB *dbh, const summitExpRow *object)
+{
+    psMetadata *where = summitExpMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, SUMMITEXP_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from summitExp");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "summitExpRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long summitExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        summitExpRow *object = objects->data[i];
+        psMetadata *where = summitExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, SUMMITEXP_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from summitExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool summitExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = summitExpMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            SUMMITEXP_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool summitExpPrintObject(FILE *stream, summitExpRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = summitExpMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void summitImfileRowFree(summitImfileRow *object);
+
+summitImfileRow *summitImfileRowAlloc(const char *exp_id, const char *camera, const char *telescope, const char *file_id, psS32 bytes, const char *md5sum, const char *class, const char *class_id, const char *uri)
+{
+    summitImfileRow *_object;
+
+    _object = psAlloc(sizeof(summitImfileRow));
+    psMemSetDeallocator(_object, (psFreeFunc)summitImfileRowFree);
+
+    _object->exp_id = psStringCopy(exp_id);
+    _object->camera = psStringCopy(camera);
+    _object->telescope = psStringCopy(telescope);
+    _object->file_id = psStringCopy(file_id);
+    _object->bytes = bytes;
+    _object->md5sum = psStringCopy(md5sum);
+    _object->class = psStringCopy(class);
+    _object->class_id = psStringCopy(class_id);
+    _object->uri = psStringCopy(uri);
+
+    return _object;
+}
+
+static void summitImfileRowFree(summitImfileRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->file_id);
+    psFree(object->md5sum);
+    psFree(object->class);
+    psFree(object->class_id);
+    psFree(object->uri);
+}
+
+bool summitImfileCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "file_id", PS_DATA_STRING, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item file_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bytes", PS_DATA_S32, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bytes");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "md5sum", PS_DATA_STRING, NULL, "32")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item md5sum");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, SUMMITIMFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool summitImfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, SUMMITIMFILE_TABLE_NAME);
+}
+
+bool summitImfileInsert(psDB * dbh, const char *exp_id, const char *camera, const char *telescope, const char *file_id, psS32 bytes, const char *md5sum, const char *class, const char *class_id, const char *uri)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "file_id", PS_DATA_STRING, NULL, file_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item file_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bytes", PS_DATA_S32, NULL, bytes)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bytes");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "md5sum", PS_DATA_STRING, NULL, md5sum)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item md5sum");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, NULL, class)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, SUMMITIMFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long summitImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, SUMMITIMFILE_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from summitImfile");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool summitImfileInsertObject(psDB *dbh, summitImfileRow *object)
+{
+    return summitImfileInsert(dbh, object->exp_id, object->camera, object->telescope, object->file_id, object->bytes, object->md5sum, object->class, object->class_id, object->uri);
+}
+
+bool summitImfileInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!summitImfileInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool summitImfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  SUMMITIMFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, SUMMITIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", SUMMITIMFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, SUMMITIMFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool summitImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, SUMMITIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, SUMMITIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *summitImfileMetadataFromObject(const summitImfileRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, object->camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, object->telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "file_id", PS_DATA_STRING, NULL, object->file_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item file_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bytes", PS_DATA_S32, NULL, object->bytes)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bytes");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "md5sum", PS_DATA_STRING, NULL, object->md5sum)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item md5sum");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, NULL, object->class)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+summitImfileRow *summitImfileObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    char* exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    char* camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    char* telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    char* file_id = psMetadataLookupPtr(&status, md, "file_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item file_id");
+        return false;
+    }
+    psS32 bytes = psMetadataLookupS32(&status, md, "bytes");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bytes");
+        return false;
+    }
+    char* md5sum = psMetadataLookupPtr(&status, md, "md5sum");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item md5sum");
+        return false;
+    }
+    char* class = psMetadataLookupPtr(&status, md, "class");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class");
+        return false;
+    }
+    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        return false;
+    }
+    char* uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+
+    return summitImfileRowAlloc(exp_id, camera, telescope, file_id, bytes, md5sum, class, class_id, uri);
+}
+psArray *summitImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, SUMMITIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        summitImfileRow *object = summitImfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool summitImfileDeleteObject(psDB *dbh, const summitImfileRow *object)
+{
+    psMetadata *where = summitImfileMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, SUMMITIMFILE_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from summitImfile");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "summitImfileRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long summitImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        summitImfileRow *object = objects->data[i];
+        psMetadata *where = summitImfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, SUMMITIMFILE_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from summitImfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool summitImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = summitImfileMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            SUMMITIMFILE_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool summitImfilePrintObject(FILE *stream, summitImfileRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = summitImfileMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void pzPendingExpRowFree(pzPendingExpRow *object);
+
+pzPendingExpRow *pzPendingExpRowAlloc(const char *exp_id, const char *camera, const char *telescope)
+{
+    pzPendingExpRow *_object;
+
+    _object = psAlloc(sizeof(pzPendingExpRow));
+    psMemSetDeallocator(_object, (psFreeFunc)pzPendingExpRowFree);
+
+    _object->exp_id = psStringCopy(exp_id);
+    _object->camera = psStringCopy(camera);
+    _object->telescope = psStringCopy(telescope);
+
+    return _object;
+}
+
+static void pzPendingExpRowFree(pzPendingExpRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+}
+
+bool pzPendingExpCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, PZPENDINGEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool pzPendingExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, PZPENDINGEXP_TABLE_NAME);
+}
+
+bool pzPendingExpInsert(psDB * dbh, const char *exp_id, const char *camera, const char *telescope)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, PZPENDINGEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long pzPendingExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, PZPENDINGEXP_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from pzPendingExp");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool pzPendingExpInsertObject(psDB *dbh, pzPendingExpRow *object)
+{
+    return pzPendingExpInsert(dbh, object->exp_id, object->camera, object->telescope);
+}
+
+bool pzPendingExpInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!pzPendingExpInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool pzPendingExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  PZPENDINGEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, PZPENDINGEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", PZPENDINGEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, PZPENDINGEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool pzPendingExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, PZPENDINGEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, PZPENDINGEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *pzPendingExpMetadataFromObject(const pzPendingExpRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, object->camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, object->telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+pzPendingExpRow *pzPendingExpObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    char* exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    char* camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    char* telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+
+    return pzPendingExpRowAlloc(exp_id, camera, telescope);
+}
+psArray *pzPendingExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, PZPENDINGEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        pzPendingExpRow *object = pzPendingExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool pzPendingExpDeleteObject(psDB *dbh, const pzPendingExpRow *object)
+{
+    psMetadata *where = pzPendingExpMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, PZPENDINGEXP_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from pzPendingExp");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "pzPendingExpRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long pzPendingExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        pzPendingExpRow *object = objects->data[i];
+        psMetadata *where = pzPendingExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, PZPENDINGEXP_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from pzPendingExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool pzPendingExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = pzPendingExpMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            PZPENDINGEXP_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool pzPendingExpPrintObject(FILE *stream, pzPendingExpRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = pzPendingExpMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void pzPendingImfileRowFree(pzPendingImfileRow *object);
+
+pzPendingImfileRow *pzPendingImfileRowAlloc(const char *exp_id, const char *camera, const char *telescope, const char *class, const char *class_id, const char *exp_tag)
+{
+    pzPendingImfileRow *_object;
+
+    _object = psAlloc(sizeof(pzPendingImfileRow));
+    psMemSetDeallocator(_object, (psFreeFunc)pzPendingImfileRowFree);
+
+    _object->exp_id = psStringCopy(exp_id);
+    _object->camera = psStringCopy(camera);
+    _object->telescope = psStringCopy(telescope);
+    _object->class = psStringCopy(class);
+    _object->class_id = psStringCopy(class_id);
+    _object->exp_tag = psStringCopy(exp_tag);
+
+    return _object;
+}
+
+static void pzPendingImfileRowFree(pzPendingImfileRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->class);
+    psFree(object->class_id);
+    psFree(object->exp_tag);
+}
+
+bool pzPendingImfileCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Unique Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, PZPENDINGIMFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool pzPendingImfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, PZPENDINGIMFILE_TABLE_NAME);
+}
+
+bool pzPendingImfileInsert(psDB * dbh, const char *exp_id, const char *camera, const char *telescope, const char *class, const char *class_id, const char *exp_tag)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, NULL, class)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, PZPENDINGIMFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long pzPendingImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, PZPENDINGIMFILE_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from pzPendingImfile");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool pzPendingImfileInsertObject(psDB *dbh, pzPendingImfileRow *object)
+{
+    return pzPendingImfileInsert(dbh, object->exp_id, object->camera, object->telescope, object->class, object->class_id, object->exp_tag);
+}
+
+bool pzPendingImfileInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!pzPendingImfileInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool pzPendingImfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  PZPENDINGIMFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, PZPENDINGIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", PZPENDINGIMFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, PZPENDINGIMFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool pzPendingImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, PZPENDINGIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, PZPENDINGIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *pzPendingImfileMetadataFromObject(const pzPendingImfileRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, object->camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, object->telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, NULL, object->class)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+pzPendingImfileRow *pzPendingImfileObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    char* exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    char* camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    char* telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    char* class = psMetadataLookupPtr(&status, md, "class");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class");
+        return false;
+    }
+    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        return false;
+    }
+    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
+        return false;
+    }
+
+    return pzPendingImfileRowAlloc(exp_id, camera, telescope, class, class_id, exp_tag);
+}
+psArray *pzPendingImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, PZPENDINGIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        pzPendingImfileRow *object = pzPendingImfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool pzPendingImfileDeleteObject(psDB *dbh, const pzPendingImfileRow *object)
+{
+    psMetadata *where = pzPendingImfileMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, PZPENDINGIMFILE_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from pzPendingImfile");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "pzPendingImfileRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long pzPendingImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        pzPendingImfileRow *object = objects->data[i];
+        psMetadata *where = pzPendingImfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, PZPENDINGIMFILE_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from pzPendingImfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool pzPendingImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = pzPendingImfileMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            PZPENDINGIMFILE_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool pzPendingImfilePrintObject(FILE *stream, pzPendingImfileRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = pzPendingImfileMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void pzDoneExpRowFree(pzDoneExpRow *object);
+
+pzDoneExpRow *pzDoneExpRowAlloc(const char *exp_id, const char *camera, const char *telescope)
+{
+    pzDoneExpRow    *_object;
+
+    _object = psAlloc(sizeof(pzDoneExpRow));
+    psMemSetDeallocator(_object, (psFreeFunc)pzDoneExpRowFree);
+
+    _object->exp_id = psStringCopy(exp_id);
+    _object->camera = psStringCopy(camera);
+    _object->telescope = psStringCopy(telescope);
+
+    return _object;
+}
+
+static void pzDoneExpRowFree(pzDoneExpRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+}
+
+bool pzDoneExpCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, PZDONEEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool pzDoneExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, PZDONEEXP_TABLE_NAME);
+}
+
+bool pzDoneExpInsert(psDB * dbh, const char *exp_id, const char *camera, const char *telescope)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, PZDONEEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long pzDoneExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, PZDONEEXP_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from pzDoneExp");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool pzDoneExpInsertObject(psDB *dbh, pzDoneExpRow *object)
+{
+    return pzDoneExpInsert(dbh, object->exp_id, object->camera, object->telescope);
+}
+
+bool pzDoneExpInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!pzDoneExpInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool pzDoneExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  PZDONEEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, PZDONEEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", PZDONEEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, PZDONEEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool pzDoneExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, PZDONEEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, PZDONEEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *pzDoneExpMetadataFromObject(const pzDoneExpRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, object->camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, object->telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+pzDoneExpRow *pzDoneExpObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    char* exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    char* camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    char* telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+
+    return pzDoneExpRowAlloc(exp_id, camera, telescope);
+}
+psArray *pzDoneExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, PZDONEEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        pzDoneExpRow *object = pzDoneExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool pzDoneExpDeleteObject(psDB *dbh, const pzDoneExpRow *object)
+{
+    psMetadata *where = pzDoneExpMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, PZDONEEXP_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from pzDoneExp");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "pzDoneExpRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long pzDoneExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        pzDoneExpRow *object = objects->data[i];
+        psMetadata *where = pzDoneExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, PZDONEEXP_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from pzDoneExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool pzDoneExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = pzDoneExpMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            PZDONEEXP_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool pzDoneExpPrintObject(FILE *stream, pzDoneExpRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = pzDoneExpMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void pzDoneImfileRowFree(pzDoneImfileRow *object);
+
+pzDoneImfileRow *pzDoneImfileRowAlloc(const char *exp_id, const char *camera, const char *telescope, const char *class, const char *class_id, const char *exp_tag, const char *uri)
+{
+    pzDoneImfileRow *_object;
+
+    _object = psAlloc(sizeof(pzDoneImfileRow));
+    psMemSetDeallocator(_object, (psFreeFunc)pzDoneImfileRowFree);
+
+    _object->exp_id = psStringCopy(exp_id);
+    _object->camera = psStringCopy(camera);
+    _object->telescope = psStringCopy(telescope);
+    _object->class = psStringCopy(class);
+    _object->class_id = psStringCopy(class_id);
+    _object->exp_tag = psStringCopy(exp_tag);
+    _object->uri = psStringCopy(uri);
+
+    return _object;
+}
+
+static void pzDoneImfileRowFree(pzDoneImfileRow *object)
+{
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->class);
+    psFree(object->class_id);
+    psFree(object->exp_tag);
+    psFree(object->uri);
+}
+
+bool pzDoneImfileCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Unique Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, PZDONEIMFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool pzDoneImfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, PZDONEIMFILE_TABLE_NAME);
+}
+
+bool pzDoneImfileInsert(psDB * dbh, const char *exp_id, const char *camera, const char *telescope, const char *class, const char *class_id, const char *exp_tag, const char *uri)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, NULL, class)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, PZDONEIMFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long pzDoneImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, PZDONEIMFILE_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from pzDoneImfile");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool pzDoneImfileInsertObject(psDB *dbh, pzDoneImfileRow *object)
+{
+    return pzDoneImfileInsert(dbh, object->exp_id, object->camera, object->telescope, object->class, object->class_id, object->exp_tag, object->uri);
+}
+
+bool pzDoneImfileInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!pzDoneImfileInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool pzDoneImfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  PZDONEIMFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, PZDONEIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", PZDONEIMFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, PZDONEIMFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool pzDoneImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, PZDONEIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, PZDONEIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *pzDoneImfileMetadataFromObject(const pzDoneImfileRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, object->camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, object->telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, NULL, object->class)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+pzDoneImfileRow *pzDoneImfileObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    char* exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    char* camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    char* telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    char* class = psMetadataLookupPtr(&status, md, "class");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class");
+        return false;
+    }
+    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        return false;
+    }
+    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
+        return false;
+    }
+    char* uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+
+    return pzDoneImfileRowAlloc(exp_id, camera, telescope, class, class_id, exp_tag, uri);
+}
+psArray *pzDoneImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, PZDONEIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        pzDoneImfileRow *object = pzDoneImfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool pzDoneImfileDeleteObject(psDB *dbh, const pzDoneImfileRow *object)
+{
+    psMetadata *where = pzDoneImfileMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, PZDONEIMFILE_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from pzDoneImfile");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "pzDoneImfileRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long pzDoneImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        pzDoneImfileRow *object = objects->data[i];
+        psMetadata *where = pzDoneImfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, PZDONEIMFILE_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from pzDoneImfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool pzDoneImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = pzDoneImfileMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            PZDONEIMFILE_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool pzDoneImfilePrintObject(FILE *stream, pzDoneImfileRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = pzDoneImfileMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void newExpRowFree(newExpRow *object);
+
+newExpRow *newExpRowAlloc(const char *exp_tag, const char *exp_id, const char *camera, const char *telescope, psS32 imfiles, const char *workdir)
+{
+    newExpRow       *_object;
+
+    _object = psAlloc(sizeof(newExpRow));
+    psMemSetDeallocator(_object, (psFreeFunc)newExpRowFree);
+
+    _object->exp_tag = psStringCopy(exp_tag);
+    _object->exp_id = psStringCopy(exp_id);
+    _object->camera = psStringCopy(camera);
+    _object->telescope = psStringCopy(telescope);
+    _object->imfiles = imfiles;
+    _object->workdir = psStringCopy(workdir);
+
+    return _object;
+}
+
+static void newExpRowFree(newExpRow *object)
+{
+    psFree(object->exp_tag);
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->workdir);
+}
+
+bool newExpCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "imfiles", PS_DATA_S32, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, "destination for output files", "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, NEWEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool newExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, NEWEXP_TABLE_NAME);
+}
+
+bool newExpInsert(psDB * dbh, const char *exp_tag, const char *exp_id, const char *camera, const char *telescope, psS32 imfiles, const char *workdir)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "imfiles", PS_DATA_S32, NULL, imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, workdir)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, NEWEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long newExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, NEWEXP_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from newExp");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool newExpInsertObject(psDB *dbh, newExpRow *object)
+{
+    return newExpInsert(dbh, object->exp_tag, object->exp_id, object->camera, object->telescope, object->imfiles, object->workdir);
+}
+
+bool newExpInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!newExpInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool newExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  NEWEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, NEWEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", NEWEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, NEWEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool newExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, NEWEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, NEWEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *newExpMetadataFromObject(const newExpRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, object->camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, object->telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "imfiles", PS_DATA_S32, NULL, object->imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, object->workdir)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+newExpRow *newExpObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
+        return false;
+    }
+    char* exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    char* camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    char* telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    psS32 imfiles = psMetadataLookupS32(&status, md, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        return false;
+    }
+    char* workdir = psMetadataLookupPtr(&status, md, "workdir");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
+        return false;
+    }
+
+    return newExpRowAlloc(exp_tag, exp_id, camera, telescope, imfiles, workdir);
+}
+psArray *newExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, NEWEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        newExpRow *object = newExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool newExpDeleteObject(psDB *dbh, const newExpRow *object)
+{
+    psMetadata *where = newExpMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, NEWEXP_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from newExp");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "newExpRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long newExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        newExpRow *object = objects->data[i];
+        psMetadata *where = newExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, NEWEXP_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from newExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool newExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = newExpMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            NEWEXP_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool newExpPrintObject(FILE *stream, newExpRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = newExpMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void newImfileRowFree(newImfileRow *object);
+
+newImfileRow *newImfileRowAlloc(const char *exp_tag, const char *class, const char *class_id, const char *uri)
+{
+    newImfileRow    *_object;
+
+    _object = psAlloc(sizeof(newImfileRow));
+    psMemSetDeallocator(_object, (psFreeFunc)newImfileRowFree);
+
+    _object->exp_tag = psStringCopy(exp_tag);
+    _object->class = psStringCopy(class);
+    _object->class_id = psStringCopy(class_id);
+    _object->uri = psStringCopy(uri);
+
+    return _object;
+}
+
+static void newImfileRowFree(newImfileRow *object)
+{
+    psFree(object->exp_tag);
+    psFree(object->class);
+    psFree(object->class_id);
+    psFree(object->uri);
+}
+
+bool newImfileCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, NEWIMFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool newImfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, NEWIMFILE_TABLE_NAME);
+}
+
+bool newImfileInsert(psDB * dbh, const char *exp_tag, const char *class, const char *class_id, const char *uri)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, NULL, class)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, NEWIMFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long newImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, NEWIMFILE_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from newImfile");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool newImfileInsertObject(psDB *dbh, newImfileRow *object)
+{
+    return newImfileInsert(dbh, object->exp_tag, object->class, object->class_id, object->uri);
+}
+
+bool newImfileInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!newImfileInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool newImfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  NEWIMFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, NEWIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", NEWIMFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, NEWIMFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool newImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, NEWIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, NEWIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *newImfileMetadataFromObject(const newImfileRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, NULL, object->class)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+newImfileRow *newImfileObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
+        return false;
+    }
+    char* class = psMetadataLookupPtr(&status, md, "class");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class");
+        return false;
+    }
+    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        return false;
+    }
+    char* uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+
+    return newImfileRowAlloc(exp_tag, class, class_id, uri);
+}
+psArray *newImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, NEWIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        newImfileRow *object = newImfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool newImfileDeleteObject(psDB *dbh, const newImfileRow *object)
+{
+    psMetadata *where = newImfileMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, NEWIMFILE_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from newImfile");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "newImfileRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long newImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        newImfileRow *object = objects->data[i];
+        psMetadata *where = newImfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, NEWIMFILE_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from newImfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool newImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = newImfileMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            NEWIMFILE_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool newImfilePrintObject(FILE *stream, newImfileRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = newImfileMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void rawExpRowFree(rawExpRow *object);
+
+rawExpRow *rawExpRowAlloc(const char *exp_tag, const char *exp_id, const char *camera, const char *telescope, psTime* dateobs, const char *exp_type, psS32 imfiles, const char *filelevel, const char *workdir, const char *filter, psF32 airmass, psF64 ra, psF64 decl, psF32 exp_time, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 alt, psF64 az, psF32 ccd_temp, psF64 posang, const char *object, psF32 solang, psS16 fault)
+{
+    rawExpRow       *_object;
+
+    _object = psAlloc(sizeof(rawExpRow));
+    psMemSetDeallocator(_object, (psFreeFunc)rawExpRowFree);
+
+    _object->exp_tag = psStringCopy(exp_tag);
+    _object->exp_id = psStringCopy(exp_id);
+    _object->camera = psStringCopy(camera);
+    _object->telescope = psStringCopy(telescope);
+    _object->dateobs = psTimeCopy(dateobs);
+    _object->exp_type = psStringCopy(exp_type);
+    _object->imfiles = imfiles;
+    _object->filelevel = psStringCopy(filelevel);
+    _object->workdir = psStringCopy(workdir);
+    _object->filter = psStringCopy(filter);
+    _object->airmass = airmass;
+    _object->ra = ra;
+    _object->decl = decl;
+    _object->exp_time = exp_time;
+    _object->bg = bg;
+    _object->bg_stdev = bg_stdev;
+    _object->bg_mean_stdev = bg_mean_stdev;
+    _object->alt = alt;
+    _object->az = az;
+    _object->ccd_temp = ccd_temp;
+    _object->posang = posang;
+    _object->object = psStringCopy(object);
+    _object->solang = solang;
+    _object->fault = fault;
+
+    return _object;
+}
+
+static void rawExpRowFree(rawExpRow *object)
+{
+    psFree(object->exp_tag);
+    psFree(object->exp_id);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->dateobs);
+    psFree(object->exp_type);
+    psFree(object->filelevel);
+    psFree(object->workdir);
+    psFree(object->filter);
+    psFree(object->object);
+}
+
+bool rawExpCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dateobs", PS_DATA_TIME, NULL, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "imfiles", PS_DATA_S32, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "filelevel", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filelevel");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, "destination for output files", "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass", PS_DATA_F32, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time", PS_DATA_F32, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "alt", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item alt");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "az", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp", PS_DATA_F32, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item posang");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "object", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item object");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "solang", PS_DATA_F32, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item solang");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, RAWEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool rawExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, RAWEXP_TABLE_NAME);
+}
+
+bool rawExpInsert(psDB * dbh, const char *exp_tag, const char *exp_id, const char *camera, const char *telescope, psTime* dateobs, const char *exp_type, psS32 imfiles, const char *filelevel, const char *workdir, const char *filter, psF32 airmass, psF64 ra, psF64 decl, psF32 exp_time, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 alt, psF64 az, psF32 ccd_temp, psF64 posang, const char *object, psF32 solang, psS16 fault)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, NULL, exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dateobs", PS_DATA_TIME, NULL, dateobs)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "imfiles", PS_DATA_S32, NULL, imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "filelevel", PS_DATA_STRING, NULL, filelevel)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filelevel");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, workdir)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, filter)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass", PS_DATA_F32, NULL, airmass)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra", PS_DATA_F64, NULL, ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl", PS_DATA_F64, NULL, decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time", PS_DATA_F32, NULL, exp_time)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "alt", PS_DATA_F64, NULL, alt)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item alt");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "az", PS_DATA_F64, NULL, az)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp", PS_DATA_F32, NULL, ccd_temp)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang", PS_DATA_F64, NULL, posang)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item posang");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "object", PS_DATA_STRING, NULL, object)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item object");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "solang", PS_DATA_F32, NULL, solang)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item solang");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, RAWEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long rawExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, RAWEXP_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from rawExp");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool rawExpInsertObject(psDB *dbh, rawExpRow *object)
+{
+    return rawExpInsert(dbh, object->exp_tag, object->exp_id, object->camera, object->telescope, object->dateobs, object->exp_type, object->imfiles, object->filelevel, object->workdir, object->filter, object->airmass, object->ra, object->decl, object->exp_time, object->bg, object->bg_stdev, object->bg_mean_stdev, object->alt, object->az, object->ccd_temp, object->posang, object->object, object->solang, object->fault);
+}
+
+bool rawExpInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!rawExpInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool rawExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  RAWEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, RAWEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", RAWEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, RAWEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool rawExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, RAWEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, RAWEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *rawExpMetadataFromObject(const rawExpRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, NULL, object->exp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, object->camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, object->telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dateobs", PS_DATA_TIME, NULL, object->dateobs)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, object->exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "imfiles", PS_DATA_S32, NULL, object->imfiles)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "filelevel", PS_DATA_STRING, NULL, object->filelevel)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filelevel");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, object->workdir)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, object->filter)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass", PS_DATA_F32, NULL, object->airmass)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra", PS_DATA_F64, NULL, object->ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl", PS_DATA_F64, NULL, object->decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time", PS_DATA_F32, NULL, object->exp_time)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "alt", PS_DATA_F64, NULL, object->alt)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item alt");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "az", PS_DATA_F64, NULL, object->az)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp", PS_DATA_F32, NULL, object->ccd_temp)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang", PS_DATA_F64, NULL, object->posang)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item posang");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "object", PS_DATA_STRING, NULL, object->object)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item object");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "solang", PS_DATA_F32, NULL, object->solang)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item solang");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+rawExpRow *rawExpObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
+        return false;
+    }
+    char* exp_id = psMetadataLookupPtr(&status, md, "exp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
+        return false;
+    }
+    char* camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    char* telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    psTime* dateobs = psMetadataLookupPtr(&status, md, "dateobs");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dateobs");
+        return false;
+    }
+    char* exp_type = psMetadataLookupPtr(&status, md, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        return false;
+    }
+    psS32 imfiles = psMetadataLookupS32(&status, md, "imfiles");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
+        return false;
+    }
+    char* filelevel = psMetadataLookupPtr(&status, md, "filelevel");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filelevel");
+        return false;
+    }
+    char* workdir = psMetadataLookupPtr(&status, md, "workdir");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
+        return false;
+    }
+    char* filter = psMetadataLookupPtr(&status, md, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        return false;
+    }
+    psF32 airmass = psMetadataLookupF32(&status, md, "airmass");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass");
+        return false;
+    }
+    psF64 ra = psMetadataLookupF64(&status, md, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        return false;
+    }
+    psF64 decl = psMetadataLookupF64(&status, md, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        return false;
+    }
+    psF32 exp_time = psMetadataLookupF32(&status, md, "exp_time");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time");
+        return false;
+    }
+    psF64 bg = psMetadataLookupF64(&status, md, "bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
+        return false;
+    }
+    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
+        return false;
+    }
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
+        return false;
+    }
+    psF64 alt = psMetadataLookupF64(&status, md, "alt");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item alt");
+        return false;
+    }
+    psF64 az = psMetadataLookupF64(&status, md, "az");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item az");
+        return false;
+    }
+    psF32 ccd_temp = psMetadataLookupF32(&status, md, "ccd_temp");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ccd_temp");
+        return false;
+    }
+    psF64 posang = psMetadataLookupF64(&status, md, "posang");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item posang");
+        return false;
+    }
+    char* object = psMetadataLookupPtr(&status, md, "object");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item object");
+        return false;
+    }
+    psF32 solang = psMetadataLookupF32(&status, md, "solang");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item solang");
+        return false;
+    }
+    psS16 fault = psMetadataLookupS16(&status, md, "fault");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
+        return false;
+    }
+
+    return rawExpRowAlloc(exp_tag, exp_id, camera, telescope, dateobs, exp_type, imfiles, filelevel, workdir, filter, airmass, ra, decl, exp_time, bg, bg_stdev, bg_mean_stdev, alt, az, ccd_temp, posang, object, solang, fault);
+}
+psArray *rawExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, RAWEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        rawExpRow *object = rawExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool rawExpDeleteObject(psDB *dbh, const rawExpRow *object)
+{
+    psMetadata *where = rawExpMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, RAWEXP_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from rawExp");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "rawExpRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long rawExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        rawExpRow *object = objects->data[i];
+        psMetadata *where = rawExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, RAWEXP_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from rawExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool rawExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = rawExpMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            RAWEXP_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool rawExpPrintObject(FILE *stream, rawExpRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = rawExpMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void rawImfileRowFree(rawImfileRow *object);
+
+rawImfileRow *rawImfileRowAlloc(const char *exp_tag, const char *class_id, const char *uri, const char *exp_type, const char *filelevel, const char *filter, psF32 airmass, psF64 ra, psF64 decl, psF32 exp_time, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 alt, psF64 az, psF32 ccd_temp, psF64 posang, const char *object, psTime* dateobs, psS16 fault)
+{
+    rawImfileRow    *_object;
+
+    _object = psAlloc(sizeof(rawImfileRow));
+    psMemSetDeallocator(_object, (psFreeFunc)rawImfileRowFree);
+
+    _object->exp_tag = psStringCopy(exp_tag);
+    _object->class_id = psStringCopy(class_id);
+    _object->uri = psStringCopy(uri);
+    _object->exp_type = psStringCopy(exp_type);
+    _object->filelevel = psStringCopy(filelevel);
+    _object->filter = psStringCopy(filter);
+    _object->airmass = airmass;
+    _object->ra = ra;
+    _object->decl = decl;
+    _object->exp_time = exp_time;
+    _object->bg = bg;
+    _object->bg_stdev = bg_stdev;
+    _object->bg_mean_stdev = bg_mean_stdev;
+    _object->alt = alt;
+    _object->az = az;
+    _object->ccd_temp = ccd_temp;
+    _object->posang = posang;
+    _object->object = psStringCopy(object);
+    _object->dateobs = psTimeCopy(dateobs);
+    _object->fault = fault;
+
+    return _object;
+}
+
+static void rawImfileRowFree(rawImfileRow *object)
+{
+    psFree(object->exp_tag);
+    psFree(object->class_id);
+    psFree(object->uri);
+    psFree(object->exp_type);
+    psFree(object->filelevel);
+    psFree(object->filter);
+    psFree(object->object);
+    psFree(object->dateobs);
+}
+
+bool rawImfileCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "filelevel", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filelevel");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass", PS_DATA_F32, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time", PS_DATA_F32, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "alt", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item alt");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "az", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp", PS_DATA_F32, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item posang");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "object", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item object");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dateobs", PS_DATA_TIME, NULL, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, RAWIMFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool rawImfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, RAWIMFILE_TABLE_NAME);
+}
+
+bool rawImfileInsert(psDB * dbh, const char *exp_tag, const char *class_id, const char *uri, const char *exp_type, const char *filelevel, const char *filter, psF32 airmass, psF64 ra, psF64 decl, psF32 exp_time, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 alt, psF64 az, psF32 ccd_temp, psF64 posang, const char *object, psTime* dateobs, psS16 fault)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "filelevel", PS_DATA_STRING, NULL, filelevel)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filelevel");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, filter)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass", PS_DATA_F32, NULL, airmass)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra", PS_DATA_F64, NULL, ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl", PS_DATA_F64, NULL, decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time", PS_DATA_F32, NULL, exp_time)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "alt", PS_DATA_F64, NULL, alt)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item alt");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "az", PS_DATA_F64, NULL, az)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp", PS_DATA_F32, NULL, ccd_temp)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang", PS_DATA_F64, NULL, posang)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item posang");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "object", PS_DATA_STRING, NULL, object)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item object");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dateobs", PS_DATA_TIME, NULL, dateobs)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, RAWIMFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long rawImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, RAWIMFILE_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from rawImfile");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool rawImfileInsertObject(psDB *dbh, rawImfileRow *object)
+{
+    return rawImfileInsert(dbh, object->exp_tag, object->class_id, object->uri, object->exp_type, object->filelevel, object->filter, object->airmass, object->ra, object->decl, object->exp_time, object->bg, object->bg_stdev, object->bg_mean_stdev, object->alt, object->az, object->ccd_temp, object->posang, object->object, object->dateobs, object->fault);
+}
+
+bool rawImfileInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!rawImfileInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool rawImfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  RAWIMFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, RAWIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", RAWIMFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, RAWIMFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool rawImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, RAWIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, RAWIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *rawImfileMetadataFromObject(const rawImfileRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, object->exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "filelevel", PS_DATA_STRING, NULL, object->filelevel)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filelevel");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, object->filter)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass", PS_DATA_F32, NULL, object->airmass)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra", PS_DATA_F64, NULL, object->ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl", PS_DATA_F64, NULL, object->decl)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time", PS_DATA_F32, NULL, object->exp_time)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "alt", PS_DATA_F64, NULL, object->alt)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item alt");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "az", PS_DATA_F64, NULL, object->az)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp", PS_DATA_F32, NULL, object->ccd_temp)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang", PS_DATA_F64, NULL, object->posang)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item posang");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "object", PS_DATA_STRING, NULL, object->object)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item object");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dateobs", PS_DATA_TIME, NULL, object->dateobs)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+rawImfileRow *rawImfileObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
+        return false;
+    }
+    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        return false;
+    }
+    char* uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    char* exp_type = psMetadataLookupPtr(&status, md, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        return false;
+    }
+    char* filelevel = psMetadataLookupPtr(&status, md, "filelevel");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filelevel");
+        return false;
+    }
+    char* filter = psMetadataLookupPtr(&status, md, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        return false;
+    }
+    psF32 airmass = psMetadataLookupF32(&status, md, "airmass");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass");
+        return false;
+    }
+    psF64 ra = psMetadataLookupF64(&status, md, "ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
+        return false;
+    }
+    psF64 decl = psMetadataLookupF64(&status, md, "decl");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
+        return false;
+    }
+    psF32 exp_time = psMetadataLookupF32(&status, md, "exp_time");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time");
+        return false;
+    }
+    psF64 bg = psMetadataLookupF64(&status, md, "bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
+        return false;
+    }
+    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
+        return false;
+    }
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
+        return false;
+    }
+    psF64 alt = psMetadataLookupF64(&status, md, "alt");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item alt");
+        return false;
+    }
+    psF64 az = psMetadataLookupF64(&status, md, "az");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item az");
+        return false;
+    }
+    psF32 ccd_temp = psMetadataLookupF32(&status, md, "ccd_temp");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ccd_temp");
+        return false;
+    }
+    psF64 posang = psMetadataLookupF64(&status, md, "posang");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item posang");
+        return false;
+    }
+    char* object = psMetadataLookupPtr(&status, md, "object");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item object");
+        return false;
+    }
+    psTime* dateobs = psMetadataLookupPtr(&status, md, "dateobs");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dateobs");
+        return false;
+    }
+    psS16 fault = psMetadataLookupS16(&status, md, "fault");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
+        return false;
+    }
+
+    return rawImfileRowAlloc(exp_tag, class_id, uri, exp_type, filelevel, filter, airmass, ra, decl, exp_time, bg, bg_stdev, bg_mean_stdev, alt, az, ccd_temp, posang, object, dateobs, fault);
+}
+psArray *rawImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, RAWIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        rawImfileRow *object = rawImfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool rawImfileDeleteObject(psDB *dbh, const rawImfileRow *object)
+{
+    psMetadata *where = rawImfileMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, RAWIMFILE_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from rawImfile");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "rawImfileRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long rawImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        rawImfileRow *object = objects->data[i];
+        psMetadata *where = rawImfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, RAWIMFILE_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from rawImfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool rawImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = rawImfileMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            RAWIMFILE_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool rawImfilePrintObject(FILE *stream, rawImfileRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = rawImfileMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void guidePendingExpRowFree(guidePendingExpRow *object);
+
+guidePendingExpRow *guidePendingExpRowAlloc(psS64 guide_id, const char *exp_tag, const char *recipe)
+{
+    guidePendingExpRow *_object;
+
+    _object = psAlloc(sizeof(guidePendingExpRow));
+    psMemSetDeallocator(_object, (psFreeFunc)guidePendingExpRowFree);
+
+    _object->guide_id = guide_id;
+    _object->exp_tag = psStringCopy(exp_tag);
+    _object->recipe = psStringCopy(recipe);
+
+    return _object;
+}
+
+static void guidePendingExpRowFree(guidePendingExpRow *object)
+{
+    psFree(object->exp_tag);
+    psFree(object->recipe);
+}
+
+bool guidePendingExpCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "guide_id", PS_DATA_S64, "Primary Key AUTO_INCREMENT", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item guide_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, GUIDEPENDINGEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool guidePendingExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, GUIDEPENDINGEXP_TABLE_NAME);
+}
+
+bool guidePendingExpInsert(psDB * dbh, psS64 guide_id, const char *exp_tag, const char *recipe)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "guide_id", PS_DATA_S64, NULL, guide_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item guide_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, GUIDEPENDINGEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long guidePendingExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, GUIDEPENDINGEXP_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from guidePendingExp");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool guidePendingExpInsertObject(psDB *dbh, guidePendingExpRow *object)
+{
+    return guidePendingExpInsert(dbh, object->guide_id, object->exp_tag, object->recipe);
+}
+
+bool guidePendingExpInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!guidePendingExpInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool guidePendingExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  GUIDEPENDINGEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, GUIDEPENDINGEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", GUIDEPENDINGEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, GUIDEPENDINGEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool guidePendingExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, GUIDEPENDINGEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, GUIDEPENDINGEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *guidePendingExpMetadataFromObject(const guidePendingExpRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "guide_id", PS_DATA_S64, NULL, object->guide_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item guide_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, object->recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+guidePendingExpRow *guidePendingExpObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 guide_id = psMetadataLookupS64(&status, md, "guide_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item guide_id");
+        return false;
+    }
+    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
+        return false;
+    }
+    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+
+    return guidePendingExpRowAlloc(guide_id, exp_tag, recipe);
+}
+psArray *guidePendingExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, GUIDEPENDINGEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        guidePendingExpRow *object = guidePendingExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool guidePendingExpDeleteObject(psDB *dbh, const guidePendingExpRow *object)
+{
+    psMetadata *where = guidePendingExpMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, GUIDEPENDINGEXP_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from guidePendingExp");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "guidePendingExpRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long guidePendingExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        guidePendingExpRow *object = objects->data[i];
+        psMetadata *where = guidePendingExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, GUIDEPENDINGEXP_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from guidePendingExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool guidePendingExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = guidePendingExpMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            GUIDEPENDINGEXP_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool guidePendingExpPrintObject(FILE *stream, guidePendingExpRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = guidePendingExpMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void chipPendingExpRowFree(chipPendingExpRow *object);
+
+chipPendingExpRow *chipPendingExpRowAlloc(psS64 chip_id, const char *exp_tag, psS64 guide_id, const char *workdir, const char *label, const char *recipe, const char *expgroup, const char *dvodb)
+{
+    chipPendingExpRow *_object;
+
+    _object = psAlloc(sizeof(chipPendingExpRow));
+    psMemSetDeallocator(_object, (psFreeFunc)chipPendingExpRowFree);
+
+    _object->chip_id = chip_id;
+    _object->exp_tag = psStringCopy(exp_tag);
+    _object->guide_id = guide_id;
+    _object->workdir = psStringCopy(workdir);
+    _object->label = psStringCopy(label);
+    _object->recipe = psStringCopy(recipe);
+    _object->expgroup = psStringCopy(expgroup);
+    _object->dvodb = psStringCopy(dvodb);
+
+    return _object;
+}
+
+static void chipPendingExpRowFree(chipPendingExpRow *object)
+{
+    psFree(object->exp_tag);
+    psFree(object->workdir);
+    psFree(object->label);
+    psFree(object->recipe);
+    psFree(object->expgroup);
+    psFree(object->dvodb);
+}
+
+bool chipPendingExpCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, "Primary Key AUTO_INCREMENT", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "guide_id", PS_DATA_S64, "Not NULL", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item guide_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, "key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "expgroup", PS_DATA_STRING, "key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item expgroup");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, CHIPPENDINGEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool chipPendingExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, CHIPPENDINGEXP_TABLE_NAME);
+}
+
+bool chipPendingExpInsert(psDB * dbh, psS64 chip_id, const char *exp_tag, psS64 guide_id, const char *workdir, const char *label, const char *recipe, const char *expgroup, const char *dvodb)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, NULL, chip_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "guide_id", PS_DATA_S64, NULL, guide_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item guide_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, workdir)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, label)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "expgroup", PS_DATA_STRING, NULL, expgroup)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item expgroup");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, dvodb)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, CHIPPENDINGEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long chipPendingExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, CHIPPENDINGEXP_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from chipPendingExp");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool chipPendingExpInsertObject(psDB *dbh, chipPendingExpRow *object)
+{
+    return chipPendingExpInsert(dbh, object->chip_id, object->exp_tag, object->guide_id, object->workdir, object->label, object->recipe, object->expgroup, object->dvodb);
+}
+
+bool chipPendingExpInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!chipPendingExpInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool chipPendingExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  CHIPPENDINGEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, CHIPPENDINGEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", CHIPPENDINGEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, CHIPPENDINGEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool chipPendingExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, CHIPPENDINGEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, CHIPPENDINGEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *chipPendingExpMetadataFromObject(const chipPendingExpRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, NULL, object->chip_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "guide_id", PS_DATA_S64, NULL, object->guide_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item guide_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, object->workdir)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, object->label)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, object->recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "expgroup", PS_DATA_STRING, NULL, object->expgroup)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item expgroup");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, object->dvodb)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+chipPendingExpRow *chipPendingExpObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 chip_id = psMetadataLookupS64(&status, md, "chip_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item chip_id");
+        return false;
+    }
+    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
+        return false;
+    }
+    psS64 guide_id = psMetadataLookupS64(&status, md, "guide_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item guide_id");
+        return false;
+    }
+    char* workdir = psMetadataLookupPtr(&status, md, "workdir");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
+        return false;
+    }
+    char* label = psMetadataLookupPtr(&status, md, "label");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item label");
+        return false;
+    }
+    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    char* expgroup = psMetadataLookupPtr(&status, md, "expgroup");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item expgroup");
+        return false;
+    }
+    char* dvodb = psMetadataLookupPtr(&status, md, "dvodb");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dvodb");
+        return false;
+    }
+
+    return chipPendingExpRowAlloc(chip_id, exp_tag, guide_id, workdir, label, recipe, expgroup, dvodb);
+}
+psArray *chipPendingExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, CHIPPENDINGEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        chipPendingExpRow *object = chipPendingExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool chipPendingExpDeleteObject(psDB *dbh, const chipPendingExpRow *object)
+{
+    psMetadata *where = chipPendingExpMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, CHIPPENDINGEXP_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from chipPendingExp");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "chipPendingExpRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long chipPendingExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        chipPendingExpRow *object = objects->data[i];
+        psMetadata *where = chipPendingExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, CHIPPENDINGEXP_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from chipPendingExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool chipPendingExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = chipPendingExpMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            CHIPPENDINGEXP_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool chipPendingExpPrintObject(FILE *stream, chipPendingExpRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = chipPendingExpMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void chipPendingImfileRowFree(chipPendingImfileRow *object);
+
+chipPendingImfileRow *chipPendingImfileRowAlloc(psS64 chip_id, const char *class_id, const char *uri)
+{
+    chipPendingImfileRow *_object;
+
+    _object = psAlloc(sizeof(chipPendingImfileRow));
+    psMemSetDeallocator(_object, (psFreeFunc)chipPendingImfileRowFree);
+
+    _object->chip_id = chip_id;
+    _object->class_id = psStringCopy(class_id);
+    _object->uri = psStringCopy(uri);
+
+    return _object;
+}
+
+static void chipPendingImfileRowFree(chipPendingImfileRow *object)
+{
+    psFree(object->class_id);
+    psFree(object->uri);
+}
+
+bool chipPendingImfileCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, CHIPPENDINGIMFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool chipPendingImfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, CHIPPENDINGIMFILE_TABLE_NAME);
+}
+
+bool chipPendingImfileInsert(psDB * dbh, psS64 chip_id, const char *class_id, const char *uri)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, NULL, chip_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, CHIPPENDINGIMFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long chipPendingImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, CHIPPENDINGIMFILE_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from chipPendingImfile");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool chipPendingImfileInsertObject(psDB *dbh, chipPendingImfileRow *object)
+{
+    return chipPendingImfileInsert(dbh, object->chip_id, object->class_id, object->uri);
+}
+
+bool chipPendingImfileInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!chipPendingImfileInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool chipPendingImfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  CHIPPENDINGIMFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, CHIPPENDINGIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", CHIPPENDINGIMFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, CHIPPENDINGIMFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool chipPendingImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, CHIPPENDINGIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, CHIPPENDINGIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *chipPendingImfileMetadataFromObject(const chipPendingImfileRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, NULL, object->chip_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+chipPendingImfileRow *chipPendingImfileObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 chip_id = psMetadataLookupS64(&status, md, "chip_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item chip_id");
+        return false;
+    }
+    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        return false;
+    }
+    char* uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+
+    return chipPendingImfileRowAlloc(chip_id, class_id, uri);
+}
+psArray *chipPendingImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, CHIPPENDINGIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        chipPendingImfileRow *object = chipPendingImfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool chipPendingImfileDeleteObject(psDB *dbh, const chipPendingImfileRow *object)
+{
+    psMetadata *where = chipPendingImfileMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, CHIPPENDINGIMFILE_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from chipPendingImfile");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "chipPendingImfileRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long chipPendingImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        chipPendingImfileRow *object = objects->data[i];
+        psMetadata *where = chipPendingImfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, CHIPPENDINGIMFILE_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from chipPendingImfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool chipPendingImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = chipPendingImfileMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            CHIPPENDINGIMFILE_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool chipPendingImfilePrintObject(FILE *stream, chipPendingImfileRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = chipPendingImfileMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void chipProcessedExpRowFree(chipProcessedExpRow *object);
+
+chipProcessedExpRow *chipProcessedExpRowAlloc(psS64 chip_id, const char *exp_tag, psS64 guide_id, const char *workdir, const char *label, const char *recipe, const char *expgroup, const char *dvodb)
+{
+    chipProcessedExpRow *_object;
+
+    _object = psAlloc(sizeof(chipProcessedExpRow));
+    psMemSetDeallocator(_object, (psFreeFunc)chipProcessedExpRowFree);
+
+    _object->chip_id = chip_id;
+    _object->exp_tag = psStringCopy(exp_tag);
+    _object->guide_id = guide_id;
+    _object->workdir = psStringCopy(workdir);
+    _object->label = psStringCopy(label);
+    _object->recipe = psStringCopy(recipe);
+    _object->expgroup = psStringCopy(expgroup);
+    _object->dvodb = psStringCopy(dvodb);
+
+    return _object;
+}
+
+static void chipProcessedExpRowFree(chipProcessedExpRow *object)
+{
+    psFree(object->exp_tag);
+    psFree(object->workdir);
+    psFree(object->label);
+    psFree(object->recipe);
+    psFree(object->expgroup);
+    psFree(object->dvodb);
+}
+
+bool chipProcessedExpCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "guide_id", PS_DATA_S64, "Not NULL", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item guide_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, "key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "expgroup", PS_DATA_STRING, "key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item expgroup");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, CHIPPROCESSEDEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool chipProcessedExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, CHIPPROCESSEDEXP_TABLE_NAME);
+}
+
+bool chipProcessedExpInsert(psDB * dbh, psS64 chip_id, const char *exp_tag, psS64 guide_id, const char *workdir, const char *label, const char *recipe, const char *expgroup, const char *dvodb)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, NULL, chip_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "guide_id", PS_DATA_S64, NULL, guide_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item guide_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, workdir)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, label)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "expgroup", PS_DATA_STRING, NULL, expgroup)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item expgroup");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, dvodb)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, CHIPPROCESSEDEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long chipProcessedExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, CHIPPROCESSEDEXP_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from chipProcessedExp");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool chipProcessedExpInsertObject(psDB *dbh, chipProcessedExpRow *object)
+{
+    return chipProcessedExpInsert(dbh, object->chip_id, object->exp_tag, object->guide_id, object->workdir, object->label, object->recipe, object->expgroup, object->dvodb);
+}
+
+bool chipProcessedExpInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!chipProcessedExpInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool chipProcessedExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  CHIPPROCESSEDEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, CHIPPROCESSEDEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", CHIPPROCESSEDEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, CHIPPROCESSEDEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool chipProcessedExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, CHIPPROCESSEDEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, CHIPPROCESSEDEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *chipProcessedExpMetadataFromObject(const chipProcessedExpRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, NULL, object->chip_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "guide_id", PS_DATA_S64, NULL, object->guide_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item guide_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, object->workdir)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, object->label)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, object->recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "expgroup", PS_DATA_STRING, NULL, object->expgroup)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item expgroup");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, object->dvodb)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+chipProcessedExpRow *chipProcessedExpObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 chip_id = psMetadataLookupS64(&status, md, "chip_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item chip_id");
+        return false;
+    }
+    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
+        return false;
+    }
+    psS64 guide_id = psMetadataLookupS64(&status, md, "guide_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item guide_id");
+        return false;
+    }
+    char* workdir = psMetadataLookupPtr(&status, md, "workdir");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
+        return false;
+    }
+    char* label = psMetadataLookupPtr(&status, md, "label");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item label");
+        return false;
+    }
+    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    char* expgroup = psMetadataLookupPtr(&status, md, "expgroup");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item expgroup");
+        return false;
+    }
+    char* dvodb = psMetadataLookupPtr(&status, md, "dvodb");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dvodb");
+        return false;
+    }
+
+    return chipProcessedExpRowAlloc(chip_id, exp_tag, guide_id, workdir, label, recipe, expgroup, dvodb);
+}
+psArray *chipProcessedExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, CHIPPROCESSEDEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        chipProcessedExpRow *object = chipProcessedExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool chipProcessedExpDeleteObject(psDB *dbh, const chipProcessedExpRow *object)
+{
+    psMetadata *where = chipProcessedExpMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, CHIPPROCESSEDEXP_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from chipProcessedExp");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "chipProcessedExpRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long chipProcessedExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        chipProcessedExpRow *object = objects->data[i];
+        psMetadata *where = chipProcessedExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, CHIPPROCESSEDEXP_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from chipProcessedExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool chipProcessedExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = chipProcessedExpMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            CHIPPROCESSEDEXP_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool chipProcessedExpPrintObject(FILE *stream, chipProcessedExpRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = chipProcessedExpMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void chipMaskRowFree(chipMaskRow *object);
+
+chipMaskRow *chipMaskRowAlloc(const char *label)
+{
+    chipMaskRow     *_object;
+
+    _object = psAlloc(sizeof(chipMaskRow));
+    psMemSetDeallocator(_object, (psFreeFunc)chipMaskRowFree);
+
+    _object->label = psStringCopy(label);
+
+    return _object;
+}
+
+static void chipMaskRowFree(chipMaskRow *object)
+{
+    psFree(object->label);
+}
+
+bool chipMaskCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, CHIPMASK_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool chipMaskDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, CHIPMASK_TABLE_NAME);
+}
+
+bool chipMaskInsert(psDB * dbh, const char *label)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, label)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, CHIPMASK_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long chipMaskDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, CHIPMASK_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from chipMask");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool chipMaskInsertObject(psDB *dbh, chipMaskRow *object)
+{
+    return chipMaskInsert(dbh, object->label);
+}
+
+bool chipMaskInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!chipMaskInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool chipMaskInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  CHIPMASK_TABLE_NAME
+    if (!psFitsMoveExtName(fits, CHIPMASK_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", CHIPMASK_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, CHIPMASK_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool chipMaskSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, CHIPMASK_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, CHIPMASK_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *chipMaskMetadataFromObject(const chipMaskRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, object->label)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+chipMaskRow *chipMaskObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    char* label = psMetadataLookupPtr(&status, md, "label");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item label");
+        return false;
+    }
+
+    return chipMaskRowAlloc(label);
+}
+psArray *chipMaskSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, CHIPMASK_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        chipMaskRow *object = chipMaskObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool chipMaskDeleteObject(psDB *dbh, const chipMaskRow *object)
+{
+    psMetadata *where = chipMaskMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, CHIPMASK_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from chipMask");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "chipMaskRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long chipMaskDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        chipMaskRow *object = objects->data[i];
+        psMetadata *where = chipMaskMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, CHIPMASK_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from chipMask");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool chipMaskPrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = chipMaskMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            CHIPMASK_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool chipMaskPrintObject(FILE *stream, chipMaskRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = chipMaskMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void chipProcessedImfileRowFree(chipProcessedImfileRow *object);
+
+chipProcessedImfileRow *chipProcessedImfileRowAlloc(psS64 chip_id, const char *class_id, const char *uri, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *path_base, psS16 fault)
+{
+    chipProcessedImfileRow *_object;
+
+    _object = psAlloc(sizeof(chipProcessedImfileRow));
+    psMemSetDeallocator(_object, (psFreeFunc)chipProcessedImfileRowFree);
+
+    _object->chip_id = chip_id;
+    _object->class_id = psStringCopy(class_id);
+    _object->uri = psStringCopy(uri);
+    _object->bg = bg;
+    _object->bg_stdev = bg_stdev;
+    _object->bg_mean_stdev = bg_mean_stdev;
+    _object->path_base = psStringCopy(path_base);
+    _object->fault = fault;
+
+    return _object;
+}
+
+static void chipProcessedImfileRowFree(chipProcessedImfileRow *object)
+{
+    psFree(object->class_id);
+    psFree(object->uri);
+    psFree(object->path_base);
+}
+
+bool chipProcessedImfileCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, CHIPPROCESSEDIMFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool chipProcessedImfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, CHIPPROCESSEDIMFILE_TABLE_NAME);
+}
+
+bool chipProcessedImfileInsert(psDB * dbh, psS64 chip_id, const char *class_id, const char *uri, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *path_base, psS16 fault)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, NULL, chip_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, path_base)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, CHIPPROCESSEDIMFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long chipProcessedImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, CHIPPROCESSEDIMFILE_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from chipProcessedImfile");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool chipProcessedImfileInsertObject(psDB *dbh, chipProcessedImfileRow *object)
+{
+    return chipProcessedImfileInsert(dbh, object->chip_id, object->class_id, object->uri, object->bg, object->bg_stdev, object->bg_mean_stdev, object->path_base, object->fault);
+}
+
+bool chipProcessedImfileInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!chipProcessedImfileInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool chipProcessedImfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  CHIPPROCESSEDIMFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, CHIPPROCESSEDIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", CHIPPROCESSEDIMFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, CHIPPROCESSEDIMFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool chipProcessedImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, CHIPPROCESSEDIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, CHIPPROCESSEDIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *chipProcessedImfileMetadataFromObject(const chipProcessedImfileRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, NULL, object->chip_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, object->path_base)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+chipProcessedImfileRow *chipProcessedImfileObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 chip_id = psMetadataLookupS64(&status, md, "chip_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item chip_id");
+        return false;
+    }
+    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        return false;
+    }
+    char* uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    psF64 bg = psMetadataLookupF64(&status, md, "bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
+        return false;
+    }
+    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
+        return false;
+    }
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
+        return false;
+    }
+    char* path_base = psMetadataLookupPtr(&status, md, "path_base");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item path_base");
+        return false;
+    }
+    psS16 fault = psMetadataLookupS16(&status, md, "fault");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
+        return false;
+    }
+
+    return chipProcessedImfileRowAlloc(chip_id, class_id, uri, bg, bg_stdev, bg_mean_stdev, path_base, fault);
+}
+psArray *chipProcessedImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, CHIPPROCESSEDIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        chipProcessedImfileRow *object = chipProcessedImfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool chipProcessedImfileDeleteObject(psDB *dbh, const chipProcessedImfileRow *object)
+{
+    psMetadata *where = chipProcessedImfileMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, CHIPPROCESSEDIMFILE_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from chipProcessedImfile");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "chipProcessedImfileRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long chipProcessedImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        chipProcessedImfileRow *object = objects->data[i];
+        psMetadata *where = chipProcessedImfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, CHIPPROCESSEDIMFILE_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from chipProcessedImfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool chipProcessedImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = chipProcessedImfileMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            CHIPPROCESSEDIMFILE_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool chipProcessedImfilePrintObject(FILE *stream, chipProcessedImfileRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = chipProcessedImfileMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void camPendingExpRowFree(camPendingExpRow *object);
+
+camPendingExpRow *camPendingExpRowAlloc(psS64 cam_id, psS64 chip_id, const char *workdir, const char *label, const char *recipe, const char *expgroup, const char *dvodb)
+{
+    camPendingExpRow *_object;
+
+    _object = psAlloc(sizeof(camPendingExpRow));
+    psMemSetDeallocator(_object, (psFreeFunc)camPendingExpRowFree);
+
+    _object->cam_id = cam_id;
+    _object->chip_id = chip_id;
+    _object->workdir = psStringCopy(workdir);
+    _object->label = psStringCopy(label);
+    _object->recipe = psStringCopy(recipe);
+    _object->expgroup = psStringCopy(expgroup);
+    _object->dvodb = psStringCopy(dvodb);
+
+    return _object;
+}
+
+static void camPendingExpRowFree(camPendingExpRow *object)
+{
+    psFree(object->workdir);
+    psFree(object->label);
+    psFree(object->recipe);
+    psFree(object->expgroup);
+    psFree(object->dvodb);
+}
+
+bool camPendingExpCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "cam_id", PS_DATA_S64, "Primary Key AUTO_INCREMENT", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item cam_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, "Primary Key fkey(chip_id) ref chipProcessedExp(chip_id)", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, "key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "expgroup", PS_DATA_STRING, "key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item expgroup");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, CAMPENDINGEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool camPendingExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, CAMPENDINGEXP_TABLE_NAME);
+}
+
+bool camPendingExpInsert(psDB * dbh, psS64 cam_id, psS64 chip_id, const char *workdir, const char *label, const char *recipe, const char *expgroup, const char *dvodb)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "cam_id", PS_DATA_S64, NULL, cam_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item cam_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, NULL, chip_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, workdir)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, label)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "expgroup", PS_DATA_STRING, NULL, expgroup)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item expgroup");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, dvodb)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, CAMPENDINGEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long camPendingExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, CAMPENDINGEXP_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from camPendingExp");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool camPendingExpInsertObject(psDB *dbh, camPendingExpRow *object)
+{
+    return camPendingExpInsert(dbh, object->cam_id, object->chip_id, object->workdir, object->label, object->recipe, object->expgroup, object->dvodb);
+}
+
+bool camPendingExpInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!camPendingExpInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool camPendingExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  CAMPENDINGEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, CAMPENDINGEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", CAMPENDINGEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, CAMPENDINGEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool camPendingExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, CAMPENDINGEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, CAMPENDINGEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *camPendingExpMetadataFromObject(const camPendingExpRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "cam_id", PS_DATA_S64, NULL, object->cam_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item cam_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, NULL, object->chip_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, object->workdir)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, object->label)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, object->recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "expgroup", PS_DATA_STRING, NULL, object->expgroup)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item expgroup");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, object->dvodb)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+camPendingExpRow *camPendingExpObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 cam_id = psMetadataLookupS64(&status, md, "cam_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item cam_id");
+        return false;
+    }
+    psS64 chip_id = psMetadataLookupS64(&status, md, "chip_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item chip_id");
+        return false;
+    }
+    char* workdir = psMetadataLookupPtr(&status, md, "workdir");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
+        return false;
+    }
+    char* label = psMetadataLookupPtr(&status, md, "label");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item label");
+        return false;
+    }
+    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    char* expgroup = psMetadataLookupPtr(&status, md, "expgroup");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item expgroup");
+        return false;
+    }
+    char* dvodb = psMetadataLookupPtr(&status, md, "dvodb");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dvodb");
+        return false;
+    }
+
+    return camPendingExpRowAlloc(cam_id, chip_id, workdir, label, recipe, expgroup, dvodb);
+}
+psArray *camPendingExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, CAMPENDINGEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        camPendingExpRow *object = camPendingExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool camPendingExpDeleteObject(psDB *dbh, const camPendingExpRow *object)
+{
+    psMetadata *where = camPendingExpMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, CAMPENDINGEXP_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from camPendingExp");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "camPendingExpRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long camPendingExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        camPendingExpRow *object = objects->data[i];
+        psMetadata *where = camPendingExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, CAMPENDINGEXP_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from camPendingExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool camPendingExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = camPendingExpMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            CAMPENDINGEXP_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool camPendingExpPrintObject(FILE *stream, camPendingExpRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = camPendingExpMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void camProcessedExpRowFree(camProcessedExpRow *object);
+
+camProcessedExpRow *camProcessedExpRowAlloc(psS64 cam_id, psS64 chip_id, const char *workdir, const char *label, const char *recipe, const char *expgroup, const char *dvodb, const char *uri, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF32 sigma_ra, psF32 sigma_dec, psS32 nastro, const char *path_base, psF32 zp_mean, psF32 zp_stdev, psS16 fault)
+{
+    camProcessedExpRow *_object;
+
+    _object = psAlloc(sizeof(camProcessedExpRow));
+    psMemSetDeallocator(_object, (psFreeFunc)camProcessedExpRowFree);
+
+    _object->cam_id = cam_id;
+    _object->chip_id = chip_id;
+    _object->workdir = psStringCopy(workdir);
+    _object->label = psStringCopy(label);
+    _object->recipe = psStringCopy(recipe);
+    _object->expgroup = psStringCopy(expgroup);
+    _object->dvodb = psStringCopy(dvodb);
+    _object->uri = psStringCopy(uri);
+    _object->bg = bg;
+    _object->bg_stdev = bg_stdev;
+    _object->bg_mean_stdev = bg_mean_stdev;
+    _object->sigma_ra = sigma_ra;
+    _object->sigma_dec = sigma_dec;
+    _object->nastro = nastro;
+    _object->path_base = psStringCopy(path_base);
+    _object->zp_mean = zp_mean;
+    _object->zp_stdev = zp_stdev;
+    _object->fault = fault;
+
+    return _object;
+}
+
+static void camProcessedExpRowFree(camProcessedExpRow *object)
+{
+    psFree(object->workdir);
+    psFree(object->label);
+    psFree(object->recipe);
+    psFree(object->expgroup);
+    psFree(object->dvodb);
+    psFree(object->uri);
+    psFree(object->path_base);
+}
+
+bool camProcessedExpCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "cam_id", PS_DATA_S64, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item cam_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, "Primary Key fkey(chip_id) ref chipProcessedExp(chip_id)", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, "key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "expgroup", PS_DATA_STRING, "key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item expgroup");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "sigma_ra", PS_DATA_F32, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sigma_ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "sigma_dec", PS_DATA_F32, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sigma_dec");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "nastro", PS_DATA_S32, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item nastro");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "zp_mean", PS_DATA_F32, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item zp_mean");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "zp_stdev", PS_DATA_F32, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item zp_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, CAMPROCESSEDEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool camProcessedExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, CAMPROCESSEDEXP_TABLE_NAME);
+}
+
+bool camProcessedExpInsert(psDB * dbh, psS64 cam_id, psS64 chip_id, const char *workdir, const char *label, const char *recipe, const char *expgroup, const char *dvodb, const char *uri, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF32 sigma_ra, psF32 sigma_dec, psS32 nastro, const char *path_base, psF32 zp_mean, psF32 zp_stdev, psS16 fault)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "cam_id", PS_DATA_S64, NULL, cam_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item cam_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, NULL, chip_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, workdir)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, label)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "expgroup", PS_DATA_STRING, NULL, expgroup)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item expgroup");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, dvodb)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "sigma_ra", PS_DATA_F32, NULL, sigma_ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sigma_ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "sigma_dec", PS_DATA_F32, NULL, sigma_dec)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sigma_dec");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "nastro", PS_DATA_S32, NULL, nastro)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item nastro");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, path_base)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "zp_mean", PS_DATA_F32, NULL, zp_mean)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item zp_mean");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "zp_stdev", PS_DATA_F32, NULL, zp_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item zp_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, CAMPROCESSEDEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long camProcessedExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, CAMPROCESSEDEXP_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from camProcessedExp");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool camProcessedExpInsertObject(psDB *dbh, camProcessedExpRow *object)
+{
+    return camProcessedExpInsert(dbh, object->cam_id, object->chip_id, object->workdir, object->label, object->recipe, object->expgroup, object->dvodb, object->uri, object->bg, object->bg_stdev, object->bg_mean_stdev, object->sigma_ra, object->sigma_dec, object->nastro, object->path_base, object->zp_mean, object->zp_stdev, object->fault);
+}
+
+bool camProcessedExpInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!camProcessedExpInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool camProcessedExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  CAMPROCESSEDEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, CAMPROCESSEDEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", CAMPROCESSEDEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, CAMPROCESSEDEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool camProcessedExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, CAMPROCESSEDEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, CAMPROCESSEDEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *camProcessedExpMetadataFromObject(const camProcessedExpRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "cam_id", PS_DATA_S64, NULL, object->cam_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item cam_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, NULL, object->chip_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, object->workdir)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, object->label)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, object->recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "expgroup", PS_DATA_STRING, NULL, object->expgroup)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item expgroup");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, object->dvodb)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "sigma_ra", PS_DATA_F32, NULL, object->sigma_ra)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sigma_ra");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "sigma_dec", PS_DATA_F32, NULL, object->sigma_dec)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item sigma_dec");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "nastro", PS_DATA_S32, NULL, object->nastro)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item nastro");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, object->path_base)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "zp_mean", PS_DATA_F32, NULL, object->zp_mean)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item zp_mean");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "zp_stdev", PS_DATA_F32, NULL, object->zp_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item zp_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+camProcessedExpRow *camProcessedExpObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 cam_id = psMetadataLookupS64(&status, md, "cam_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item cam_id");
+        return false;
+    }
+    psS64 chip_id = psMetadataLookupS64(&status, md, "chip_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item chip_id");
+        return false;
+    }
+    char* workdir = psMetadataLookupPtr(&status, md, "workdir");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
+        return false;
+    }
+    char* label = psMetadataLookupPtr(&status, md, "label");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item label");
+        return false;
+    }
+    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    char* expgroup = psMetadataLookupPtr(&status, md, "expgroup");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item expgroup");
+        return false;
+    }
+    char* dvodb = psMetadataLookupPtr(&status, md, "dvodb");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dvodb");
+        return false;
+    }
+    char* uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    psF64 bg = psMetadataLookupF64(&status, md, "bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
+        return false;
+    }
+    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
+        return false;
+    }
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
+        return false;
+    }
+    psF32 sigma_ra = psMetadataLookupF32(&status, md, "sigma_ra");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sigma_ra");
+        return false;
+    }
+    psF32 sigma_dec = psMetadataLookupF32(&status, md, "sigma_dec");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sigma_dec");
+        return false;
+    }
+    psS32 nastro = psMetadataLookupS32(&status, md, "nastro");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item nastro");
+        return false;
+    }
+    char* path_base = psMetadataLookupPtr(&status, md, "path_base");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item path_base");
+        return false;
+    }
+    psF32 zp_mean = psMetadataLookupF32(&status, md, "zp_mean");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item zp_mean");
+        return false;
+    }
+    psF32 zp_stdev = psMetadataLookupF32(&status, md, "zp_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item zp_stdev");
+        return false;
+    }
+    psS16 fault = psMetadataLookupS16(&status, md, "fault");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
+        return false;
+    }
+
+    return camProcessedExpRowAlloc(cam_id, chip_id, workdir, label, recipe, expgroup, dvodb, uri, bg, bg_stdev, bg_mean_stdev, sigma_ra, sigma_dec, nastro, path_base, zp_mean, zp_stdev, fault);
+}
+psArray *camProcessedExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, CAMPROCESSEDEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        camProcessedExpRow *object = camProcessedExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool camProcessedExpDeleteObject(psDB *dbh, const camProcessedExpRow *object)
+{
+    psMetadata *where = camProcessedExpMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, CAMPROCESSEDEXP_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from camProcessedExp");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "camProcessedExpRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long camProcessedExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        camProcessedExpRow *object = objects->data[i];
+        psMetadata *where = camProcessedExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, CAMPROCESSEDEXP_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from camProcessedExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool camProcessedExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = camProcessedExpMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            CAMPROCESSEDEXP_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool camProcessedExpPrintObject(FILE *stream, camProcessedExpRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = camProcessedExpMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void camMaskRowFree(camMaskRow *object);
+
+camMaskRow *camMaskRowAlloc(const char *label)
+{
+    camMaskRow      *_object;
+
+    _object = psAlloc(sizeof(camMaskRow));
+    psMemSetDeallocator(_object, (psFreeFunc)camMaskRowFree);
+
+    _object->label = psStringCopy(label);
+
+    return _object;
+}
+
+static void camMaskRowFree(camMaskRow *object)
+{
+    psFree(object->label);
+}
+
+bool camMaskCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, CAMMASK_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool camMaskDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, CAMMASK_TABLE_NAME);
+}
+
+bool camMaskInsert(psDB * dbh, const char *label)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, label)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, CAMMASK_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long camMaskDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, CAMMASK_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from camMask");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool camMaskInsertObject(psDB *dbh, camMaskRow *object)
+{
+    return camMaskInsert(dbh, object->label);
+}
+
+bool camMaskInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!camMaskInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool camMaskInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  CAMMASK_TABLE_NAME
+    if (!psFitsMoveExtName(fits, CAMMASK_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", CAMMASK_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, CAMMASK_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool camMaskSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, CAMMASK_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, CAMMASK_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *camMaskMetadataFromObject(const camMaskRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, object->label)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+camMaskRow *camMaskObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    char* label = psMetadataLookupPtr(&status, md, "label");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item label");
+        return false;
+    }
+
+    return camMaskRowAlloc(label);
+}
+psArray *camMaskSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, CAMMASK_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        camMaskRow *object = camMaskObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool camMaskDeleteObject(psDB *dbh, const camMaskRow *object)
+{
+    psMetadata *where = camMaskMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, CAMMASK_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from camMask");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "camMaskRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long camMaskDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        camMaskRow *object = objects->data[i];
+        psMetadata *where = camMaskMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, CAMMASK_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from camMask");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool camMaskPrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = camMaskMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            CAMMASK_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool camMaskPrintObject(FILE *stream, camMaskRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = camMaskMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void warpRunRowFree(warpRunRow *object);
+
+warpRunRow *warpRunRowAlloc(psS64 warp_id, const char *mode, const char *state, const char *workdir, const char *dvodb, psTime* registered)
+{
+    warpRunRow      *_object;
+
+    _object = psAlloc(sizeof(warpRunRow));
+    psMemSetDeallocator(_object, (psFreeFunc)warpRunRowFree);
+
+    _object->warp_id = warp_id;
+    _object->mode = psStringCopy(mode);
+    _object->state = psStringCopy(state);
+    _object->workdir = psStringCopy(workdir);
+    _object->dvodb = psStringCopy(dvodb);
+    _object->registered = psTimeCopy(registered);
+
+    return _object;
+}
+
+static void warpRunRowFree(warpRunRow *object)
+{
+    psFree(object->mode);
+    psFree(object->state);
+    psFree(object->workdir);
+    psFree(object->dvodb);
+    psFree(object->registered);
+}
+
+bool warpRunCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, "Primary Key AUTO_INCREMENT", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "mode", PS_DATA_STRING, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item mode");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, WARPRUN_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool warpRunDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, WARPRUN_TABLE_NAME);
+}
+
+bool warpRunInsert(psDB * dbh, psS64 warp_id, const char *mode, const char *state, const char *workdir, const char *dvodb, psTime* registered)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, NULL, warp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "mode", PS_DATA_STRING, NULL, mode)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item mode");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, state)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, workdir)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, dvodb)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, registered)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, WARPRUN_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long warpRunDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, WARPRUN_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from warpRun");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool warpRunInsertObject(psDB *dbh, warpRunRow *object)
+{
+    return warpRunInsert(dbh, object->warp_id, object->mode, object->state, object->workdir, object->dvodb, object->registered);
+}
+
+bool warpRunInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!warpRunInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool warpRunInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  WARPRUN_TABLE_NAME
+    if (!psFitsMoveExtName(fits, WARPRUN_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", WARPRUN_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, WARPRUN_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool warpRunSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, WARPRUN_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, WARPRUN_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *warpRunMetadataFromObject(const warpRunRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, NULL, object->warp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "mode", PS_DATA_STRING, NULL, object->mode)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item mode");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, object->state)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, object->workdir)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, object->dvodb)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, object->registered)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+warpRunRow *warpRunObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 warp_id = psMetadataLookupS64(&status, md, "warp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item warp_id");
+        return false;
+    }
+    char* mode = psMetadataLookupPtr(&status, md, "mode");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item mode");
+        return false;
+    }
+    char* state = psMetadataLookupPtr(&status, md, "state");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item state");
+        return false;
+    }
+    char* workdir = psMetadataLookupPtr(&status, md, "workdir");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
+        return false;
+    }
+    char* dvodb = psMetadataLookupPtr(&status, md, "dvodb");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dvodb");
+        return false;
+    }
+    psTime* registered = psMetadataLookupPtr(&status, md, "registered");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item registered");
+        return false;
+    }
+
+    return warpRunRowAlloc(warp_id, mode, state, workdir, dvodb, registered);
+}
+psArray *warpRunSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, WARPRUN_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        warpRunRow *object = warpRunObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool warpRunDeleteObject(psDB *dbh, const warpRunRow *object)
+{
+    psMetadata *where = warpRunMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, WARPRUN_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from warpRun");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "warpRunRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long warpRunDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        warpRunRow *object = objects->data[i];
+        psMetadata *where = warpRunMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, WARPRUN_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from warpRun");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool warpRunPrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = warpRunMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            WARPRUN_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool warpRunPrintObject(FILE *stream, warpRunRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = warpRunMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void warpInputExpRowFree(warpInputExpRow *object);
+
+warpInputExpRow *warpInputExpRowAlloc(psS64 warp_id, psS64 cam_id, bool magiced)
+{
+    warpInputExpRow *_object;
+
+    _object = psAlloc(sizeof(warpInputExpRow));
+    psMemSetDeallocator(_object, (psFreeFunc)warpInputExpRowFree);
+
+    _object->warp_id = warp_id;
+    _object->cam_id = cam_id;
+    _object->magiced = magiced;
+
+    return _object;
+}
+
+static void warpInputExpRowFree(warpInputExpRow *object)
+{
+}
+
+bool warpInputExpCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, "Primary Key fkey(warp_id) ref warpRun(warp_id)", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "cam_id", PS_DATA_S64, "Primary Key fkey(cam_id) ref camProcessedExp(cam_id)", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item cam_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "magiced", PS_DATA_BOOL, "Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item magiced");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, WARPINPUTEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool warpInputExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, WARPINPUTEXP_TABLE_NAME);
+}
+
+bool warpInputExpInsert(psDB * dbh, psS64 warp_id, psS64 cam_id, bool magiced)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, NULL, warp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "cam_id", PS_DATA_S64, NULL, cam_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item cam_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "magiced", PS_DATA_BOOL, NULL, magiced)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item magiced");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, WARPINPUTEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long warpInputExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, WARPINPUTEXP_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from warpInputExp");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool warpInputExpInsertObject(psDB *dbh, warpInputExpRow *object)
+{
+    return warpInputExpInsert(dbh, object->warp_id, object->cam_id, object->magiced);
+}
+
+bool warpInputExpInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!warpInputExpInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool warpInputExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  WARPINPUTEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, WARPINPUTEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", WARPINPUTEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, WARPINPUTEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool warpInputExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, WARPINPUTEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, WARPINPUTEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *warpInputExpMetadataFromObject(const warpInputExpRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, NULL, object->warp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "cam_id", PS_DATA_S64, NULL, object->cam_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item cam_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "magiced", PS_DATA_BOOL, NULL, object->magiced)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item magiced");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+warpInputExpRow *warpInputExpObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 warp_id = psMetadataLookupS64(&status, md, "warp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item warp_id");
+        return false;
+    }
+    psS64 cam_id = psMetadataLookupS64(&status, md, "cam_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item cam_id");
+        return false;
+    }
+    bool magiced = psMetadataLookupBool(&status, md, "magiced");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item magiced");
+        return false;
+    }
+
+    return warpInputExpRowAlloc(warp_id, cam_id, magiced);
+}
+psArray *warpInputExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, WARPINPUTEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        warpInputExpRow *object = warpInputExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool warpInputExpDeleteObject(psDB *dbh, const warpInputExpRow *object)
+{
+    psMetadata *where = warpInputExpMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, WARPINPUTEXP_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from warpInputExp");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "warpInputExpRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long warpInputExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        warpInputExpRow *object = objects->data[i];
+        psMetadata *where = warpInputExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, WARPINPUTEXP_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from warpInputExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool warpInputExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = warpInputExpMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            WARPINPUTEXP_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool warpInputExpPrintObject(FILE *stream, warpInputExpRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = warpInputExpMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void warpSkyCellMapRowFree(warpSkyCellMapRow *object);
+
+warpSkyCellMapRow *warpSkyCellMapRowAlloc(psS64 warp_id, const char *skycell_id, const char *tess_id, psS64 cam_id, const char *class_id, psS16 fault)
+{
+    warpSkyCellMapRow *_object;
+
+    _object = psAlloc(sizeof(warpSkyCellMapRow));
+    psMemSetDeallocator(_object, (psFreeFunc)warpSkyCellMapRowFree);
+
+    _object->warp_id = warp_id;
+    _object->skycell_id = psStringCopy(skycell_id);
+    _object->tess_id = psStringCopy(tess_id);
+    _object->cam_id = cam_id;
+    _object->class_id = psStringCopy(class_id);
+    _object->fault = fault;
+
+    return _object;
+}
+
+static void warpSkyCellMapRowFree(warpSkyCellMapRow *object)
+{
+    psFree(object->skycell_id);
+    psFree(object->tess_id);
+    psFree(object->class_id);
+}
+
+bool warpSkyCellMapCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, "Primary Key fkey(warp_id, cam_id) ref warpInputExp(warp_id, cam_id)", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "cam_id", PS_DATA_S64, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item cam_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, WARPSKYCELLMAP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool warpSkyCellMapDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, WARPSKYCELLMAP_TABLE_NAME);
+}
+
+bool warpSkyCellMapInsert(psDB * dbh, psS64 warp_id, const char *skycell_id, const char *tess_id, psS64 cam_id, const char *class_id, psS16 fault)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, NULL, warp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, NULL, skycell_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, tess_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "cam_id", PS_DATA_S64, NULL, cam_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item cam_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, WARPSKYCELLMAP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long warpSkyCellMapDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, WARPSKYCELLMAP_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from warpSkyCellMap");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool warpSkyCellMapInsertObject(psDB *dbh, warpSkyCellMapRow *object)
+{
+    return warpSkyCellMapInsert(dbh, object->warp_id, object->skycell_id, object->tess_id, object->cam_id, object->class_id, object->fault);
+}
+
+bool warpSkyCellMapInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!warpSkyCellMapInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool warpSkyCellMapInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  WARPSKYCELLMAP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, WARPSKYCELLMAP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", WARPSKYCELLMAP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, WARPSKYCELLMAP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool warpSkyCellMapSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, WARPSKYCELLMAP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, WARPSKYCELLMAP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *warpSkyCellMapMetadataFromObject(const warpSkyCellMapRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, NULL, object->warp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, NULL, object->skycell_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, object->tess_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "cam_id", PS_DATA_S64, NULL, object->cam_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item cam_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+warpSkyCellMapRow *warpSkyCellMapObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 warp_id = psMetadataLookupS64(&status, md, "warp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item warp_id");
+        return false;
+    }
+    char* skycell_id = psMetadataLookupPtr(&status, md, "skycell_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item skycell_id");
+        return false;
+    }
+    char* tess_id = psMetadataLookupPtr(&status, md, "tess_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item tess_id");
+        return false;
+    }
+    psS64 cam_id = psMetadataLookupS64(&status, md, "cam_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item cam_id");
+        return false;
+    }
+    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        return false;
+    }
+    psS16 fault = psMetadataLookupS16(&status, md, "fault");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
+        return false;
+    }
+
+    return warpSkyCellMapRowAlloc(warp_id, skycell_id, tess_id, cam_id, class_id, fault);
+}
+psArray *warpSkyCellMapSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, WARPSKYCELLMAP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        warpSkyCellMapRow *object = warpSkyCellMapObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool warpSkyCellMapDeleteObject(psDB *dbh, const warpSkyCellMapRow *object)
+{
+    psMetadata *where = warpSkyCellMapMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, WARPSKYCELLMAP_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from warpSkyCellMap");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "warpSkyCellMapRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long warpSkyCellMapDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        warpSkyCellMapRow *object = objects->data[i];
+        psMetadata *where = warpSkyCellMapMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, WARPSKYCELLMAP_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from warpSkyCellMap");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool warpSkyCellMapPrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = warpSkyCellMapMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            WARPSKYCELLMAP_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool warpSkyCellMapPrintObject(FILE *stream, warpSkyCellMapRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = warpSkyCellMapMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void warpSkyfileRowFree(warpSkyfileRow *object);
+
+warpSkyfileRow *warpSkyfileRowAlloc(psS64 warp_id, const char *skycell_id, const char *tess_id, const char *uri, psF64 bg, psF64 bg_stdev)
+{
+    warpSkyfileRow  *_object;
+
+    _object = psAlloc(sizeof(warpSkyfileRow));
+    psMemSetDeallocator(_object, (psFreeFunc)warpSkyfileRowFree);
+
+    _object->warp_id = warp_id;
+    _object->skycell_id = psStringCopy(skycell_id);
+    _object->tess_id = psStringCopy(tess_id);
+    _object->uri = psStringCopy(uri);
+    _object->bg = bg;
+    _object->bg_stdev = bg_stdev;
+
+    return _object;
+}
+
+static void warpSkyfileRowFree(warpSkyfileRow *object)
+{
+    psFree(object->skycell_id);
+    psFree(object->tess_id);
+    psFree(object->uri);
+}
+
+bool warpSkyfileCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, "Primary Key fkey(warp_id, skycell_id, tess_id) ref warpSkyCellMap(warp_id, skycell_id, tess_id)", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, WARPSKYFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool warpSkyfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, WARPSKYFILE_TABLE_NAME);
+}
+
+bool warpSkyfileInsert(psDB * dbh, psS64 warp_id, const char *skycell_id, const char *tess_id, const char *uri, psF64 bg, psF64 bg_stdev)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, NULL, warp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, NULL, skycell_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, tess_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, WARPSKYFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long warpSkyfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, WARPSKYFILE_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from warpSkyfile");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool warpSkyfileInsertObject(psDB *dbh, warpSkyfileRow *object)
+{
+    return warpSkyfileInsert(dbh, object->warp_id, object->skycell_id, object->tess_id, object->uri, object->bg, object->bg_stdev);
+}
+
+bool warpSkyfileInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!warpSkyfileInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool warpSkyfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  WARPSKYFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, WARPSKYFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", WARPSKYFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, WARPSKYFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool warpSkyfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, WARPSKYFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, WARPSKYFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *warpSkyfileMetadataFromObject(const warpSkyfileRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, NULL, object->warp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, NULL, object->skycell_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, object->tess_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+warpSkyfileRow *warpSkyfileObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 warp_id = psMetadataLookupS64(&status, md, "warp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item warp_id");
+        return false;
+    }
+    char* skycell_id = psMetadataLookupPtr(&status, md, "skycell_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item skycell_id");
+        return false;
+    }
+    char* tess_id = psMetadataLookupPtr(&status, md, "tess_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item tess_id");
+        return false;
+    }
+    char* uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    psF64 bg = psMetadataLookupF64(&status, md, "bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
+        return false;
+    }
+    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
+        return false;
+    }
+
+    return warpSkyfileRowAlloc(warp_id, skycell_id, tess_id, uri, bg, bg_stdev);
+}
+psArray *warpSkyfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, WARPSKYFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        warpSkyfileRow *object = warpSkyfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool warpSkyfileDeleteObject(psDB *dbh, const warpSkyfileRow *object)
+{
+    psMetadata *where = warpSkyfileMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, WARPSKYFILE_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from warpSkyfile");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "warpSkyfileRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long warpSkyfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        warpSkyfileRow *object = objects->data[i];
+        psMetadata *where = warpSkyfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, WARPSKYFILE_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from warpSkyfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool warpSkyfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = warpSkyfileMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            WARPSKYFILE_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool warpSkyfilePrintObject(FILE *stream, warpSkyfileRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = warpSkyfileMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void diffRunRowFree(diffRunRow *object);
+
+diffRunRow *diffRunRowAlloc(psS64 diff_id, const char *state, const char *workdir, const char *dvodb, psTime* registered, const char *skycell_id, const char *tess_id)
+{
+    diffRunRow      *_object;
+
+    _object = psAlloc(sizeof(diffRunRow));
+    psMemSetDeallocator(_object, (psFreeFunc)diffRunRowFree);
+
+    _object->diff_id = diff_id;
+    _object->state = psStringCopy(state);
+    _object->workdir = psStringCopy(workdir);
+    _object->dvodb = psStringCopy(dvodb);
+    _object->registered = psTimeCopy(registered);
+    _object->skycell_id = psStringCopy(skycell_id);
+    _object->tess_id = psStringCopy(tess_id);
+
+    return _object;
+}
+
+static void diffRunRowFree(diffRunRow *object)
+{
+    psFree(object->state);
+    psFree(object->workdir);
+    psFree(object->dvodb);
+    psFree(object->registered);
+    psFree(object->skycell_id);
+    psFree(object->tess_id);
+}
+
+bool diffRunCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "diff_id", PS_DATA_S64, "Primary Key AUTO_INCREMENT", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item diff_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, DIFFRUN_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool diffRunDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DIFFRUN_TABLE_NAME);
+}
+
+bool diffRunInsert(psDB * dbh, psS64 diff_id, const char *state, const char *workdir, const char *dvodb, psTime* registered, const char *skycell_id, const char *tess_id)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "diff_id", PS_DATA_S64, NULL, diff_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item diff_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, state)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, workdir)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, dvodb)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, registered)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, NULL, skycell_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, tess_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, DIFFRUN_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long diffRunDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, DIFFRUN_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from diffRun");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool diffRunInsertObject(psDB *dbh, diffRunRow *object)
+{
+    return diffRunInsert(dbh, object->diff_id, object->state, object->workdir, object->dvodb, object->registered, object->skycell_id, object->tess_id);
+}
+
+bool diffRunInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!diffRunInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool diffRunInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DIFFRUN_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DIFFRUN_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DIFFRUN_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DIFFRUN_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool diffRunSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, DIFFRUN_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DIFFRUN_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *diffRunMetadataFromObject(const diffRunRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "diff_id", PS_DATA_S64, NULL, object->diff_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item diff_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, object->state)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, object->workdir)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, object->dvodb)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, object->registered)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, NULL, object->skycell_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, object->tess_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+diffRunRow *diffRunObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 diff_id = psMetadataLookupS64(&status, md, "diff_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item diff_id");
+        return false;
+    }
+    char* state = psMetadataLookupPtr(&status, md, "state");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item state");
+        return false;
+    }
+    char* workdir = psMetadataLookupPtr(&status, md, "workdir");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
+        return false;
+    }
+    char* dvodb = psMetadataLookupPtr(&status, md, "dvodb");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dvodb");
+        return false;
+    }
+    psTime* registered = psMetadataLookupPtr(&status, md, "registered");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item registered");
+        return false;
+    }
+    char* skycell_id = psMetadataLookupPtr(&status, md, "skycell_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item skycell_id");
+        return false;
+    }
+    char* tess_id = psMetadataLookupPtr(&status, md, "tess_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item tess_id");
+        return false;
+    }
+
+    return diffRunRowAlloc(diff_id, state, workdir, dvodb, registered, skycell_id, tess_id);
+}
+psArray *diffRunSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DIFFRUN_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        diffRunRow *object = diffRunObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool diffRunDeleteObject(psDB *dbh, const diffRunRow *object)
+{
+    psMetadata *where = diffRunMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, DIFFRUN_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from diffRun");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "diffRunRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long diffRunDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        diffRunRow *object = objects->data[i];
+        psMetadata *where = diffRunMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DIFFRUN_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from diffRun");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool diffRunPrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = diffRunMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            DIFFRUN_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool diffRunPrintObject(FILE *stream, diffRunRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = diffRunMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void diffInputSkyfileRowFree(diffInputSkyfileRow *object);
+
+diffInputSkyfileRow *diffInputSkyfileRowAlloc(psS64 diff_id, psS64 warp_id, const char *skycell_id, const char *tess_id, const char *kind, bool template)
+{
+    diffInputSkyfileRow *_object;
+
+    _object = psAlloc(sizeof(diffInputSkyfileRow));
+    psMemSetDeallocator(_object, (psFreeFunc)diffInputSkyfileRowFree);
+
+    _object->diff_id = diff_id;
+    _object->warp_id = warp_id;
+    _object->skycell_id = psStringCopy(skycell_id);
+    _object->tess_id = psStringCopy(tess_id);
+    _object->kind = psStringCopy(kind);
+    _object->template = template;
+
+    return _object;
+}
+
+static void diffInputSkyfileRowFree(diffInputSkyfileRow *object)
+{
+    psFree(object->skycell_id);
+    psFree(object->tess_id);
+    psFree(object->kind);
+}
+
+bool diffInputSkyfileCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "diff_id", PS_DATA_S64, "Primary Key fkey(diff_id) ref diffRun(diff_id)", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item diff_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, "Primary Key fkey(warp_id, skycell_id, tess_id) ref warpSkyfile(warp_id, skycell_id, tess_id)", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "kind", PS_DATA_STRING, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item kind");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "template", PS_DATA_BOOL, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item template");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, DIFFINPUTSKYFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool diffInputSkyfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DIFFINPUTSKYFILE_TABLE_NAME);
+}
+
+bool diffInputSkyfileInsert(psDB * dbh, psS64 diff_id, psS64 warp_id, const char *skycell_id, const char *tess_id, const char *kind, bool template)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "diff_id", PS_DATA_S64, NULL, diff_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item diff_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, NULL, warp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, NULL, skycell_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, tess_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "kind", PS_DATA_STRING, NULL, kind)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item kind");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "template", PS_DATA_BOOL, NULL, template)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item template");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, DIFFINPUTSKYFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long diffInputSkyfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, DIFFINPUTSKYFILE_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from diffInputSkyfile");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool diffInputSkyfileInsertObject(psDB *dbh, diffInputSkyfileRow *object)
+{
+    return diffInputSkyfileInsert(dbh, object->diff_id, object->warp_id, object->skycell_id, object->tess_id, object->kind, object->template);
+}
+
+bool diffInputSkyfileInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!diffInputSkyfileInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool diffInputSkyfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DIFFINPUTSKYFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DIFFINPUTSKYFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DIFFINPUTSKYFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DIFFINPUTSKYFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool diffInputSkyfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, DIFFINPUTSKYFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DIFFINPUTSKYFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *diffInputSkyfileMetadataFromObject(const diffInputSkyfileRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "diff_id", PS_DATA_S64, NULL, object->diff_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item diff_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, NULL, object->warp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, NULL, object->skycell_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, object->tess_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "kind", PS_DATA_STRING, NULL, object->kind)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item kind");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "template", PS_DATA_BOOL, NULL, object->template)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item template");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+diffInputSkyfileRow *diffInputSkyfileObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 diff_id = psMetadataLookupS64(&status, md, "diff_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item diff_id");
+        return false;
+    }
+    psS64 warp_id = psMetadataLookupS64(&status, md, "warp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item warp_id");
+        return false;
+    }
+    char* skycell_id = psMetadataLookupPtr(&status, md, "skycell_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item skycell_id");
+        return false;
+    }
+    char* tess_id = psMetadataLookupPtr(&status, md, "tess_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item tess_id");
+        return false;
+    }
+    char* kind = psMetadataLookupPtr(&status, md, "kind");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item kind");
+        return false;
+    }
+    bool template = psMetadataLookupBool(&status, md, "template");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item template");
+        return false;
+    }
+
+    return diffInputSkyfileRowAlloc(diff_id, warp_id, skycell_id, tess_id, kind, template);
+}
+psArray *diffInputSkyfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DIFFINPUTSKYFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        diffInputSkyfileRow *object = diffInputSkyfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool diffInputSkyfileDeleteObject(psDB *dbh, const diffInputSkyfileRow *object)
+{
+    psMetadata *where = diffInputSkyfileMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, DIFFINPUTSKYFILE_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from diffInputSkyfile");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "diffInputSkyfileRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long diffInputSkyfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        diffInputSkyfileRow *object = objects->data[i];
+        psMetadata *where = diffInputSkyfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DIFFINPUTSKYFILE_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from diffInputSkyfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool diffInputSkyfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = diffInputSkyfileMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            DIFFINPUTSKYFILE_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool diffInputSkyfilePrintObject(FILE *stream, diffInputSkyfileRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = diffInputSkyfileMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void diffSkyfileRowFree(diffSkyfileRow *object);
+
+diffSkyfileRow *diffSkyfileRowAlloc(psS64 diff_id, const char *uri, psF64 bg, psF64 bg_stdev)
+{
+    diffSkyfileRow  *_object;
+
+    _object = psAlloc(sizeof(diffSkyfileRow));
+    psMemSetDeallocator(_object, (psFreeFunc)diffSkyfileRowFree);
+
+    _object->diff_id = diff_id;
+    _object->uri = psStringCopy(uri);
+    _object->bg = bg;
+    _object->bg_stdev = bg_stdev;
+
+    return _object;
+}
+
+static void diffSkyfileRowFree(diffSkyfileRow *object)
+{
+    psFree(object->uri);
+}
+
+bool diffSkyfileCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "diff_id", PS_DATA_S64, "Primary Key fkey(diff_id) ref diffRun(diff_id)", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item diff_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, DIFFSKYFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool diffSkyfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DIFFSKYFILE_TABLE_NAME);
+}
+
+bool diffSkyfileInsert(psDB * dbh, psS64 diff_id, const char *uri, psF64 bg, psF64 bg_stdev)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "diff_id", PS_DATA_S64, NULL, diff_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item diff_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, DIFFSKYFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long diffSkyfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, DIFFSKYFILE_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from diffSkyfile");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool diffSkyfileInsertObject(psDB *dbh, diffSkyfileRow *object)
+{
+    return diffSkyfileInsert(dbh, object->diff_id, object->uri, object->bg, object->bg_stdev);
+}
+
+bool diffSkyfileInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!diffSkyfileInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool diffSkyfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DIFFSKYFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DIFFSKYFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DIFFSKYFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DIFFSKYFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool diffSkyfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, DIFFSKYFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DIFFSKYFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *diffSkyfileMetadataFromObject(const diffSkyfileRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "diff_id", PS_DATA_S64, NULL, object->diff_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item diff_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+diffSkyfileRow *diffSkyfileObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 diff_id = psMetadataLookupS64(&status, md, "diff_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item diff_id");
+        return false;
+    }
+    char* uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    psF64 bg = psMetadataLookupF64(&status, md, "bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
+        return false;
+    }
+    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
+        return false;
+    }
+
+    return diffSkyfileRowAlloc(diff_id, uri, bg, bg_stdev);
+}
+psArray *diffSkyfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DIFFSKYFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        diffSkyfileRow *object = diffSkyfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool diffSkyfileDeleteObject(psDB *dbh, const diffSkyfileRow *object)
+{
+    psMetadata *where = diffSkyfileMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, DIFFSKYFILE_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from diffSkyfile");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "diffSkyfileRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long diffSkyfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        diffSkyfileRow *object = objects->data[i];
+        psMetadata *where = diffSkyfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DIFFSKYFILE_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from diffSkyfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool diffSkyfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = diffSkyfileMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            DIFFSKYFILE_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool diffSkyfilePrintObject(FILE *stream, diffSkyfileRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = diffSkyfileMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void stackRunRowFree(stackRunRow *object);
+
+stackRunRow *stackRunRowAlloc(psS64 stack_id, const char *state, const char *workdir, const char *dvodb, psTime* registered, const char *skycell_id, const char *tess_id)
+{
+    stackRunRow     *_object;
+
+    _object = psAlloc(sizeof(stackRunRow));
+    psMemSetDeallocator(_object, (psFreeFunc)stackRunRowFree);
+
+    _object->stack_id = stack_id;
+    _object->state = psStringCopy(state);
+    _object->workdir = psStringCopy(workdir);
+    _object->dvodb = psStringCopy(dvodb);
+    _object->registered = psTimeCopy(registered);
+    _object->skycell_id = psStringCopy(skycell_id);
+    _object->tess_id = psStringCopy(tess_id);
+
+    return _object;
+}
+
+static void stackRunRowFree(stackRunRow *object)
+{
+    psFree(object->state);
+    psFree(object->workdir);
+    psFree(object->dvodb);
+    psFree(object->registered);
+    psFree(object->skycell_id);
+    psFree(object->tess_id);
+}
+
+bool stackRunCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "stack_id", PS_DATA_S64, "Primary Key AUTO_INCREMENT", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stack_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, STACKRUN_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool stackRunDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, STACKRUN_TABLE_NAME);
+}
+
+bool stackRunInsert(psDB * dbh, psS64 stack_id, const char *state, const char *workdir, const char *dvodb, psTime* registered, const char *skycell_id, const char *tess_id)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "stack_id", PS_DATA_S64, NULL, stack_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stack_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, state)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, workdir)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, dvodb)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, registered)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, NULL, skycell_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, tess_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, STACKRUN_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long stackRunDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, STACKRUN_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from stackRun");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool stackRunInsertObject(psDB *dbh, stackRunRow *object)
+{
+    return stackRunInsert(dbh, object->stack_id, object->state, object->workdir, object->dvodb, object->registered, object->skycell_id, object->tess_id);
+}
+
+bool stackRunInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!stackRunInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool stackRunInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  STACKRUN_TABLE_NAME
+    if (!psFitsMoveExtName(fits, STACKRUN_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", STACKRUN_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, STACKRUN_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool stackRunSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, STACKRUN_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, STACKRUN_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *stackRunMetadataFromObject(const stackRunRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "stack_id", PS_DATA_S64, NULL, object->stack_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stack_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, object->state)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, object->workdir)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, object->dvodb)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, object->registered)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, NULL, object->skycell_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, object->tess_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+stackRunRow *stackRunObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 stack_id = psMetadataLookupS64(&status, md, "stack_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stack_id");
+        return false;
+    }
+    char* state = psMetadataLookupPtr(&status, md, "state");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item state");
+        return false;
+    }
+    char* workdir = psMetadataLookupPtr(&status, md, "workdir");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
+        return false;
+    }
+    char* dvodb = psMetadataLookupPtr(&status, md, "dvodb");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dvodb");
+        return false;
+    }
+    psTime* registered = psMetadataLookupPtr(&status, md, "registered");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item registered");
+        return false;
+    }
+    char* skycell_id = psMetadataLookupPtr(&status, md, "skycell_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item skycell_id");
+        return false;
+    }
+    char* tess_id = psMetadataLookupPtr(&status, md, "tess_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item tess_id");
+        return false;
+    }
+
+    return stackRunRowAlloc(stack_id, state, workdir, dvodb, registered, skycell_id, tess_id);
+}
+psArray *stackRunSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, STACKRUN_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        stackRunRow *object = stackRunObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool stackRunDeleteObject(psDB *dbh, const stackRunRow *object)
+{
+    psMetadata *where = stackRunMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, STACKRUN_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from stackRun");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "stackRunRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long stackRunDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        stackRunRow *object = objects->data[i];
+        psMetadata *where = stackRunMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, STACKRUN_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from stackRun");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool stackRunPrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = stackRunMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            STACKRUN_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool stackRunPrintObject(FILE *stream, stackRunRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = stackRunMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void stackInputSkyfileRowFree(stackInputSkyfileRow *object);
+
+stackInputSkyfileRow *stackInputSkyfileRowAlloc(psS64 stack_id, psS64 warp_id)
+{
+    stackInputSkyfileRow *_object;
+
+    _object = psAlloc(sizeof(stackInputSkyfileRow));
+    psMemSetDeallocator(_object, (psFreeFunc)stackInputSkyfileRowFree);
+
+    _object->stack_id = stack_id;
+    _object->warp_id = warp_id;
+
+    return _object;
+}
+
+static void stackInputSkyfileRowFree(stackInputSkyfileRow *object)
+{
+}
+
+bool stackInputSkyfileCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "stack_id", PS_DATA_S64, "Primary Key fkey(stack_id) ref stackRun(stack_id)", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stack_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, "Primary Key fkey(warp_id) ref warpSkyfile(warp_id)", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, STACKINPUTSKYFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool stackInputSkyfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, STACKINPUTSKYFILE_TABLE_NAME);
+}
+
+bool stackInputSkyfileInsert(psDB * dbh, psS64 stack_id, psS64 warp_id)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "stack_id", PS_DATA_S64, NULL, stack_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stack_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, NULL, warp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, STACKINPUTSKYFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long stackInputSkyfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, STACKINPUTSKYFILE_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from stackInputSkyfile");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool stackInputSkyfileInsertObject(psDB *dbh, stackInputSkyfileRow *object)
+{
+    return stackInputSkyfileInsert(dbh, object->stack_id, object->warp_id);
+}
+
+bool stackInputSkyfileInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!stackInputSkyfileInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool stackInputSkyfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  STACKINPUTSKYFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, STACKINPUTSKYFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", STACKINPUTSKYFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, STACKINPUTSKYFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool stackInputSkyfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, STACKINPUTSKYFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, STACKINPUTSKYFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *stackInputSkyfileMetadataFromObject(const stackInputSkyfileRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "stack_id", PS_DATA_S64, NULL, object->stack_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stack_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, NULL, object->warp_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+stackInputSkyfileRow *stackInputSkyfileObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 stack_id = psMetadataLookupS64(&status, md, "stack_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stack_id");
+        return false;
+    }
+    psS64 warp_id = psMetadataLookupS64(&status, md, "warp_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item warp_id");
+        return false;
+    }
+
+    return stackInputSkyfileRowAlloc(stack_id, warp_id);
+}
+psArray *stackInputSkyfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, STACKINPUTSKYFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        stackInputSkyfileRow *object = stackInputSkyfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool stackInputSkyfileDeleteObject(psDB *dbh, const stackInputSkyfileRow *object)
+{
+    psMetadata *where = stackInputSkyfileMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, STACKINPUTSKYFILE_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from stackInputSkyfile");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "stackInputSkyfileRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long stackInputSkyfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        stackInputSkyfileRow *object = objects->data[i];
+        psMetadata *where = stackInputSkyfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, STACKINPUTSKYFILE_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from stackInputSkyfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool stackInputSkyfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = stackInputSkyfileMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            STACKINPUTSKYFILE_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool stackInputSkyfilePrintObject(FILE *stream, stackInputSkyfileRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = stackInputSkyfileMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void stackSumSkyfileRowFree(stackSumSkyfileRow *object);
+
+stackSumSkyfileRow *stackSumSkyfileRowAlloc(psS64 stack_id, const char *uri, psF64 bg, psF64 bg_stdev)
+{
+    stackSumSkyfileRow *_object;
+
+    _object = psAlloc(sizeof(stackSumSkyfileRow));
+    psMemSetDeallocator(_object, (psFreeFunc)stackSumSkyfileRowFree);
+
+    _object->stack_id = stack_id;
+    _object->uri = psStringCopy(uri);
+    _object->bg = bg;
+    _object->bg_stdev = bg_stdev;
+
+    return _object;
+}
+
+static void stackSumSkyfileRowFree(stackSumSkyfileRow *object)
+{
+    psFree(object->uri);
+}
+
+bool stackSumSkyfileCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "stack_id", PS_DATA_S64, "Primary Key fkey(stack_id) ref stackRun(stack_id)", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stack_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, STACKSUMSKYFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool stackSumSkyfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, STACKSUMSKYFILE_TABLE_NAME);
+}
+
+bool stackSumSkyfileInsert(psDB * dbh, psS64 stack_id, const char *uri, psF64 bg, psF64 bg_stdev)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "stack_id", PS_DATA_S64, NULL, stack_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stack_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, STACKSUMSKYFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long stackSumSkyfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, STACKSUMSKYFILE_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from stackSumSkyfile");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool stackSumSkyfileInsertObject(psDB *dbh, stackSumSkyfileRow *object)
+{
+    return stackSumSkyfileInsert(dbh, object->stack_id, object->uri, object->bg, object->bg_stdev);
+}
+
+bool stackSumSkyfileInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!stackSumSkyfileInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool stackSumSkyfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  STACKSUMSKYFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, STACKSUMSKYFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", STACKSUMSKYFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, STACKSUMSKYFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool stackSumSkyfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, STACKSUMSKYFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, STACKSUMSKYFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *stackSumSkyfileMetadataFromObject(const stackSumSkyfileRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "stack_id", PS_DATA_S64, NULL, object->stack_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item stack_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+stackSumSkyfileRow *stackSumSkyfileObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 stack_id = psMetadataLookupS64(&status, md, "stack_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stack_id");
+        return false;
+    }
+    char* uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    psF64 bg = psMetadataLookupF64(&status, md, "bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
+        return false;
+    }
+    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
+        return false;
+    }
+
+    return stackSumSkyfileRowAlloc(stack_id, uri, bg, bg_stdev);
+}
+psArray *stackSumSkyfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, STACKSUMSKYFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        stackSumSkyfileRow *object = stackSumSkyfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool stackSumSkyfileDeleteObject(psDB *dbh, const stackSumSkyfileRow *object)
+{
+    psMetadata *where = stackSumSkyfileMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, STACKSUMSKYFILE_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from stackSumSkyfile");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "stackSumSkyfileRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long stackSumSkyfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        stackSumSkyfileRow *object = objects->data[i];
+        psMetadata *where = stackSumSkyfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, STACKSUMSKYFILE_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from stackSumSkyfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool stackSumSkyfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = stackSumSkyfileMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            STACKSUMSKYFILE_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool stackSumSkyfilePrintObject(FILE *stream, stackSumSkyfileRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = stackSumSkyfileMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void detRunRowFree(detRunRow *object);
+
+detRunRow *detRunRowAlloc(psS64 det_id, psS32 iteration, const char *det_type, const char *mode, const char *state, const char *filelevel, const char *workdir, const char *camera, const char *telescope, const char *exp_type, const char *filter, psF32 airmass_min, psF32 airmass_max, psF32 exp_time_min, psF32 exp_time_max, psF32 ccd_temp_min, psF32 ccd_temp_max, psF64 posang_min, psF64 posang_max, psTime* registered, psTime* time_begin, psTime* time_end, psTime* use_begin, psTime* use_end, psF32 solang_min, psF32 solang_max, const char *label, psS32 parent)
+{
+    detRunRow       *_object;
+
+    _object = psAlloc(sizeof(detRunRow));
+    psMemSetDeallocator(_object, (psFreeFunc)detRunRowFree);
+
+    _object->det_id = det_id;
+    _object->iteration = iteration;
+    _object->det_type = psStringCopy(det_type);
+    _object->mode = psStringCopy(mode);
+    _object->state = psStringCopy(state);
+    _object->filelevel = psStringCopy(filelevel);
+    _object->workdir = psStringCopy(workdir);
+    _object->camera = psStringCopy(camera);
+    _object->telescope = psStringCopy(telescope);
+    _object->exp_type = psStringCopy(exp_type);
+    _object->filter = psStringCopy(filter);
+    _object->airmass_min = airmass_min;
+    _object->airmass_max = airmass_max;
+    _object->exp_time_min = exp_time_min;
+    _object->exp_time_max = exp_time_max;
+    _object->ccd_temp_min = ccd_temp_min;
+    _object->ccd_temp_max = ccd_temp_max;
+    _object->posang_min = posang_min;
+    _object->posang_max = posang_max;
+    _object->registered = psTimeCopy(registered);
+    _object->time_begin = psTimeCopy(time_begin);
+    _object->time_end = psTimeCopy(time_end);
+    _object->use_begin = psTimeCopy(use_begin);
+    _object->use_end = psTimeCopy(use_end);
+    _object->solang_min = solang_min;
+    _object->solang_max = solang_max;
+    _object->label = psStringCopy(label);
+    _object->parent = parent;
+
+    return _object;
+}
+
+static void detRunRowFree(detRunRow *object)
+{
+    psFree(object->det_type);
+    psFree(object->mode);
+    psFree(object->state);
+    psFree(object->filelevel);
+    psFree(object->workdir);
+    psFree(object->camera);
+    psFree(object->telescope);
+    psFree(object->exp_type);
+    psFree(object->filter);
+    psFree(object->registered);
+    psFree(object->time_begin);
+    psFree(object->time_end);
+    psFree(object->use_begin);
+    psFree(object->use_end);
+    psFree(object->label);
+}
+
+bool detRunCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key AUTO_INCREMENT", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_type", PS_DATA_STRING, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "mode", PS_DATA_STRING, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item mode");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, "Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "filelevel", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filelevel");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, "destination for output files", "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, "XXX this should be dropped", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass_min", PS_DATA_F32, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass_min");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass_max", PS_DATA_F32, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass_max");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time_min", PS_DATA_F32, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time_min");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time_max", PS_DATA_F32, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time_max");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp_min", PS_DATA_F32, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp_min");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp_max", PS_DATA_F32, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp_max");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang_min", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item posang_min");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang_max", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item posang_max");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "time_begin", PS_DATA_TIME, NULL, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item time_begin");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "time_end", PS_DATA_TIME, NULL, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item time_end");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "use_begin", PS_DATA_TIME, NULL, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item use_begin");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "use_end", PS_DATA_TIME, NULL, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item use_end");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "solang_min", PS_DATA_F32, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item solang_min");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "solang_max", PS_DATA_F32, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item solang_max");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, "key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "parent", PS_DATA_S32, "Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item parent");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, DETRUN_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool detRunDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DETRUN_TABLE_NAME);
+}
+
+bool detRunInsert(psDB * dbh, psS64 det_id, psS32 iteration, const char *det_type, const char *mode, const char *state, const char *filelevel, const char *workdir, const char *camera, const char *telescope, const char *exp_type, const char *filter, psF32 airmass_min, psF32 airmass_max, psF32 exp_time_min, psF32 exp_time_max, psF32 ccd_temp_min, psF32 ccd_temp_max, psF64 posang_min, psF64 posang_max, psTime* registered, psTime* time_begin, psTime* time_end, psTime* use_begin, psTime* use_end, psF32 solang_min, psF32 solang_max, const char *label, psS32 parent)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_type", PS_DATA_STRING, NULL, det_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "mode", PS_DATA_STRING, NULL, mode)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item mode");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, state)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "filelevel", PS_DATA_STRING, NULL, filelevel)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filelevel");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, workdir)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, filter)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass_min", PS_DATA_F32, NULL, airmass_min)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass_min");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass_max", PS_DATA_F32, NULL, airmass_max)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass_max");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time_min", PS_DATA_F32, NULL, exp_time_min)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time_min");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time_max", PS_DATA_F32, NULL, exp_time_max)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time_max");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp_min", PS_DATA_F32, NULL, ccd_temp_min)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp_min");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp_max", PS_DATA_F32, NULL, ccd_temp_max)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp_max");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang_min", PS_DATA_F64, NULL, posang_min)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item posang_min");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang_max", PS_DATA_F64, NULL, posang_max)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item posang_max");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, registered)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "time_begin", PS_DATA_TIME, NULL, time_begin)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item time_begin");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "time_end", PS_DATA_TIME, NULL, time_end)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item time_end");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "use_begin", PS_DATA_TIME, NULL, use_begin)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item use_begin");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "use_end", PS_DATA_TIME, NULL, use_end)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item use_end");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "solang_min", PS_DATA_F32, NULL, solang_min)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item solang_min");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "solang_max", PS_DATA_F32, NULL, solang_max)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item solang_max");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, label)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "parent", PS_DATA_S32, NULL, parent)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item parent");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, DETRUN_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long detRunDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, DETRUN_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detRun");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detRunInsertObject(psDB *dbh, detRunRow *object)
+{
+    return detRunInsert(dbh, object->det_id, object->iteration, object->det_type, object->mode, object->state, object->filelevel, object->workdir, object->camera, object->telescope, object->exp_type, object->filter, object->airmass_min, object->airmass_max, object->exp_time_min, object->exp_time_max, object->ccd_temp_min, object->ccd_temp_max, object->posang_min, object->posang_max, object->registered, object->time_begin, object->time_end, object->use_begin, object->use_end, object->solang_min, object->solang_max, object->label, object->parent);
+}
+
+bool detRunInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!detRunInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool detRunInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DETRUN_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DETRUN_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETRUN_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DETRUN_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool detRunSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, DETRUN_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DETRUN_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *detRunMetadataFromObject(const detRunRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, object->iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_type", PS_DATA_STRING, NULL, object->det_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "mode", PS_DATA_STRING, NULL, object->mode)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item mode");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, object->state)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "filelevel", PS_DATA_STRING, NULL, object->filelevel)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filelevel");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, object->workdir)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, object->camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, object->telescope)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, object->exp_type)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, object->filter)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass_min", PS_DATA_F32, NULL, object->airmass_min)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass_min");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass_max", PS_DATA_F32, NULL, object->airmass_max)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass_max");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time_min", PS_DATA_F32, NULL, object->exp_time_min)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time_min");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time_max", PS_DATA_F32, NULL, object->exp_time_max)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time_max");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp_min", PS_DATA_F32, NULL, object->ccd_temp_min)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp_min");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp_max", PS_DATA_F32, NULL, object->ccd_temp_max)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp_max");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang_min", PS_DATA_F64, NULL, object->posang_min)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item posang_min");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang_max", PS_DATA_F64, NULL, object->posang_max)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item posang_max");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, object->registered)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "time_begin", PS_DATA_TIME, NULL, object->time_begin)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item time_begin");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "time_end", PS_DATA_TIME, NULL, object->time_end)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item time_end");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "use_begin", PS_DATA_TIME, NULL, object->use_begin)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item use_begin");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "use_end", PS_DATA_TIME, NULL, object->use_end)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item use_end");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "solang_min", PS_DATA_F32, NULL, object->solang_min)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item solang_min");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "solang_max", PS_DATA_F32, NULL, object->solang_max)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item solang_max");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, object->label)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "parent", PS_DATA_S32, NULL, object->parent)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item parent");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+detRunRow *detRunObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        return false;
+    }
+    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        return false;
+    }
+    char* det_type = psMetadataLookupPtr(&status, md, "det_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_type");
+        return false;
+    }
+    char* mode = psMetadataLookupPtr(&status, md, "mode");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item mode");
+        return false;
+    }
+    char* state = psMetadataLookupPtr(&status, md, "state");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item state");
+        return false;
+    }
+    char* filelevel = psMetadataLookupPtr(&status, md, "filelevel");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filelevel");
+        return false;
+    }
+    char* workdir = psMetadataLookupPtr(&status, md, "workdir");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
+        return false;
+    }
+    char* camera = psMetadataLookupPtr(&status, md, "camera");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
+        return false;
+    }
+    char* telescope = psMetadataLookupPtr(&status, md, "telescope");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
+        return false;
+    }
+    char* exp_type = psMetadataLookupPtr(&status, md, "exp_type");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
+        return false;
+    }
+    char* filter = psMetadataLookupPtr(&status, md, "filter");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
+        return false;
+    }
+    psF32 airmass_min = psMetadataLookupF32(&status, md, "airmass_min");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass_min");
+        return false;
+    }
+    psF32 airmass_max = psMetadataLookupF32(&status, md, "airmass_max");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass_max");
+        return false;
+    }
+    psF32 exp_time_min = psMetadataLookupF32(&status, md, "exp_time_min");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time_min");
+        return false;
+    }
+    psF32 exp_time_max = psMetadataLookupF32(&status, md, "exp_time_max");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time_max");
+        return false;
+    }
+    psF32 ccd_temp_min = psMetadataLookupF32(&status, md, "ccd_temp_min");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ccd_temp_min");
+        return false;
+    }
+    psF32 ccd_temp_max = psMetadataLookupF32(&status, md, "ccd_temp_max");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ccd_temp_max");
+        return false;
+    }
+    psF64 posang_min = psMetadataLookupF64(&status, md, "posang_min");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item posang_min");
+        return false;
+    }
+    psF64 posang_max = psMetadataLookupF64(&status, md, "posang_max");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item posang_max");
+        return false;
+    }
+    psTime* registered = psMetadataLookupPtr(&status, md, "registered");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item registered");
+        return false;
+    }
+    psTime* time_begin = psMetadataLookupPtr(&status, md, "time_begin");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item time_begin");
+        return false;
+    }
+    psTime* time_end = psMetadataLookupPtr(&status, md, "time_end");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item time_end");
+        return false;
+    }
+    psTime* use_begin = psMetadataLookupPtr(&status, md, "use_begin");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item use_begin");
+        return false;
+    }
+    psTime* use_end = psMetadataLookupPtr(&status, md, "use_end");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item use_end");
+        return false;
+    }
+    psF32 solang_min = psMetadataLookupF32(&status, md, "solang_min");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item solang_min");
+        return false;
+    }
+    psF32 solang_max = psMetadataLookupF32(&status, md, "solang_max");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item solang_max");
+        return false;
+    }
+    char* label = psMetadataLookupPtr(&status, md, "label");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item label");
+        return false;
+    }
+    psS32 parent = psMetadataLookupS32(&status, md, "parent");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item parent");
+        return false;
+    }
+
+    return detRunRowAlloc(det_id, iteration, det_type, mode, state, filelevel, workdir, camera, telescope, exp_type, filter, airmass_min, airmass_max, exp_time_min, exp_time_max, ccd_temp_min, ccd_temp_max, posang_min, posang_max, registered, time_begin, time_end, use_begin, use_end, solang_min, solang_max, label, parent);
+}
+psArray *detRunSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETRUN_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        detRunRow *object = detRunObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool detRunDeleteObject(psDB *dbh, const detRunRow *object)
+{
+    psMetadata *where = detRunMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, DETRUN_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detRun");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "detRunRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long detRunDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        detRunRow *object = objects->data[i];
+        psMetadata *where = detRunMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DETRUN_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detRun");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detRunPrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = detRunMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            DETRUN_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool detRunPrintObject(FILE *stream, detRunRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = detRunMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void detInputExpRowFree(detInputExpRow *object);
+
+detInputExpRow *detInputExpRowAlloc(psS64 det_id, psS32 iteration, const char *exp_tag, bool include)
+{
+    detInputExpRow  *_object;
+
+    _object = psAlloc(sizeof(detInputExpRow));
+    psMemSetDeallocator(_object, (psFreeFunc)detInputExpRowFree);
+
+    _object->det_id = det_id;
+    _object->iteration = iteration;
+    _object->exp_tag = psStringCopy(exp_tag);
+    _object->include = include;
+
+    return _object;
+}
+
+static void detInputExpRowFree(detInputExpRow *object)
+{
+    psFree(object->exp_tag);
+}
+
+bool detInputExpCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "include", PS_DATA_BOOL, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item include");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, DETINPUTEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool detInputExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DETINPUTEXP_TABLE_NAME);
+}
+
+bool detInputExpInsert(psDB * dbh, psS64 det_id, psS32 iteration, const char *exp_tag, bool include)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "include", PS_DATA_BOOL, NULL, include)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item include");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, DETINPUTEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long detInputExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, DETINPUTEXP_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detInputExp");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detInputExpInsertObject(psDB *dbh, detInputExpRow *object)
+{
+    return detInputExpInsert(dbh, object->det_id, object->iteration, object->exp_tag, object->include);
+}
+
+bool detInputExpInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!detInputExpInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool detInputExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DETINPUTEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DETINPUTEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETINPUTEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DETINPUTEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool detInputExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, DETINPUTEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DETINPUTEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *detInputExpMetadataFromObject(const detInputExpRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, object->iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "include", PS_DATA_BOOL, NULL, object->include)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item include");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+detInputExpRow *detInputExpObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        return false;
+    }
+    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        return false;
+    }
+    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
+        return false;
+    }
+    bool include = psMetadataLookupBool(&status, md, "include");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item include");
+        return false;
+    }
+
+    return detInputExpRowAlloc(det_id, iteration, exp_tag, include);
+}
+psArray *detInputExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETINPUTEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        detInputExpRow *object = detInputExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool detInputExpDeleteObject(psDB *dbh, const detInputExpRow *object)
+{
+    psMetadata *where = detInputExpMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, DETINPUTEXP_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detInputExp");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "detInputExpRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long detInputExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        detInputExpRow *object = objects->data[i];
+        psMetadata *where = detInputExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DETINPUTEXP_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detInputExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detInputExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = detInputExpMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            DETINPUTEXP_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool detInputExpPrintObject(FILE *stream, detInputExpRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = detInputExpMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void detProcessedImfileRowFree(detProcessedImfileRow *object);
+
+detProcessedImfileRow *detProcessedImfileRowAlloc(psS64 det_id, const char *exp_tag, const char *class_id, const char *uri, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *path_base, psS16 fault)
+{
+    detProcessedImfileRow *_object;
+
+    _object = psAlloc(sizeof(detProcessedImfileRow));
+    psMemSetDeallocator(_object, (psFreeFunc)detProcessedImfileRowFree);
+
+    _object->det_id = det_id;
+    _object->exp_tag = psStringCopy(exp_tag);
+    _object->class_id = psStringCopy(class_id);
+    _object->uri = psStringCopy(uri);
+    _object->recipe = psStringCopy(recipe);
+    _object->bg = bg;
+    _object->bg_stdev = bg_stdev;
+    _object->bg_mean_stdev = bg_mean_stdev;
+    _object->path_base = psStringCopy(path_base);
+    _object->fault = fault;
+
+    return _object;
+}
+
+static void detProcessedImfileRowFree(detProcessedImfileRow *object)
+{
+    psFree(object->exp_tag);
+    psFree(object->class_id);
+    psFree(object->uri);
+    psFree(object->recipe);
+    psFree(object->path_base);
+}
+
+bool detProcessedImfileCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, DETPROCESSEDIMFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool detProcessedImfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DETPROCESSEDIMFILE_TABLE_NAME);
+}
+
+bool detProcessedImfileInsert(psDB * dbh, psS64 det_id, const char *exp_tag, const char *class_id, const char *uri, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *path_base, psS16 fault)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, path_base)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, DETPROCESSEDIMFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long detProcessedImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, DETPROCESSEDIMFILE_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detProcessedImfile");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detProcessedImfileInsertObject(psDB *dbh, detProcessedImfileRow *object)
+{
+    return detProcessedImfileInsert(dbh, object->det_id, object->exp_tag, object->class_id, object->uri, object->recipe, object->bg, object->bg_stdev, object->bg_mean_stdev, object->path_base, object->fault);
+}
+
+bool detProcessedImfileInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!detProcessedImfileInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool detProcessedImfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DETPROCESSEDIMFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DETPROCESSEDIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETPROCESSEDIMFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DETPROCESSEDIMFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool detProcessedImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, DETPROCESSEDIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DETPROCESSEDIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *detProcessedImfileMetadataFromObject(const detProcessedImfileRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, object->recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, object->path_base)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+detProcessedImfileRow *detProcessedImfileObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        return false;
+    }
+    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
+        return false;
+    }
+    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        return false;
+    }
+    char* uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    psF64 bg = psMetadataLookupF64(&status, md, "bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
+        return false;
+    }
+    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
+        return false;
+    }
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
+        return false;
+    }
+    char* path_base = psMetadataLookupPtr(&status, md, "path_base");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item path_base");
+        return false;
+    }
+    psS16 fault = psMetadataLookupS16(&status, md, "fault");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
+        return false;
+    }
+
+    return detProcessedImfileRowAlloc(det_id, exp_tag, class_id, uri, recipe, bg, bg_stdev, bg_mean_stdev, path_base, fault);
+}
+psArray *detProcessedImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETPROCESSEDIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        detProcessedImfileRow *object = detProcessedImfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool detProcessedImfileDeleteObject(psDB *dbh, const detProcessedImfileRow *object)
+{
+    psMetadata *where = detProcessedImfileMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, DETPROCESSEDIMFILE_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detProcessedImfile");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "detProcessedImfileRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long detProcessedImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        detProcessedImfileRow *object = objects->data[i];
+        psMetadata *where = detProcessedImfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DETPROCESSEDIMFILE_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detProcessedImfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detProcessedImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = detProcessedImfileMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            DETPROCESSEDIMFILE_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool detProcessedImfilePrintObject(FILE *stream, detProcessedImfileRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = detProcessedImfileMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void detProcessedExpRowFree(detProcessedExpRow *object);
+
+detProcessedExpRow *detProcessedExpRowAlloc(psS64 det_id, const char *exp_tag, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *path_base, psS16 fault)
+{
+    detProcessedExpRow *_object;
+
+    _object = psAlloc(sizeof(detProcessedExpRow));
+    psMemSetDeallocator(_object, (psFreeFunc)detProcessedExpRowFree);
+
+    _object->det_id = det_id;
+    _object->exp_tag = psStringCopy(exp_tag);
+    _object->recipe = psStringCopy(recipe);
+    _object->bg = bg;
+    _object->bg_stdev = bg_stdev;
+    _object->bg_mean_stdev = bg_mean_stdev;
+    _object->path_base = psStringCopy(path_base);
+    _object->fault = fault;
+
+    return _object;
+}
+
+static void detProcessedExpRowFree(detProcessedExpRow *object)
+{
+    psFree(object->exp_tag);
+    psFree(object->recipe);
+    psFree(object->path_base);
+}
+
+bool detProcessedExpCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, DETPROCESSEDEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool detProcessedExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DETPROCESSEDEXP_TABLE_NAME);
+}
+
+bool detProcessedExpInsert(psDB * dbh, psS64 det_id, const char *exp_tag, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *path_base, psS16 fault)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, path_base)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, DETPROCESSEDEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long detProcessedExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, DETPROCESSEDEXP_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detProcessedExp");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detProcessedExpInsertObject(psDB *dbh, detProcessedExpRow *object)
+{
+    return detProcessedExpInsert(dbh, object->det_id, object->exp_tag, object->recipe, object->bg, object->bg_stdev, object->bg_mean_stdev, object->path_base, object->fault);
+}
+
+bool detProcessedExpInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!detProcessedExpInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool detProcessedExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DETPROCESSEDEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DETPROCESSEDEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETPROCESSEDEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DETPROCESSEDEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool detProcessedExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, DETPROCESSEDEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DETPROCESSEDEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *detProcessedExpMetadataFromObject(const detProcessedExpRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, object->recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, object->path_base)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+detProcessedExpRow *detProcessedExpObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        return false;
+    }
+    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
+        return false;
+    }
+    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    psF64 bg = psMetadataLookupF64(&status, md, "bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
+        return false;
+    }
+    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
+        return false;
+    }
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
+        return false;
+    }
+    char* path_base = psMetadataLookupPtr(&status, md, "path_base");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item path_base");
+        return false;
+    }
+    psS16 fault = psMetadataLookupS16(&status, md, "fault");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
+        return false;
+    }
+
+    return detProcessedExpRowAlloc(det_id, exp_tag, recipe, bg, bg_stdev, bg_mean_stdev, path_base, fault);
+}
+psArray *detProcessedExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETPROCESSEDEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        detProcessedExpRow *object = detProcessedExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool detProcessedExpDeleteObject(psDB *dbh, const detProcessedExpRow *object)
+{
+    psMetadata *where = detProcessedExpMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, DETPROCESSEDEXP_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detProcessedExp");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "detProcessedExpRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long detProcessedExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        detProcessedExpRow *object = objects->data[i];
+        psMetadata *where = detProcessedExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DETPROCESSEDEXP_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detProcessedExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detProcessedExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = detProcessedExpMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            DETPROCESSEDEXP_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool detProcessedExpPrintObject(FILE *stream, detProcessedExpRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = detProcessedExpMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void detStackedImfileRowFree(detStackedImfileRow *object);
+
+detStackedImfileRow *detStackedImfileRowAlloc(psS64 det_id, psS32 iteration, const char *class_id, const char *uri, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psS16 fault)
+{
+    detStackedImfileRow *_object;
+
+    _object = psAlloc(sizeof(detStackedImfileRow));
+    psMemSetDeallocator(_object, (psFreeFunc)detStackedImfileRowFree);
+
+    _object->det_id = det_id;
+    _object->iteration = iteration;
+    _object->class_id = psStringCopy(class_id);
+    _object->uri = psStringCopy(uri);
+    _object->recipe = psStringCopy(recipe);
+    _object->bg = bg;
+    _object->bg_stdev = bg_stdev;
+    _object->bg_mean_stdev = bg_mean_stdev;
+    _object->fault = fault;
+
+    return _object;
+}
+
+static void detStackedImfileRowFree(detStackedImfileRow *object)
+{
+    psFree(object->class_id);
+    psFree(object->uri);
+    psFree(object->recipe);
+}
+
+bool detStackedImfileCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, DETSTACKEDIMFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool detStackedImfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DETSTACKEDIMFILE_TABLE_NAME);
+}
+
+bool detStackedImfileInsert(psDB * dbh, psS64 det_id, psS32 iteration, const char *class_id, const char *uri, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psS16 fault)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, DETSTACKEDIMFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long detStackedImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, DETSTACKEDIMFILE_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detStackedImfile");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detStackedImfileInsertObject(psDB *dbh, detStackedImfileRow *object)
+{
+    return detStackedImfileInsert(dbh, object->det_id, object->iteration, object->class_id, object->uri, object->recipe, object->bg, object->bg_stdev, object->bg_mean_stdev, object->fault);
+}
+
+bool detStackedImfileInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!detStackedImfileInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool detStackedImfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DETSTACKEDIMFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DETSTACKEDIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETSTACKEDIMFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DETSTACKEDIMFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool detStackedImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, DETSTACKEDIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DETSTACKEDIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *detStackedImfileMetadataFromObject(const detStackedImfileRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, object->iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, object->recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+detStackedImfileRow *detStackedImfileObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        return false;
+    }
+    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        return false;
+    }
+    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        return false;
+    }
+    char* uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    psF64 bg = psMetadataLookupF64(&status, md, "bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
+        return false;
+    }
+    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
+        return false;
+    }
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
+        return false;
+    }
+    psS16 fault = psMetadataLookupS16(&status, md, "fault");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
+        return false;
+    }
+
+    return detStackedImfileRowAlloc(det_id, iteration, class_id, uri, recipe, bg, bg_stdev, bg_mean_stdev, fault);
+}
+psArray *detStackedImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETSTACKEDIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        detStackedImfileRow *object = detStackedImfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool detStackedImfileDeleteObject(psDB *dbh, const detStackedImfileRow *object)
+{
+    psMetadata *where = detStackedImfileMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, DETSTACKEDIMFILE_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detStackedImfile");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "detStackedImfileRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long detStackedImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        detStackedImfileRow *object = objects->data[i];
+        psMetadata *where = detStackedImfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DETSTACKEDIMFILE_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detStackedImfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detStackedImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = detStackedImfileMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            DETSTACKEDIMFILE_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool detStackedImfilePrintObject(FILE *stream, detStackedImfileRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = detStackedImfileMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void detNormalizedStatImfileRowFree(detNormalizedStatImfileRow *object);
+
+detNormalizedStatImfileRow *detNormalizedStatImfileRowAlloc(psS64 det_id, psS32 iteration, const char *class_id, psF32 norm, psS16 fault)
+{
+    detNormalizedStatImfileRow *_object;
+
+    _object = psAlloc(sizeof(detNormalizedStatImfileRow));
+    psMemSetDeallocator(_object, (psFreeFunc)detNormalizedStatImfileRowFree);
+
+    _object->det_id = det_id;
+    _object->iteration = iteration;
+    _object->class_id = psStringCopy(class_id);
+    _object->norm = norm;
+    _object->fault = fault;
+
+    return _object;
+}
+
+static void detNormalizedStatImfileRowFree(detNormalizedStatImfileRow *object)
+{
+    psFree(object->class_id);
+}
+
+bool detNormalizedStatImfileCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "norm", PS_DATA_F32, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item norm");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, DETNORMALIZEDSTATIMFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool detNormalizedStatImfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DETNORMALIZEDSTATIMFILE_TABLE_NAME);
+}
+
+bool detNormalizedStatImfileInsert(psDB * dbh, psS64 det_id, psS32 iteration, const char *class_id, psF32 norm, psS16 fault)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "norm", PS_DATA_F32, NULL, norm)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item norm");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, DETNORMALIZEDSTATIMFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long detNormalizedStatImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, DETNORMALIZEDSTATIMFILE_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detNormalizedStatImfile");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detNormalizedStatImfileInsertObject(psDB *dbh, detNormalizedStatImfileRow *object)
+{
+    return detNormalizedStatImfileInsert(dbh, object->det_id, object->iteration, object->class_id, object->norm, object->fault);
+}
+
+bool detNormalizedStatImfileInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!detNormalizedStatImfileInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool detNormalizedStatImfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DETNORMALIZEDSTATIMFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DETNORMALIZEDSTATIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETNORMALIZEDSTATIMFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DETNORMALIZEDSTATIMFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool detNormalizedStatImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, DETNORMALIZEDSTATIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DETNORMALIZEDSTATIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *detNormalizedStatImfileMetadataFromObject(const detNormalizedStatImfileRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, object->iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "norm", PS_DATA_F32, NULL, object->norm)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item norm");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+detNormalizedStatImfileRow *detNormalizedStatImfileObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        return false;
+    }
+    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        return false;
+    }
+    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        return false;
+    }
+    psF32 norm = psMetadataLookupF32(&status, md, "norm");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item norm");
+        return false;
+    }
+    psS16 fault = psMetadataLookupS16(&status, md, "fault");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
+        return false;
+    }
+
+    return detNormalizedStatImfileRowAlloc(det_id, iteration, class_id, norm, fault);
+}
+psArray *detNormalizedStatImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETNORMALIZEDSTATIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        detNormalizedStatImfileRow *object = detNormalizedStatImfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool detNormalizedStatImfileDeleteObject(psDB *dbh, const detNormalizedStatImfileRow *object)
+{
+    psMetadata *where = detNormalizedStatImfileMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, DETNORMALIZEDSTATIMFILE_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detNormalizedStatImfile");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "detNormalizedStatImfileRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long detNormalizedStatImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        detNormalizedStatImfileRow *object = objects->data[i];
+        psMetadata *where = detNormalizedStatImfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DETNORMALIZEDSTATIMFILE_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detNormalizedStatImfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detNormalizedStatImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = detNormalizedStatImfileMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            DETNORMALIZEDSTATIMFILE_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool detNormalizedStatImfilePrintObject(FILE *stream, detNormalizedStatImfileRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = detNormalizedStatImfileMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void detNormalizedImfileRowFree(detNormalizedImfileRow *object);
+
+detNormalizedImfileRow *detNormalizedImfileRowAlloc(psS64 det_id, psS32 iteration, const char *class_id, const char *uri, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *path_base, psS16 fault)
+{
+    detNormalizedImfileRow *_object;
+
+    _object = psAlloc(sizeof(detNormalizedImfileRow));
+    psMemSetDeallocator(_object, (psFreeFunc)detNormalizedImfileRowFree);
+
+    _object->det_id = det_id;
+    _object->iteration = iteration;
+    _object->class_id = psStringCopy(class_id);
+    _object->uri = psStringCopy(uri);
+    _object->bg = bg;
+    _object->bg_stdev = bg_stdev;
+    _object->bg_mean_stdev = bg_mean_stdev;
+    _object->path_base = psStringCopy(path_base);
+    _object->fault = fault;
+
+    return _object;
+}
+
+static void detNormalizedImfileRowFree(detNormalizedImfileRow *object)
+{
+    psFree(object->class_id);
+    psFree(object->uri);
+    psFree(object->path_base);
+}
+
+bool detNormalizedImfileCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool detNormalizedImfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DETNORMALIZEDIMFILE_TABLE_NAME);
+}
+
+bool detNormalizedImfileInsert(psDB * dbh, psS64 det_id, psS32 iteration, const char *class_id, const char *uri, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *path_base, psS16 fault)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, path_base)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long detNormalizedImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detNormalizedImfile");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detNormalizedImfileInsertObject(psDB *dbh, detNormalizedImfileRow *object)
+{
+    return detNormalizedImfileInsert(dbh, object->det_id, object->iteration, object->class_id, object->uri, object->bg, object->bg_stdev, object->bg_mean_stdev, object->path_base, object->fault);
+}
+
+bool detNormalizedImfileInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!detNormalizedImfileInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool detNormalizedImfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DETNORMALIZEDIMFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DETNORMALIZEDIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETNORMALIZEDIMFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool detNormalizedImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DETNORMALIZEDIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *detNormalizedImfileMetadataFromObject(const detNormalizedImfileRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, object->iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, object->path_base)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+detNormalizedImfileRow *detNormalizedImfileObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        return false;
+    }
+    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        return false;
+    }
+    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        return false;
+    }
+    char* uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    psF64 bg = psMetadataLookupF64(&status, md, "bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
+        return false;
+    }
+    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
+        return false;
+    }
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
+        return false;
+    }
+    char* path_base = psMetadataLookupPtr(&status, md, "path_base");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item path_base");
+        return false;
+    }
+    psS16 fault = psMetadataLookupS16(&status, md, "fault");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
+        return false;
+    }
+
+    return detNormalizedImfileRowAlloc(det_id, iteration, class_id, uri, bg, bg_stdev, bg_mean_stdev, path_base, fault);
+}
+psArray *detNormalizedImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        detNormalizedImfileRow *object = detNormalizedImfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool detNormalizedImfileDeleteObject(psDB *dbh, const detNormalizedImfileRow *object)
+{
+    psMetadata *where = detNormalizedImfileMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detNormalizedImfile");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "detNormalizedImfileRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long detNormalizedImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        detNormalizedImfileRow *object = objects->data[i];
+        psMetadata *where = detNormalizedImfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detNormalizedImfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detNormalizedImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = detNormalizedImfileMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            DETNORMALIZEDIMFILE_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool detNormalizedImfilePrintObject(FILE *stream, detNormalizedImfileRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = detNormalizedImfileMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void detNormalizedExpRowFree(detNormalizedExpRow *object);
+
+detNormalizedExpRow *detNormalizedExpRowAlloc(psS64 det_id, psS32 iteration, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *path_base, psS16 fault)
+{
+    detNormalizedExpRow *_object;
+
+    _object = psAlloc(sizeof(detNormalizedExpRow));
+    psMemSetDeallocator(_object, (psFreeFunc)detNormalizedExpRowFree);
+
+    _object->det_id = det_id;
+    _object->iteration = iteration;
+    _object->recipe = psStringCopy(recipe);
+    _object->bg = bg;
+    _object->bg_stdev = bg_stdev;
+    _object->bg_mean_stdev = bg_mean_stdev;
+    _object->path_base = psStringCopy(path_base);
+    _object->fault = fault;
+
+    return _object;
+}
+
+static void detNormalizedExpRowFree(detNormalizedExpRow *object)
+{
+    psFree(object->recipe);
+    psFree(object->path_base);
+}
+
+bool detNormalizedExpCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, DETNORMALIZEDEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool detNormalizedExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DETNORMALIZEDEXP_TABLE_NAME);
+}
+
+bool detNormalizedExpInsert(psDB * dbh, psS64 det_id, psS32 iteration, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *path_base, psS16 fault)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, path_base)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, DETNORMALIZEDEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long detNormalizedExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, DETNORMALIZEDEXP_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detNormalizedExp");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detNormalizedExpInsertObject(psDB *dbh, detNormalizedExpRow *object)
+{
+    return detNormalizedExpInsert(dbh, object->det_id, object->iteration, object->recipe, object->bg, object->bg_stdev, object->bg_mean_stdev, object->path_base, object->fault);
+}
+
+bool detNormalizedExpInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!detNormalizedExpInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool detNormalizedExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DETNORMALIZEDEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DETNORMALIZEDEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETNORMALIZEDEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DETNORMALIZEDEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool detNormalizedExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, DETNORMALIZEDEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DETNORMALIZEDEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *detNormalizedExpMetadataFromObject(const detNormalizedExpRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, object->iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, object->recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, object->path_base)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+detNormalizedExpRow *detNormalizedExpObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        return false;
+    }
+    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        return false;
+    }
+    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    psF64 bg = psMetadataLookupF64(&status, md, "bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
+        return false;
+    }
+    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
+        return false;
+    }
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
+        return false;
+    }
+    char* path_base = psMetadataLookupPtr(&status, md, "path_base");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item path_base");
+        return false;
+    }
+    psS16 fault = psMetadataLookupS16(&status, md, "fault");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
+        return false;
+    }
+
+    return detNormalizedExpRowAlloc(det_id, iteration, recipe, bg, bg_stdev, bg_mean_stdev, path_base, fault);
+}
+psArray *detNormalizedExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETNORMALIZEDEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        detNormalizedExpRow *object = detNormalizedExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool detNormalizedExpDeleteObject(psDB *dbh, const detNormalizedExpRow *object)
+{
+    psMetadata *where = detNormalizedExpMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, DETNORMALIZEDEXP_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detNormalizedExp");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "detNormalizedExpRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long detNormalizedExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        detNormalizedExpRow *object = objects->data[i];
+        psMetadata *where = detNormalizedExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DETNORMALIZEDEXP_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detNormalizedExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detNormalizedExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = detNormalizedExpMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            DETNORMALIZEDEXP_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool detNormalizedExpPrintObject(FILE *stream, detNormalizedExpRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = detNormalizedExpMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void detResidImfileRowFree(detResidImfileRow *object);
+
+detResidImfileRow *detResidImfileRowAlloc(psS64 det_id, psS32 iteration, const char *exp_tag, const char *class_id, const char *uri, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *path_base, psS16 fault)
+{
+    detResidImfileRow *_object;
+
+    _object = psAlloc(sizeof(detResidImfileRow));
+    psMemSetDeallocator(_object, (psFreeFunc)detResidImfileRowFree);
+
+    _object->det_id = det_id;
+    _object->iteration = iteration;
+    _object->exp_tag = psStringCopy(exp_tag);
+    _object->class_id = psStringCopy(class_id);
+    _object->uri = psStringCopy(uri);
+    _object->recipe = psStringCopy(recipe);
+    _object->bg = bg;
+    _object->bg_stdev = bg_stdev;
+    _object->bg_mean_stdev = bg_mean_stdev;
+    _object->path_base = psStringCopy(path_base);
+    _object->fault = fault;
+
+    return _object;
+}
+
+static void detResidImfileRowFree(detResidImfileRow *object)
+{
+    psFree(object->exp_tag);
+    psFree(object->class_id);
+    psFree(object->uri);
+    psFree(object->recipe);
+    psFree(object->path_base);
+}
+
+bool detResidImfileCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, DETRESIDIMFILE_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool detResidImfileDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DETRESIDIMFILE_TABLE_NAME);
+}
+
+bool detResidImfileInsert(psDB * dbh, psS64 det_id, psS32 iteration, const char *exp_tag, const char *class_id, const char *uri, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *path_base, psS16 fault)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, path_base)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, DETRESIDIMFILE_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long detResidImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, DETRESIDIMFILE_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detResidImfile");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detResidImfileInsertObject(psDB *dbh, detResidImfileRow *object)
+{
+    return detResidImfileInsert(dbh, object->det_id, object->iteration, object->exp_tag, object->class_id, object->uri, object->recipe, object->bg, object->bg_stdev, object->bg_mean_stdev, object->path_base, object->fault);
+}
+
+bool detResidImfileInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!detResidImfileInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool detResidImfileInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DETRESIDIMFILE_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DETRESIDIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETRESIDIMFILE_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DETRESIDIMFILE_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool detResidImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, DETRESIDIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DETRESIDIMFILE_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *detResidImfileMetadataFromObject(const detResidImfileRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, object->iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, object->recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, object->path_base)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+detResidImfileRow *detResidImfileObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        return false;
+    }
+    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        return false;
+    }
+    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
+        return false;
+    }
+    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        return false;
+    }
+    char* uri = psMetadataLookupPtr(&status, md, "uri");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
+        return false;
+    }
+    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    psF64 bg = psMetadataLookupF64(&status, md, "bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
+        return false;
+    }
+    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
+        return false;
+    }
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
+        return false;
+    }
+    char* path_base = psMetadataLookupPtr(&status, md, "path_base");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item path_base");
+        return false;
+    }
+    psS16 fault = psMetadataLookupS16(&status, md, "fault");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
+        return false;
+    }
+
+    return detResidImfileRowAlloc(det_id, iteration, exp_tag, class_id, uri, recipe, bg, bg_stdev, bg_mean_stdev, path_base, fault);
+}
+psArray *detResidImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETRESIDIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        detResidImfileRow *object = detResidImfileObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool detResidImfileDeleteObject(psDB *dbh, const detResidImfileRow *object)
+{
+    psMetadata *where = detResidImfileMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, DETRESIDIMFILE_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detResidImfile");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "detResidImfileRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long detResidImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        detResidImfileRow *object = objects->data[i];
+        psMetadata *where = detResidImfileMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DETRESIDIMFILE_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detResidImfile");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detResidImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = detResidImfileMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            DETRESIDIMFILE_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool detResidImfilePrintObject(FILE *stream, detResidImfileRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = detResidImfileMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void detResidExpRowFree(detResidExpRow *object);
+
+detResidExpRow *detResidExpRowAlloc(psS64 det_id, psS32 iteration, const char *exp_tag, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *path_base, bool accept, psS16 fault)
+{
+    detResidExpRow  *_object;
+
+    _object = psAlloc(sizeof(detResidExpRow));
+    psMemSetDeallocator(_object, (psFreeFunc)detResidExpRowFree);
+
+    _object->det_id = det_id;
+    _object->iteration = iteration;
+    _object->exp_tag = psStringCopy(exp_tag);
+    _object->recipe = psStringCopy(recipe);
+    _object->bg = bg;
+    _object->bg_stdev = bg_stdev;
+    _object->bg_mean_stdev = bg_mean_stdev;
+    _object->path_base = psStringCopy(path_base);
+    _object->accept = accept;
+    _object->fault = fault;
+
+    return _object;
+}
+
+static void detResidExpRowFree(detResidExpRow *object)
+{
+    psFree(object->exp_tag);
+    psFree(object->recipe);
+    psFree(object->path_base);
+}
+
+bool detResidExpCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, "255")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "accept", PS_DATA_BOOL, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item accept");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, DETRESIDEXP_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool detResidExpDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DETRESIDEXP_TABLE_NAME);
+}
+
+bool detResidExpInsert(psDB * dbh, psS64 det_id, psS32 iteration, const char *exp_tag, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *path_base, bool accept, psS16 fault)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, path_base)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "accept", PS_DATA_BOOL, NULL, accept)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item accept");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, DETRESIDEXP_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long detResidExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, DETRESIDEXP_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detResidExp");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detResidExpInsertObject(psDB *dbh, detResidExpRow *object)
+{
+    return detResidExpInsert(dbh, object->det_id, object->iteration, object->exp_tag, object->recipe, object->bg, object->bg_stdev, object->bg_mean_stdev, object->path_base, object->accept, object->fault);
+}
+
+bool detResidExpInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!detResidExpInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool detResidExpInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DETRESIDEXP_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DETRESIDEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETRESIDEXP_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DETRESIDEXP_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool detResidExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, DETRESIDEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DETRESIDEXP_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *detResidExpMetadataFromObject(const detResidExpRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, object->iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, object->recipe)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, object->path_base)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "accept", PS_DATA_BOOL, NULL, object->accept)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item accept");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+detResidExpRow *detResidExpObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        return false;
+    }
+    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        return false;
+    }
+    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
+        return false;
+    }
+    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
+        return false;
+    }
+    psF64 bg = psMetadataLookupF64(&status, md, "bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
+        return false;
+    }
+    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
+        return false;
+    }
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
+        return false;
+    }
+    char* path_base = psMetadataLookupPtr(&status, md, "path_base");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item path_base");
+        return false;
+    }
+    bool accept = psMetadataLookupBool(&status, md, "accept");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item accept");
+        return false;
+    }
+    psS16 fault = psMetadataLookupS16(&status, md, "fault");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
+        return false;
+    }
+
+    return detResidExpRowAlloc(det_id, iteration, exp_tag, recipe, bg, bg_stdev, bg_mean_stdev, path_base, accept, fault);
+}
+psArray *detResidExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETRESIDEXP_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        detResidExpRow *object = detResidExpObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool detResidExpDeleteObject(psDB *dbh, const detResidExpRow *object)
+{
+    psMetadata *where = detResidExpMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, DETRESIDEXP_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detResidExp");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "detResidExpRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long detResidExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        detResidExpRow *object = objects->data[i];
+        psMetadata *where = detResidExpMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DETRESIDEXP_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detResidExp");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detResidExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = detResidExpMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            DETRESIDEXP_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool detResidExpPrintObject(FILE *stream, detResidExpRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = detResidExpMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
+static void detRunSummaryRowFree(detRunSummaryRow *object);
+
+detRunSummaryRow *detRunSummaryRowAlloc(psS64 det_id, psS32 iteration, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, bool accept, psS16 fault)
+{
+    detRunSummaryRow *_object;
+
+    _object = psAlloc(sizeof(detRunSummaryRow));
+    psMemSetDeallocator(_object, (psFreeFunc)detRunSummaryRowFree);
+
+    _object->det_id = det_id;
+    _object->iteration = iteration;
+    _object->bg = bg;
+    _object->bg_stdev = bg_stdev;
+    _object->bg_mean_stdev = bg_mean_stdev;
+    _object->accept = accept;
+    _object->fault = fault;
+
+    return _object;
+}
+
+static void detRunSummaryRowFree(detRunSummaryRow *object)
+{
+}
+
+bool detRunSummaryCreateTable(psDB *dbh)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "accept", PS_DATA_BOOL, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item accept");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBCreateTable(dbh, DETRUNSUMMARY_TABLE_NAME, md);
+
+    psFree(md);
+
+    return status;
+}
+
+bool detRunSummaryDropTable(psDB *dbh)
+{
+    return psDBDropTable(dbh, DETRUNSUMMARY_TABLE_NAME);
+}
+
+bool detRunSummaryInsert(psDB * dbh, psS64 det_id, psS32 iteration, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, bool accept, psS16 fault)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "accept", PS_DATA_BOOL, NULL, accept)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item accept");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+    bool status = psDBInsertOneRow(dbh, DETRUNSUMMARY_TABLE_NAME, md);
+    psFree(md);
+
+    return status;
+}
+
+long long detRunSummaryDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    long long count = psDBDeleteRows(dbh, DETRUNSUMMARY_TABLE_NAME, where, limit);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detRunSummary");
+        return count;
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detRunSummaryInsertObject(psDB *dbh, detRunSummaryRow *object)
+{
+    return detRunSummaryInsert(dbh, object->det_id, object->iteration, object->bg, object->bg_stdev, object->bg_mean_stdev, object->accept, object->fault);
+}
+
+bool detRunSummaryInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!detRunSummaryInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+bool detRunSummaryInsertFits(psDB *dbh, const psFits *fits)
+{
+    psArray         *rowSet;
+
+    // move to (the first?) extension named  DETRUNSUMMARY_TABLE_NAME
+    if (!psFitsMoveExtName(fits, DETRUNSUMMARY_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETRUNSUMMARY_TABLE_NAME);
+        return false;
+    }
+
+    // check HDU type
+    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
+        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
+        return false;
+    }
+
+    // read fits table
+    rowSet = psFitsReadTable(fits);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
+        psFree(rowSet);
+        return false;
+    }
+
+    if (!psDBInsertRows(dbh, DETRUNSUMMARY_TABLE_NAME, rowSet)) {
+        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+bool detRunSummarySelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+
+    rowSet = psDBSelectRows(dbh, DETRUNSUMMARY_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // output to fits
+    if (!psFitsWriteTable(fits, NULL, rowSet, DETRUNSUMMARY_TABLE_NAME)) {
+        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
+        psFree(rowSet);
+        return false;
+    }
+
+    psFree(rowSet);
+
+    return true;
+}
+
+psMetadata *detRunSummaryMetadataFromObject(const detRunSummaryRow *object)
+{
+    psMetadata *md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, object->iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "accept", PS_DATA_BOOL, NULL, object->accept)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item accept");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
+        psFree(md);
+        return false;
+    }
+
+
+    return md;
+}
+
+detRunSummaryRow *detRunSummaryObjectFromMetadata(psMetadata *md)
+{
+
+bool status = false;
+    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        return false;
+    }
+    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        return false;
+    }
+    psF64 bg = psMetadataLookupF64(&status, md, "bg");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
+        return false;
+    }
+    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
+        return false;
+    }
+    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
+        return false;
+    }
+    bool accept = psMetadataLookupBool(&status, md, "accept");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item accept");
+        return false;
+    }
+    psS16 fault = psMetadataLookupS16(&status, md, "fault");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
+        return false;
+    }
+
+    return detRunSummaryRowAlloc(det_id, iteration, bg, bg_stdev, bg_mean_stdev, accept, fault);
+}
+psArray *detRunSummarySelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psArray         *returnSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETRUNSUMMARY_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return NULL;
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAllocEmpty(rowSet->n);
+
+    for (i = 0; i < rowSet->n; i++) {
+        detRunSummaryRow *object = detRunSummaryObjectFromMetadata(rowSet->data[i]);
+        psArrayAdd(returnSet, 0, object);
+        psFree(object);
+    }
+
+    psFree(rowSet);
+
+    return returnSet;
+}
+bool detRunSummaryDeleteObject(psDB *dbh, const detRunSummaryRow *object)
+{
+    psMetadata *where = detRunSummaryMetadataFromObject(object);
+    long long count = psDBDeleteRows(dbh, DETRUNSUMMARY_TABLE_NAME, where, 0);
+    psFree(where);
+    if (count < 0) {
+        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detRunSummary");
+        return false;
+    }
+    if (count > 1) {
+        // XXX should this be a psAbort() instead?  It is possible that
+        // having an object match multiple rows was by design.
+        psError(PS_ERR_UNKNOWN, true, "detRunSummaryRow object matched more then one row.  Check your database schema");
+        return false;
+    }
+
+    return true;
+}
+long long detRunSummaryDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
+{
+    long long       deleted = 0;
+
+    for (long long i = 0; i < objects->n; i++) {
+        detRunSummaryRow *object = objects->data[i];
+        psMetadata *where = detRunSummaryMetadataFromObject(object);
+        long long count = psDBDeleteRows(dbh, DETRUNSUMMARY_TABLE_NAME, where, limit);
+        psFree(where);
+        if (count < 0) {
+            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detRunSummary");
+            return count;
+        }
+
+        deleted += count;
+    }
+
+    return deleted;
+}
+bool detRunSummaryPrintObjects(FILE *stream, psArray *objects, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(objects, false);
+
+    psMetadata *output = psMetadataAlloc();
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        psMetadata *md = detRunSummaryMetadataFromObject(objects->data[i]);
+        if (!psMetadataAddMetadata(
+            output,
+            PS_LIST_TAIL,
+            DETRUNSUMMARY_TABLE_NAME,
+            PS_META_DUPLICATE_OK,
+            NULL,
+            md
+        )) {
+            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
+            psFree(md);
+            psFree(output);
+            return false;
+        }
+        psFree(md);
+    }
+
+    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(output);
+    }
+    psFree(output);
+
+    return true;
+}
+bool detRunSummaryPrintObject(FILE *stream, detRunSummaryRow *object, bool mdcf)
+{
+    PS_ASSERT_PTR_NON_NULL(object, false);
+
+    psMetadata *md = detRunSummaryMetadataFromObject(object);
+
+    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
+        psFree(md);
+    }
+
+    psFree(md);
+
+    return true;
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/src/ippdb.h
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/src/ippdb.h	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/src/ippdb.h	(revision 22073)
@@ -0,0 +1,8927 @@
+/*
+ * header.c
+ *
+ * Copyright (C) 2006  Joshua Hoblitt
+ *
+ * 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; version 2 of the License.
+ *
+ * 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
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+/*
+ *
+ * This file was generated by glueforge 1.01
+ *
+ * Do NOT directly edit this file.
+ *
+ */
+
+#ifndef IPPDB_H
+#define IPPDB_H 1
+
+#include <pslib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/// @addtogroup ippdb
+/// @{
+
+/** Opens a new database connection
+ *
+ *  @return A new psDB object if the database connection is successful or NULL
+ *  on failure.
+ */
+
+psDB *ippdbInit(
+    const char      *host,              ///< Database server hostname
+    const char      *user,              ///< Database username
+    const char      *passwd,            ///< Database password
+    const char      *dbname,            ///< Database table.namespace
+    unsigned int    port                ///< Database port
+);
+
+/** Closes a database connection
+ */
+
+void ippdbCleanup(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Formats and prints a metadata
+ *
+ * Any internal use fields are stripped.
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool ippdbPrintMetadata(
+    FILE            *stream,            ///< a stream
+    psMetadata      *md,                ///< An array of metadata
+    bool            mdcf                ///< format as mdconfig or simple
+);
+
+/** Formats and prints a metadata
+ *
+ * The metadata is printed verbatium without removing any fields.
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool ippdbPrintMetadataRaw(
+    FILE            *stream,            ///< a stream
+    psMetadata      *md,                ///< An array of metadata
+    bool            mdcf                ///< format as mdconfig or simple
+);
+
+/** Formats and prints an array of metadata
+ *
+ * Any internal use fields are stripped.
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool ippdbPrintMetadatas(
+    FILE            *stream,            ///< a stream
+    psArray         *mds,               ///< An array of metadata
+    const char      *mdname,            ///< name of the metadata(s)
+    bool            mdcf                ///< format as mdconfig or simple
+);
+
+/** Formats and prints an array of metadata
+ *
+ * The metadatas are printed verbatium without removing any fields.
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool ippdbPrintMetadatasRaw(
+    FILE            *stream,            ///< a stream
+    psArray         *mds,               ///< An array of metadata
+    const char      *mdname,            ///< name of the metadata(s)
+    bool            mdcf                ///< format as mdconfig or simple
+);
+
+/** expTagCounterRow data structure
+ *
+ * Structure for representing a single row of expTagCounter table data.
+ */
+
+typedef struct {
+    psU64           counter;
+} expTagCounterRow;
+
+/** Creates a new expTagCounterRow object
+ *
+ *  @return A new expTagCounterRow object or NULL on failure.
+ */
+
+expTagCounterRow *expTagCounterRowAlloc(
+    psU64           counter
+);
+
+/** Creates a new expTagCounter table
+ *
+ * @return true on success
+ */
+
+bool expTagCounterCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a expTagCounter table
+ *
+ * @return true on success
+ */
+
+bool expTagCounterDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool expTagCounterInsert(
+    psDB            *dbh,               ///< Database handle
+    psU64           counter
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long expTagCounterDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single expTagCounterRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool expTagCounterInsertObject(
+    psDB            *dbh,               ///< Database handle
+    expTagCounterRow *object             ///< expTagCounterRow object
+);
+
+/** Insert an array of expTagCounterRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool expTagCounterInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of expTagCounterRow objects
+);
+
+/** Insert data from a binary FITS table expTagCounterRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool expTagCounterInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool expTagCounterSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a expTagCounterRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *expTagCounterMetadataFromObject(
+    const expTagCounterRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A expTagCounterRow pointer or NULL on error
+ */
+
+expTagCounterRow *expTagCounterObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as expTagCounterRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *expTagCounterSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an expTagCounter
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool expTagCounterDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const expTagCounterRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long expTagCounterDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of expTagCounterRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool expTagCounterPrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of expTagCounterRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an expTagCounterRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool expTagCounterPrintObject(
+    FILE            *stream,            ///< a stream
+    expTagCounterRow *object,    ///< an expTagCounterRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** summitExpRow data structure
+ *
+ * Structure for representing a single row of summitExp table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    psTime*         dateobs;
+    char            *exp_type;
+    char            *uri;
+    psS32           imfiles;
+} summitExpRow;
+
+/** Creates a new summitExpRow object
+ *
+ *  @return A new summitExpRow object or NULL on failure.
+ */
+
+summitExpRow *summitExpRowAlloc(
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    psTime*         dateobs,
+    const char      *exp_type,
+    const char      *uri,
+    psS32           imfiles
+);
+
+/** Creates a new summitExp table
+ *
+ * @return true on success
+ */
+
+bool summitExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a summitExp table
+ *
+ * @return true on success
+ */
+
+bool summitExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool summitExpInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    psTime*         dateobs,
+    const char      *exp_type,
+    const char      *uri,
+    psS32           imfiles
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long summitExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single summitExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool summitExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    summitExpRow    *object             ///< summitExpRow object
+);
+
+/** Insert an array of summitExpRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool summitExpInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of summitExpRow objects
+);
+
+/** Insert data from a binary FITS table summitExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool summitExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool summitExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a summitExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *summitExpMetadataFromObject(
+    const summitExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A summitExpRow pointer or NULL on error
+ */
+
+summitExpRow *summitExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as summitExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *summitExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an summitExp
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool summitExpDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const summitExpRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long summitExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of summitExpRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool summitExpPrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of summitExpRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an summitExpRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool summitExpPrintObject(
+    FILE            *stream,            ///< a stream
+    summitExpRow *object,    ///< an summitExpRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** summitImfileRow data structure
+ *
+ * Structure for representing a single row of summitImfile table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *file_id;
+    psS32           bytes;
+    char            *md5sum;
+    char            *class;
+    char            *class_id;
+    char            *uri;
+} summitImfileRow;
+
+/** Creates a new summitImfileRow object
+ *
+ *  @return A new summitImfileRow object or NULL on failure.
+ */
+
+summitImfileRow *summitImfileRowAlloc(
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *file_id,
+    psS32           bytes,
+    const char      *md5sum,
+    const char      *class,
+    const char      *class_id,
+    const char      *uri
+);
+
+/** Creates a new summitImfile table
+ *
+ * @return true on success
+ */
+
+bool summitImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a summitImfile table
+ *
+ * @return true on success
+ */
+
+bool summitImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool summitImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *file_id,
+    psS32           bytes,
+    const char      *md5sum,
+    const char      *class,
+    const char      *class_id,
+    const char      *uri
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long summitImfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single summitImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool summitImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    summitImfileRow *object             ///< summitImfileRow object
+);
+
+/** Insert an array of summitImfileRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool summitImfileInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of summitImfileRow objects
+);
+
+/** Insert data from a binary FITS table summitImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool summitImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool summitImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a summitImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *summitImfileMetadataFromObject(
+    const summitImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A summitImfileRow pointer or NULL on error
+ */
+
+summitImfileRow *summitImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as summitImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *summitImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an summitImfile
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool summitImfileDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const summitImfileRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long summitImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of summitImfileRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool summitImfilePrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of summitImfileRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an summitImfileRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool summitImfilePrintObject(
+    FILE            *stream,            ///< a stream
+    summitImfileRow *object,    ///< an summitImfileRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** pzPendingExpRow data structure
+ *
+ * Structure for representing a single row of pzPendingExp table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+} pzPendingExpRow;
+
+/** Creates a new pzPendingExpRow object
+ *
+ *  @return A new pzPendingExpRow object or NULL on failure.
+ */
+
+pzPendingExpRow *pzPendingExpRowAlloc(
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope
+);
+
+/** Creates a new pzPendingExp table
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a pzPendingExp table
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long pzPendingExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single pzPendingExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    pzPendingExpRow *object             ///< pzPendingExpRow object
+);
+
+/** Insert an array of pzPendingExpRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of pzPendingExpRow objects
+);
+
+/** Insert data from a binary FITS table pzPendingExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a pzPendingExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *pzPendingExpMetadataFromObject(
+    const pzPendingExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A pzPendingExpRow pointer or NULL on error
+ */
+
+pzPendingExpRow *pzPendingExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as pzPendingExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *pzPendingExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an pzPendingExp
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool pzPendingExpDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const pzPendingExpRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long pzPendingExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of pzPendingExpRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpPrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of pzPendingExpRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an pzPendingExpRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool pzPendingExpPrintObject(
+    FILE            *stream,            ///< a stream
+    pzPendingExpRow *object,    ///< an pzPendingExpRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** pzPendingImfileRow data structure
+ *
+ * Structure for representing a single row of pzPendingImfile table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *class;
+    char            *class_id;
+    char            *exp_tag;
+} pzPendingImfileRow;
+
+/** Creates a new pzPendingImfileRow object
+ *
+ *  @return A new pzPendingImfileRow object or NULL on failure.
+ */
+
+pzPendingImfileRow *pzPendingImfileRowAlloc(
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *class,
+    const char      *class_id,
+    const char      *exp_tag
+);
+
+/** Creates a new pzPendingImfile table
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a pzPendingImfile table
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *class,
+    const char      *class_id,
+    const char      *exp_tag
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long pzPendingImfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single pzPendingImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    pzPendingImfileRow *object             ///< pzPendingImfileRow object
+);
+
+/** Insert an array of pzPendingImfileRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfileInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of pzPendingImfileRow objects
+);
+
+/** Insert data from a binary FITS table pzPendingImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a pzPendingImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *pzPendingImfileMetadataFromObject(
+    const pzPendingImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A pzPendingImfileRow pointer or NULL on error
+ */
+
+pzPendingImfileRow *pzPendingImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as pzPendingImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *pzPendingImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an pzPendingImfile
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool pzPendingImfileDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const pzPendingImfileRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long pzPendingImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of pzPendingImfileRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfilePrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of pzPendingImfileRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an pzPendingImfileRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool pzPendingImfilePrintObject(
+    FILE            *stream,            ///< a stream
+    pzPendingImfileRow *object,    ///< an pzPendingImfileRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** pzDoneExpRow data structure
+ *
+ * Structure for representing a single row of pzDoneExp table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+} pzDoneExpRow;
+
+/** Creates a new pzDoneExpRow object
+ *
+ *  @return A new pzDoneExpRow object or NULL on failure.
+ */
+
+pzDoneExpRow *pzDoneExpRowAlloc(
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope
+);
+
+/** Creates a new pzDoneExp table
+ *
+ * @return true on success
+ */
+
+bool pzDoneExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a pzDoneExp table
+ *
+ * @return true on success
+ */
+
+bool pzDoneExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool pzDoneExpInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long pzDoneExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single pzDoneExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool pzDoneExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    pzDoneExpRow    *object             ///< pzDoneExpRow object
+);
+
+/** Insert an array of pzDoneExpRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool pzDoneExpInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of pzDoneExpRow objects
+);
+
+/** Insert data from a binary FITS table pzDoneExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool pzDoneExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool pzDoneExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a pzDoneExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *pzDoneExpMetadataFromObject(
+    const pzDoneExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A pzDoneExpRow pointer or NULL on error
+ */
+
+pzDoneExpRow *pzDoneExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as pzDoneExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *pzDoneExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an pzDoneExp
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool pzDoneExpDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const pzDoneExpRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long pzDoneExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of pzDoneExpRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool pzDoneExpPrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of pzDoneExpRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an pzDoneExpRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool pzDoneExpPrintObject(
+    FILE            *stream,            ///< a stream
+    pzDoneExpRow *object,    ///< an pzDoneExpRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** pzDoneImfileRow data structure
+ *
+ * Structure for representing a single row of pzDoneImfile table data.
+ */
+
+typedef struct {
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    char            *class;
+    char            *class_id;
+    char            *exp_tag;
+    char            *uri;
+} pzDoneImfileRow;
+
+/** Creates a new pzDoneImfileRow object
+ *
+ *  @return A new pzDoneImfileRow object or NULL on failure.
+ */
+
+pzDoneImfileRow *pzDoneImfileRowAlloc(
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *class,
+    const char      *class_id,
+    const char      *exp_tag,
+    const char      *uri
+);
+
+/** Creates a new pzDoneImfile table
+ *
+ * @return true on success
+ */
+
+bool pzDoneImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a pzDoneImfile table
+ *
+ * @return true on success
+ */
+
+bool pzDoneImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool pzDoneImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    const char      *class,
+    const char      *class_id,
+    const char      *exp_tag,
+    const char      *uri
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long pzDoneImfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single pzDoneImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool pzDoneImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    pzDoneImfileRow *object             ///< pzDoneImfileRow object
+);
+
+/** Insert an array of pzDoneImfileRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool pzDoneImfileInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of pzDoneImfileRow objects
+);
+
+/** Insert data from a binary FITS table pzDoneImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool pzDoneImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool pzDoneImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a pzDoneImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *pzDoneImfileMetadataFromObject(
+    const pzDoneImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A pzDoneImfileRow pointer or NULL on error
+ */
+
+pzDoneImfileRow *pzDoneImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as pzDoneImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *pzDoneImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an pzDoneImfile
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool pzDoneImfileDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const pzDoneImfileRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long pzDoneImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of pzDoneImfileRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool pzDoneImfilePrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of pzDoneImfileRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an pzDoneImfileRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool pzDoneImfilePrintObject(
+    FILE            *stream,            ///< a stream
+    pzDoneImfileRow *object,    ///< an pzDoneImfileRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** newExpRow data structure
+ *
+ * Structure for representing a single row of newExp table data.
+ */
+
+typedef struct {
+    char            *exp_tag;
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    psS32           imfiles;
+    char            *workdir;
+} newExpRow;
+
+/** Creates a new newExpRow object
+ *
+ *  @return A new newExpRow object or NULL on failure.
+ */
+
+newExpRow *newExpRowAlloc(
+    const char      *exp_tag,
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    psS32           imfiles,
+    const char      *workdir
+);
+
+/** Creates a new newExp table
+ *
+ * @return true on success
+ */
+
+bool newExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a newExp table
+ *
+ * @return true on success
+ */
+
+bool newExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool newExpInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_tag,
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    psS32           imfiles,
+    const char      *workdir
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long newExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single newExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool newExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    newExpRow       *object             ///< newExpRow object
+);
+
+/** Insert an array of newExpRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool newExpInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of newExpRow objects
+);
+
+/** Insert data from a binary FITS table newExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool newExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool newExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a newExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *newExpMetadataFromObject(
+    const newExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A newExpRow pointer or NULL on error
+ */
+
+newExpRow *newExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as newExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *newExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an newExp
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool newExpDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const newExpRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long newExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of newExpRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool newExpPrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of newExpRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an newExpRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool newExpPrintObject(
+    FILE            *stream,            ///< a stream
+    newExpRow *object,    ///< an newExpRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** newImfileRow data structure
+ *
+ * Structure for representing a single row of newImfile table data.
+ */
+
+typedef struct {
+    char            *exp_tag;
+    char            *class;
+    char            *class_id;
+    char            *uri;
+} newImfileRow;
+
+/** Creates a new newImfileRow object
+ *
+ *  @return A new newImfileRow object or NULL on failure.
+ */
+
+newImfileRow *newImfileRowAlloc(
+    const char      *exp_tag,
+    const char      *class,
+    const char      *class_id,
+    const char      *uri
+);
+
+/** Creates a new newImfile table
+ *
+ * @return true on success
+ */
+
+bool newImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a newImfile table
+ *
+ * @return true on success
+ */
+
+bool newImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool newImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_tag,
+    const char      *class,
+    const char      *class_id,
+    const char      *uri
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long newImfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single newImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool newImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    newImfileRow    *object             ///< newImfileRow object
+);
+
+/** Insert an array of newImfileRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool newImfileInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of newImfileRow objects
+);
+
+/** Insert data from a binary FITS table newImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool newImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool newImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a newImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *newImfileMetadataFromObject(
+    const newImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A newImfileRow pointer or NULL on error
+ */
+
+newImfileRow *newImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as newImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *newImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an newImfile
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool newImfileDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const newImfileRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long newImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of newImfileRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool newImfilePrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of newImfileRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an newImfileRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool newImfilePrintObject(
+    FILE            *stream,            ///< a stream
+    newImfileRow *object,    ///< an newImfileRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** rawExpRow data structure
+ *
+ * Structure for representing a single row of rawExp table data.
+ */
+
+typedef struct {
+    char            *exp_tag;
+    char            *exp_id;
+    char            *camera;
+    char            *telescope;
+    psTime*         dateobs;
+    char            *exp_type;
+    psS32           imfiles;
+    char            *filelevel;
+    char            *workdir;
+    char            *filter;
+    psF32           airmass;
+    psF64           ra;
+    psF64           decl;
+    psF32           exp_time;
+    psF64           bg;
+    psF64           bg_stdev;
+    psF64           bg_mean_stdev;
+    psF64           alt;
+    psF64           az;
+    psF32           ccd_temp;
+    psF64           posang;
+    char            *object;
+    psF32           solang;
+    psS16           fault;
+} rawExpRow;
+
+/** Creates a new rawExpRow object
+ *
+ *  @return A new rawExpRow object or NULL on failure.
+ */
+
+rawExpRow *rawExpRowAlloc(
+    const char      *exp_tag,
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    psTime*         dateobs,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filelevel,
+    const char      *workdir,
+    const char      *filter,
+    psF32           airmass,
+    psF64           ra,
+    psF64           decl,
+    psF32           exp_time,
+    psF64           bg,
+    psF64           bg_stdev,
+    psF64           bg_mean_stdev,
+    psF64           alt,
+    psF64           az,
+    psF32           ccd_temp,
+    psF64           posang,
+    const char      *object,
+    psF32           solang,
+    psS16           fault
+);
+
+/** Creates a new rawExp table
+ *
+ * @return true on success
+ */
+
+bool rawExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a rawExp table
+ *
+ * @return true on success
+ */
+
+bool rawExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool rawExpInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_tag,
+    const char      *exp_id,
+    const char      *camera,
+    const char      *telescope,
+    psTime*         dateobs,
+    const char      *exp_type,
+    psS32           imfiles,
+    const char      *filelevel,
+    const char      *workdir,
+    const char      *filter,
+    psF32           airmass,
+    psF64           ra,
+    psF64           decl,
+    psF32           exp_time,
+    psF64           bg,
+    psF64           bg_stdev,
+    psF64           bg_mean_stdev,
+    psF64           alt,
+    psF64           az,
+    psF32           ccd_temp,
+    psF64           posang,
+    const char      *object,
+    psF32           solang,
+    psS16           fault
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long rawExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single rawExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool rawExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    rawExpRow       *object             ///< rawExpRow object
+);
+
+/** Insert an array of rawExpRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool rawExpInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of rawExpRow objects
+);
+
+/** Insert data from a binary FITS table rawExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool rawExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool rawExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a rawExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *rawExpMetadataFromObject(
+    const rawExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A rawExpRow pointer or NULL on error
+ */
+
+rawExpRow *rawExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as rawExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *rawExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an rawExp
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool rawExpDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const rawExpRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long rawExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of rawExpRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool rawExpPrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of rawExpRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an rawExpRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool rawExpPrintObject(
+    FILE            *stream,            ///< a stream
+    rawExpRow *object,    ///< an rawExpRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** rawImfileRow data structure
+ *
+ * Structure for representing a single row of rawImfile table data.
+ */
+
+typedef struct {
+    char            *exp_tag;
+    char            *class_id;
+    char            *uri;
+    char            *exp_type;
+    char            *filelevel;
+    char            *filter;
+    psF32           airmass;
+    psF64           ra;
+    psF64           decl;
+    psF32           exp_time;
+    psF64           bg;
+    psF64           bg_stdev;
+    psF64           bg_mean_stdev;
+    psF64           alt;
+    psF64           az;
+    psF32           ccd_temp;
+    psF64           posang;
+    char            *object;
+    psTime*         dateobs;
+    psS16           fault;
+} rawImfileRow;
+
+/** Creates a new rawImfileRow object
+ *
+ *  @return A new rawImfileRow object or NULL on failure.
+ */
+
+rawImfileRow *rawImfileRowAlloc(
+    const char      *exp_tag,
+    const char      *class_id,
+    const char      *uri,
+    const char      *exp_type,
+    const char      *filelevel,
+    const char      *filter,
+    psF32           airmass,
+    psF64           ra,
+    psF64           decl,
+    psF32           exp_time,
+    psF64           bg,
+    psF64           bg_stdev,
+    psF64           bg_mean_stdev,
+    psF64           alt,
+    psF64           az,
+    psF32           ccd_temp,
+    psF64           posang,
+    const char      *object,
+    psTime*         dateobs,
+    psS16           fault
+);
+
+/** Creates a new rawImfile table
+ *
+ * @return true on success
+ */
+
+bool rawImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a rawImfile table
+ *
+ * @return true on success
+ */
+
+bool rawImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool rawImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *exp_tag,
+    const char      *class_id,
+    const char      *uri,
+    const char      *exp_type,
+    const char      *filelevel,
+    const char      *filter,
+    psF32           airmass,
+    psF64           ra,
+    psF64           decl,
+    psF32           exp_time,
+    psF64           bg,
+    psF64           bg_stdev,
+    psF64           bg_mean_stdev,
+    psF64           alt,
+    psF64           az,
+    psF32           ccd_temp,
+    psF64           posang,
+    const char      *object,
+    psTime*         dateobs,
+    psS16           fault
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long rawImfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single rawImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool rawImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    rawImfileRow    *object             ///< rawImfileRow object
+);
+
+/** Insert an array of rawImfileRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool rawImfileInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of rawImfileRow objects
+);
+
+/** Insert data from a binary FITS table rawImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool rawImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool rawImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a rawImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *rawImfileMetadataFromObject(
+    const rawImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A rawImfileRow pointer or NULL on error
+ */
+
+rawImfileRow *rawImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as rawImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *rawImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an rawImfile
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool rawImfileDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const rawImfileRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long rawImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of rawImfileRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool rawImfilePrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of rawImfileRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an rawImfileRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool rawImfilePrintObject(
+    FILE            *stream,            ///< a stream
+    rawImfileRow *object,    ///< an rawImfileRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** guidePendingExpRow data structure
+ *
+ * Structure for representing a single row of guidePendingExp table data.
+ */
+
+typedef struct {
+    psS64           guide_id;
+    char            *exp_tag;
+    char            *recipe;
+} guidePendingExpRow;
+
+/** Creates a new guidePendingExpRow object
+ *
+ *  @return A new guidePendingExpRow object or NULL on failure.
+ */
+
+guidePendingExpRow *guidePendingExpRowAlloc(
+    psS64           guide_id,
+    const char      *exp_tag,
+    const char      *recipe
+);
+
+/** Creates a new guidePendingExp table
+ *
+ * @return true on success
+ */
+
+bool guidePendingExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a guidePendingExp table
+ *
+ * @return true on success
+ */
+
+bool guidePendingExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool guidePendingExpInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           guide_id,
+    const char      *exp_tag,
+    const char      *recipe
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long guidePendingExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single guidePendingExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool guidePendingExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    guidePendingExpRow *object             ///< guidePendingExpRow object
+);
+
+/** Insert an array of guidePendingExpRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool guidePendingExpInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of guidePendingExpRow objects
+);
+
+/** Insert data from a binary FITS table guidePendingExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool guidePendingExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool guidePendingExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a guidePendingExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *guidePendingExpMetadataFromObject(
+    const guidePendingExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A guidePendingExpRow pointer or NULL on error
+ */
+
+guidePendingExpRow *guidePendingExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as guidePendingExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *guidePendingExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an guidePendingExp
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool guidePendingExpDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const guidePendingExpRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long guidePendingExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of guidePendingExpRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool guidePendingExpPrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of guidePendingExpRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an guidePendingExpRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool guidePendingExpPrintObject(
+    FILE            *stream,            ///< a stream
+    guidePendingExpRow *object,    ///< an guidePendingExpRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** chipPendingExpRow data structure
+ *
+ * Structure for representing a single row of chipPendingExp table data.
+ */
+
+typedef struct {
+    psS64           chip_id;
+    char            *exp_tag;
+    psS64           guide_id;
+    char            *workdir;
+    char            *label;
+    char            *recipe;
+    char            *expgroup;
+    char            *dvodb;
+} chipPendingExpRow;
+
+/** Creates a new chipPendingExpRow object
+ *
+ *  @return A new chipPendingExpRow object or NULL on failure.
+ */
+
+chipPendingExpRow *chipPendingExpRowAlloc(
+    psS64           chip_id,
+    const char      *exp_tag,
+    psS64           guide_id,
+    const char      *workdir,
+    const char      *label,
+    const char      *recipe,
+    const char      *expgroup,
+    const char      *dvodb
+);
+
+/** Creates a new chipPendingExp table
+ *
+ * @return true on success
+ */
+
+bool chipPendingExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a chipPendingExp table
+ *
+ * @return true on success
+ */
+
+bool chipPendingExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool chipPendingExpInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           chip_id,
+    const char      *exp_tag,
+    psS64           guide_id,
+    const char      *workdir,
+    const char      *label,
+    const char      *recipe,
+    const char      *expgroup,
+    const char      *dvodb
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long chipPendingExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single chipPendingExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool chipPendingExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    chipPendingExpRow *object             ///< chipPendingExpRow object
+);
+
+/** Insert an array of chipPendingExpRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool chipPendingExpInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of chipPendingExpRow objects
+);
+
+/** Insert data from a binary FITS table chipPendingExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool chipPendingExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool chipPendingExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a chipPendingExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *chipPendingExpMetadataFromObject(
+    const chipPendingExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A chipPendingExpRow pointer or NULL on error
+ */
+
+chipPendingExpRow *chipPendingExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as chipPendingExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *chipPendingExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an chipPendingExp
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool chipPendingExpDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const chipPendingExpRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long chipPendingExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of chipPendingExpRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool chipPendingExpPrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of chipPendingExpRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an chipPendingExpRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool chipPendingExpPrintObject(
+    FILE            *stream,            ///< a stream
+    chipPendingExpRow *object,    ///< an chipPendingExpRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** chipPendingImfileRow data structure
+ *
+ * Structure for representing a single row of chipPendingImfile table data.
+ */
+
+typedef struct {
+    psS64           chip_id;
+    char            *class_id;
+    char            *uri;
+} chipPendingImfileRow;
+
+/** Creates a new chipPendingImfileRow object
+ *
+ *  @return A new chipPendingImfileRow object or NULL on failure.
+ */
+
+chipPendingImfileRow *chipPendingImfileRowAlloc(
+    psS64           chip_id,
+    const char      *class_id,
+    const char      *uri
+);
+
+/** Creates a new chipPendingImfile table
+ *
+ * @return true on success
+ */
+
+bool chipPendingImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a chipPendingImfile table
+ *
+ * @return true on success
+ */
+
+bool chipPendingImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool chipPendingImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           chip_id,
+    const char      *class_id,
+    const char      *uri
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long chipPendingImfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single chipPendingImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool chipPendingImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    chipPendingImfileRow *object             ///< chipPendingImfileRow object
+);
+
+/** Insert an array of chipPendingImfileRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool chipPendingImfileInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of chipPendingImfileRow objects
+);
+
+/** Insert data from a binary FITS table chipPendingImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool chipPendingImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool chipPendingImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a chipPendingImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *chipPendingImfileMetadataFromObject(
+    const chipPendingImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A chipPendingImfileRow pointer or NULL on error
+ */
+
+chipPendingImfileRow *chipPendingImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as chipPendingImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *chipPendingImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an chipPendingImfile
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool chipPendingImfileDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const chipPendingImfileRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long chipPendingImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of chipPendingImfileRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool chipPendingImfilePrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of chipPendingImfileRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an chipPendingImfileRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool chipPendingImfilePrintObject(
+    FILE            *stream,            ///< a stream
+    chipPendingImfileRow *object,    ///< an chipPendingImfileRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** chipProcessedExpRow data structure
+ *
+ * Structure for representing a single row of chipProcessedExp table data.
+ */
+
+typedef struct {
+    psS64           chip_id;
+    char            *exp_tag;
+    psS64           guide_id;
+    char            *workdir;
+    char            *label;
+    char            *recipe;
+    char            *expgroup;
+    char            *dvodb;
+} chipProcessedExpRow;
+
+/** Creates a new chipProcessedExpRow object
+ *
+ *  @return A new chipProcessedExpRow object or NULL on failure.
+ */
+
+chipProcessedExpRow *chipProcessedExpRowAlloc(
+    psS64           chip_id,
+    const char      *exp_tag,
+    psS64           guide_id,
+    const char      *workdir,
+    const char      *label,
+    const char      *recipe,
+    const char      *expgroup,
+    const char      *dvodb
+);
+
+/** Creates a new chipProcessedExp table
+ *
+ * @return true on success
+ */
+
+bool chipProcessedExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a chipProcessedExp table
+ *
+ * @return true on success
+ */
+
+bool chipProcessedExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool chipProcessedExpInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           chip_id,
+    const char      *exp_tag,
+    psS64           guide_id,
+    const char      *workdir,
+    const char      *label,
+    const char      *recipe,
+    const char      *expgroup,
+    const char      *dvodb
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long chipProcessedExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single chipProcessedExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool chipProcessedExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    chipProcessedExpRow *object             ///< chipProcessedExpRow object
+);
+
+/** Insert an array of chipProcessedExpRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool chipProcessedExpInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of chipProcessedExpRow objects
+);
+
+/** Insert data from a binary FITS table chipProcessedExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool chipProcessedExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool chipProcessedExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a chipProcessedExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *chipProcessedExpMetadataFromObject(
+    const chipProcessedExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A chipProcessedExpRow pointer or NULL on error
+ */
+
+chipProcessedExpRow *chipProcessedExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as chipProcessedExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *chipProcessedExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an chipProcessedExp
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool chipProcessedExpDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const chipProcessedExpRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long chipProcessedExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of chipProcessedExpRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool chipProcessedExpPrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of chipProcessedExpRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an chipProcessedExpRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool chipProcessedExpPrintObject(
+    FILE            *stream,            ///< a stream
+    chipProcessedExpRow *object,    ///< an chipProcessedExpRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** chipMaskRow data structure
+ *
+ * Structure for representing a single row of chipMask table data.
+ */
+
+typedef struct {
+    char            *label;
+} chipMaskRow;
+
+/** Creates a new chipMaskRow object
+ *
+ *  @return A new chipMaskRow object or NULL on failure.
+ */
+
+chipMaskRow *chipMaskRowAlloc(
+    const char      *label
+);
+
+/** Creates a new chipMask table
+ *
+ * @return true on success
+ */
+
+bool chipMaskCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a chipMask table
+ *
+ * @return true on success
+ */
+
+bool chipMaskDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool chipMaskInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *label
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long chipMaskDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single chipMaskRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool chipMaskInsertObject(
+    psDB            *dbh,               ///< Database handle
+    chipMaskRow     *object             ///< chipMaskRow object
+);
+
+/** Insert an array of chipMaskRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool chipMaskInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of chipMaskRow objects
+);
+
+/** Insert data from a binary FITS table chipMaskRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool chipMaskInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool chipMaskSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a chipMaskRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *chipMaskMetadataFromObject(
+    const chipMaskRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A chipMaskRow pointer or NULL on error
+ */
+
+chipMaskRow *chipMaskObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as chipMaskRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *chipMaskSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an chipMask
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool chipMaskDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const chipMaskRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long chipMaskDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of chipMaskRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool chipMaskPrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of chipMaskRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an chipMaskRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool chipMaskPrintObject(
+    FILE            *stream,            ///< a stream
+    chipMaskRow *object,    ///< an chipMaskRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** chipProcessedImfileRow data structure
+ *
+ * Structure for representing a single row of chipProcessedImfile table data.
+ */
+
+typedef struct {
+    psS64           chip_id;
+    char            *class_id;
+    char            *uri;
+    psF64           bg;
+    psF64           bg_stdev;
+    psF64           bg_mean_stdev;
+    char            *path_base;
+    psS16           fault;
+} chipProcessedImfileRow;
+
+/** Creates a new chipProcessedImfileRow object
+ *
+ *  @return A new chipProcessedImfileRow object or NULL on failure.
+ */
+
+chipProcessedImfileRow *chipProcessedImfileRowAlloc(
+    psS64           chip_id,
+    const char      *class_id,
+    const char      *uri,
+    psF64           bg,
+    psF64           bg_stdev,
+    psF64           bg_mean_stdev,
+    const char      *path_base,
+    psS16           fault
+);
+
+/** Creates a new chipProcessedImfile table
+ *
+ * @return true on success
+ */
+
+bool chipProcessedImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a chipProcessedImfile table
+ *
+ * @return true on success
+ */
+
+bool chipProcessedImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool chipProcessedImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           chip_id,
+    const char      *class_id,
+    const char      *uri,
+    psF64           bg,
+    psF64           bg_stdev,
+    psF64           bg_mean_stdev,
+    const char      *path_base,
+    psS16           fault
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long chipProcessedImfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single chipProcessedImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool chipProcessedImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    chipProcessedImfileRow *object             ///< chipProcessedImfileRow object
+);
+
+/** Insert an array of chipProcessedImfileRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool chipProcessedImfileInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of chipProcessedImfileRow objects
+);
+
+/** Insert data from a binary FITS table chipProcessedImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool chipProcessedImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool chipProcessedImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a chipProcessedImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *chipProcessedImfileMetadataFromObject(
+    const chipProcessedImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A chipProcessedImfileRow pointer or NULL on error
+ */
+
+chipProcessedImfileRow *chipProcessedImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as chipProcessedImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *chipProcessedImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an chipProcessedImfile
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool chipProcessedImfileDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const chipProcessedImfileRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long chipProcessedImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of chipProcessedImfileRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool chipProcessedImfilePrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of chipProcessedImfileRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an chipProcessedImfileRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool chipProcessedImfilePrintObject(
+    FILE            *stream,            ///< a stream
+    chipProcessedImfileRow *object,    ///< an chipProcessedImfileRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** camPendingExpRow data structure
+ *
+ * Structure for representing a single row of camPendingExp table data.
+ */
+
+typedef struct {
+    psS64           cam_id;
+    psS64           chip_id;
+    char            *workdir;
+    char            *label;
+    char            *recipe;
+    char            *expgroup;
+    char            *dvodb;
+} camPendingExpRow;
+
+/** Creates a new camPendingExpRow object
+ *
+ *  @return A new camPendingExpRow object or NULL on failure.
+ */
+
+camPendingExpRow *camPendingExpRowAlloc(
+    psS64           cam_id,
+    psS64           chip_id,
+    const char      *workdir,
+    const char      *label,
+    const char      *recipe,
+    const char      *expgroup,
+    const char      *dvodb
+);
+
+/** Creates a new camPendingExp table
+ *
+ * @return true on success
+ */
+
+bool camPendingExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a camPendingExp table
+ *
+ * @return true on success
+ */
+
+bool camPendingExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool camPendingExpInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           cam_id,
+    psS64           chip_id,
+    const char      *workdir,
+    const char      *label,
+    const char      *recipe,
+    const char      *expgroup,
+    const char      *dvodb
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long camPendingExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single camPendingExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool camPendingExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    camPendingExpRow *object             ///< camPendingExpRow object
+);
+
+/** Insert an array of camPendingExpRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool camPendingExpInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of camPendingExpRow objects
+);
+
+/** Insert data from a binary FITS table camPendingExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool camPendingExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool camPendingExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a camPendingExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *camPendingExpMetadataFromObject(
+    const camPendingExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A camPendingExpRow pointer or NULL on error
+ */
+
+camPendingExpRow *camPendingExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as camPendingExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *camPendingExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an camPendingExp
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool camPendingExpDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const camPendingExpRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long camPendingExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of camPendingExpRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool camPendingExpPrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of camPendingExpRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an camPendingExpRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool camPendingExpPrintObject(
+    FILE            *stream,            ///< a stream
+    camPendingExpRow *object,    ///< an camPendingExpRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** camProcessedExpRow data structure
+ *
+ * Structure for representing a single row of camProcessedExp table data.
+ */
+
+typedef struct {
+    psS64           cam_id;
+    psS64           chip_id;
+    char            *workdir;
+    char            *label;
+    char            *recipe;
+    char            *expgroup;
+    char            *dvodb;
+    char            *uri;
+    psF64           bg;
+    psF64           bg_stdev;
+    psF64           bg_mean_stdev;
+    psF32           sigma_ra;
+    psF32           sigma_dec;
+    psS32           nastro;
+    char            *path_base;
+    psF32           zp_mean;
+    psF32           zp_stdev;
+    psS16           fault;
+} camProcessedExpRow;
+
+/** Creates a new camProcessedExpRow object
+ *
+ *  @return A new camProcessedExpRow object or NULL on failure.
+ */
+
+camProcessedExpRow *camProcessedExpRowAlloc(
+    psS64           cam_id,
+    psS64           chip_id,
+    const char      *workdir,
+    const char      *label,
+    const char      *recipe,
+    const char      *expgroup,
+    const char      *dvodb,
+    const char      *uri,
+    psF64           bg,
+    psF64           bg_stdev,
+    psF64           bg_mean_stdev,
+    psF32           sigma_ra,
+    psF32           sigma_dec,
+    psS32           nastro,
+    const char      *path_base,
+    psF32           zp_mean,
+    psF32           zp_stdev,
+    psS16           fault
+);
+
+/** Creates a new camProcessedExp table
+ *
+ * @return true on success
+ */
+
+bool camProcessedExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a camProcessedExp table
+ *
+ * @return true on success
+ */
+
+bool camProcessedExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool camProcessedExpInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           cam_id,
+    psS64           chip_id,
+    const char      *workdir,
+    const char      *label,
+    const char      *recipe,
+    const char      *expgroup,
+    const char      *dvodb,
+    const char      *uri,
+    psF64           bg,
+    psF64           bg_stdev,
+    psF64           bg_mean_stdev,
+    psF32           sigma_ra,
+    psF32           sigma_dec,
+    psS32           nastro,
+    const char      *path_base,
+    psF32           zp_mean,
+    psF32           zp_stdev,
+    psS16           fault
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long camProcessedExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single camProcessedExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool camProcessedExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    camProcessedExpRow *object             ///< camProcessedExpRow object
+);
+
+/** Insert an array of camProcessedExpRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool camProcessedExpInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of camProcessedExpRow objects
+);
+
+/** Insert data from a binary FITS table camProcessedExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool camProcessedExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool camProcessedExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a camProcessedExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *camProcessedExpMetadataFromObject(
+    const camProcessedExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A camProcessedExpRow pointer or NULL on error
+ */
+
+camProcessedExpRow *camProcessedExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as camProcessedExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *camProcessedExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an camProcessedExp
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool camProcessedExpDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const camProcessedExpRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long camProcessedExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of camProcessedExpRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool camProcessedExpPrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of camProcessedExpRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an camProcessedExpRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool camProcessedExpPrintObject(
+    FILE            *stream,            ///< a stream
+    camProcessedExpRow *object,    ///< an camProcessedExpRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** camMaskRow data structure
+ *
+ * Structure for representing a single row of camMask table data.
+ */
+
+typedef struct {
+    char            *label;
+} camMaskRow;
+
+/** Creates a new camMaskRow object
+ *
+ *  @return A new camMaskRow object or NULL on failure.
+ */
+
+camMaskRow *camMaskRowAlloc(
+    const char      *label
+);
+
+/** Creates a new camMask table
+ *
+ * @return true on success
+ */
+
+bool camMaskCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a camMask table
+ *
+ * @return true on success
+ */
+
+bool camMaskDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool camMaskInsert(
+    psDB            *dbh,               ///< Database handle
+    const char      *label
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long camMaskDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single camMaskRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool camMaskInsertObject(
+    psDB            *dbh,               ///< Database handle
+    camMaskRow      *object             ///< camMaskRow object
+);
+
+/** Insert an array of camMaskRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool camMaskInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of camMaskRow objects
+);
+
+/** Insert data from a binary FITS table camMaskRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool camMaskInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool camMaskSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a camMaskRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *camMaskMetadataFromObject(
+    const camMaskRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A camMaskRow pointer or NULL on error
+ */
+
+camMaskRow *camMaskObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as camMaskRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *camMaskSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an camMask
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool camMaskDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const camMaskRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long camMaskDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of camMaskRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool camMaskPrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of camMaskRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an camMaskRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool camMaskPrintObject(
+    FILE            *stream,            ///< a stream
+    camMaskRow *object,    ///< an camMaskRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** warpRunRow data structure
+ *
+ * Structure for representing a single row of warpRun table data.
+ */
+
+typedef struct {
+    psS64           warp_id;
+    char            *mode;
+    char            *state;
+    char            *workdir;
+    char            *dvodb;
+    psTime*         registered;
+} warpRunRow;
+
+/** Creates a new warpRunRow object
+ *
+ *  @return A new warpRunRow object or NULL on failure.
+ */
+
+warpRunRow *warpRunRowAlloc(
+    psS64           warp_id,
+    const char      *mode,
+    const char      *state,
+    const char      *workdir,
+    const char      *dvodb,
+    psTime*         registered
+);
+
+/** Creates a new warpRun table
+ *
+ * @return true on success
+ */
+
+bool warpRunCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a warpRun table
+ *
+ * @return true on success
+ */
+
+bool warpRunDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool warpRunInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           warp_id,
+    const char      *mode,
+    const char      *state,
+    const char      *workdir,
+    const char      *dvodb,
+    psTime*         registered
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long warpRunDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single warpRunRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool warpRunInsertObject(
+    psDB            *dbh,               ///< Database handle
+    warpRunRow      *object             ///< warpRunRow object
+);
+
+/** Insert an array of warpRunRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool warpRunInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of warpRunRow objects
+);
+
+/** Insert data from a binary FITS table warpRunRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool warpRunInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool warpRunSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a warpRunRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *warpRunMetadataFromObject(
+    const warpRunRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A warpRunRow pointer or NULL on error
+ */
+
+warpRunRow *warpRunObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as warpRunRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *warpRunSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an warpRun
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool warpRunDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const warpRunRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long warpRunDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of warpRunRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool warpRunPrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of warpRunRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an warpRunRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool warpRunPrintObject(
+    FILE            *stream,            ///< a stream
+    warpRunRow *object,    ///< an warpRunRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** warpInputExpRow data structure
+ *
+ * Structure for representing a single row of warpInputExp table data.
+ */
+
+typedef struct {
+    psS64           warp_id;
+    psS64           cam_id;
+    bool            magiced;
+} warpInputExpRow;
+
+/** Creates a new warpInputExpRow object
+ *
+ *  @return A new warpInputExpRow object or NULL on failure.
+ */
+
+warpInputExpRow *warpInputExpRowAlloc(
+    psS64           warp_id,
+    psS64           cam_id,
+    bool            magiced
+);
+
+/** Creates a new warpInputExp table
+ *
+ * @return true on success
+ */
+
+bool warpInputExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a warpInputExp table
+ *
+ * @return true on success
+ */
+
+bool warpInputExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool warpInputExpInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           warp_id,
+    psS64           cam_id,
+    bool            magiced
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long warpInputExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single warpInputExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool warpInputExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    warpInputExpRow *object             ///< warpInputExpRow object
+);
+
+/** Insert an array of warpInputExpRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool warpInputExpInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of warpInputExpRow objects
+);
+
+/** Insert data from a binary FITS table warpInputExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool warpInputExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool warpInputExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a warpInputExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *warpInputExpMetadataFromObject(
+    const warpInputExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A warpInputExpRow pointer or NULL on error
+ */
+
+warpInputExpRow *warpInputExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as warpInputExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *warpInputExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an warpInputExp
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool warpInputExpDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const warpInputExpRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long warpInputExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of warpInputExpRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool warpInputExpPrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of warpInputExpRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an warpInputExpRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool warpInputExpPrintObject(
+    FILE            *stream,            ///< a stream
+    warpInputExpRow *object,    ///< an warpInputExpRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** warpSkyCellMapRow data structure
+ *
+ * Structure for representing a single row of warpSkyCellMap table data.
+ */
+
+typedef struct {
+    psS64           warp_id;
+    char            *skycell_id;
+    char            *tess_id;
+    psS64           cam_id;
+    char            *class_id;
+    psS16           fault;
+} warpSkyCellMapRow;
+
+/** Creates a new warpSkyCellMapRow object
+ *
+ *  @return A new warpSkyCellMapRow object or NULL on failure.
+ */
+
+warpSkyCellMapRow *warpSkyCellMapRowAlloc(
+    psS64           warp_id,
+    const char      *skycell_id,
+    const char      *tess_id,
+    psS64           cam_id,
+    const char      *class_id,
+    psS16           fault
+);
+
+/** Creates a new warpSkyCellMap table
+ *
+ * @return true on success
+ */
+
+bool warpSkyCellMapCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a warpSkyCellMap table
+ *
+ * @return true on success
+ */
+
+bool warpSkyCellMapDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool warpSkyCellMapInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           warp_id,
+    const char      *skycell_id,
+    const char      *tess_id,
+    psS64           cam_id,
+    const char      *class_id,
+    psS16           fault
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long warpSkyCellMapDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single warpSkyCellMapRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool warpSkyCellMapInsertObject(
+    psDB            *dbh,               ///< Database handle
+    warpSkyCellMapRow *object             ///< warpSkyCellMapRow object
+);
+
+/** Insert an array of warpSkyCellMapRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool warpSkyCellMapInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of warpSkyCellMapRow objects
+);
+
+/** Insert data from a binary FITS table warpSkyCellMapRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool warpSkyCellMapInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool warpSkyCellMapSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a warpSkyCellMapRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *warpSkyCellMapMetadataFromObject(
+    const warpSkyCellMapRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A warpSkyCellMapRow pointer or NULL on error
+ */
+
+warpSkyCellMapRow *warpSkyCellMapObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as warpSkyCellMapRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *warpSkyCellMapSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an warpSkyCellMap
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool warpSkyCellMapDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const warpSkyCellMapRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long warpSkyCellMapDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of warpSkyCellMapRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool warpSkyCellMapPrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of warpSkyCellMapRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an warpSkyCellMapRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool warpSkyCellMapPrintObject(
+    FILE            *stream,            ///< a stream
+    warpSkyCellMapRow *object,    ///< an warpSkyCellMapRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** warpSkyfileRow data structure
+ *
+ * Structure for representing a single row of warpSkyfile table data.
+ */
+
+typedef struct {
+    psS64           warp_id;
+    char            *skycell_id;
+    char            *tess_id;
+    char            *uri;
+    psF64           bg;
+    psF64           bg_stdev;
+} warpSkyfileRow;
+
+/** Creates a new warpSkyfileRow object
+ *
+ *  @return A new warpSkyfileRow object or NULL on failure.
+ */
+
+warpSkyfileRow *warpSkyfileRowAlloc(
+    psS64           warp_id,
+    const char      *skycell_id,
+    const char      *tess_id,
+    const char      *uri,
+    psF64           bg,
+    psF64           bg_stdev
+);
+
+/** Creates a new warpSkyfile table
+ *
+ * @return true on success
+ */
+
+bool warpSkyfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a warpSkyfile table
+ *
+ * @return true on success
+ */
+
+bool warpSkyfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool warpSkyfileInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           warp_id,
+    const char      *skycell_id,
+    const char      *tess_id,
+    const char      *uri,
+    psF64           bg,
+    psF64           bg_stdev
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long warpSkyfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single warpSkyfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool warpSkyfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    warpSkyfileRow  *object             ///< warpSkyfileRow object
+);
+
+/** Insert an array of warpSkyfileRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool warpSkyfileInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of warpSkyfileRow objects
+);
+
+/** Insert data from a binary FITS table warpSkyfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool warpSkyfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool warpSkyfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a warpSkyfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *warpSkyfileMetadataFromObject(
+    const warpSkyfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A warpSkyfileRow pointer or NULL on error
+ */
+
+warpSkyfileRow *warpSkyfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as warpSkyfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *warpSkyfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an warpSkyfile
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool warpSkyfileDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const warpSkyfileRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long warpSkyfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of warpSkyfileRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool warpSkyfilePrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of warpSkyfileRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an warpSkyfileRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool warpSkyfilePrintObject(
+    FILE            *stream,            ///< a stream
+    warpSkyfileRow *object,    ///< an warpSkyfileRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** diffRunRow data structure
+ *
+ * Structure for representing a single row of diffRun table data.
+ */
+
+typedef struct {
+    psS64           diff_id;
+    char            *state;
+    char            *workdir;
+    char            *dvodb;
+    psTime*         registered;
+    char            *skycell_id;
+    char            *tess_id;
+} diffRunRow;
+
+/** Creates a new diffRunRow object
+ *
+ *  @return A new diffRunRow object or NULL on failure.
+ */
+
+diffRunRow *diffRunRowAlloc(
+    psS64           diff_id,
+    const char      *state,
+    const char      *workdir,
+    const char      *dvodb,
+    psTime*         registered,
+    const char      *skycell_id,
+    const char      *tess_id
+);
+
+/** Creates a new diffRun table
+ *
+ * @return true on success
+ */
+
+bool diffRunCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a diffRun table
+ *
+ * @return true on success
+ */
+
+bool diffRunDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool diffRunInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           diff_id,
+    const char      *state,
+    const char      *workdir,
+    const char      *dvodb,
+    psTime*         registered,
+    const char      *skycell_id,
+    const char      *tess_id
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long diffRunDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single diffRunRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool diffRunInsertObject(
+    psDB            *dbh,               ///< Database handle
+    diffRunRow      *object             ///< diffRunRow object
+);
+
+/** Insert an array of diffRunRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool diffRunInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of diffRunRow objects
+);
+
+/** Insert data from a binary FITS table diffRunRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool diffRunInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool diffRunSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a diffRunRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *diffRunMetadataFromObject(
+    const diffRunRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A diffRunRow pointer or NULL on error
+ */
+
+diffRunRow *diffRunObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as diffRunRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *diffRunSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an diffRun
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool diffRunDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const diffRunRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long diffRunDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of diffRunRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool diffRunPrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of diffRunRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an diffRunRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool diffRunPrintObject(
+    FILE            *stream,            ///< a stream
+    diffRunRow *object,    ///< an diffRunRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** diffInputSkyfileRow data structure
+ *
+ * Structure for representing a single row of diffInputSkyfile table data.
+ */
+
+typedef struct {
+    psS64           diff_id;
+    psS64           warp_id;
+    char            *skycell_id;
+    char            *tess_id;
+    char            *kind;
+    bool            template;
+} diffInputSkyfileRow;
+
+/** Creates a new diffInputSkyfileRow object
+ *
+ *  @return A new diffInputSkyfileRow object or NULL on failure.
+ */
+
+diffInputSkyfileRow *diffInputSkyfileRowAlloc(
+    psS64           diff_id,
+    psS64           warp_id,
+    const char      *skycell_id,
+    const char      *tess_id,
+    const char      *kind,
+    bool            template
+);
+
+/** Creates a new diffInputSkyfile table
+ *
+ * @return true on success
+ */
+
+bool diffInputSkyfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a diffInputSkyfile table
+ *
+ * @return true on success
+ */
+
+bool diffInputSkyfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool diffInputSkyfileInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           diff_id,
+    psS64           warp_id,
+    const char      *skycell_id,
+    const char      *tess_id,
+    const char      *kind,
+    bool            template
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long diffInputSkyfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single diffInputSkyfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool diffInputSkyfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    diffInputSkyfileRow *object             ///< diffInputSkyfileRow object
+);
+
+/** Insert an array of diffInputSkyfileRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool diffInputSkyfileInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of diffInputSkyfileRow objects
+);
+
+/** Insert data from a binary FITS table diffInputSkyfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool diffInputSkyfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool diffInputSkyfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a diffInputSkyfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *diffInputSkyfileMetadataFromObject(
+    const diffInputSkyfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A diffInputSkyfileRow pointer or NULL on error
+ */
+
+diffInputSkyfileRow *diffInputSkyfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as diffInputSkyfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *diffInputSkyfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an diffInputSkyfile
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool diffInputSkyfileDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const diffInputSkyfileRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long diffInputSkyfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of diffInputSkyfileRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool diffInputSkyfilePrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of diffInputSkyfileRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an diffInputSkyfileRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool diffInputSkyfilePrintObject(
+    FILE            *stream,            ///< a stream
+    diffInputSkyfileRow *object,    ///< an diffInputSkyfileRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** diffSkyfileRow data structure
+ *
+ * Structure for representing a single row of diffSkyfile table data.
+ */
+
+typedef struct {
+    psS64           diff_id;
+    char            *uri;
+    psF64           bg;
+    psF64           bg_stdev;
+} diffSkyfileRow;
+
+/** Creates a new diffSkyfileRow object
+ *
+ *  @return A new diffSkyfileRow object or NULL on failure.
+ */
+
+diffSkyfileRow *diffSkyfileRowAlloc(
+    psS64           diff_id,
+    const char      *uri,
+    psF64           bg,
+    psF64           bg_stdev
+);
+
+/** Creates a new diffSkyfile table
+ *
+ * @return true on success
+ */
+
+bool diffSkyfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a diffSkyfile table
+ *
+ * @return true on success
+ */
+
+bool diffSkyfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool diffSkyfileInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           diff_id,
+    const char      *uri,
+    psF64           bg,
+    psF64           bg_stdev
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long diffSkyfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single diffSkyfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool diffSkyfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    diffSkyfileRow  *object             ///< diffSkyfileRow object
+);
+
+/** Insert an array of diffSkyfileRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool diffSkyfileInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of diffSkyfileRow objects
+);
+
+/** Insert data from a binary FITS table diffSkyfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool diffSkyfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool diffSkyfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a diffSkyfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *diffSkyfileMetadataFromObject(
+    const diffSkyfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A diffSkyfileRow pointer or NULL on error
+ */
+
+diffSkyfileRow *diffSkyfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as diffSkyfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *diffSkyfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an diffSkyfile
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool diffSkyfileDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const diffSkyfileRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long diffSkyfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of diffSkyfileRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool diffSkyfilePrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of diffSkyfileRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an diffSkyfileRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool diffSkyfilePrintObject(
+    FILE            *stream,            ///< a stream
+    diffSkyfileRow *object,    ///< an diffSkyfileRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** stackRunRow data structure
+ *
+ * Structure for representing a single row of stackRun table data.
+ */
+
+typedef struct {
+    psS64           stack_id;
+    char            *state;
+    char            *workdir;
+    char            *dvodb;
+    psTime*         registered;
+    char            *skycell_id;
+    char            *tess_id;
+} stackRunRow;
+
+/** Creates a new stackRunRow object
+ *
+ *  @return A new stackRunRow object or NULL on failure.
+ */
+
+stackRunRow *stackRunRowAlloc(
+    psS64           stack_id,
+    const char      *state,
+    const char      *workdir,
+    const char      *dvodb,
+    psTime*         registered,
+    const char      *skycell_id,
+    const char      *tess_id
+);
+
+/** Creates a new stackRun table
+ *
+ * @return true on success
+ */
+
+bool stackRunCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a stackRun table
+ *
+ * @return true on success
+ */
+
+bool stackRunDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool stackRunInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           stack_id,
+    const char      *state,
+    const char      *workdir,
+    const char      *dvodb,
+    psTime*         registered,
+    const char      *skycell_id,
+    const char      *tess_id
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long stackRunDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single stackRunRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool stackRunInsertObject(
+    psDB            *dbh,               ///< Database handle
+    stackRunRow     *object             ///< stackRunRow object
+);
+
+/** Insert an array of stackRunRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool stackRunInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of stackRunRow objects
+);
+
+/** Insert data from a binary FITS table stackRunRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool stackRunInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool stackRunSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a stackRunRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *stackRunMetadataFromObject(
+    const stackRunRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A stackRunRow pointer or NULL on error
+ */
+
+stackRunRow *stackRunObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as stackRunRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *stackRunSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an stackRun
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool stackRunDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const stackRunRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long stackRunDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of stackRunRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool stackRunPrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of stackRunRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an stackRunRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool stackRunPrintObject(
+    FILE            *stream,            ///< a stream
+    stackRunRow *object,    ///< an stackRunRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** stackInputSkyfileRow data structure
+ *
+ * Structure for representing a single row of stackInputSkyfile table data.
+ */
+
+typedef struct {
+    psS64           stack_id;
+    psS64           warp_id;
+} stackInputSkyfileRow;
+
+/** Creates a new stackInputSkyfileRow object
+ *
+ *  @return A new stackInputSkyfileRow object or NULL on failure.
+ */
+
+stackInputSkyfileRow *stackInputSkyfileRowAlloc(
+    psS64           stack_id,
+    psS64           warp_id
+);
+
+/** Creates a new stackInputSkyfile table
+ *
+ * @return true on success
+ */
+
+bool stackInputSkyfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a stackInputSkyfile table
+ *
+ * @return true on success
+ */
+
+bool stackInputSkyfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool stackInputSkyfileInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           stack_id,
+    psS64           warp_id
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long stackInputSkyfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single stackInputSkyfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool stackInputSkyfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    stackInputSkyfileRow *object             ///< stackInputSkyfileRow object
+);
+
+/** Insert an array of stackInputSkyfileRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool stackInputSkyfileInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of stackInputSkyfileRow objects
+);
+
+/** Insert data from a binary FITS table stackInputSkyfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool stackInputSkyfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool stackInputSkyfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a stackInputSkyfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *stackInputSkyfileMetadataFromObject(
+    const stackInputSkyfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A stackInputSkyfileRow pointer or NULL on error
+ */
+
+stackInputSkyfileRow *stackInputSkyfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as stackInputSkyfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *stackInputSkyfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an stackInputSkyfile
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool stackInputSkyfileDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const stackInputSkyfileRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long stackInputSkyfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of stackInputSkyfileRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool stackInputSkyfilePrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of stackInputSkyfileRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an stackInputSkyfileRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool stackInputSkyfilePrintObject(
+    FILE            *stream,            ///< a stream
+    stackInputSkyfileRow *object,    ///< an stackInputSkyfileRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** stackSumSkyfileRow data structure
+ *
+ * Structure for representing a single row of stackSumSkyfile table data.
+ */
+
+typedef struct {
+    psS64           stack_id;
+    char            *uri;
+    psF64           bg;
+    psF64           bg_stdev;
+} stackSumSkyfileRow;
+
+/** Creates a new stackSumSkyfileRow object
+ *
+ *  @return A new stackSumSkyfileRow object or NULL on failure.
+ */
+
+stackSumSkyfileRow *stackSumSkyfileRowAlloc(
+    psS64           stack_id,
+    const char      *uri,
+    psF64           bg,
+    psF64           bg_stdev
+);
+
+/** Creates a new stackSumSkyfile table
+ *
+ * @return true on success
+ */
+
+bool stackSumSkyfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a stackSumSkyfile table
+ *
+ * @return true on success
+ */
+
+bool stackSumSkyfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool stackSumSkyfileInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           stack_id,
+    const char      *uri,
+    psF64           bg,
+    psF64           bg_stdev
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long stackSumSkyfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single stackSumSkyfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool stackSumSkyfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    stackSumSkyfileRow *object             ///< stackSumSkyfileRow object
+);
+
+/** Insert an array of stackSumSkyfileRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool stackSumSkyfileInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of stackSumSkyfileRow objects
+);
+
+/** Insert data from a binary FITS table stackSumSkyfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool stackSumSkyfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool stackSumSkyfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a stackSumSkyfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *stackSumSkyfileMetadataFromObject(
+    const stackSumSkyfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A stackSumSkyfileRow pointer or NULL on error
+ */
+
+stackSumSkyfileRow *stackSumSkyfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as stackSumSkyfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *stackSumSkyfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an stackSumSkyfile
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool stackSumSkyfileDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const stackSumSkyfileRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long stackSumSkyfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of stackSumSkyfileRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool stackSumSkyfilePrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of stackSumSkyfileRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an stackSumSkyfileRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool stackSumSkyfilePrintObject(
+    FILE            *stream,            ///< a stream
+    stackSumSkyfileRow *object,    ///< an stackSumSkyfileRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** detRunRow data structure
+ *
+ * Structure for representing a single row of detRun table data.
+ */
+
+typedef struct {
+    psS64           det_id;
+    psS32           iteration;
+    char            *det_type;
+    char            *mode;
+    char            *state;
+    char            *filelevel;
+    char            *workdir;
+    char            *camera;
+    char            *telescope;
+    char            *exp_type;
+    char            *filter;
+    psF32           airmass_min;
+    psF32           airmass_max;
+    psF32           exp_time_min;
+    psF32           exp_time_max;
+    psF32           ccd_temp_min;
+    psF32           ccd_temp_max;
+    psF64           posang_min;
+    psF64           posang_max;
+    psTime*         registered;
+    psTime*         time_begin;
+    psTime*         time_end;
+    psTime*         use_begin;
+    psTime*         use_end;
+    psF32           solang_min;
+    psF32           solang_max;
+    char            *label;
+    psS32           parent;
+} detRunRow;
+
+/** Creates a new detRunRow object
+ *
+ *  @return A new detRunRow object or NULL on failure.
+ */
+
+detRunRow *detRunRowAlloc(
+    psS64           det_id,
+    psS32           iteration,
+    const char      *det_type,
+    const char      *mode,
+    const char      *state,
+    const char      *filelevel,
+    const char      *workdir,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    const char      *filter,
+    psF32           airmass_min,
+    psF32           airmass_max,
+    psF32           exp_time_min,
+    psF32           exp_time_max,
+    psF32           ccd_temp_min,
+    psF32           ccd_temp_max,
+    psF64           posang_min,
+    psF64           posang_max,
+    psTime*         registered,
+    psTime*         time_begin,
+    psTime*         time_end,
+    psTime*         use_begin,
+    psTime*         use_end,
+    psF32           solang_min,
+    psF32           solang_max,
+    const char      *label,
+    psS32           parent
+);
+
+/** Creates a new detRun table
+ *
+ * @return true on success
+ */
+
+bool detRunCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detRun table
+ *
+ * @return true on success
+ */
+
+bool detRunDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detRunInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           det_id,
+    psS32           iteration,
+    const char      *det_type,
+    const char      *mode,
+    const char      *state,
+    const char      *filelevel,
+    const char      *workdir,
+    const char      *camera,
+    const char      *telescope,
+    const char      *exp_type,
+    const char      *filter,
+    psF32           airmass_min,
+    psF32           airmass_max,
+    psF32           exp_time_min,
+    psF32           exp_time_max,
+    psF32           ccd_temp_min,
+    psF32           ccd_temp_max,
+    psF64           posang_min,
+    psF64           posang_max,
+    psTime*         registered,
+    psTime*         time_begin,
+    psTime*         time_end,
+    psTime*         use_begin,
+    psTime*         use_end,
+    psF32           solang_min,
+    psF32           solang_max,
+    const char      *label,
+    psS32           parent
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detRunDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single detRunRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detRunInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detRunRow       *object             ///< detRunRow object
+);
+
+/** Insert an array of detRunRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detRunInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of detRunRow objects
+);
+
+/** Insert data from a binary FITS table detRunRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detRunInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detRunSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detRunRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detRunMetadataFromObject(
+    const detRunRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detRunRow pointer or NULL on error
+ */
+
+detRunRow *detRunObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detRunRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detRunSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an detRun
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool detRunDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const detRunRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detRunDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of detRunRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool detRunPrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of detRunRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an detRunRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool detRunPrintObject(
+    FILE            *stream,            ///< a stream
+    detRunRow *object,    ///< an detRunRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** detInputExpRow data structure
+ *
+ * Structure for representing a single row of detInputExp table data.
+ */
+
+typedef struct {
+    psS64           det_id;
+    psS32           iteration;
+    char            *exp_tag;
+    bool            include;
+} detInputExpRow;
+
+/** Creates a new detInputExpRow object
+ *
+ *  @return A new detInputExpRow object or NULL on failure.
+ */
+
+detInputExpRow *detInputExpRowAlloc(
+    psS64           det_id,
+    psS32           iteration,
+    const char      *exp_tag,
+    bool            include
+);
+
+/** Creates a new detInputExp table
+ *
+ * @return true on success
+ */
+
+bool detInputExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detInputExp table
+ *
+ * @return true on success
+ */
+
+bool detInputExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detInputExpInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           det_id,
+    psS32           iteration,
+    const char      *exp_tag,
+    bool            include
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detInputExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single detInputExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detInputExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detInputExpRow  *object             ///< detInputExpRow object
+);
+
+/** Insert an array of detInputExpRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detInputExpInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of detInputExpRow objects
+);
+
+/** Insert data from a binary FITS table detInputExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detInputExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detInputExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detInputExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detInputExpMetadataFromObject(
+    const detInputExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detInputExpRow pointer or NULL on error
+ */
+
+detInputExpRow *detInputExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detInputExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detInputExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an detInputExp
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool detInputExpDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const detInputExpRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detInputExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of detInputExpRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool detInputExpPrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of detInputExpRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an detInputExpRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool detInputExpPrintObject(
+    FILE            *stream,            ///< a stream
+    detInputExpRow *object,    ///< an detInputExpRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** detProcessedImfileRow data structure
+ *
+ * Structure for representing a single row of detProcessedImfile table data.
+ */
+
+typedef struct {
+    psS64           det_id;
+    char            *exp_tag;
+    char            *class_id;
+    char            *uri;
+    char            *recipe;
+    psF64           bg;
+    psF64           bg_stdev;
+    psF64           bg_mean_stdev;
+    char            *path_base;
+    psS16           fault;
+} detProcessedImfileRow;
+
+/** Creates a new detProcessedImfileRow object
+ *
+ *  @return A new detProcessedImfileRow object or NULL on failure.
+ */
+
+detProcessedImfileRow *detProcessedImfileRowAlloc(
+    psS64           det_id,
+    const char      *exp_tag,
+    const char      *class_id,
+    const char      *uri,
+    const char      *recipe,
+    psF64           bg,
+    psF64           bg_stdev,
+    psF64           bg_mean_stdev,
+    const char      *path_base,
+    psS16           fault
+);
+
+/** Creates a new detProcessedImfile table
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detProcessedImfile table
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           det_id,
+    const char      *exp_tag,
+    const char      *class_id,
+    const char      *uri,
+    const char      *recipe,
+    psF64           bg,
+    psF64           bg_stdev,
+    psF64           bg_mean_stdev,
+    const char      *path_base,
+    psS16           fault
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detProcessedImfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single detProcessedImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detProcessedImfileRow *object             ///< detProcessedImfileRow object
+);
+
+/** Insert an array of detProcessedImfileRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfileInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of detProcessedImfileRow objects
+);
+
+/** Insert data from a binary FITS table detProcessedImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detProcessedImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detProcessedImfileMetadataFromObject(
+    const detProcessedImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detProcessedImfileRow pointer or NULL on error
+ */
+
+detProcessedImfileRow *detProcessedImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detProcessedImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detProcessedImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an detProcessedImfile
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool detProcessedImfileDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const detProcessedImfileRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detProcessedImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of detProcessedImfileRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfilePrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of detProcessedImfileRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an detProcessedImfileRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool detProcessedImfilePrintObject(
+    FILE            *stream,            ///< a stream
+    detProcessedImfileRow *object,    ///< an detProcessedImfileRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** detProcessedExpRow data structure
+ *
+ * Structure for representing a single row of detProcessedExp table data.
+ */
+
+typedef struct {
+    psS64           det_id;
+    char            *exp_tag;
+    char            *recipe;
+    psF64           bg;
+    psF64           bg_stdev;
+    psF64           bg_mean_stdev;
+    char            *path_base;
+    psS16           fault;
+} detProcessedExpRow;
+
+/** Creates a new detProcessedExpRow object
+ *
+ *  @return A new detProcessedExpRow object or NULL on failure.
+ */
+
+detProcessedExpRow *detProcessedExpRowAlloc(
+    psS64           det_id,
+    const char      *exp_tag,
+    const char      *recipe,
+    psF64           bg,
+    psF64           bg_stdev,
+    psF64           bg_mean_stdev,
+    const char      *path_base,
+    psS16           fault
+);
+
+/** Creates a new detProcessedExp table
+ *
+ * @return true on success
+ */
+
+bool detProcessedExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detProcessedExp table
+ *
+ * @return true on success
+ */
+
+bool detProcessedExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detProcessedExpInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           det_id,
+    const char      *exp_tag,
+    const char      *recipe,
+    psF64           bg,
+    psF64           bg_stdev,
+    psF64           bg_mean_stdev,
+    const char      *path_base,
+    psS16           fault
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detProcessedExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single detProcessedExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detProcessedExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detProcessedExpRow *object             ///< detProcessedExpRow object
+);
+
+/** Insert an array of detProcessedExpRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detProcessedExpInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of detProcessedExpRow objects
+);
+
+/** Insert data from a binary FITS table detProcessedExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detProcessedExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detProcessedExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detProcessedExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detProcessedExpMetadataFromObject(
+    const detProcessedExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detProcessedExpRow pointer or NULL on error
+ */
+
+detProcessedExpRow *detProcessedExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detProcessedExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detProcessedExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an detProcessedExp
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool detProcessedExpDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const detProcessedExpRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detProcessedExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of detProcessedExpRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool detProcessedExpPrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of detProcessedExpRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an detProcessedExpRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool detProcessedExpPrintObject(
+    FILE            *stream,            ///< a stream
+    detProcessedExpRow *object,    ///< an detProcessedExpRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** detStackedImfileRow data structure
+ *
+ * Structure for representing a single row of detStackedImfile table data.
+ */
+
+typedef struct {
+    psS64           det_id;
+    psS32           iteration;
+    char            *class_id;
+    char            *uri;
+    char            *recipe;
+    psF64           bg;
+    psF64           bg_stdev;
+    psF64           bg_mean_stdev;
+    psS16           fault;
+} detStackedImfileRow;
+
+/** Creates a new detStackedImfileRow object
+ *
+ *  @return A new detStackedImfileRow object or NULL on failure.
+ */
+
+detStackedImfileRow *detStackedImfileRowAlloc(
+    psS64           det_id,
+    psS32           iteration,
+    const char      *class_id,
+    const char      *uri,
+    const char      *recipe,
+    psF64           bg,
+    psF64           bg_stdev,
+    psF64           bg_mean_stdev,
+    psS16           fault
+);
+
+/** Creates a new detStackedImfile table
+ *
+ * @return true on success
+ */
+
+bool detStackedImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detStackedImfile table
+ *
+ * @return true on success
+ */
+
+bool detStackedImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detStackedImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           det_id,
+    psS32           iteration,
+    const char      *class_id,
+    const char      *uri,
+    const char      *recipe,
+    psF64           bg,
+    psF64           bg_stdev,
+    psF64           bg_mean_stdev,
+    psS16           fault
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detStackedImfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single detStackedImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detStackedImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detStackedImfileRow *object             ///< detStackedImfileRow object
+);
+
+/** Insert an array of detStackedImfileRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detStackedImfileInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of detStackedImfileRow objects
+);
+
+/** Insert data from a binary FITS table detStackedImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detStackedImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detStackedImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detStackedImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detStackedImfileMetadataFromObject(
+    const detStackedImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detStackedImfileRow pointer or NULL on error
+ */
+
+detStackedImfileRow *detStackedImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detStackedImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detStackedImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an detStackedImfile
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool detStackedImfileDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const detStackedImfileRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detStackedImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of detStackedImfileRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool detStackedImfilePrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of detStackedImfileRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an detStackedImfileRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool detStackedImfilePrintObject(
+    FILE            *stream,            ///< a stream
+    detStackedImfileRow *object,    ///< an detStackedImfileRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** detNormalizedStatImfileRow data structure
+ *
+ * Structure for representing a single row of detNormalizedStatImfile table data.
+ */
+
+typedef struct {
+    psS64           det_id;
+    psS32           iteration;
+    char            *class_id;
+    psF32           norm;
+    psS16           fault;
+} detNormalizedStatImfileRow;
+
+/** Creates a new detNormalizedStatImfileRow object
+ *
+ *  @return A new detNormalizedStatImfileRow object or NULL on failure.
+ */
+
+detNormalizedStatImfileRow *detNormalizedStatImfileRowAlloc(
+    psS64           det_id,
+    psS32           iteration,
+    const char      *class_id,
+    psF32           norm,
+    psS16           fault
+);
+
+/** Creates a new detNormalizedStatImfile table
+ *
+ * @return true on success
+ */
+
+bool detNormalizedStatImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detNormalizedStatImfile table
+ *
+ * @return true on success
+ */
+
+bool detNormalizedStatImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedStatImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           det_id,
+    psS32           iteration,
+    const char      *class_id,
+    psF32           norm,
+    psS16           fault
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detNormalizedStatImfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single detNormalizedStatImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedStatImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detNormalizedStatImfileRow *object             ///< detNormalizedStatImfileRow object
+);
+
+/** Insert an array of detNormalizedStatImfileRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedStatImfileInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of detNormalizedStatImfileRow objects
+);
+
+/** Insert data from a binary FITS table detNormalizedStatImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedStatImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedStatImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detNormalizedStatImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detNormalizedStatImfileMetadataFromObject(
+    const detNormalizedStatImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detNormalizedStatImfileRow pointer or NULL on error
+ */
+
+detNormalizedStatImfileRow *detNormalizedStatImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detNormalizedStatImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detNormalizedStatImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an detNormalizedStatImfile
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool detNormalizedStatImfileDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const detNormalizedStatImfileRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detNormalizedStatImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of detNormalizedStatImfileRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedStatImfilePrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of detNormalizedStatImfileRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an detNormalizedStatImfileRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedStatImfilePrintObject(
+    FILE            *stream,            ///< a stream
+    detNormalizedStatImfileRow *object,    ///< an detNormalizedStatImfileRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** detNormalizedImfileRow data structure
+ *
+ * Structure for representing a single row of detNormalizedImfile table data.
+ */
+
+typedef struct {
+    psS64           det_id;
+    psS32           iteration;
+    char            *class_id;
+    char            *uri;
+    psF64           bg;
+    psF64           bg_stdev;
+    psF64           bg_mean_stdev;
+    char            *path_base;
+    psS16           fault;
+} detNormalizedImfileRow;
+
+/** Creates a new detNormalizedImfileRow object
+ *
+ *  @return A new detNormalizedImfileRow object or NULL on failure.
+ */
+
+detNormalizedImfileRow *detNormalizedImfileRowAlloc(
+    psS64           det_id,
+    psS32           iteration,
+    const char      *class_id,
+    const char      *uri,
+    psF64           bg,
+    psF64           bg_stdev,
+    psF64           bg_mean_stdev,
+    const char      *path_base,
+    psS16           fault
+);
+
+/** Creates a new detNormalizedImfile table
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detNormalizedImfile table
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           det_id,
+    psS32           iteration,
+    const char      *class_id,
+    const char      *uri,
+    psF64           bg,
+    psF64           bg_stdev,
+    psF64           bg_mean_stdev,
+    const char      *path_base,
+    psS16           fault
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detNormalizedImfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single detNormalizedImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detNormalizedImfileRow *object             ///< detNormalizedImfileRow object
+);
+
+/** Insert an array of detNormalizedImfileRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfileInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of detNormalizedImfileRow objects
+);
+
+/** Insert data from a binary FITS table detNormalizedImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detNormalizedImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detNormalizedImfileMetadataFromObject(
+    const detNormalizedImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detNormalizedImfileRow pointer or NULL on error
+ */
+
+detNormalizedImfileRow *detNormalizedImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detNormalizedImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detNormalizedImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an detNormalizedImfile
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool detNormalizedImfileDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const detNormalizedImfileRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detNormalizedImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of detNormalizedImfileRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfilePrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of detNormalizedImfileRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an detNormalizedImfileRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedImfilePrintObject(
+    FILE            *stream,            ///< a stream
+    detNormalizedImfileRow *object,    ///< an detNormalizedImfileRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** detNormalizedExpRow data structure
+ *
+ * Structure for representing a single row of detNormalizedExp table data.
+ */
+
+typedef struct {
+    psS64           det_id;
+    psS32           iteration;
+    char            *recipe;
+    psF64           bg;
+    psF64           bg_stdev;
+    psF64           bg_mean_stdev;
+    char            *path_base;
+    psS16           fault;
+} detNormalizedExpRow;
+
+/** Creates a new detNormalizedExpRow object
+ *
+ *  @return A new detNormalizedExpRow object or NULL on failure.
+ */
+
+detNormalizedExpRow *detNormalizedExpRowAlloc(
+    psS64           det_id,
+    psS32           iteration,
+    const char      *recipe,
+    psF64           bg,
+    psF64           bg_stdev,
+    psF64           bg_mean_stdev,
+    const char      *path_base,
+    psS16           fault
+);
+
+/** Creates a new detNormalizedExp table
+ *
+ * @return true on success
+ */
+
+bool detNormalizedExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detNormalizedExp table
+ *
+ * @return true on success
+ */
+
+bool detNormalizedExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedExpInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           det_id,
+    psS32           iteration,
+    const char      *recipe,
+    psF64           bg,
+    psF64           bg_stdev,
+    psF64           bg_mean_stdev,
+    const char      *path_base,
+    psS16           fault
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detNormalizedExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single detNormalizedExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detNormalizedExpRow *object             ///< detNormalizedExpRow object
+);
+
+/** Insert an array of detNormalizedExpRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedExpInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of detNormalizedExpRow objects
+);
+
+/** Insert data from a binary FITS table detNormalizedExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detNormalizedExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detNormalizedExpMetadataFromObject(
+    const detNormalizedExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detNormalizedExpRow pointer or NULL on error
+ */
+
+detNormalizedExpRow *detNormalizedExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detNormalizedExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detNormalizedExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an detNormalizedExp
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool detNormalizedExpDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const detNormalizedExpRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detNormalizedExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of detNormalizedExpRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedExpPrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of detNormalizedExpRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an detNormalizedExpRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool detNormalizedExpPrintObject(
+    FILE            *stream,            ///< a stream
+    detNormalizedExpRow *object,    ///< an detNormalizedExpRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** detResidImfileRow data structure
+ *
+ * Structure for representing a single row of detResidImfile table data.
+ */
+
+typedef struct {
+    psS64           det_id;
+    psS32           iteration;
+    char            *exp_tag;
+    char            *class_id;
+    char            *uri;
+    char            *recipe;
+    psF64           bg;
+    psF64           bg_stdev;
+    psF64           bg_mean_stdev;
+    char            *path_base;
+    psS16           fault;
+} detResidImfileRow;
+
+/** Creates a new detResidImfileRow object
+ *
+ *  @return A new detResidImfileRow object or NULL on failure.
+ */
+
+detResidImfileRow *detResidImfileRowAlloc(
+    psS64           det_id,
+    psS32           iteration,
+    const char      *exp_tag,
+    const char      *class_id,
+    const char      *uri,
+    const char      *recipe,
+    psF64           bg,
+    psF64           bg_stdev,
+    psF64           bg_mean_stdev,
+    const char      *path_base,
+    psS16           fault
+);
+
+/** Creates a new detResidImfile table
+ *
+ * @return true on success
+ */
+
+bool detResidImfileCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detResidImfile table
+ *
+ * @return true on success
+ */
+
+bool detResidImfileDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detResidImfileInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           det_id,
+    psS32           iteration,
+    const char      *exp_tag,
+    const char      *class_id,
+    const char      *uri,
+    const char      *recipe,
+    psF64           bg,
+    psF64           bg_stdev,
+    psF64           bg_mean_stdev,
+    const char      *path_base,
+    psS16           fault
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detResidImfileDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single detResidImfileRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detResidImfileInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detResidImfileRow *object             ///< detResidImfileRow object
+);
+
+/** Insert an array of detResidImfileRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detResidImfileInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of detResidImfileRow objects
+);
+
+/** Insert data from a binary FITS table detResidImfileRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detResidImfileInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detResidImfileSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detResidImfileRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detResidImfileMetadataFromObject(
+    const detResidImfileRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detResidImfileRow pointer or NULL on error
+ */
+
+detResidImfileRow *detResidImfileObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detResidImfileRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detResidImfileSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an detResidImfile
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool detResidImfileDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const detResidImfileRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detResidImfileDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of detResidImfileRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool detResidImfilePrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of detResidImfileRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an detResidImfileRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool detResidImfilePrintObject(
+    FILE            *stream,            ///< a stream
+    detResidImfileRow *object,    ///< an detResidImfileRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** detResidExpRow data structure
+ *
+ * Structure for representing a single row of detResidExp table data.
+ */
+
+typedef struct {
+    psS64           det_id;
+    psS32           iteration;
+    char            *exp_tag;
+    char            *recipe;
+    psF64           bg;
+    psF64           bg_stdev;
+    psF64           bg_mean_stdev;
+    char            *path_base;
+    bool            accept;
+    psS16           fault;
+} detResidExpRow;
+
+/** Creates a new detResidExpRow object
+ *
+ *  @return A new detResidExpRow object or NULL on failure.
+ */
+
+detResidExpRow *detResidExpRowAlloc(
+    psS64           det_id,
+    psS32           iteration,
+    const char      *exp_tag,
+    const char      *recipe,
+    psF64           bg,
+    psF64           bg_stdev,
+    psF64           bg_mean_stdev,
+    const char      *path_base,
+    bool            accept,
+    psS16           fault
+);
+
+/** Creates a new detResidExp table
+ *
+ * @return true on success
+ */
+
+bool detResidExpCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detResidExp table
+ *
+ * @return true on success
+ */
+
+bool detResidExpDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detResidExpInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           det_id,
+    psS32           iteration,
+    const char      *exp_tag,
+    const char      *recipe,
+    psF64           bg,
+    psF64           bg_stdev,
+    psF64           bg_mean_stdev,
+    const char      *path_base,
+    bool            accept,
+    psS16           fault
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detResidExpDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single detResidExpRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detResidExpInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detResidExpRow  *object             ///< detResidExpRow object
+);
+
+/** Insert an array of detResidExpRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detResidExpInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of detResidExpRow objects
+);
+
+/** Insert data from a binary FITS table detResidExpRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detResidExpInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detResidExpSelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detResidExpRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detResidExpMetadataFromObject(
+    const detResidExpRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detResidExpRow pointer or NULL on error
+ */
+
+detResidExpRow *detResidExpObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detResidExpRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detResidExpSelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an detResidExp
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool detResidExpDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const detResidExpRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detResidExpDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of detResidExpRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool detResidExpPrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of detResidExpRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an detResidExpRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool detResidExpPrintObject(
+    FILE            *stream,            ///< a stream
+    detResidExpRow *object,    ///< an detResidExpRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** detRunSummaryRow data structure
+ *
+ * Structure for representing a single row of detRunSummary table data.
+ */
+
+typedef struct {
+    psS64           det_id;
+    psS32           iteration;
+    psF64           bg;
+    psF64           bg_stdev;
+    psF64           bg_mean_stdev;
+    bool            accept;
+    psS16           fault;
+} detRunSummaryRow;
+
+/** Creates a new detRunSummaryRow object
+ *
+ *  @return A new detRunSummaryRow object or NULL on failure.
+ */
+
+detRunSummaryRow *detRunSummaryRowAlloc(
+    psS64           det_id,
+    psS32           iteration,
+    psF64           bg,
+    psF64           bg_stdev,
+    psF64           bg_mean_stdev,
+    bool            accept,
+    psS16           fault
+);
+
+/** Creates a new detRunSummary table
+ *
+ * @return true on success
+ */
+
+bool detRunSummaryCreateTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Deletes a detRunSummary table
+ *
+ * @return true on success
+ */
+
+bool detRunSummaryDropTable(
+    psDB            *dbh                ///< Database handle
+);
+
+/** Insert a single row into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detRunSummaryInsert(
+    psDB            *dbh,               ///< Database handle
+    psS64           det_id,
+    psS32           iteration,
+    psF64           bg,
+    psF64           bg_stdev,
+    psF64           bg_mean_stdev,
+    bool            accept,
+    psS16           fault
+);
+
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detRunSummaryDelete(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+
+/** Insert a single detRunSummaryRow object into a table
+ *
+ * This function constructs and inserts a single row based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detRunSummaryInsertObject(
+    psDB            *dbh,               ///< Database handle
+    detRunSummaryRow *object             ///< detRunSummaryRow object
+);
+
+/** Insert an array of detRunSummaryRow object into a table
+ *
+ * This function constructs and inserts multiple rows based on it's parameters.
+ *
+ * @return true on success
+ */
+
+bool detRunSummaryInsertObjects(
+    psDB            *dbh,               ///< Database handle
+    psArray         *objects            ///< array of detRunSummaryRow objects
+);
+
+/** Insert data from a binary FITS table detRunSummaryRow into the database
+ *
+ * This function expects a psFits object with a FITS table as the first
+ * extension.  The table must have at least one row of data in it, that is of
+ * the appropriate format (number of columns and their type).  All other
+ * extensions are ignored.
+ *
+ * @return true on success
+ */
+
+bool detRunSummaryInsertFits(
+    psDB            *dbh,               ///< Database handle
+    const psFits    *fits               ///< psFits object
+);
+
+/** Selects up to limit from the database and returns them in a binary FITS table
+ *
+ * This function assumes an empty psFits object and will create a FITS table
+ * as the first extension.
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return true on success
+ */
+
+bool detRunSummarySelectRowsFits(
+    psDB            *dbh,               ///< Database handle
+    psFits          *fits,              ///< psFits object
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+
+/** Convert a detRunSummaryRow into an equivalent psMetadata
+ *
+ * @return A psMetadata pointer or NULL on error
+ */
+
+psMetadata *detRunSummaryMetadataFromObject(
+    const detRunSummaryRow *object             ///< fooRow to convert into a psMetadata
+);
+
+/** Convert a psMetadata into an equivalent fooRow
+ *
+ * @return A detRunSummaryRow pointer or NULL on error
+ */
+
+detRunSummaryRow *detRunSummaryObjectFromMetadata(
+    psMetadata      *md                 ///< psMetadata to convert into a fooRow
+);
+/** Selects up to limit rows from the database and returns as detRunSummaryRow objects in a psArray
+ *
+ *  See psDBSelectRows() for documentation on the format of where.
+ *
+ * @return A psArray pointer or NULL on error
+ */
+
+psArray *detRunSummarySelectRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psMetadata *where,            ///< Row match criteria
+    unsigned long long limit            ///< Maximum number of elements to return
+);
+/** Deletes a row from the database coresponding to an detRunSummary
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+bool detRunSummaryDeleteObject(
+    psDB            *dbh,               ///< Database handle
+    const detRunSummaryRow *object    ///< Object to delete
+);
+/** Deletes up to limit rows from the database and returns the number of rows actually deleted. 
+ *
+ *  Note that a 'where' search psMetadata is constructed from each object and
+ *  used to find rows to delete.
+ *
+ * @return A The number of rows removed or a negative value on error
+ */
+
+long long detRunSummaryDeleteRowObjects(
+    psDB            *dbh,               ///< Database handle
+    const psArray   *objects,           ///< Array of objects to delete
+    unsigned long long limit            ///< Maximum number of elements to delete 
+);
+/** Formats and prints an array of detRunSummaryRow objects
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool detRunSummaryPrintObjects(
+    FILE            *stream,            ///< a stream
+    psArray         *objects,           ///< An array of detRunSummaryRow objects
+    bool            mdcf                ///< format as mdconfig or simple
+);
+/** Formats and prints an detRunSummaryRow object
+ *
+ * When mdcf is set the formated output is in psMetadataConfig
+ * format, otherwise it is in a simple tabular format.
+ *
+ * @return true on success
+ */
+
+bool detRunSummaryPrintObject(
+    FILE            *stream,            ///< a stream
+    detRunSummaryRow *object,    ///< an detRunSummaryRow object
+    bool            mdcf                ///< format as mdconfig or simple
+);
+
+/// @}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // DETRUNSUMMARY_DB_H
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/tests/.cvsignore
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/tests/.cvsignore	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/tests/.cvsignore	(revision 22073)
@@ -0,0 +1,25 @@
+.deps
+Makefile
+Makefile.in
+atconfig
+.libs
+alloc
+cleanup
+createtable
+dbcleanup
+dbsetup
+droptable
+init
+insert
+insertfits
+insertobject
+metadatafromobject
+objectfrommetadata
+package.m4
+pop
+popfits
+popobject
+selectrowsfits
+testsuite
+testsuite.dir
+testsuite.log
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/tests/Makefile.am
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/tests/Makefile.am	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/tests/Makefile.am	(revision 22073)
@@ -0,0 +1,64 @@
+# copied from bison-1.875d
+
+EXTRA_DIST = $(TESTSUITE_AT) testsuite package.m4
+
+DISTCLEANFILES       = atconfig $(check_SCRIPTS)
+MAINTAINERCLEANFILES = Makefile.in $(TESTSUITE)
+
+## ------------ ##
+## package.m4.  ##
+## ------------ ##
+
+$(srcdir)/package.m4: $(top_srcdir)/configure.ac
+	{					\
+	  echo '# Signature of the current package.'; \
+	  echo 'm4_define([AT_PACKAGE_NAME],      [@PACKAGE_NAME@])'; \
+	  echo 'm4_define([AT_PACKAGE_TARNAME],   [@PACKAGE_TARNAME@])'; \
+	  echo 'm4_define([AT_PACKAGE_VERSION],   [@PACKAGE_VERSION@])'; \
+	  echo 'm4_define([AT_PACKAGE_STRING],    [@PACKAGE_STRING@])'; \
+	  echo 'm4_define([AT_PACKAGE_BUGREPORT], [@PACKAGE_BUGREPORT@])'; \
+	} >$(srcdir)/package.m4
+
+## ------------ ##
+## Test suite.  ##
+## ------------ ##
+
+TESTSUITE = $(srcdir)/testsuite
+
+AUTOTEST = $(AUTOM4TE) --language=autotest
+$(TESTSUITE): package.m4 $(TESTSUITE_AT)
+	$(AUTOTEST) -I $(srcdir) testsuite.at -o $@.tmp
+	mv $@.tmp $@
+
+atconfig: $(top_builddir)/config.status
+	cd $(top_builddir) && ./config.status tests/$@
+
+clean-local:
+	test ! -f $(TESTSUITE) || $(SHELL) $(TESTSUITE) --clean
+
+check-local: atconfig $(TESTSUITE)
+	$(SHELL) $(TESTSUITE)
+
+#
+# END BISON
+#
+
+UNITS = \
+	alloc \
+	init \
+	cleanup \
+	createtable \
+    droptable \
+    insert \
+    insertobject \
+    insertfits \
+    selectrowsfits \
+    metadatafromobject \
+    objectfrommetadata
+
+AM_CPPFLAGS = -I$(top_srcdir)/src$
+AM_CFLAGS   = @ippdb_CFLAGS@ $(PSLIB_CFLAGS)$
+AM_LDFLAGS  = $(PSLIB_LIBS)$
+LDADD       = $(top_builddir)/src/libippdb.la$
+
+check_PROGRAMS = dbsetup dbcleanup $(UNITS)
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/tests/alloc.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/tests/alloc.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/tests/alloc.c	(revision 22073)
@@ -0,0 +1,1761 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define MAX_STRING_LENGTH 1024
+
+int main ()
+{
+    {
+        expTagCounterRow *object;
+
+        object = expTagCounterRowAlloc(64    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->counter == 64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        summitExpRow    *object;
+
+        object = summitExpRowAlloc("a string", "a string", "a string", "0001-01-01T00:00:00Z", "a string", "a string", -32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        summitImfileRow *object;
+
+        object = summitImfileRowAlloc("a string", "a string", "a string", "a string", -32, "a string", "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->file_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bytes == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->md5sum, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        pzPendingExpRow *object;
+
+        object = pzPendingExpRowAlloc("a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        pzPendingImfileRow *object;
+
+        object = pzPendingImfileRowAlloc("a string", "a string", "a string", "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        pzDoneExpRow    *object;
+
+        object = pzDoneExpRowAlloc("a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        pzDoneImfileRow *object;
+
+        object = pzDoneImfileRowAlloc("a string", "a string", "a string", "a string", "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        newExpRow       *object;
+
+        object = newExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->workdir, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        newImfileRow    *object;
+
+        object = newImfileRowAlloc("a string", "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        rawExpRow       *object;
+
+        object = rawExpRowAlloc("a string", "a string", "a string", "a string", "0001-01-01T00:00:00Z", "a string", -32, "a string", "a string", "a string", 32.32, 64.64, 64.64, 32.32, 64.64, 64.64, 64.64, 64.64, 64.64, 32.32, 64.64, "a string", 32.32, -16    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filelevel, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->workdir, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->airmass == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exp_time == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_mean_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->alt == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->az == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ccd_temp == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->posang == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->object, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->solang == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fault == -16) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        rawImfileRow    *object;
+
+        object = rawImfileRowAlloc("a string", "a string", "a string", "a string", "a string", "a string", 32.32, 64.64, 64.64, 32.32, 64.64, 64.64, 64.64, 64.64, 64.64, 32.32, 64.64, "a string", "0001-01-01T00:00:00Z", -16    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filelevel, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->airmass == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exp_time == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_mean_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->alt == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->az == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ccd_temp == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->posang == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->object, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fault == -16) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        guidePendingExpRow *object;
+
+        object = guidePendingExpRowAlloc(-64, "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->guide_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        chipPendingExpRow *object;
+
+        object = chipPendingExpRowAlloc(-64, "a string", -64, "a string", "a string", "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->chip_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->guide_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->workdir, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->label, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->expgroup, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->dvodb, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        chipPendingImfileRow *object;
+
+        object = chipPendingImfileRowAlloc(-64, "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->chip_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        chipProcessedExpRow *object;
+
+        object = chipProcessedExpRowAlloc(-64, "a string", -64, "a string", "a string", "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->chip_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->guide_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->workdir, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->label, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->expgroup, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->dvodb, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        chipMaskRow     *object;
+
+        object = chipMaskRowAlloc("a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->label, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        chipProcessedImfileRow *object;
+
+        object = chipProcessedImfileRowAlloc(-64, "a string", "a string", 64.64, 64.64, 64.64, "a string", -16    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->chip_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_mean_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->path_base, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fault == -16) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        camPendingExpRow *object;
+
+        object = camPendingExpRowAlloc(-64, -64, "a string", "a string", "a string", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->cam_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->chip_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->workdir, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->label, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->expgroup, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->dvodb, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        camProcessedExpRow *object;
+
+        object = camProcessedExpRowAlloc(-64, -64, "a string", "a string", "a string", "a string", "a string", "a string", 64.64, 64.64, 64.64, 32.32, 32.32, -32, "a string", 32.32, 32.32, -16    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->cam_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->chip_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->workdir, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->label, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->expgroup, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->dvodb, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_mean_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->sigma_ra == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->sigma_dec == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->nastro == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->path_base, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->zp_mean == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->zp_stdev == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fault == -16) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        camMaskRow      *object;
+
+        object = camMaskRowAlloc("a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (strncmp(object->label, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        warpRunRow      *object;
+
+        object = warpRunRowAlloc(-64, "a string", "a string", "a string", "a string", "0001-01-01T00:00:00Z"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->warp_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->mode, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->state, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->workdir, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->dvodb, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        warpInputExpRow *object;
+
+        object = warpInputExpRowAlloc(-64, -64, true    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->warp_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->cam_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->magiced == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        warpSkyCellMapRow *object;
+
+        object = warpSkyCellMapRowAlloc(-64, "a string", "a string", -64, "a string", -16    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->warp_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->skycell_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->tess_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->cam_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fault == -16) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        warpSkyfileRow  *object;
+
+        object = warpSkyfileRowAlloc(-64, "a string", "a string", "a string", 64.64, 64.64    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->warp_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->skycell_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->tess_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        diffRunRow      *object;
+
+        object = diffRunRowAlloc(-64, "a string", "a string", "a string", "0001-01-01T00:00:00Z", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->diff_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->state, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->workdir, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->dvodb, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->skycell_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->tess_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        diffInputSkyfileRow *object;
+
+        object = diffInputSkyfileRowAlloc(-64, -64, "a string", "a string", "a string", true    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->diff_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->warp_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->skycell_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->tess_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->kind, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->template == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        diffSkyfileRow  *object;
+
+        object = diffSkyfileRowAlloc(-64, "a string", 64.64, 64.64    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->diff_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        stackRunRow     *object;
+
+        object = stackRunRowAlloc(-64, "a string", "a string", "a string", "0001-01-01T00:00:00Z", "a string", "a string"    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->stack_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->state, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->workdir, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->dvodb, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->skycell_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->tess_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        stackInputSkyfileRow *object;
+
+        object = stackInputSkyfileRowAlloc(-64, -64    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->stack_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->warp_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        stackSumSkyfileRow *object;
+
+        object = stackSumSkyfileRowAlloc(-64, "a string", 64.64, 64.64    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->stack_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detRunRow       *object;
+
+        object = detRunRowAlloc(-64, -32, "a string", "a string", "a string", "a string", "a string", "a string", "a string", "a string", "a string", 32.32, 32.32, 32.32, 32.32, 32.32, 32.32, 64.64, 64.64, "0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z", 32.32, 32.32, "a string", -32    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->det_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->mode, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->state, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filelevel, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->workdir, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->airmass_min == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->airmass_max == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exp_time_min == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exp_time_max == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ccd_temp_min == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ccd_temp_max == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->posang_min == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->posang_max == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->solang_min == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->solang_max == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->label, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->parent == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detInputExpRow  *object;
+
+        object = detInputExpRowAlloc(-64, -32, "a string", true    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->include == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detProcessedImfileRow *object;
+
+        object = detProcessedImfileRowAlloc(-64, "a string", "a string", "a string", "a string", 64.64, 64.64, 64.64, "a string", -16    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_mean_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->path_base, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fault == -16) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detProcessedExpRow *object;
+
+        object = detProcessedExpRowAlloc(-64, "a string", "a string", 64.64, 64.64, 64.64, "a string", -16    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_mean_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->path_base, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fault == -16) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detStackedImfileRow *object;
+
+        object = detStackedImfileRowAlloc(-64, -32, "a string", "a string", "a string", 64.64, 64.64, 64.64, -16    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_mean_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fault == -16) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detNormalizedStatImfileRow *object;
+
+        object = detNormalizedStatImfileRowAlloc(-64, -32, "a string", 32.32, -16    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->norm == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fault == -16) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detNormalizedImfileRow *object;
+
+        object = detNormalizedImfileRowAlloc(-64, -32, "a string", "a string", 64.64, 64.64, 64.64, "a string", -16    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_mean_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->path_base, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fault == -16) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detNormalizedExpRow *object;
+
+        object = detNormalizedExpRowAlloc(-64, -32, "a string", 64.64, 64.64, 64.64, "a string", -16    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_mean_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->path_base, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fault == -16) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detResidImfileRow *object;
+
+        object = detResidImfileRowAlloc(-64, -32, "a string", "a string", "a string", "a string", 64.64, 64.64, 64.64, "a string", -16    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_mean_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->path_base, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fault == -16) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detResidExpRow  *object;
+
+        object = detResidExpRowAlloc(-64, -32, "a string", "a string", 64.64, 64.64, 64.64, "a string", true, -16    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_mean_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->path_base, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->accept == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fault == -16) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        detRunSummaryRow *object;
+
+        object = detRunSummaryRowAlloc(-64, -32, 64.64, 64.64, 64.64, true, -16    );
+
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!object->det_id == -64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_mean_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->accept == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->fault == -16) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/tests/cleanup.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/tests/cleanup.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/tests/cleanup.c	(revision 22073)
@@ -0,0 +1,17 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+
+    dbh = psDBInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    ippdbCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/tests/createtable.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/tests/createtable.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/tests/createtable.c	(revision 22073)
@@ -0,0 +1,623 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!expTagCounterCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!summitExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!summitImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!pzPendingExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!pzPendingImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!pzDoneExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!pzDoneImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!newExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!newImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!rawExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!rawImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!guidePendingExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!chipPendingExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!chipPendingImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!chipProcessedExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!chipMaskCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!chipProcessedImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!camPendingExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!camProcessedExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!camMaskCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!warpRunCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!warpInputExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!warpSkyCellMapCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!warpSkyfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!diffRunCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!diffInputSkyfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!diffSkyfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!stackRunCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!stackInputSkyfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!stackSumSkyfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detRunCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detInputExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detProcessedImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detProcessedExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detStackedImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detNormalizedStatImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detNormalizedImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detNormalizedExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detResidImfileCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detResidExpCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if(!detRunSummaryCreateTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/tests/dbcleanup.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/tests/dbcleanup.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/tests/dbcleanup.c	(revision 22073)
@@ -0,0 +1,58 @@
+#include <pslib.h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+
+    dbh = psDBInit("localhost", "test", NULL, "test", 0);
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS expTagCounter");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS summitExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS summitImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS pzPendingExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS pzPendingImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS pzDoneExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS pzDoneImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS newExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS newImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS rawExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS rawImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS guidePendingExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS chipPendingExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS chipPendingImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS chipProcessedExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS chipMask");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS chipProcessedImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS camPendingExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS camProcessedExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS camMask");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS warpRun");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS warpInputExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS warpSkyCellMap");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS warpSkyfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS diffRun");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS diffInputSkyfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS diffSkyfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS stackRun");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS stackInputSkyfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS stackSumSkyfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detRun");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detInputExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detProcessedImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detProcessedExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detStackedImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detNormalizedStatImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detNormalizedImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detNormalizedExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detResidImfile");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detResidExp");
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detRunSummary");
+
+    psDBCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/tests/dbsetup.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/tests/dbsetup.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/tests/dbsetup.c	(revision 22073)
@@ -0,0 +1,141 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+
+    dbh = psDBInit("localhost", "test", NULL, "test", 0);
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    // remove the table if it already exists
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS expTagCounter");
+    expTagCounterCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS summitExp");
+    summitExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS summitImfile");
+    summitImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS pzPendingExp");
+    pzPendingExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS pzPendingImfile");
+    pzPendingImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS pzDoneExp");
+    pzDoneExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS pzDoneImfile");
+    pzDoneImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS newExp");
+    newExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS newImfile");
+    newImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS rawExp");
+    rawExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS rawImfile");
+    rawImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS guidePendingExp");
+    guidePendingExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS chipPendingExp");
+    chipPendingExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS chipPendingImfile");
+    chipPendingImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS chipProcessedExp");
+    chipProcessedExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS chipMask");
+    chipMaskCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS chipProcessedImfile");
+    chipProcessedImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS camPendingExp");
+    camPendingExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS camProcessedExp");
+    camProcessedExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS camMask");
+    camMaskCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS warpRun");
+    warpRunCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS warpInputExp");
+    warpInputExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS warpSkyCellMap");
+    warpSkyCellMapCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS warpSkyfile");
+    warpSkyfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS diffRun");
+    diffRunCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS diffInputSkyfile");
+    diffInputSkyfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS diffSkyfile");
+    diffSkyfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS stackRun");
+    stackRunCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS stackInputSkyfile");
+    stackInputSkyfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS stackSumSkyfile");
+    stackSumSkyfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detRun");
+    detRunCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detInputExp");
+    detInputExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detProcessedImfile");
+    detProcessedImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detProcessedExp");
+    detProcessedExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detStackedImfile");
+    detStackedImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detNormalizedStatImfile");
+    detNormalizedStatImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detNormalizedImfile");
+    detNormalizedImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detNormalizedExp");
+    detNormalizedExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detResidImfile");
+    detResidImfileCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detResidExp");
+    detResidExpCreateTable(dbh);
+
+    p_psDBRunQuery(dbh, "DROP TABLE IF EXISTS detRunSummary");
+    detRunSummaryCreateTable(dbh);
+
+    psDBCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/tests/droptable.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/tests/droptable.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/tests/droptable.c	(revision 22073)
@@ -0,0 +1,623 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!expTagCounterDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!summitExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!summitImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzDoneExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzDoneImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!guidePendingExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipPendingExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipPendingImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipProcessedExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipMaskDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipProcessedImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!camPendingExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!camProcessedExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!camMaskDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!warpRunDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!warpInputExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!warpSkyCellMapDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!warpSkyfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!diffRunDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!diffInputSkyfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!diffSkyfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!stackRunDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!stackInputSkyfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!stackSumSkyfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detRunDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detInputExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detProcessedImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detProcessedExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detStackedImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedStatImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidImfileDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidExpDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detRunSummaryDropTable(dbh)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/tests/init.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/tests/init.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/tests/init.c	(revision 22073)
@@ -0,0 +1,17 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    psDB            *dbh;
+
+    dbh = ippdbInit("localhost", "test", NULL, "test");
+    if (!dbh) {
+        exit(EXIT_FAILURE);
+    }
+
+    psDBCleanup(dbh);
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/tests/insert.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/tests/insert.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/tests/insert.c	(revision 22073)
@@ -0,0 +1,623 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!expTagCounterInsert(dbh, 64)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!summitExpInsert(dbh, "a string", "a string", "a string", "0001-01-01T00:00:00Z", "a string", "a string", -32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!summitImfileInsert(dbh, "a string", "a string", "a string", "a string", -32, "a string", "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingExpInsert(dbh, "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingImfileInsert(dbh, "a string", "a string", "a string", "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzDoneExpInsert(dbh, "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzDoneImfileInsert(dbh, "a string", "a string", "a string", "a string", "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newExpInsert(dbh, "a string", "a string", "a string", "a string", -32, "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newImfileInsert(dbh, "a string", "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawExpInsert(dbh, "a string", "a string", "a string", "a string", "0001-01-01T00:00:00Z", "a string", -32, "a string", "a string", "a string", 32.32, 64.64, 64.64, 32.32, 64.64, 64.64, 64.64, 64.64, 64.64, 32.32, 64.64, "a string", 32.32, -16)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawImfileInsert(dbh, "a string", "a string", "a string", "a string", "a string", "a string", 32.32, 64.64, 64.64, 32.32, 64.64, 64.64, 64.64, 64.64, 64.64, 32.32, 64.64, "a string", "0001-01-01T00:00:00Z", -16)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!guidePendingExpInsert(dbh, -64, "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipPendingExpInsert(dbh, -64, "a string", -64, "a string", "a string", "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipPendingImfileInsert(dbh, -64, "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipProcessedExpInsert(dbh, -64, "a string", -64, "a string", "a string", "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipMaskInsert(dbh, "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipProcessedImfileInsert(dbh, -64, "a string", "a string", 64.64, 64.64, 64.64, "a string", -16)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!camPendingExpInsert(dbh, -64, -64, "a string", "a string", "a string", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!camProcessedExpInsert(dbh, -64, -64, "a string", "a string", "a string", "a string", "a string", "a string", 64.64, 64.64, 64.64, 32.32, 32.32, -32, "a string", 32.32, 32.32, -16)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!camMaskInsert(dbh, "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!warpRunInsert(dbh, -64, "a string", "a string", "a string", "a string", "0001-01-01T00:00:00Z")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!warpInputExpInsert(dbh, -64, -64, true)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!warpSkyCellMapInsert(dbh, -64, "a string", "a string", -64, "a string", -16)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!warpSkyfileInsert(dbh, -64, "a string", "a string", "a string", 64.64, 64.64)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!diffRunInsert(dbh, -64, "a string", "a string", "a string", "0001-01-01T00:00:00Z", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!diffInputSkyfileInsert(dbh, -64, -64, "a string", "a string", "a string", true)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!diffSkyfileInsert(dbh, -64, "a string", 64.64, 64.64)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!stackRunInsert(dbh, -64, "a string", "a string", "a string", "0001-01-01T00:00:00Z", "a string", "a string")) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!stackInputSkyfileInsert(dbh, -64, -64)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!stackSumSkyfileInsert(dbh, -64, "a string", 64.64, 64.64)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detRunInsert(dbh, -64, -32, "a string", "a string", "a string", "a string", "a string", "a string", "a string", "a string", "a string", 32.32, 32.32, 32.32, 32.32, 32.32, 32.32, 64.64, 64.64, "0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z", 32.32, 32.32, "a string", -32)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detInputExpInsert(dbh, -64, -32, "a string", true)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detProcessedImfileInsert(dbh, -64, "a string", "a string", "a string", "a string", 64.64, 64.64, 64.64, "a string", -16)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detProcessedExpInsert(dbh, -64, "a string", "a string", 64.64, 64.64, 64.64, "a string", -16)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detStackedImfileInsert(dbh, -64, -32, "a string", "a string", "a string", 64.64, 64.64, 64.64, -16)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedStatImfileInsert(dbh, -64, -32, "a string", 32.32, -16)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedImfileInsert(dbh, -64, -32, "a string", "a string", 64.64, 64.64, 64.64, "a string", -16)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedExpInsert(dbh, -64, -32, "a string", 64.64, 64.64, 64.64, "a string", -16)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidImfileInsert(dbh, -64, -32, "a string", "a string", "a string", "a string", 64.64, 64.64, 64.64, "a string", -16)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidExpInsert(dbh, -64, -32, "a string", "a string", 64.64, 64.64, 64.64, "a string", true, -16)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detRunSummaryInsert(dbh, -64, -32, 64.64, 64.64, 64.64, true, -16)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/tests/insertfits.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/tests/insertfits.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/tests/insertfits.c	(revision 22073)
@@ -0,0 +1,1078 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#define TMP_FILENAME "./blargh"
+
+int main ()
+{
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!expTagCounterInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!summitExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!summitImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzDoneExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzDoneImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!guidePendingExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipPendingExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipPendingImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipProcessedExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipMaskInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipProcessedImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!camPendingExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!camProcessedExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!camMaskInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!warpRunInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!warpInputExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!warpSkyCellMapInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!warpSkyfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!diffRunInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!diffInputSkyfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!diffSkyfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!stackRunInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!stackInputSkyfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!stackSumSkyfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detRunInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detInputExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detProcessedImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detProcessedExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detStackedImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedStatImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidImfileInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidExpInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        // open a temp
+        fits = psFitsOpen(TMP_FILENAME, "r");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detRunSummaryInsertFits(dbh, fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!psFitsClose(fits)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psDBCleanup(dbh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/tests/insertobject.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/tests/insertobject.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/tests/insertobject.c	(revision 22073)
@@ -0,0 +1,910 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+int main ()
+{
+    {
+        psDB            *dbh;
+        expTagCounterRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = expTagCounterRowAlloc(64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!expTagCounterInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        summitExpRow    *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = summitExpRowAlloc("a string", "a string", "a string", "0001-01-01T00:00:00Z", "a string", "a string", -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!summitExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        summitImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = summitImfileRowAlloc("a string", "a string", "a string", "a string", -32, "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!summitImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        pzPendingExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = pzPendingExpRowAlloc("a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        pzPendingImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = pzPendingImfileRowAlloc("a string", "a string", "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        pzDoneExpRow    *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = pzDoneExpRowAlloc("a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzDoneExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        pzDoneImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = pzDoneImfileRowAlloc("a string", "a string", "a string", "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzDoneImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        newExpRow       *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = newExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        newImfileRow    *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = newImfileRowAlloc("a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        rawExpRow       *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = rawExpRowAlloc("a string", "a string", "a string", "a string", "0001-01-01T00:00:00Z", "a string", -32, "a string", "a string", "a string", 32.32, 64.64, 64.64, 32.32, 64.64, 64.64, 64.64, 64.64, 64.64, 32.32, 64.64, "a string", 32.32, -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        rawImfileRow    *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = rawImfileRowAlloc("a string", "a string", "a string", "a string", "a string", "a string", 32.32, 64.64, 64.64, 32.32, 64.64, 64.64, 64.64, 64.64, 64.64, 32.32, 64.64, "a string", "0001-01-01T00:00:00Z", -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        guidePendingExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = guidePendingExpRowAlloc(-64, "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!guidePendingExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        chipPendingExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = chipPendingExpRowAlloc(-64, "a string", -64, "a string", "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipPendingExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        chipPendingImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = chipPendingImfileRowAlloc(-64, "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipPendingImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        chipProcessedExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = chipProcessedExpRowAlloc(-64, "a string", -64, "a string", "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipProcessedExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        chipMaskRow     *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = chipMaskRowAlloc("a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipMaskInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        chipProcessedImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = chipProcessedImfileRowAlloc(-64, "a string", "a string", 64.64, 64.64, 64.64, "a string", -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipProcessedImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        camPendingExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = camPendingExpRowAlloc(-64, -64, "a string", "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!camPendingExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        camProcessedExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = camProcessedExpRowAlloc(-64, -64, "a string", "a string", "a string", "a string", "a string", "a string", 64.64, 64.64, 64.64, 32.32, 32.32, -32, "a string", 32.32, 32.32, -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!camProcessedExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        camMaskRow      *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = camMaskRowAlloc("a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!camMaskInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        warpRunRow      *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = warpRunRowAlloc(-64, "a string", "a string", "a string", "a string", "0001-01-01T00:00:00Z");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!warpRunInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        warpInputExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = warpInputExpRowAlloc(-64, -64, true);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!warpInputExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        warpSkyCellMapRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = warpSkyCellMapRowAlloc(-64, "a string", "a string", -64, "a string", -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!warpSkyCellMapInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        warpSkyfileRow  *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = warpSkyfileRowAlloc(-64, "a string", "a string", "a string", 64.64, 64.64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!warpSkyfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        diffRunRow      *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = diffRunRowAlloc(-64, "a string", "a string", "a string", "0001-01-01T00:00:00Z", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!diffRunInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        diffInputSkyfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = diffInputSkyfileRowAlloc(-64, -64, "a string", "a string", "a string", true);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!diffInputSkyfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        diffSkyfileRow  *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = diffSkyfileRowAlloc(-64, "a string", 64.64, 64.64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!diffSkyfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        stackRunRow     *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = stackRunRowAlloc(-64, "a string", "a string", "a string", "0001-01-01T00:00:00Z", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!stackRunInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        stackInputSkyfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = stackInputSkyfileRowAlloc(-64, -64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!stackInputSkyfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        stackSumSkyfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = stackSumSkyfileRowAlloc(-64, "a string", 64.64, 64.64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!stackSumSkyfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detRunRow       *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detRunRowAlloc(-64, -32, "a string", "a string", "a string", "a string", "a string", "a string", "a string", "a string", "a string", 32.32, 32.32, 32.32, 32.32, 32.32, 32.32, 64.64, 64.64, "0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z", 32.32, 32.32, "a string", -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detRunInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detInputExpRow  *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detInputExpRowAlloc(-64, -32, "a string", true);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detInputExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detProcessedImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detProcessedImfileRowAlloc(-64, "a string", "a string", "a string", "a string", 64.64, 64.64, 64.64, "a string", -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detProcessedImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detProcessedExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detProcessedExpRowAlloc(-64, "a string", "a string", 64.64, 64.64, 64.64, "a string", -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detProcessedExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detStackedImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detStackedImfileRowAlloc(-64, -32, "a string", "a string", "a string", 64.64, 64.64, 64.64, -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detStackedImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detNormalizedStatImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detNormalizedStatImfileRowAlloc(-64, -32, "a string", 32.32, -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedStatImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detNormalizedImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detNormalizedImfileRowAlloc(-64, -32, "a string", "a string", 64.64, 64.64, 64.64, "a string", -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detNormalizedExpRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detNormalizedExpRowAlloc(-64, -32, "a string", 64.64, 64.64, 64.64, "a string", -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detResidImfileRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detResidImfileRowAlloc(-64, -32, "a string", "a string", "a string", "a string", 64.64, 64.64, 64.64, "a string", -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidImfileInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detResidExpRow  *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detResidExpRowAlloc(-64, -32, "a string", "a string", 64.64, 64.64, 64.64, "a string", true, -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidExpInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        detRunSummaryRow *object;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        object = detRunSummaryRowAlloc(-64, -32, 64.64, 64.64, 64.64, true, -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detRunSummaryInsertObject(dbh, object)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+        psDBCleanup(dbh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/tests/metadatafromobject.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/tests/metadatafromobject.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/tests/metadatafromobject.c	(revision 22073)
@@ -0,0 +1,2038 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define MAX_STRING_LENGTH 1024
+
+int main ()
+{
+    {
+        psMetadata      *md;
+        expTagCounterRow *object;
+        bool            status;
+
+        object = expTagCounterRowAlloc(64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = expTagCounterMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        summitExpRow    *object;
+        bool            status;
+
+        object = summitExpRowAlloc("a string", "a string", "a string", "0001-01-01T00:00:00Z", "a string", "a string", -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = summitExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "imfiles") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        summitImfileRow *object;
+        bool            status;
+
+        object = summitImfileRowAlloc("a string", "a string", "a string", "a string", -32, "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = summitImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "file_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "bytes") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "md5sum"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        pzPendingExpRow *object;
+        bool            status;
+
+        object = pzPendingExpRowAlloc("a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = pzPendingExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        pzPendingImfileRow *object;
+        bool            status;
+
+        object = pzPendingImfileRowAlloc("a string", "a string", "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = pzPendingImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_tag"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        pzDoneExpRow    *object;
+        bool            status;
+
+        object = pzDoneExpRowAlloc("a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = pzDoneExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        pzDoneImfileRow *object;
+        bool            status;
+
+        object = pzDoneImfileRowAlloc("a string", "a string", "a string", "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = pzDoneImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_tag"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        newExpRow       *object;
+        bool            status;
+
+        object = newExpRowAlloc("a string", "a string", "a string", "a string", -32, "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = newExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_tag"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "imfiles") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "workdir"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        newImfileRow    *object;
+        bool            status;
+
+        object = newImfileRowAlloc("a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = newImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_tag"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        rawExpRow       *object;
+        bool            status;
+
+        object = rawExpRowAlloc("a string", "a string", "a string", "a string", "0001-01-01T00:00:00Z", "a string", -32, "a string", "a string", "a string", 32.32, 64.64, 64.64, 32.32, 64.64, 64.64, 64.64, 64.64, 64.64, 32.32, 64.64, "a string", 32.32, -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = rawExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_tag"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "imfiles") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "filelevel"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "workdir"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "filter"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "airmass") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "ra") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "decl") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "exp_time") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_mean_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "alt") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "az") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "ccd_temp") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "posang") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "object"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "solang") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        rawImfileRow    *object;
+        bool            status;
+
+        object = rawImfileRowAlloc("a string", "a string", "a string", "a string", "a string", "a string", 32.32, 64.64, 64.64, 32.32, 64.64, 64.64, 64.64, 64.64, 64.64, 32.32, 64.64, "a string", "0001-01-01T00:00:00Z", -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = rawImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_tag"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "filelevel"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "filter"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "airmass") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "ra") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "decl") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "exp_time") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_mean_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "alt") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "az") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "ccd_temp") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "posang") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "object"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        guidePendingExpRow *object;
+        bool            status;
+
+        object = guidePendingExpRowAlloc(-64, "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = guidePendingExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_tag"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        chipPendingExpRow *object;
+        bool            status;
+
+        object = chipPendingExpRowAlloc(-64, "a string", -64, "a string", "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = chipPendingExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_tag"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "workdir"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "label"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "expgroup"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "dvodb"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        chipPendingImfileRow *object;
+        bool            status;
+
+        object = chipPendingImfileRowAlloc(-64, "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = chipPendingImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        chipProcessedExpRow *object;
+        bool            status;
+
+        object = chipProcessedExpRowAlloc(-64, "a string", -64, "a string", "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = chipProcessedExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_tag"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "workdir"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "label"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "expgroup"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "dvodb"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        chipMaskRow     *object;
+        bool            status;
+
+        object = chipMaskRowAlloc("a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = chipMaskMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "label"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        chipProcessedImfileRow *object;
+        bool            status;
+
+        object = chipProcessedImfileRowAlloc(-64, "a string", "a string", 64.64, 64.64, 64.64, "a string", -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = chipProcessedImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_mean_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "path_base"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        camPendingExpRow *object;
+        bool            status;
+
+        object = camPendingExpRowAlloc(-64, -64, "a string", "a string", "a string", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = camPendingExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "workdir"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "label"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "expgroup"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "dvodb"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        camProcessedExpRow *object;
+        bool            status;
+
+        object = camProcessedExpRowAlloc(-64, -64, "a string", "a string", "a string", "a string", "a string", "a string", 64.64, 64.64, 64.64, 32.32, 32.32, -32, "a string", 32.32, 32.32, -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = camProcessedExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "workdir"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "label"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "expgroup"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "dvodb"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_mean_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "sigma_ra") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "sigma_dec") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "nastro") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "path_base"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "zp_mean") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "zp_stdev") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        camMaskRow      *object;
+        bool            status;
+
+        object = camMaskRowAlloc("a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = camMaskMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+        if (strncmp(psMetadataLookupPtr(&status, md, "label"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        warpRunRow      *object;
+        bool            status;
+
+        object = warpRunRowAlloc(-64, "a string", "a string", "a string", "a string", "0001-01-01T00:00:00Z");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = warpRunMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "mode"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "state"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "workdir"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "dvodb"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        warpInputExpRow *object;
+        bool            status;
+
+        object = warpInputExpRowAlloc(-64, -64, true);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = warpInputExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupBool(&status, md, "magiced") == true) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        warpSkyCellMapRow *object;
+        bool            status;
+
+        object = warpSkyCellMapRowAlloc(-64, "a string", "a string", -64, "a string", -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = warpSkyCellMapMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "skycell_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "tess_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        warpSkyfileRow  *object;
+        bool            status;
+
+        object = warpSkyfileRowAlloc(-64, "a string", "a string", "a string", 64.64, 64.64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = warpSkyfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "skycell_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "tess_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        diffRunRow      *object;
+        bool            status;
+
+        object = diffRunRowAlloc(-64, "a string", "a string", "a string", "0001-01-01T00:00:00Z", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = diffRunMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "state"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "workdir"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "dvodb"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "skycell_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "tess_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        diffInputSkyfileRow *object;
+        bool            status;
+
+        object = diffInputSkyfileRowAlloc(-64, -64, "a string", "a string", "a string", true);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = diffInputSkyfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "skycell_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "tess_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "kind"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupBool(&status, md, "template") == true) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        diffSkyfileRow  *object;
+        bool            status;
+
+        object = diffSkyfileRowAlloc(-64, "a string", 64.64, 64.64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = diffSkyfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        stackRunRow     *object;
+        bool            status;
+
+        object = stackRunRowAlloc(-64, "a string", "a string", "a string", "0001-01-01T00:00:00Z", "a string", "a string");
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = stackRunMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "state"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "workdir"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "dvodb"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "skycell_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "tess_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        stackInputSkyfileRow *object;
+        bool            status;
+
+        object = stackInputSkyfileRowAlloc(-64, -64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = stackInputSkyfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        stackSumSkyfileRow *object;
+        bool            status;
+
+        object = stackSumSkyfileRowAlloc(-64, "a string", 64.64, 64.64);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = stackSumSkyfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detRunRow       *object;
+        bool            status;
+
+        object = detRunRowAlloc(-64, -32, "a string", "a string", "a string", "a string", "a string", "a string", "a string", "a string", "a string", 32.32, 32.32, 32.32, 32.32, 32.32, 32.32, 64.64, 64.64, "0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z", "0001-01-01T00:00:00Z", 32.32, 32.32, "a string", -32);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detRunMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "iteration") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "det_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "mode"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "state"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "filelevel"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "workdir"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "camera"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "telescope"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_type"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "filter"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "airmass_min") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "airmass_max") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "exp_time_min") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "exp_time_max") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "ccd_temp_min") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "ccd_temp_max") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "posang_min") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "posang_max") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "solang_min") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "solang_max") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "label"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "parent") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detInputExpRow  *object;
+        bool            status;
+
+        object = detInputExpRowAlloc(-64, -32, "a string", true);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detInputExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "iteration") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_tag"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupBool(&status, md, "include") == true) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detProcessedImfileRow *object;
+        bool            status;
+
+        object = detProcessedImfileRowAlloc(-64, "a string", "a string", "a string", "a string", 64.64, 64.64, 64.64, "a string", -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detProcessedImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_tag"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_mean_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "path_base"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detProcessedExpRow *object;
+        bool            status;
+
+        object = detProcessedExpRowAlloc(-64, "a string", "a string", 64.64, 64.64, 64.64, "a string", -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detProcessedExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_tag"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_mean_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "path_base"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detStackedImfileRow *object;
+        bool            status;
+
+        object = detStackedImfileRowAlloc(-64, -32, "a string", "a string", "a string", 64.64, 64.64, 64.64, -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detStackedImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "iteration") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_mean_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detNormalizedStatImfileRow *object;
+        bool            status;
+
+        object = detNormalizedStatImfileRowAlloc(-64, -32, "a string", 32.32, -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detNormalizedStatImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "iteration") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF32(&status, md, "norm") == 32.32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detNormalizedImfileRow *object;
+        bool            status;
+
+        object = detNormalizedImfileRowAlloc(-64, -32, "a string", "a string", 64.64, 64.64, 64.64, "a string", -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detNormalizedImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "iteration") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_mean_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "path_base"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detNormalizedExpRow *object;
+        bool            status;
+
+        object = detNormalizedExpRowAlloc(-64, -32, "a string", 64.64, 64.64, 64.64, "a string", -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detNormalizedExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "iteration") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_mean_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "path_base"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detResidImfileRow *object;
+        bool            status;
+
+        object = detResidImfileRowAlloc(-64, -32, "a string", "a string", "a string", "a string", 64.64, 64.64, 64.64, "a string", -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detResidImfileMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "iteration") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_tag"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "class_id"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "uri"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_mean_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "path_base"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detResidExpRow  *object;
+        bool            status;
+
+        object = detResidExpRowAlloc(-64, -32, "a string", "a string", 64.64, 64.64, 64.64, "a string", true, -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detResidExpMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "iteration") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "exp_tag"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "recipe"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_mean_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(psMetadataLookupPtr(&status, md, "path_base"), "a string", MAX_STRING_LENGTH)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupBool(&status, md, "accept") == true) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    {
+        psMetadata      *md;
+        detRunSummaryRow *object;
+        bool            status;
+
+        object = detRunSummaryRowAlloc(-64, -32, 64.64, 64.64, 64.64, true, -16);
+        if (!object) {
+            exit(EXIT_FAILURE);
+        }
+
+        md = detRunSummaryMetadataFromObject(object);
+        if (!md) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupS32(&status, md, "iteration") == -32) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupF64(&status, md, "bg_mean_stdev") == 64.64) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataLookupBool(&status, md, "accept") == true) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/tests/objectfrommetadata.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/tests/objectfrommetadata.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/tests/objectfrommetadata.c	(revision 22073)
@@ -0,0 +1,3081 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define MAX_STRING_LENGTH 1024
+
+int main ()
+{
+    {
+        psMetadata      *md;
+        expTagCounterRow *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = expTagCounterObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        summitExpRow    *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = summitExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        summitImfileRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "file_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "bytes", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "md5sum", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = summitImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->file_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bytes == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->md5sum, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        pzPendingExpRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = pzPendingExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        pzPendingImfileRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_tag", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = pzPendingImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        pzDoneExpRow    *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = pzDoneExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        pzDoneImfileRow *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_tag", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = pzDoneImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        newExpRow       *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_tag", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "workdir", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = newExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->workdir, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        newImfileRow    *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_tag", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = newImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        rawExpRow       *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_tag", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "imfiles", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "filelevel", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "workdir", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_mean_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "alt", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "az", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "ccd_temp", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "posang", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "object", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "solang", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = rawExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->imfiles == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filelevel, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->workdir, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->airmass == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exp_time == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_mean_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->alt == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->az == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ccd_temp == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->posang == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->object, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->solang == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        rawImfileRow    *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_tag", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "filelevel", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "ra", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "decl", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_mean_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "alt", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "az", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "ccd_temp", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "posang", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "object", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = rawImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filelevel, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->airmass == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ra == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->decl == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exp_time == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_mean_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->alt == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->az == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ccd_temp == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->posang == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->object, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        guidePendingExpRow *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_tag", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = guidePendingExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        chipPendingExpRow *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_tag", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "workdir", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "label", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "expgroup", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "dvodb", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = chipPendingExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->workdir, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->label, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->expgroup, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->dvodb, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        chipPendingImfileRow *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = chipPendingImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        chipProcessedExpRow *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_tag", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "workdir", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "label", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "expgroup", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "dvodb", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = chipProcessedExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->workdir, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->label, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->expgroup, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->dvodb, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        chipMaskRow     *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "label", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = chipMaskObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->label, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        chipProcessedImfileRow *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_mean_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "path_base", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = chipProcessedImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_mean_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->path_base, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        camPendingExpRow *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "workdir", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "label", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "expgroup", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "dvodb", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = camPendingExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->workdir, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->label, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->expgroup, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->dvodb, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        camProcessedExpRow *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "workdir", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "label", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "expgroup", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "dvodb", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_mean_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "sigma_ra", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "sigma_dec", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "nastro", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "path_base", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "zp_mean", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "zp_stdev", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = camProcessedExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->workdir, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->label, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->expgroup, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->dvodb, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_mean_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->sigma_ra == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->sigma_dec == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->nastro == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->path_base, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->zp_mean == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->zp_stdev == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        camMaskRow      *object;
+
+        md = psMetadataAlloc();
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "label", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = camMaskObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+        if (strncmp(object->label, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        warpRunRow      *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "mode", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "state", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "workdir", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "dvodb", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = warpRunObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->mode, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->state, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->workdir, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->dvodb, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        warpInputExpRow *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAdd(md, PS_LIST_TAIL, "magiced", PS_DATA_BOOL, NULL, true)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = warpInputExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->magiced == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        warpSkyCellMapRow *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "skycell_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "tess_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = warpSkyCellMapObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->skycell_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->tess_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        warpSkyfileRow  *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "skycell_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "tess_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = warpSkyfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->skycell_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->tess_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        diffRunRow      *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "state", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "workdir", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "dvodb", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "skycell_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "tess_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = diffRunObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->state, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->workdir, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->dvodb, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->skycell_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->tess_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        diffInputSkyfileRow *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "skycell_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "tess_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "kind", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAdd(md, PS_LIST_TAIL, "template", PS_DATA_BOOL, NULL, true)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = diffInputSkyfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->skycell_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->tess_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->kind, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->template == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        diffSkyfileRow  *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = diffSkyfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        stackRunRow     *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "state", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "workdir", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "dvodb", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "skycell_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "tess_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = stackRunObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->state, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->workdir, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->dvodb, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->skycell_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->tess_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        stackInputSkyfileRow *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = stackInputSkyfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        stackSumSkyfileRow *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = stackSumSkyfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detRunRow       *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "det_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "mode", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "state", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "filelevel", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "workdir", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "camera", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "telescope", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_type", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "filter", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass_min", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "airmass_max", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time_min", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "exp_time_max", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "ccd_temp_min", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "ccd_temp_max", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "posang_min", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "posang_max", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "solang_min", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "solang_max", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "label", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "parent", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detRunObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->det_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->mode, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->state, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filelevel, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->workdir, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->camera, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->telescope, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_type, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->filter, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->airmass_min == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->airmass_max == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exp_time_min == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->exp_time_max == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ccd_temp_min == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->ccd_temp_max == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->posang_min == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->posang_max == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->solang_min == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->solang_max == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->label, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->parent == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detInputExpRow  *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_tag", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAdd(md, PS_LIST_TAIL, "include", PS_DATA_BOOL, NULL, true)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detInputExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->include == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detProcessedImfileRow *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_tag", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_mean_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "path_base", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detProcessedImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_mean_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->path_base, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detProcessedExpRow *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_tag", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_mean_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "path_base", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detProcessedExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_mean_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->path_base, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detStackedImfileRow *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_mean_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detStackedImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_mean_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detNormalizedStatImfileRow *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF32(md, PS_LIST_TAIL, "norm", 0, NULL, 32.32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detNormalizedStatImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->norm == 32.32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detNormalizedImfileRow *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_mean_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "path_base", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detNormalizedImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_mean_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->path_base, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detNormalizedExpRow *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_mean_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "path_base", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detNormalizedExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_mean_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->path_base, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detResidImfileRow *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_tag", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "uri", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_mean_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "path_base", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detResidImfileObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->class_id, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->uri, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_mean_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->path_base, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detResidExpRow  *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "exp_tag", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "recipe", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_mean_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddStr(md, PS_LIST_TAIL, "path_base", 0, NULL, "a string")) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAdd(md, PS_LIST_TAIL, "accept", PS_DATA_BOOL, NULL, true)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detResidExpObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->exp_tag, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->recipe, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_mean_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (strncmp(object->path_base, "a string", MAX_STRING_LENGTH)) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->accept == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    {
+        psMetadata      *md;
+        detRunSummaryRow *object;
+
+        md = psMetadataAlloc();
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, -32)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAddF64(md, PS_LIST_TAIL, "bg_mean_stdev", 0, NULL, 64.64)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+        if (!psMetadataAdd(md, PS_LIST_TAIL, "accept", PS_DATA_BOOL, NULL, true)) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        object = detRunSummaryObjectFromMetadata(md);
+        if (!object) {
+            psFree(md);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(md);
+
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->iteration == -32) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->bg_mean_stdev == 64.64) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+        if (!object->accept == true) {
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+            psFree(object);
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(object);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/tests/selectrowsfits.c
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/tests/selectrowsfits.c	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/tests/selectrowsfits.c	(revision 22073)
@@ -0,0 +1,912 @@
+#include <pslib.h>
+#include <ippdb.h>
+#include <stdlib.h>
+
+#define TMP_FILENAME "./blargh"
+
+int main ()
+{
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!expTagCounterSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!summitExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!summitImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzPendingImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzDoneExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!pzDoneImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!newImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!rawImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!guidePendingExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipPendingExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipPendingImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipProcessedExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipMaskSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!chipProcessedImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!camPendingExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!camProcessedExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!camMaskSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!warpRunSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!warpInputExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!warpSkyCellMapSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!warpSkyfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!diffRunSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!diffInputSkyfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!diffSkyfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!stackRunSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!stackInputSkyfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!stackSumSkyfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detRunSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detInputExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detProcessedImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detProcessedExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detStackedImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedStatImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detNormalizedExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidImfileSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detResidExpSelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    {
+        psDB            *dbh;
+        psFits          *fits;
+
+        dbh = psDBInit("localhost", "test", NULL, "test");
+        if (!dbh) {
+            exit(EXIT_FAILURE);
+        }
+
+        fits = psFitsOpen(TMP_FILENAME, "w");
+        if (!fits) {
+            exit(EXIT_FAILURE);
+        }
+
+        if (!detRunSummarySelectRowsFits(dbh, fits, NULL, 1)) {
+            exit(EXIT_FAILURE);
+        }
+
+        psFree(fits);
+        psDBCleanup(dbh);
+    }
+
+    exit(EXIT_SUCCESS);
+}
Index: /tags/ipp-1-X/rel-1_1_19/ippdb/tests/testsuite.at
===================================================================
--- /tags/ipp-1-X/rel-1_1_19/ippdb/tests/testsuite.at	(revision 22073)
+++ /tags/ipp-1-X/rel-1_1_19/ippdb/tests/testsuite.at	(revision 22073)
@@ -0,0 +1,119 @@
+m4_version_prereq([2.59])
+
+AT_INIT
+
+###
+AT_SETUP([Alloc()])
+AT_KEYWORDS([Alloc])
+
+AT_CHECK([alloc])
+
+AT_CLEANUP
+
+###
+AT_SETUP([Init()])
+AT_KEYWORDS([Init])
+
+AT_CHECK([init])
+
+AT_CLEANUP
+
+###
+AT_SETUP([Cleanup()])
+AT_KEYWORDS([Cleanup])
+
+AT_CHECK([cleanup])
+
+AT_CLEANUP
+
+###
+AT_SETUP([CreateTable()])
+AT_KEYWORDS([CreateTable])
+
+# make sure the table is not there.
+AT_CHECK([dbcleanup])
+AT_CHECK([createtable])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
+
+###
+AT_SETUP([DropTable()])
+AT_KEYWORDS([DropTable])
+
+# make sure the table is not there.
+AT_CHECK([dbsetup])
+AT_CHECK([droptable])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
+
+###
+AT_SETUP([Insert()])
+AT_KEYWORDS([Insert])
+
+AT_CHECK([dbsetup])
+AT_CHECK([insert])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
+
+###
+AT_SETUP([InsertObject()])
+AT_KEYWORDS([InsertObject])
+
+AT_CHECK([dbsetup])
+AT_CHECK([insertobject])
+AT_CHECK([dbcleanup])
+
+AT_CLEANUP
+
+###
+AT_SETUP([InsertFits()])
+AT_KEYWORDS([InsertFits])
+
+AT_CHECK([dbsetup])
+# run insert so there is a row in the table
+AT_CHECK([insert])
+# run popfis so there is afits file to read
+AT_CHECK([selectrowsfits])
+AT_CHECK([insertfits])
+AT_CHECK([dbcleanup])
+if [ test -f "blargh" ] ; then
+    rm -f "blargh"
+fi
+
+AT_CLEANUP
+
+###
+AT_SETUP([SelectRowsFits()])
+AT_KEYWORDS([SelectRowsFits])
+
+AT_CHECK([dbsetup])
+# run insert so there is a row in the table
+AT_CHECK([insert])
+AT_CHECK([selectrowsfits])
+AT_CHECK([dbcleanup])
+if [ test -f "blargh" ] ; then
+    rm -f "blargh"
+fi
+
+AT_CLEANUP
+
+###
+AT_SETUP([MetadataFromObject()])
+AT_KEYWORDS([MetadataFromObject])
+
+AT_CHECK([metadatafromobject])
+
+AT_CLEANUP
+
+###
+AT_SETUP([ObjectFromMetadata()])
+AT_KEYWORDS([ObjectFromMetadata])
+
+AT_CHECK([objectfrommetadata])
+
+AT_CLEANUP
+
+###
