Index: trunk/dbconfig/changes.txt
===================================================================
--- trunk/dbconfig/changes.txt	(revision 26377)
+++ trunk/dbconfig/changes.txt	(revision 26378)
@@ -1466,2 +1466,15 @@
 ) ENGINE=innodb DEFAULT CHARSET=latin1;
 
+-- Version 1.1.60
+
+ALTER TABLE dqstatsRun ADD COLUMN label VARCHAR(64) AFTER registered;
+ALTER TABLE dqstatsRun ADD KEY(label);
+
+ALTER TABLE dqstatsRun ADD COLUMN fault SMALLINT NOT NULL AFTER label;
+ALTER TABLE dqstatsRun ADD KEY(fault);
+
+-- Version 1.1.61
+
+ALTER TABLE pzDataStore ADD COLUMN use_compress SMALLINT NOT NULL AFTER epoch;
+
+UPDATE dbversion set schema_version = '1.1.61',  updated= CURRENT_TIMESTAMP();
Index: trunk/dbconfig/dqstats.md
===================================================================
--- trunk/dbconfig/dqstats.md	(revision 26377)
+++ trunk/dbconfig/dqstats.md	(revision 26378)
@@ -3,4 +3,6 @@
     state          STR          64
     registered     TAI          NULL
+    label          STR          64    # Key
+    fault          S16          0     # Key NOT NULL
 END
 
Index: trunk/dbconfig/summitcopy.md
===================================================================
--- trunk/dbconfig/summitcopy.md	(revision 26377)
+++ trunk/dbconfig/summitcopy.md	(revision 26378)
@@ -4,4 +4,5 @@
     uri         STR         255
     epoch       UTC         0001-01-01T00:00:00Z
+    use_compress S16        0
 END
 
Index: trunk/ippScripts/Build.PL
===================================================================
--- trunk/ippScripts/Build.PL	(revision 26377)
+++ trunk/ippScripts/Build.PL	(revision 26378)
@@ -98,4 +98,5 @@
         scripts/rcserver_checkstatus.pl
         scripts/publish_file.pl
+	scripts/dqstats_bundle.pl
         scripts/whichimage
     )],
Index: trunk/ippScripts/scripts/dqstats_bundle.pl
===================================================================
--- trunk/ippScripts/scripts/dqstats_bundle.pl	(revision 26378)
+++ trunk/ippScripts/scripts/dqstats_bundle.pl	(revision 26378)
@@ -0,0 +1,162 @@
+#! /usr/bin/env perl
+
+use warnings;
+use strict;
+use Carp;
+
+## report the program and machine
+use Sys::Hostname;
+my $host = hostname();
+print "\n\n";
+print "Starting script $0 on $host\n\n";
+
+use DateTime;
+my $mjd_start = DateTime->now->mjd;   # MJD of starting script
+
+use vars qw( $VERSION );
+$VERSION = '0.01';
+
+use IPC::Cmd 0.36 qw( can_run run );
+use PS::IPP::Metadata::Config;
+use PS::IPP::Metadata::List qw( parse_md_list );
+use PS::IPP::Config 1.01 qw( :standard );
+use File::Temp qw( tempfile );
+
+use Getopt::Long qw( GetOptions :config auto_help auto_version gnu_getopt );
+use Pod::Usage qw( pod2usage );
+
+# Look for programs we need
+my $missing_tools;
+my $dqstatstool = can_run('dqstatstool') or (warn "Can't find dqstatstool" and $missing_tools = 1);
+my $dsreg       = can_run('dsreg') or (warn "Can't find dsreg" and $missing_tools = 1);
+if ($missing_tools) {
+    warn("Can't find required tools.");
+    exit($PS_EXIT_CONFIG_ERROR);
+}
+
+my ( $exp_tag, $dqstats_id, $camera, $uri, $dbname, $verbose, $no_update, $no_op, $redirect, $save_temps);
+GetOptions(
+	   'exp_tag=s'       => \$exp_tag, # exposure identifier
+	   'dqstats_id=s'    => \$dqstats_id, # dqstatsrun ID
+	   'camera|c=s'      => \$camera,  # camera
+	   'dbname|d=s'      => \$dbname,  # database name
+	   'uri=s'           => \$uri, # output file destination
+	   'verbose'         => \$verbose, # print to stdout
+	   'no-update'       => \$no_update, # Update the database
+	   'no-op'           => \$no_op,   # don't do the operations
+	   'redirect-output' => \$redirect, 
+	   'save-temps'      => \$save_temps, # save temporary files
+	   ) or pod2usage( 2 );
+
+pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
+pod2usage( -msg => "Required options --exp_tag --dqstats_id --camera",
+	   -exitval => 3,
+	   ) unless
+    defined $exp_tag and
+    defined $dqstats_id and
+    defined $camera;
+
+my $ipprc = PS::IPP::Config->new( $camera ) 
+    or my_die( "Unable to set up", $dqstats_id, $PS_EXIT_CONFIG_ERROR ); # IPP config
+#my $logDest = $ipprc->filename("LOG.EXP", $uri) 
+#    or my_die("Missing entry from camera config", $dqstats_id, $PS_EXIT_CONFIG_ERROR);
+
+if ($redirect) {
+#    $ipprc->redirect_output($logDest) 
+#	or my_die( "Unable to redirect output", $dqstats_id, $PS_EXIT_SYS_ERROR );
+    print "\n\n";
+    print "Starting script $0 on $host\n\n";
+    print "COMMAND IS: @ARGV\n\n";
+}
+
+my $mdcParser = PS::IPP::Metadata::Config->new; # Parser for metadata config files
+
+# Output products
+unless (defined($uri)) {
+    $uri = "/data/${host}.0/tmp/dqstats.${dqstats_id}.fits";
+}
+#$ipprc->outroot_prepare($uri); # hm....need to think more here.
+
+my $bundle_command = "$dqstatstool -dbname $dbname  -createbundle ";
+$bundle_command .= "-dqstats_id $dqstats_id ";
+$bundle_command .= "-uri $uri ";
+
+my $filesetID = "dqstats.${dqstats_id}";
+
+unless ($no_op) {
+    # This bit needs to make the bundle.
+    
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run(command => $bundle_command, verbose => $verbose);
+    unless ($success) {
+	$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	&my_die("Unable to create the bundle.: $error_code", $dqstats_id, $error_code);
+    }
+    
+    # Parse stdout_buf to get output file name
+    ## Begin partial
+    my ($tempFile, $tempName) = tempfile( "/tmp/dqstats.XXXX",
+					  UNLINK => !$save_temps, SUFFIX => 'dsin' );
+    print $tempFile $uri . '|||notset' . "\n";
+
+    my $register_cmd = "$dsreg --product 'DQstats' --add $filesetID --type table --list $tempName --abspath --copy";
+
+    # It can't be that simple, can it? This should probably add a fault on failure.
+    ($success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run(command => $register_cmd, verbose => $verbose);
+    unless ($success) {
+	$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	&my_die("Unable to register the bundle.: $error_code", $dqstats_id, $error_code);
+    }
+    
+    # We no longer need the file generated by dqstatstool.
+    unlink($uri);
+}
+
+my $update_command = "$dqstatstool -dbname $dbname  -dqstats_id $dqstats_id";
+$update_command .= " -updaterun -set_state 'full' ";
+
+unless ($no_update) {
+    # This bit needs to set the database state to full
+    my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
+	run(command => $update_command, verbose => $verbose);
+    unless ($success) {
+	$error_code = (($error_code >> 8) or $PS_EXIT_PROG_ERROR);
+	warn("Unable to add result to database: $error_code\n");
+	exit($error_code);
+    }
+} else {
+    print "skipping command: $update_command\n";
+}
+
+
+sub my_die
+{
+    my $msg = shift; # Warning message on die
+    my $dqstats_id = shift; # Camtool identifier
+    my $exit_code = shift; # Exit code to add
+
+    $exit_code = $PS_EXIT_PROG_ERROR unless defined $exit_code;
+
+    carp($msg);
+    if (defined $dqstats_id and not $no_update) {
+	# There is no fault handling, which may be a mistake. This is where a fault coding would be.
+#         my $command = "$addtool -add_id $add_id";
+#         $command .= " -addprocessedexp";
+#         $command .= " -fault $exit_code";
+#         $command .= " -path_base $outroot" if defined $outroot;
+#         $command .= (" -dtime_addstar " . ((DateTime->now->mjd - $mjd_start) * 86400));
+#         $command .= " -dbname $dbname" if defined $dbname;
+#         system ($command);
+    }
+    exit $exit_code;
+}
+
+END {
+    my $status = $?;
+    system("sync") == 0
+        or die "failed to execute sync: $!" ;
+    $? = $status;
+}
+
+__END__
Index: trunk/ippScripts/scripts/register_imfile.pl
===================================================================
--- trunk/ippScripts/scripts/register_imfile.pl	(revision 26377)
+++ trunk/ippScripts/scripts/register_imfile.pl	(revision 26378)
@@ -130,4 +130,5 @@
     }
     elsif (($isGPC1 == 1) && ($burntoolState == 1)) {
+#	print STDERR "In the good region: >>$burntoolState<<\n";
 	my $ppConfigDump_cmd = "$ppConfigDump -camera GPC1 -dump-camera -";
 	my ( $success, $error_code, $full_buf, $stdout_buf, $stderr_buf ) =
@@ -148,4 +149,5 @@
 	}
 	$burntoolState = $burntoolStateGood; # Positive because this has the header table.
+	
     }
     $cmdflags .= " -burntool_state $burntoolState ";
Index: trunk/ippTasks/Makefile.am
===================================================================
--- trunk/ippTasks/Makefile.am	(revision 26377)
+++ trunk/ippTasks/Makefile.am	(revision 26378)
@@ -29,4 +29,5 @@
 	receive.pro \
 	publish.pro \
+	dqstats.pro \
 	science.cleanup.pro
 
Index: trunk/ippTasks/dqstats.pro
===================================================================
--- trunk/ippTasks/dqstats.pro	(revision 26378)
+++ trunk/ippTasks/dqstats.pro	(revision 26378)
@@ -0,0 +1,239 @@
+## dqstats.pro : globals and support macros : -*- sh -*-
+## this file contains the tasks for running the dqstats analysis stage
+## these tasks use the book dqstatsPendingExp
+
+# test for required global variables
+check.globals
+
+book init dqstatsPendingExp
+
+macro dqstats.status
+  book listbook dqstatsPendingExp
+end
+
+macro dqstats.reset
+  book init dqstatsPendingExp
+end
+
+macro dqstats.on
+  task dqstats.load
+    active true
+  end
+  task dqstats.run
+    active true
+  end
+  task dqstats.revert
+    active true
+  end
+end
+
+macro dqstats.off
+  task dqstats.load
+    active false
+  end
+  task dqstats.run
+    active false
+  end
+  task dqstats.revert
+    active false
+  end
+end
+
+# this variable will cycle through the known database names
+$dqstats_DB = 0
+$dqstats_revert_DB = 0
+
+# select images ready for dqstats analysis
+# new entries are added to dqstatsPendingExp
+# skip already-present entries
+task	       dqstats.load
+  host         local
+
+  periods      -poll $LOADPOLL
+  periods      -exec $LOADEXEC
+  periods      -timeout 30
+  npending     1
+
+  stdout NULL
+  stderr $LOGDIR/dqstats.log
+
+  task.exec
+    if ($LABEL:n == 0) break
+    $run = dqstatstool -pendingbundle
+    if ($DB:n == 0)
+      option DEFAULT
+    else
+      # save the DB name for the exit tasks
+      option $DB:$dqstats_DB
+      $run = $run -dbname $DB:$dqstats_DB
+      $dqstats_DB ++
+      if ($dqstats_DB >= $DB:n) set dqstats_DB = 0
+    end
+    add_poll_args run
+    add_poll_labels run
+    command $run
+  end
+
+  # success
+  task.exit    0
+    # convert 'stdout' to book format
+    ipptool2book stdout dqstatsPendingExp -key dqstats_id -uniq -setword dbname $options:0 -setword pantaskState INIT
+    if ($VERBOSE > 2)
+      book listbook dqstatsPendingExp
+    end
+
+    # delete existing entries in the appropriate pantaskStates
+    process_cleanup dqstatsPendingExp
+  end
+
+  # default exit status
+  task.exit    default
+    showcommand failure
+  end
+
+  task.exit    crash
+    showcommand crash
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
+
+# run the dqstats script on pending exposures
+task	       dqstats.run
+  periods      -poll $RUNPOLL
+  periods      -exec $RUNEXEC
+  periods      -timeout 10
+
+  ## we want only a single outstanding dqstats job.  
+  host         local
+  npending     1
+
+  task.exec
+    book npages dqstatsPendingExp -var N
+    if ($N == 0) break
+    if ($NETWORK == 0) break
+    
+    # look for new images in dqstatsPendingExp (pantaskState == INIT)
+    book getpage dqstatsPendingExp 0 -var pageName -key pantaskState INIT
+    if ("$pageName" == "NULL") break
+
+    book setword dqstatsPendingExp $pageName pantaskState RUN
+    book getword dqstatsPendingExp $pageName camera -var CAMERA
+    book getword dqstatsPendingExp $pageName exp_tag -var EXP_TAG
+    book getword dqstatsPendingExp $pageName dqstats_id -var DQSTATS_ID
+#    book getword dqstatsPendingExp $pageName camroot -var CAMROOT
+#    book getword dqstatsPendingExp $pageName workdir -var WORKDIR_TEMPLATE
+#    book getword dqstatsPendingExp $pageName reduction -var REDUCTION
+#    book getword dqstatsPendingExp $pageName dvodb  -var DVODB
+#    book getword dqstatsPendingExp $pageName image_only -var IMAGE_ONLY
+    book getword dqstatsPendingExp $pageName dbname -var DBNAME
+
+    # specify choice of remote host based on camera and chip (class_id)
+    # set.host.for.camera $CAMERA FPA
+
+    # set the WORKDIR variable
+#    set.workdir.by.camera $CAMERA FPA $WORKDIR_TEMPLATE $default_host WORKDIR
+
+    # notes on how this works:
+    # -- raw workdir examples:
+    # file://data/@HOST@.0/gpc1/20080130
+    # neb:///@HOST@-vol0/gpc1/20080130 (need to supply volname?, or are we re-defining this each time?)
+    # -- out workdir examples:
+    # file://data/ipp004.0/gpc1/20080130
+    # neb:///ipp004-vol0/gpc1/20080130
+
+    ## generate outroot specific to this exposure (& chip)
+    # Uri's need to be standardized when the run is created.
+#    sprintf outroot "%s/%s/%s.dqstats.%s" $WORKDIR $EXP_TAG $EXP_TAG $DQSTATS_ID
+
+    stdout $LOGDIR/dqstats.log
+    stderr $LOGDIR/dqstats.log
+
+    $run = dqstats_bundle.pl --exp_tag $EXP_TAG --dqstats_id $DQSTATS_ID --camera GPC1 --redirect-output
+#     if ("$REDUCTION" != "NULL")
+#       $run = $run --reduction $REDUCTION
+#     end
+#     if ("$IMAGE_ONLY" == "T")
+#       $run = $run --image-only
+#     end
+
+        
+    add_standard_args run
+
+    # save the pageName for future reference below
+    options $pageName
+
+    # create the command line
+    if ($VERBOSE > 1)
+      echo command $run
+    end
+    command $run
+  end
+
+  # success
+  task.exit default
+    process_exit dqstatsPendingExp $options:0 $JOB_STATUS
+  end
+
+  # locked list
+  task.exit    crash
+    showcommand crash
+    echo "hostname: $JOB_HOSTNAME"
+    book setword dqstatsPendingExp $options:0 pantaskState CRASH
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+    book setword dqstatsPendingExp $options:0 pantaskState TIMEOUT
+  end
+end
+
+task dqstats.revert
+  host         local
+
+  periods      -poll 5.0
+  periods      -exec 60.0
+  periods      -timeout 120.0
+  npending     1
+
+  stdout NULL
+  stderr $LOGDIR/revert.log
+
+  task.exec
+    if ($LABEL:n == 0) break
+    $run = dqstatstool -revertrun
+    if ($DB:n == 0)
+      option DEFAULT
+    else
+      # save the DB name for the exit tasks
+      option $DB:$dqstats_revert_DB
+      $run = $run -dbname $DB:$dqstats_revert_DB
+      $dqstats_revert_DB ++
+      if ($dqstats_revert_DB >= $DB:n) set dqstats_revert_DB = 0
+    end
+    add_poll_labels run
+    command $run
+  end
+
+  # success
+  task.exit    0
+  end
+
+  # locked list
+  task.exit    default
+    showcommand failure
+  end
+
+  task.exit    crash
+    showcommand crash
+  end
+
+  # operation times out?
+  task.exit    timeout
+    showcommand timeout
+  end
+end
Index: trunk/ippTasks/summit.copy.pro
===================================================================
--- trunk/ippTasks/summit.copy.pro	(revision 26377)
+++ trunk/ippTasks/summit.copy.pro	(revision 26378)
@@ -147,5 +147,5 @@
 # run pzgetexp periodically to populate pzPendingExp in the database (no I/O)
 # this task is querying the data store for a list of exposures ("filesets")
-# and inserting these into a db table on the local cluster (pzPendingExp)
+# and inserting these into a db table on the local cluster (pzDownloadExp)
 task pzgetexp
   periods      -exec     30
@@ -446,14 +446,15 @@
 	stderr $LOGDIR/summit.copy.log
 
+	book getpage pzDataStore 0 -var PZDSPAGE -key dbname $DBNAME 
+	book getword pzDataStore $PZDSPAGE use_compress  -var USECOMPRESS
+
         $run = summit_copy.pl --uri $URI --filename $FILENAME --exp_name $EXP_NAME --inst $CAMERA --telescope $TELESCOPE --class $CLASS --class_id $CLASS_ID --bytes $BYTES --md5 $MD5SUM --dbname $DBNAME --timeout 120 --verbose --copies 2
-	if ($COMPRESS) 
+	if ($USECOMPRESS == 1) 
             $run = $run --compress
-        else
-            $run = $run --bytes $BYTES 
-        end
-        if (("$MD5SUM" != "NULL") && ("$MD5SUM" != "0") && (not($COMPRESS)))
+        end
+#        if (("$MD5SUM" != "NULL") && ("$MD5SUM" != "0") && (not($COMPRESS)))
 # && (($YEAR > 2008) || (("$YEAR" = "2007") && ($MONTH > 8))))
-            $run = $run --md5 $MD5SUM
-        end
+#            $run = $run --md5 $MD5SUM
+#        end
 	if ($NEBULOUS) 
             $run = $run --nebulous
@@ -463,5 +464,5 @@
         # store the pageName for future reference below
         options $pageName
-
+	
         # create the command line
         if ($VERBOSE > 1)
Index: trunk/ippTools/share/Makefile.am
===================================================================
--- trunk/ippTools/share/Makefile.am	(revision 26377)
+++ trunk/ippTools/share/Makefile.am	(revision 26378)
@@ -140,4 +140,8 @@
      disttool_updateinterest.sql \
      disttool_updatercrun.sql \
+     dqstatstool_createbundle.sql \
+     dqstatstool_definebyquery.sql \
+     dqstatstool_get_contents.sql \
+     dqstatstool_get_run.sql \
      faketool_change_exp_state.sql \
      faketool_change_imfile_data_state.sql \
Index: trunk/ippTools/share/dqstatstool_createbundle.sql
===================================================================
--- trunk/ippTools/share/dqstatstool_createbundle.sql	(revision 26378)
+++ trunk/ippTools/share/dqstatstool_createbundle.sql	(revision 26378)
@@ -0,0 +1,7 @@
+-- Select the information we want from the various tables
+SELECT @COLLIST@ FROM rawExp 
+        JOIN chipRun USING(exp_id) 
+        JOIN camRun USING(chip_id) 
+	JOIN camProcessedExp USING(cam_id)
+        LEFT OUTER JOIN fakeRun USING(cam_id) 
+        LEFT OUTER JOIN warpRun USING(fake_id)
Index: trunk/ippTools/share/dqstatstool_definebyquery.sql
===================================================================
--- trunk/ippTools/share/dqstatstool_definebyquery.sql	(revision 26378)
+++ trunk/ippTools/share/dqstatstool_definebyquery.sql	(revision 26378)
@@ -0,0 +1,9 @@
+-- query to identify unique data to bundle
+
+SELECT exp_id,chip_id,cam_id,warp_id,invalid from rawExp 
+	JOIN chipRun USING(exp_id) 
+	JOIN camRun USING(chip_id) 
+	LEFT OUTER JOIN fakeRun USING(cam_id) 
+	LEFT OUTER JOIN warpRun USING(fake_id)
+	LEFT OUTER JOIN dqstatsContent USING (exp_id,chip_id,cam_id,warp_id)
+WHERE (invalid IS NULL OR invalid = 1)
Index: trunk/ippTools/share/dqstatstool_get_contents.sql
===================================================================
--- trunk/ippTools/share/dqstatstool_get_contents.sql	(revision 26378)
+++ trunk/ippTools/share/dqstatstool_get_contents.sql	(revision 26378)
@@ -0,0 +1,2 @@
+SELECT dqstats_id,exp_id,chip_id,cam_id,warp_id from dqstatsContent
+	WHERE invalid = 0
Index: trunk/ippTools/share/dqstatstool_get_run.sql
===================================================================
--- trunk/ippTools/share/dqstatstool_get_run.sql	(revision 26378)
+++ trunk/ippTools/share/dqstatstool_get_run.sql	(revision 26378)
@@ -0,0 +1,2 @@
+SELECT * from dqstatsRun
+	WHERE state = 'new' AND (fault = 0)
Index: trunk/ippTools/src/Makefile.am
===================================================================
--- trunk/ippTools/src/Makefile.am	(revision 26377)
+++ trunk/ippTools/src/Makefile.am	(revision 26378)
@@ -9,4 +9,5 @@
 	disttool \
 	flatcorr \
+	dqstatstool \
 	magictool \
 	magicdstool \
@@ -32,4 +33,5 @@
 	pxdata.h \
 	pxfake.h \
+	pxdqstats.h
 	pxmagic.h \
 	pxregister.h \
@@ -52,4 +54,5 @@
 	flatcorr.h \
 	faketool.h \
+	dqstatstool.h \
 	magictool.h \
 	magicdstool.h \
@@ -77,4 +80,5 @@
 	pxfake.c \
 	pxfault.c \
+	pxdqstats.c \
 	pxmagic.c \
 	pxregister.c \
@@ -142,4 +146,10 @@
     faketool.c \
     faketoolConfig.c
+
+dqstatstool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+dqstatstool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+dqstatstool_SOURCES = \
+    dqstatstool.c \
+    dqstatstoolConfig.c
 
 magictool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
Index: trunk/ippTools/src/dqstatstool.c
===================================================================
--- trunk/ippTools/src/dqstatstool.c	(revision 26378)
+++ trunk/ippTools/src/dqstatstool.c	(revision 26378)
@@ -0,0 +1,675 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+
+#include "pxtools.h"
+#include "pxdqstats.h"
+#include "dqstatstool.h"
+
+static bool definebyqueryMode(pxConfig *config);
+static bool createbundleMode(pxConfig *config);
+//static bool cleanbundleMode(pxConfig *config);
+static bool pendingbundleMode(pxConfig *config);
+static bool updaterunMode(pxConfig *config);
+static bool revertrunMode(pxConfig *config);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv) {
+  psLibInit(NULL);
+
+  pxConfig *config = dqstatstoolConfig(NULL, argc, argv);
+  if (!config) {
+    psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+    goto FAIL;
+  }
+
+  switch (config->mode) {
+    MODECASE(DQSTATS_MODE_DEFINEBYQUERY, definebyqueryMode);
+    MODECASE(DQSTATS_MODE_PENDINGBUNDLE, pendingbundleMode);
+    MODECASE(DQSTATS_MODE_CREATEBUNDLE, createbundleMode);
+    //    MODECASE(DQSTATS_MODE_CLEANBUNDLE, cleanbundleMode);
+    MODECASE(DQSTATS_MODE_UPDATERUN, updaterunMode);
+    MODECASE(DQSTATS_MODE_REVERTRUN, revertrunMode);
+  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 definebyqueryMode(pxConfig *config) {
+  PS_ASSERT_PTR_NON_NULL(config,NULL);
+
+  // optional
+  PXOPT_LOOKUP_STR(label,       config->args, "-label", false, false);
+  PXOPT_LOOKUP_TIME(registered, config->args, "-set_registered", false, false);
+  PXOPT_LOOKUP_STR(set_label,   config->args, "-set_label", false, false);
+  PXOPT_LOOKUP_BOOL(pretend,    config->args, "-pretend", false);
+  PXOPT_LOOKUP_BOOL(simple,     config->args, "-simple", false);
+  PXOPT_LOOKUP_BOOL(force,      config->args, "-force", false);
+  PXOPT_LOOKUP_U64(limit,       config->args, "-limit", false, 0);
+  
+  // properties of the raw exposures
+  psMetadata *where = psMetadataAlloc();
+
+  pxdqstatsGetSearchArgs(config, where);
+
+  psString query = pxDataGet("dqstatstool_definebyquery.sql");
+  if (!query) {
+    psError(PXTOOLS_ERR_DATA, false, "failed to retreive SQL statement");
+    psFree(where);
+    return(false);
+  }
+
+  if (label) {
+    PXOPT_COPY_STR(config->args, where, "-label", "camRun.label", "==");
+  }
+  // use psDBGenerateWhereConditionSQL because the SQL ends in a WHERE
+  if (where && psListLength(where->list)) {
+    psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+    psStringAppend(&query, " AND %s", whereClause);
+    psFree(whereClause);
+  }
+  if (force) {
+    psStringAppend(&query, " OR invalid = 0");
+  }
+  
+  psFree(where);
+  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;
+  }
+  //  printf("%s\n",query);
+  psFree(query);
+
+  psArray *output = p_psDBFetchResult(config->dbh);
+  if (!output) {
+    psError(PS_ERR_UNKNOWN, false, "database error");
+    return false;
+  }
+  if (!psArrayLength(output)) {
+    psTrace("dqstatstool", PS_LOG_INFO, "no rows found");
+    psWarning("dqstatstool: no rows found");
+    psFree(output);
+    return true;
+  }
+
+  if (pretend) {
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "dqstatsContent", !simple)) {
+      psError(PS_ERR_UNKNOWN, false, "failed to print array");
+      psFree(output);
+      return false;
+    }
+    psFree(output);
+    return true;
+  }
+
+  if (!psDBTransaction(config->dbh)) {
+    psError(PS_ERR_UNKNOWN, false, "database error");
+    psFree(where);
+    return false;
+  }
+  
+  // Create the run entry and grab the dqstats_id
+  dqstatsRunRow *run = dqstatsRunRowAlloc(
+					  0,        // ID
+					  "new",    // state
+					  registered,
+					  set_label,
+					  0         // fault
+					  );
+  
+  if (!dqstatsRunInsertObject(config->dbh, run)) {
+    if (!psDBRollback(config->dbh)) {
+      psError(PS_ERR_UNKNOWN, false, "database error");
+    }
+    psError(PS_ERR_UNKNOWN, false, "database error");
+    psFree(output);
+    psFree(run);
+    return(false);
+  }
+
+  psS64 dqstats_id = psDBLastInsertID(config->dbh);
+
+  psArray *list = psArrayAllocEmpty(16); // List of runs to print
+  psArrayAdd(list, list->n, run);
+  if (!dqstatsRunPrintObjects(stdout, list, !simple)) {
+    psError(PS_ERR_UNKNOWN, false, "failed to print object");
+    psFree(list);
+    return(false);
+  }
+  psFree(list);
+
+  // Create the content entry.
+  for (long i = 0; i < output->n; i++) {
+    psMetadata *row = output->data[i]; // Row from select
+    bool status;
+
+    psS64 exp_id = psMetadataLookupS64(&status, row, "exp_id");
+    psS64 chip_id= psMetadataLookupS64(&status, row, "chip_id");
+    psS64 cam_id = psMetadataLookupS64(&status, row, "cam_id");
+    psS64 warp_id= psMetadataLookupS64(&status, row, "warp_id");
+
+    if (!dqstatsContentInsert(config->dbh,
+			      dqstats_id,
+			      exp_id,
+			      chip_id,
+			      cam_id,
+			      warp_id,
+			      0 // do not create automatically invalid contents
+			      )) {
+      psError(PS_ERR_UNKNOWN, false, "database error");
+      psFree(output);
+      if (!psDBRollback(config->dbh)) {
+	psError(PS_ERR_UNKNOWN, false, "database error");
+      }
+      return(false);
+    }
+  }
+
+  psFree(output);
+
+  if (!psDBCommit(config->dbh)) {
+    psError(PS_ERR_UNKNOWN, false, "database error");
+    return(false);
+  }
+
+  return(true);
+}
+
+static bool pendingbundleMode(pxConfig *config) {
+  PS_ASSERT_PTR_NON_NULL(config,false);
+  
+  PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+  PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+  
+  
+  psMetadata *where = psMetadataAlloc();
+  //  pxdqstatsGetSearchArgs (config, where); 
+  PXOPT_COPY_S64(config->args, where, "-dqstats_id", "dqstatsRun.dqstats_id", "==");
+  PXOPT_COPY_STR(config->args, where, "-label", "dqstatsRun.label", "==");
+  
+  psString query = pxDataGet("dqstatstool_get_run.sql");           // query
+  if (!query) {
+    psError(PXTOOLS_ERR_DATA, false, "Failed to retrieve sql statement (dqstatstool_get_run.sql)");
+    return(false);
+  }
+
+  if (where && psListLength(where->list)) {
+    psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+    psStringAppend(&query, " AND %s", whereClause);
+    psFree(whereClause);
+  }
+  psFree(where);
+
+  // 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);                                                  // where whereClause
+  
+  psArray *output = p_psDBFetchResult(config->dbh);               // where whereClause output
+  if (!output) {
+    psError(PS_ERR_UNKNOWN, false, "database error");
+    return(false);
+  }
+  if (!psArrayLength(output)) {
+    psTrace("dqstatstool", PS_LOG_INFO, "no rows found");
+    psFree(output);
+    return true;
+  }
+
+  // negative simple so the default is true
+  if (!ippdbPrintMetadatas(stdout, output, "dqstatsPendingBundle", !simple)) {
+    psError(PS_ERR_UNKNOWN, false, "failed to print array");
+    psFree(output);
+    return false;
+  }
+
+  psFree(output);
+
+  return(true);
+}
+//static bool cleanbundleMode(pxConfig *config) {
+//  return(true);
+//}
+
+
+static bool createbundleMode(pxConfig *config) {
+  PS_ASSERT_PTR_NON_NULL(config,false);
+
+  bool status = true;
+  PXOPT_LOOKUP_S64(dqstats_id,       config->args, "-dqstats_id", true, 0);
+  PXOPT_LOOKUP_STR(bundleUri,        config->args, "-uri",        true, 0);
+
+  // Determine the information about this run.
+  psString runQuery = pxDataGet("dqstatstool_get_run.sql");
+  if (!runQuery) {
+    psError(PXTOOLS_ERR_DATA, false, "Failed to retrieve sql statement (dqstatstool_get_run.sql)");
+    return(false);
+  }
+
+  psMetadata *where = psMetadataAlloc();
+  PXOPT_COPY_S64(config->args, where, "-dqstats_id",      "dqstats_id", "==");
+  psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);  // Save for content query
+  psStringAppend(&runQuery, " AND %s", whereClause);
+  psFree(where); 
+
+  if (!p_psDBRunQuery(config->dbh, runQuery)) {
+    psError(PS_ERR_UNKNOWN, false, "database error");
+    psFree(runQuery);
+    psFree(whereClause);
+    return(false);
+  }
+  psFree(runQuery);
+  
+  psArray *output = p_psDBFetchResult(config->dbh);
+  if (!output) {
+    psError(PS_ERR_UNKNOWN, false, "database error");
+    psFree(whereClause);
+    return(false);
+  }
+
+  if (psArrayLength(output) != 1) {
+    psError(PS_ERR_UNKNOWN, false, "dqstatstool: single return row not found.");
+    psFree(output);
+    psFree(whereClause);
+    return(false);
+  }
+
+  psMetadata *run = output->data[0];
+  if (strcmp(psMetadataLookupStr(&status,run,"state"),"new") != 0) {
+    psError(PS_ERR_UNKNOWN, false, "dqstatstool: bundle already created for this run (should not happen).");
+    psFree(output);
+    psFree(whereClause);
+    return(false);
+  }
+  psFree(output);   // This held the information about the Run.
+
+  // Read column list from config file.
+  psMetadata *recipe = psMetadataLookupPtr(&status, config->modules->recipes, "DQSTATS");
+  psMetadata *columns = psMetadataLookupMetadata(&status,recipe,"STATS.COLUMNS");
+  if (!columns) {
+    psError(PS_ERR_UNKNOWN, false, "Could not read STATS.COLUMN metadata");
+    psFree(whereClause);
+    return(false);
+  }
+  
+  psMetadataIterator *iter = psMetadataIteratorAlloc(columns, PS_LIST_HEAD,NULL);
+
+  psMetadataItem *colItem = NULL;
+  psString columnlist = NULL;
+
+  psMetadata *colNames = psMetadataAlloc();
+  psMetadata *colTypes = psMetadataAlloc();
+  while ((colItem = psMetadataGetAndIncrement(iter))) {
+    bool status;
+    psMetadata *coldef = colItem->data.md;
+
+    if (columnlist) { // Stick intervening commas
+      psStringAppend(&columnlist, ", ");
+    }
+    
+    psStringAppend(&columnlist, "%s.%s",
+		   psMetadataLookupStr(&status,coldef,"TABLE"),
+		   psMetadataLookupStr(&status,coldef,"ENTRY"));
+
+    psMetadataAddStr(colNames,PS_LIST_TAIL,
+		     psMetadataLookupStr(&status,coldef,"ENTRY"),0,"",
+		     psMetadataLookupStr(&status,coldef,"COLNAME"));
+    psMetadataAddStr(colTypes,PS_LIST_TAIL,
+		     psMetadataLookupStr(&status,coldef,"ENTRY"),0,"",
+		     psMetadataLookupStr(&status,coldef,"DATATYPE"));
+  }
+  psFree(iter);
+  
+  if (!columnlist) {
+    psError(PS_ERR_UNKNOWN, false, "Failed to find a columnlist");
+    psFree(whereClause);
+    psFree(colNames);
+    psFree(colTypes);
+    return(false);
+  }
+  
+  // Find the contents that comprise this run
+  psString contentQuery = pxDataGet("dqstatstool_get_contents.sql");
+  if (!contentQuery) {
+    psError(PXTOOLS_ERR_DATA, false, "Failed to retrieve sql statement (dqstatstool_get_contents.sql)");
+    psFree(whereClause);
+    psFree(colNames);
+    psFree(colTypes);
+    psFree(columnlist);
+    return(false);
+  }
+
+  psStringAppend(&contentQuery, " AND %s", whereClause);
+  psFree(whereClause);
+
+  if (!p_psDBRunQuery(config->dbh, contentQuery)) {
+    psError(PS_ERR_UNKNOWN, false, "database error");
+    psFree(colNames);
+    psFree(colTypes);
+    psFree(columnlist);
+    psFree(contentQuery);
+    return(false);
+  }
+  psFree(contentQuery);
+
+  psArray *contents = p_psDBFetchResult(config->dbh);
+  if (!contents) {
+    psError(PS_ERR_UNKNOWN, false, "database error");
+    psFree(colNames);
+    psFree(colTypes);
+    psFree(columnlist);
+    return(false);
+  }
+  if (!psArrayLength(contents)) {
+    psWarning("dqstatstool No rows found");
+    psFree(colNames);
+    psFree(colTypes);
+    psFree(columnlist);
+    psFree(contents);
+    return(false);
+  }
+
+  // Grab data from the database for a row of the bundle.
+  psString query = pxDataGet("dqstatstool_createbundle.sql");
+  if (!query) {
+    psError(PXTOOLS_ERR_DATA, false, "Failed to retrieve sql statement");
+    psFree(colNames);
+    psFree(colTypes);
+    psFree(columnlist);
+    psFree(contents);
+    return(false);
+  }
+  // From disttool.c:
+  // "psStringSubstitute fails unless the input is a psString which it determines by
+  //  comparing the memory blocks free function to an expected value.
+  //  pxDataGet uses psSlurp which leaves a different free function on the memory block.
+  //  To work around this make a copy of the query before doing the substitution."
+  psString queryCopy = psStringCopy(query);
+  psFree(query);
+  query = queryCopy;
+  psStringSubstitute(&query,columnlist, "@COLLIST@");
+  psFree(columnlist);
+
+  psArray *outTable = psArrayAllocEmpty(contents->n);
+  for (long i = 0; i < contents->n; i++) {   // Loop over content entries.
+    psMetadata *row = contents->data[i];
+
+    // Limit it just to the stuff we care about for this row.
+    bool status;
+    psString MYquery = psStringCopy(query);
+    psMetadata *where = psMetadataAlloc();
+
+    psMetadataAddS64(where,PS_LIST_TAIL, "rawExp.exp_id", 0, "==", psMetadataLookupS64(&status,row,"exp_id"));
+    psMetadataAddS64(where,PS_LIST_TAIL, "chipRun.chip_id", 0, "==", psMetadataLookupS64(&status,row,"chip_id"));
+    psMetadataAddS64(where,PS_LIST_TAIL, "camRun.cam_id", 0, "==", psMetadataLookupS64(&status,row,"cam_id"));
+    // This may not be real, so we need to check first. Is there a better way to check for a null here?
+    if (psMetadataLookupS64(&status,row,"warp_id") && (psMetadataLookupS64(&status,row,"warp_id") != 9223372036854775807)) {
+      psMetadataAddS64(where,PS_LIST_TAIL, "warpRun.warp_id", 0, "==", psMetadataLookupS64(&status,row,"warp_id"));
+    }
+    
+    psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+    psStringAppend(&MYquery, " WHERE %s", whereClause);    
+    psFree(whereClause);
+    psFree(where);
+    //    printf("%s\n",MYquery);
+
+    if (!p_psDBRunQuery(config->dbh, MYquery)) {
+      psError(PS_ERR_UNKNOWN, false, "database error");
+      psFree(MYquery);
+      psFree(query);
+      psFree(colNames);
+      psFree(colTypes);
+      psFree(contents);
+      psFree(outTable);
+      return(false);
+    }
+    psFree(MYquery);
+    
+    psArray *statsoutput = p_psDBFetchResult(config->dbh);
+    if (!statsoutput) {
+      psError(PS_ERR_UNKNOWN, false, "database error");
+      psFree(colNames);
+      psFree(colTypes);
+      psFree(contents);
+      psFree(query);
+      psFree(outTable);
+      return(false);
+    }
+    if (!psArrayLength(statsoutput)) {
+      psWarning("dqstatstool No stats rows found");
+      psFree(colNames);
+      psFree(colTypes);
+      psFree(contents);
+      psFree(query);
+      psFree(outTable);
+      psFree(statsoutput);
+      return(false);
+    }
+
+    // Magic happens and the fits table is filled.
+    psMetadata *statsrow = statsoutput->data[0]; // Because there should only be one row.
+
+    psMetadataIterator *statIter = psMetadataIteratorAlloc(statsrow,PS_LIST_HEAD,NULL);
+
+    psMetadataItem *rowItem = NULL;
+    psMetadata *tableRow = psMetadataAlloc();
+    // set args -dbname gpc1 -createbundle -dqstats_id 2 -uri /tmp/czwtest.fits
+    while ((rowItem = psMetadataGetAndIncrement(statIter))) {
+      bool success = false;
+
+      psString colType = psMetadataLookupStr(&status,colTypes,rowItem->name);
+      if (!colType) {
+	psError(PS_ERR_UNKNOWN,false, "Could not find type for %s",rowItem->name);
+	return(false);
+      }
+      psString colName = psMetadataLookupStr(&status,colNames,rowItem->name);
+      if (!colName) {
+	psError(PS_ERR_UNKNOWN,false, "Could not find name for %s",rowItem->name);
+	return(false);
+      }
+
+      if (!strcmp(colType,"PS_DATA_BOOL")) {
+	success = psMetadataAddBool(tableRow,PS_LIST_TAIL,colName,0,"",rowItem->data.B);
+      }
+      else if (!strcmp(colType,"PS_DATA_S8")) {
+	success = psMetadataAddS8(tableRow,PS_LIST_TAIL,colName,0,"",rowItem->data.S8);
+      }
+      else if (!strcmp(colType,"PS_DATA_S16")) {
+	success = psMetadataAddS16(tableRow,PS_LIST_TAIL,colName,0,"",rowItem->data.S16);
+      }
+      else if (!strcmp(colType,"PS_DATA_S32")) {
+	success = psMetadataAddS32(tableRow,PS_LIST_TAIL,colName,0,"",rowItem->data.S32);
+      }
+      else if (!strcmp(colType,"PS_DATA_S64")) {
+	success = psMetadataAddS64(tableRow,PS_LIST_TAIL,colName,0,"",rowItem->data.S64);
+      }
+      else if (!strcmp(colType,"PS_DATA_U8")) {
+	success = psMetadataAddU8(tableRow,PS_LIST_TAIL,colName,0,"",rowItem->data.U8);
+      }
+      else if (!strcmp(colType,"PS_DATA_U16")) {
+	success = psMetadataAddU16(tableRow,PS_LIST_TAIL,colName,0,"",rowItem->data.U16);
+      }
+      else if (!strcmp(colType,"PS_DATA_U32")) {
+	success = psMetadataAddU32(tableRow,PS_LIST_TAIL,colName,0,"",rowItem->data.U32);
+      }
+      else if (!strcmp(colType,"PS_DATA_U64")) {
+	success = psMetadataAddU64(tableRow,PS_LIST_TAIL,colName,0,"",rowItem->data.U64);
+      }
+      else if (!strcmp(colType,"PS_DATA_F32")) {
+	success = psMetadataAddF32(tableRow,PS_LIST_TAIL,colName,0,"",rowItem->data.F32);
+      }
+      else if (!strcmp(colType,"PS_DATA_F64")) {
+	success = psMetadataAddF64(tableRow,PS_LIST_TAIL,colName,0,"",rowItem->data.F64);
+      }
+      else if (!strcmp(colType,"PS_DATA_STRING")) {
+	success = psMetadataAddStr(tableRow,PS_LIST_TAIL,colName,0,"",rowItem->data.str);
+      }
+      
+      if (!success) {
+	psError(PS_ERR_UNKNOWN,false, "Error constructing fits table row.");
+	psFree(colNames);
+	psFree(colTypes);
+	psFree(contents);
+	psFree(query);
+	psFree(outTable);
+	psFree(statsoutput);
+	psFree(statIter);
+	psFree(tableRow);
+	psFree(colType);
+	psFree(colName);
+	return(false);
+      }
+    }
+
+    psArrayAdd(outTable,0,tableRow);
+    psFree(tableRow);
+  }
+  psFree(colNames);
+  psFree(colTypes);
+  psFree(contents);
+  psFree(query);
+  // Define the fits table here.
+  
+  psFits *Table = psFitsOpen(bundleUri,"w");
+  if (!Table) {
+    psError(PS_ERR_UNKNOWN, false, "Could not open the fits table for writing.");
+    psFree(outTable);
+    return(false);
+  }  
+
+  psMetadata *header = psMetadataAlloc();
+  psMetadataAddS64(header, PS_LIST_TAIL, "DQSTATS_ID", 0, "ID number of DQSTATS run", dqstats_id);
+  
+  if (!psFitsWriteTable(Table,header,outTable,"DQSTATS")) {
+    psError(PS_ERR_UNKNOWN, false, "Unable to write table to fits file.");
+    psFree(outTable);
+    psFree(Table);
+    psFree(header);
+    return(false);
+  }
+  psFree(outTable);
+  psFree(header);
+
+  // Tear down, cleanup.
+  psFitsClose(Table);
+  return(true);  
+}
+  
+
+static bool updaterunMode(pxConfig *config) {
+  PS_ASSERT_PTR_NON_NULL(config,false);
+  PXOPT_LOOKUP_S64(dqstats_id, config->args, "-dqstats_id",     "dqstatsRun.dqstats_id", "==");
+  PXOPT_LOOKUP_STR(state,      config->args, "-set_state",      "state",                 "==");
+  PXOPT_LOOKUP_S16(fault,      config->args, "-fault",          "fault",                 "==");
+  
+  if (state && ! pxIsValidState(state)) {
+    psError(PXTOOLS_ERR_DATA, false, "pxIsValidState failed");
+    return(false);
+  }
+  
+  psMetadata *where = psMetadataAlloc();
+  PXOPT_COPY_S64(config->args, where, "-dqstats_id", "dqstats_id", "==");
+  PXOPT_COPY_S64(config->args, where, "-state", "state", "==");
+  
+  if (!psListLength(where->list)) {
+    psFree(where);
+    psError(PXTOOLS_ERR_DATA, false, "search parameters are required");
+    return(false);
+  }
+  
+  psString query = psStringCopy("UPDATE dqstatsRun ");
+  psStringAppend(&query," SET state = '%s' ", state);
+  if (fault) {
+    psStringAppend(&query, ", fault = %d ", fault);
+  }
+  psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+  psStringAppend(&query," WHERE %s ",whereClause);
+  
+  if (!p_psDBRunQuery(config->dbh, query)) {
+    psError(PS_ERR_UNKNOWN, false, "database error");
+    return(false);
+  }
+  
+  psFree(query);
+  psFree(where);
+  psFree(whereClause);
+  
+  return(true);
+}
+
+static bool revertrunMode(pxConfig *config) {
+  PS_ASSERT_PTR_NON_NULL(config,false);
+
+  psMetadata *where = psMetadataAlloc();
+  PXOPT_COPY_S64(config->args, where, "-dqstats_id", "dqstats_id", "==");
+  PXOPT_COPY_STR(config->args, where, "-label", "label", "==");
+
+  if (!psListLength(where->list)) {
+    psFree(where);
+    psError(PXTOOLS_ERR_DATA, false, "search parameters are required");
+    return(false);
+  }
+
+  psString query = psStringCopy("UPDATE dqstatsRun SET fault = 0 ");
+  psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+  psStringAppend(&query," WHERE fault != 0 AND %s ",whereClause);
+
+  if (!p_psDBRunQuery(config->dbh, query)) {
+    psError(PS_ERR_UNKNOWN, false, "database error");
+    return(false);
+  }
+
+  psFree(query);
+  psFree(whereClause);
+  psFree(where);
+
+  return(true);
+}
+    
+  
+  
+    
+
+    
+    
Index: trunk/ippTools/src/dqstatstool.h
===================================================================
--- trunk/ippTools/src/dqstatstool.h	(revision 26378)
+++ trunk/ippTools/src/dqstatstool.h	(revision 26378)
@@ -0,0 +1,18 @@
+#ifndef DQSTATS_H
+#define DQSTATS_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+  DQSTATS_MODE_NONE            = 0x0,
+  DQSTATS_MODE_DEFINEBYQUERY,
+  DQSTATS_MODE_PENDINGBUNDLE,  
+  DQSTATS_MODE_CREATEBUNDLE,
+  DQSTATS_MODE_CLEANBUNDLE,
+  DQSTATS_MODE_UPDATERUN,
+  DQSTATS_MODE_REVERTRUN,
+} dqstatstoolMode;
+
+pxConfig *dqstatstoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif
Index: trunk/ippTools/src/dqstatstoolConfig.c
===================================================================
--- trunk/ippTools/src/dqstatstoolConfig.c	(revision 26378)
+++ trunk/ippTools/src/dqstatstoolConfig.c	(revision 26378)
@@ -0,0 +1,99 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+#include <stdint.h>
+#include <psmodules.h>
+#include "pxtools.h"
+#include "pxdqstats.h"
+#include "dqstatstool.h"
+
+pxConfig *dqstatstoolConfig(pxConfig *config, int argc, char **argv) {
+
+  if (!config) {
+    config = pxConfigAlloc();
+  }
+
+  pmConfigReadParamsSet(false);
+
+  config->modules = pmConfigRead(&argc, argv, "DQSTATS");
+  if (!config->modules) {
+    psError(PS_ERR_UNKNOWN, false, "Can't find site configuration");
+    psFree(config);
+    return(NULL);
+  }
+  psTime *now = psTimeGetNow(PS_TIME_TAI);
+  // -definebyquery
+  psMetadata *definebyqueryArgs = psMetadataAlloc();
+  psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-label",          PS_META_DUPLICATE_OK, "search by camRun label", NULL);
+  psMetadataAddTime(definebyqueryArgs, PS_LIST_TAIL, "-set_registered",  0,                   "time detrend run was registered", now);
+  psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-set_label",       0,                   "set run label", NULL);
+  
+  pxdqstatsSetSearchArgs(definebyqueryArgs);
+   
+  psMetadataAddBool(definebyqueryArgs, PS_LIST_TAIL, "-pretend",       0, "do not actually modify the database", false);
+  psMetadataAddBool(definebyqueryArgs, PS_LIST_TAIL, "-simple",        0, "use the simple output format", false);
+  psMetadataAddBool(definebyqueryArgs, PS_LIST_TAIL, "-force",         0, "invalidate old entries that match", false);
+  psMetadataAddU64(definebyqueryArgs, PS_LIST_TAIL, "-limit",          0, "limit bundle size to N rows", 0);
+
+  // -createbundle
+  psMetadata *createbundleArgs = psMetadataAlloc();
+  psMetadataAddS64(createbundleArgs, PS_LIST_TAIL, "-dqstats_id",      0, "search by dqstats_id", 0);
+  psMetadataAddStr(createbundleArgs, PS_LIST_TAIL, "-uri",             0, "output table URI", 0);
+
+  // -updaterun
+  psMetadata *updaterunArgs = psMetadataAlloc();
+  psMetadataAddS64(updaterunArgs, PS_LIST_TAIL, "-dqstats_id",         0, "search by dqstats_id", 0);
+  psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-state",              0, "search by dqstatsRun state", NULL);
+  psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_label",          0, "set label", NULL);
+  psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_state",          0, "set dqstatsRun stats", NULL);
+  
+  // -pendingbundle
+  psMetadata *pendingbundleArgs = psMetadataAlloc();
+  psMetadataAddS64(pendingbundleArgs, PS_LIST_TAIL, "-dqstats_id",         0, "search by dqstats_id", 0);
+  psMetadataAddU64(pendingbundleArgs, PS_LIST_TAIL, "-limit",              0, "limit number of bundles to N", 0);
+  psMetadataAddBool(pendingbundleArgs, PS_LIST_TAIL, "-simple",        0, "use the simple output format", false);
+  psMetadataAddStr(pendingbundleArgs, PS_LIST_TAIL, "-label",          0, "search by dqstatsRun.label", NULL);
+
+  // -revertrun
+  psMetadata *revertrunArgs = psMetadataAlloc();
+  psMetadataAddStr(revertrunArgs, PS_LIST_TAIL, "-label",              0, "search by dqstatsRun.label", NULL);
+  psMetadataAddS64(revertrunArgs, PS_LIST_TAIL, "-dqstats_id",         0, "search by dqstats_id", 0);
+  
+  psMetadata *argSets = psMetadataAlloc();
+  psMetadata *modes = psMetadataAlloc();
+
+  PXOPT_ADD_MODE("-definebyquery",   "create runs from cam stage",       DQSTATS_MODE_DEFINEBYQUERY, definebyqueryArgs);
+  PXOPT_ADD_MODE("-updaterun",       "change dqstats run properties",    DQSTATS_MODE_UPDATERUN,     updaterunArgs);
+  PXOPT_ADD_MODE("-createbundle",    "create the bundle for a run",      DQSTATS_MODE_CREATEBUNDLE,  createbundleArgs);
+  PXOPT_ADD_MODE("-pendingbundle",   "list bundles needing creation",    DQSTATS_MODE_PENDINGBUNDLE, pendingbundleArgs);
+  PXOPT_ADD_MODE("-revertrun",       "revert runs with errors",          DQSTATS_MODE_REVERTRUN,     revertrunArgs);  
+
+  if (!pxGetOptions(stderr, argc, argv, config, modes, argSets)) {
+    psError(PS_ERR_UNKNOWN, false, "option parsing failed");
+    psFree(argSets);
+    psFree(modes);
+    psFree(config);
+    return(NULL);
+  }
+
+  psFree(argSets);
+  psFree(modes);
+
+  // define Database handle, if used
+  config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+  if (!config->dbh) {
+    psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+    psFree(config);
+    return(NULL);
+  }
+
+  //  config = psMetadataConfigRead(config,1,"dqstatsTool.config",1);
+  
+  return config;
+}
+ 
+  
+   
+
Index: trunk/ippTools/src/pxdqstats.c
===================================================================
--- trunk/ippTools/src/pxdqstats.c	(revision 26378)
+++ trunk/ippTools/src/pxdqstats.c	(revision 26378)
@@ -0,0 +1,103 @@
+/* pxdqstats.c
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <ippdb.h>
+#include <string.h>
+
+#include "pxtools.h"
+#include "pxdqstats.h"
+
+bool pxdqstatsSetSearchArgs (psMetadata *md) {
+
+  psMetadataAddS64(md,  PS_LIST_TAIL, "-cam_id",            0, "search by cam_id", 0);
+  psMetadataAddS64(md,  PS_LIST_TAIL, "-exp_id",             0, "search by exp_id", 0);
+  psMetadataAddStr(md,  PS_LIST_TAIL, "-exp_name",           0, "search by exp_name", NULL);
+  psMetadataAddStr(md,  PS_LIST_TAIL, "-inst",               0, "search for camera", NULL);
+  psMetadataAddStr(md,  PS_LIST_TAIL, "-telescope",          0, "search for telescope", NULL);
+  psMetadataAddTime(md, PS_LIST_TAIL, "-dateobs_begin",      0, "search for exposures by time (>=)", NULL);
+  psMetadataAddTime(md, PS_LIST_TAIL, "-dateobs_end",        0, "search for exposures by time (<)", NULL);
+  psMetadataAddStr(md,  PS_LIST_TAIL, "-exp_tag",            0, "search by exp_tag", NULL);
+  psMetadataAddStr(md,  PS_LIST_TAIL, "-exp_type",           0, "search by exp_type", NULL);
+  psMetadataAddStr(md,  PS_LIST_TAIL, "-comment",            0, "search by comment", NULL);
+  psMetadataAddStr(md,  PS_LIST_TAIL, "-filelevel",          0, "search by filelevel", NULL);
+  psMetadataAddStr(md,  PS_LIST_TAIL, "-filter",             0, "search for filter", NULL);
+  psMetadataAddF64(md,  PS_LIST_TAIL, "-airmass_min",        0, "define min airmass", NAN);
+  psMetadataAddF64(md,  PS_LIST_TAIL, "-airmass_max",        0, "define max airmass", NAN);
+  psMetadataAddF64(md,  PS_LIST_TAIL, "-ra_min",             0, "define min RA (degrees) ", NAN);
+  psMetadataAddF64(md,  PS_LIST_TAIL, "-ra_max",             0, "define max RA (degrees) ", NAN);
+  psMetadataAddF64(md,  PS_LIST_TAIL, "-decl_min",           0, "define min DEC (degrees)", NAN);
+  psMetadataAddF64(md,  PS_LIST_TAIL, "-decl_max",           0, "define max DEC (degrees)", NAN);
+  psMetadataAddF32(md,  PS_LIST_TAIL, "-exp_time_min",       0, "define min exposure time", NAN);
+  psMetadataAddF32(md,  PS_LIST_TAIL, "-exp_time_max",       0, "define max exposure time", NAN);
+  psMetadataAddF32(md,  PS_LIST_TAIL, "-sat_pixel_frac_min", 0, "define max fraction of saturated pixels", NAN);
+  psMetadataAddF32(md,  PS_LIST_TAIL, "-sat_pixel_frac_max", 0, "define max fraction of saturated pixels", NAN);
+  psMetadataAddF64(md,  PS_LIST_TAIL, "-bg_min",             0, "define min background", NAN);
+  psMetadataAddF64(md,  PS_LIST_TAIL, "-bg_max",             0, "define max background", NAN);
+  psMetadataAddF64(md,  PS_LIST_TAIL, "-bg_stdev_min",       0, "define min background standard deviation", NAN);
+  psMetadataAddF64(md,  PS_LIST_TAIL, "-bg_stdev_max",       0, "define max background standard deviation", NAN);
+  psMetadataAddF64(md,  PS_LIST_TAIL, "-bg_mean_stdev_min",  0, "define min background mean standard deviation (across imfiles)", NAN);
+  psMetadataAddF64(md,  PS_LIST_TAIL, "-bg_mean_stdev_max",  0, "define max background mean standard deviation (across imfiles)", NAN);
+  psMetadataAddF64(md,  PS_LIST_TAIL, "-alt_min",            0, "define min altitude", NAN);
+  psMetadataAddF64(md,  PS_LIST_TAIL, "-alt_max",            0, "define max altitude", NAN);
+  psMetadataAddF64(md,  PS_LIST_TAIL, "-az_min",             0, "define min azimuth ", NAN);
+  psMetadataAddF64(md,  PS_LIST_TAIL, "-az_max",             0, "define max azimuth ", NAN);
+  psMetadataAddF32(md,  PS_LIST_TAIL, "-ccd_temp_min",       0, "define min ccd tempature", NAN);
+  psMetadataAddF32(md,  PS_LIST_TAIL, "-ccd_temp_max",       0, "define max ccd tempature", NAN);
+  psMetadataAddF64(md,  PS_LIST_TAIL, "-posang_min",         0, "define min rotator position angle", NAN);
+  psMetadataAddF64(md,  PS_LIST_TAIL, "-posang_max",         0, "define max rotator position angle", NAN);
+  psMetadataAddStr(md,  PS_LIST_TAIL, "-object",             0, "search by exposure object", NULL);
+  psMetadataAddF32(md,  PS_LIST_TAIL, "-sun_angle_min",         0, "define min solar angle", NAN);
+  psMetadataAddF32(md,  PS_LIST_TAIL, "-sun_angle_max",         0, "define max solar angle", NAN);
+
+  return true;
+}
+
+bool pxdqstatsGetSearchArgs (pxConfig *config, psMetadata *where) {
+
+  PXOPT_COPY_S64(config->args,   where, "-cam_id",            "camRun.cam_id",      "==");
+  PXOPT_COPY_S64(config->args,   where, "-exp_id",             "rawExp.exp_id",        "==");
+  PXOPT_COPY_STR(config->args,   where, "-exp_name",           "rawExp.exp_name",      "==");
+  PXOPT_COPY_STR(config->args,   where, "-inst",               "rawExp.camera",        "==");
+  PXOPT_COPY_STR(config->args,   where, "-telescope",          "rawExp.telescope",     "==");
+  PXOPT_COPY_TIME(config->args,  where, "-dateobs_begin",      "rawExp.dateobs",       ">=");
+  PXOPT_COPY_TIME(config->args,  where, "-dateobs_end",        "rawExp.dateobs",       "<=");
+  PXOPT_COPY_STR(config->args,   where, "-exp_tag",            "rawExp.exp_tag",       "==");
+  PXOPT_COPY_STR(config->args,   where, "-exp_type",           "rawExp.exp_type",      "==");
+  PXOPT_COPY_STR(config->args,   where, "-comment",            "rawExp.comment",       "LIKE");
+  PXOPT_COPY_STR(config->args,   where, "-filelevel",          "rawExp.filelevel",     "==");
+  PXOPT_COPY_STR(config->args,   where, "-filter",             "rawExp.filter",         "==");
+  PXOPT_COPY_F64(config->args,   where, "-airmass_min",        "rawExp.airmass",        ">=");
+  PXOPT_COPY_F64(config->args,   where, "-airmass_max",        "rawExp.airmass",        "<");
+  PXOPT_COPY_RADEC(config->args, where, "-ra_min",             "rawExp.ra",             ">=");
+  PXOPT_COPY_RADEC(config->args, where, "-ra_max",             "rawExp.ra",             "<");
+  PXOPT_COPY_RADEC(config->args, where, "-decl_min",           "rawExp.decl",           ">=");
+  PXOPT_COPY_RADEC(config->args, where, "-decl_max",           "rawExp.decl",           "<");
+  PXOPT_COPY_F32(config->args,   where, "-exp_time_min",       "rawExp.exp_time",       ">=");
+  PXOPT_COPY_F32(config->args,   where, "-exp_time_max",       "rawExp.exp_time",       "<");
+  PXOPT_COPY_F32(config->args,   where, "-sat_pixel_frac_min", "rawExp.sat_pixel_frac", ">=");
+  PXOPT_COPY_F32(config->args,   where, "-sat_pixel_frac_max", "rawExp.sat_pixel_frac", "<");
+  PXOPT_COPY_F64(config->args,   where, "-bg_min",             "rawExp.bg",             ">=");
+  PXOPT_COPY_F64(config->args,   where, "-bg_max",             "rawExp.bg",             "<");
+  PXOPT_COPY_F64(config->args,   where, "-bg_stdev_min",       "rawExp.bg_stdev",       ">=");
+  PXOPT_COPY_F64(config->args,   where, "-bg_stdev_max",       "rawExp.bg_stdev",       "<");
+  PXOPT_COPY_F64(config->args,   where, "-bg_mean_stdev_min",  "rawExp.bg_mean_stdev",  ">=");
+  PXOPT_COPY_F64(config->args,   where, "-bg_mean_stdev_max",  "rawExp.bg_mean_stdev",  "<");
+  PXOPT_COPY_F64(config->args,   where, "-alt_min",            "rawExp.alt",            ">=");
+  PXOPT_COPY_F64(config->args,   where, "-alt_max",            "rawExp.alt",            "<");
+  PXOPT_COPY_F64(config->args,   where, "-az_min",             "rawExp.az",             ">=");
+  PXOPT_COPY_F64(config->args,   where, "-az_max",             "rawExp.az",             "<");
+  PXOPT_COPY_F32(config->args,   where, "-ccd_temp_min",       "rawExp.ccd_temp",       ">=");
+  PXOPT_COPY_F32(config->args,   where, "-ccd_temp_max",       "rawExp.ccd_temp",       "<");
+  PXOPT_COPY_F64(config->args,   where, "-posang_min",         "rawExp.posang",         ">=");
+  PXOPT_COPY_F64(config->args,   where, "-posang_max",         "rawExp.posang",         "<");
+  PXOPT_COPY_STR(config->args,   where, "-object",             "rawExp.object",         "==");
+  PXOPT_COPY_F32(config->args,   where, "-sun_angle_min",         "rawExp.sun_angle",         ">=");
+  PXOPT_COPY_F32(config->args,   where, "-sun_angle_max",         "rawExp.sun_angle",         "<");
+
+  return true;
+}
Index: trunk/ippTools/src/pxdqstats.h
===================================================================
--- trunk/ippTools/src/pxdqstats.h	(revision 26378)
+++ trunk/ippTools/src/pxdqstats.h	(revision 26378)
@@ -0,0 +1,15 @@
+/* pxdqstats.h
+ */
+
+#ifndef PXDQSTATS_H
+#define PXDQSTATS_H 1
+
+#include <pslib.h>
+
+#include "pxtools.h"
+
+bool pxdqstatsSetSearchArgs (psMetadata *md);
+bool pxdqstatsGetSearchArgs (pxConfig *config, psMetadata *where);
+
+
+#endif // PXDQSTATS_H
Index: trunk/ippTools/src/pztool.c
===================================================================
--- trunk/ippTools/src/pztool.c	(revision 26377)
+++ trunk/ippTools/src/pztool.c	(revision 26378)
@@ -117,5 +117,6 @@
             telescope,
             uri,
-            NULL  // epoch
+            NULL,  // epoch
+            0      // use_compress			   
         )) {
         psError(PS_ERR_UNKNOWN, false, "database error");
