Index: trunk/ippTools/share/Makefile.am
===================================================================
--- trunk/ippTools/share/Makefile.am	(revision 26349)
+++ 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 26349)
+++ 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 26349)
+++ 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");
