Index: trunk/ippTools/scripts/make.xcstack.sh
===================================================================
--- trunk/ippTools/scripts/make.xcstack.sh	(revision 42837)
+++ trunk/ippTools/scripts/make.xcstack.sh	(revision 42837)
@@ -0,0 +1,44 @@
+#!/bin/csh
+
+set dbserver = localhost
+set dbuser = ipp
+set dbpass = ipp
+
+# create 3 databases: two cameras and a xcamera
+
+mysql -h $dbserver -u $dbuser -p$dbpass <<EOF
+  drop database if exists test_cam1;
+  drop database if exists test_cam2;
+  drop database if exists test_xcam;
+  create database test_cam1;
+  create database test_cam2;
+  create database test_xcam;
+EOF
+
+mysql -h $dbserver -u $dbuser -p$dbpass test_cam1 < make.xcstack.sql
+mysql -h $dbserver -u $dbuser -p$dbpass test_cam2 < make.xcstack.sql
+mysql -h $dbserver -u $dbuser -p$dbpass test_xcam < make.xcstack.sql
+
+# insert data into the stack tables
+
+# three different stacks (one same filter & different skycell, one same skycell & different filter)
+mysql -h $dbserver -u $dbuser -p$dbpass test_cam1 <<EOF
+  insert into stackRun (stack_id, state, workdir, label, skycell_id, tess_id, filter) values (1, 'full', '/my/path/', 'Test.Label.Cam1', 'skycell.000.001', 'Fake.Tess', 'g');
+  insert into stackSumSkyfile (stack_id, path_base, good_frac, fault, quality) values (1, '/my/path/base', 0.5, 0, 0);
+  insert into stackRun (stack_id, state, workdir, label, skycell_id, tess_id, filter) values (2, 'full', '/my/path/', 'Test.Label.Cam1', 'skycell.000.001', 'Fake.Tess', 'r');
+  insert into stackSumSkyfile (stack_id, path_base, good_frac, fault, quality) values (2, '/my/path/base', 0.5, 0, 0);
+  insert into stackRun (stack_id, state, workdir, label, skycell_id, tess_id, filter) values (3, 'full', '/my/path/', 'Test.Label.Cam1', 'skycell.000.002', 'Fake.Tess', 'g');
+  insert into stackSumSkyfile (stack_id, path_base, good_frac, fault, quality) values (3, '/my/path/base', 0.5, 0, 0);
+EOF
+
+# these stacks are each supposed to match the cam1 stacks above
+# three different stacks (one same filter & different skycell, one same skycell & different filter)
+mysql -h $dbserver -u $dbuser -p$dbpass test_cam2 <<EOF
+  insert into stackRun (stack_id, state, workdir, label, skycell_id, tess_id, filter) values (1, 'full', '/my/path/', 'Test.Label.Cam1', 'skycell.000.001', 'Fake.Tess', 'r');
+  insert into stackSumSkyfile (stack_id, path_base, good_frac, fault, quality) values (1, '/my/path/base', 0.5, 0, 0);
+  insert into stackRun (stack_id, state, workdir, label, skycell_id, tess_id, filter) values (2, 'full', '/my/path/', 'Test.Label.Cam1', 'skycell.000.002', 'Fake.Tess', 'g');
+  insert into stackSumSkyfile (stack_id, path_base, good_frac, fault, quality) values (2, '/my/path/base', 0.5, 0, 0);
+  insert into stackRun (stack_id, state, workdir, label, skycell_id, tess_id, filter) values (3, 'full', '/my/path/', 'Test.Label.Cam1', 'skycell.000.001', 'Fake.Tess', 'g');
+  insert into stackSumSkyfile (stack_id, path_base, good_frac, fault, quality) values (3, '/my/path/base', 0.5, 0, 0);
+EOF
+
Index: trunk/ippTools/scripts/make.xcstack.sql
===================================================================
--- trunk/ippTools/scripts/make.xcstack.sql	(revision 42837)
+++ trunk/ippTools/scripts/make.xcstack.sql	(revision 42837)
@@ -0,0 +1,144 @@
+
+-- test tables do not have auto_increment keys or foreign key constraints
+
+CREATE TABLE dbversion (
+    schema_version VARCHAR(64),
+    updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE Label (
+    label       VARCHAR(64),
+    priority    INT,
+    active      TINYINT,
+    comment     VARCHAR(80),
+    PRIMARY KEY(label),
+    KEY(priority),
+    KEY(active)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE stackRun (
+        stack_id BIGINT,
+        state VARCHAR(64),
+        workdir VARCHAR(255),
+        label VARCHAR(64),
+        data_group VARCHAR(64),
+        dist_group VARCHAR(64),
+        reduction VARCHAR(64),
+        dvodb VARCHAR(255),
+        registered DATETIME,
+        skycell_id VARCHAR(64),
+        tess_id VARCHAR(64),
+        filter VARCHAR(64),
+        software_ver VARCHAR(16),
+        note VARCHAR(255),
+        PRIMARY KEY(stack_id),
+        KEY(stack_id),
+        KEY(state),
+        KEY(skycell_id),
+        KEY(tess_id),
+        KEY(label),
+        KEY(data_group),
+        KEY(dist_group)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE stackInputSkyfile (
+        stack_id BIGINT,
+        warp_id BIGINT,
+        PRIMARY KEY(stack_id, warp_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE stackSumSkyfile (
+        stack_id BIGINT,
+        uri VARCHAR(255),
+        path_base VARCHAR(255),
+        bg DOUBLE,
+        bg_stdev DOUBLE,
+        dtime_stack FLOAT,
+        dtime_match_mean FLOAT,
+        dtime_match_stdev FLOAT,
+	dtime_convolve FLOAT,
+        dtime_initial FLOAT,
+        dtime_reject FLOAT,
+        dtime_final FLOAT,
+        dtime_phot FLOAT,
+        dtime_script FLOAT,
+        match_mean FLOAT,
+        match_stdev FLOAT,
+        match_rms FLOAT,
+        stamps_mean FLOAT,
+        stamps_stdev FLOAT,
+        stamps_min INT,
+        reject_images INT,
+        reject_pix_mean FLOAT,
+        reject_pix_stdev FLOAT,
+        sources INT,
+        hostname VARCHAR(64),
+        good_frac FLOAT,
+        mjd_obs DOUBLE,
+        fault SMALLINT,
+        software_ver VARCHAR(16),
+	background_model SMALLINT,
+        quality SMALLINT NOT NULL DEFAULT 0,
+        PRIMARY KEY(stack_id),
+        KEY(dtime_stack),
+        KEY(good_frac),
+        KEY(fault),
+        KEY(quality)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE xcstackRun (
+        xcstack_id BIGINT AUTO_INCREMENT,
+        state VARCHAR(64),
+        workdir VARCHAR(255),
+        label VARCHAR(64),
+        data_group VARCHAR(64),
+        dist_group VARCHAR(64),
+        reduction VARCHAR(64),
+        registered DATETIME,
+        skycell_id VARCHAR(64),
+        tess_id VARCHAR(64),
+        filter VARCHAR(64),
+        software_ver VARCHAR(16),
+        note VARCHAR(255),
+        PRIMARY KEY(xcstack_id),
+        KEY(xcstack_id),
+        KEY(state),
+        KEY(skycell_id),
+        KEY(tess_id),
+        KEY(label),
+        KEY(data_group),
+        KEY(dist_group)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE xcstackInputSkyfile (
+        xcstack_id BIGINT,
+        camera_id BIGINT,
+        stack_id BIGINT,
+        PRIMARY KEY(xcstack_id, stack_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE xcstackSumSkyfile (
+        xcstack_id BIGINT,
+        path_base VARCHAR(255),
+        bg DOUBLE,
+        bg_stdev DOUBLE,
+        dtime_stack FLOAT,
+        dtime_script FLOAT,
+        hostname VARCHAR(64),
+        good_frac FLOAT,
+        fault SMALLINT,
+        software_ver VARCHAR(16),
+        quality SMALLINT NOT NULL DEFAULT 0,
+        PRIMARY KEY(xcstack_id),
+        KEY(dtime_stack),
+        KEY(good_frac),
+        KEY(fault),
+        KEY(quality)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE xcstackCamera (
+        camera_id BIGINT AUTO_INCREMENT,
+        camera VARCHAR(255),
+        PRIMARY KEY(camera_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
Index: trunk/ippTools/share/Makefile.am
===================================================================
--- trunk/ippTools/share/Makefile.am	(revision 42806)
+++ trunk/ippTools/share/Makefile.am	(revision 42837)
@@ -554,4 +554,41 @@
 	fpcamtool_queue_cam_id.sql \
 	fpcamtool_revertprocessedexp.sql \
-	fpcamtool_revertupdatedexp.sql
-
+	fpcamtool_revertupdatedexp.sql \
+	xcstacktool_definebyquery_select.sql \
+	xcstacktool_definebyquery_select_XC.sql \
+	xcstacktool_addbyquery_select.sql \
+	xcstacktool_addcamera.sql \
+	xcstacktool_inputskyfile.sql \
+	xcstacktool_tosum.sql \
+	xcstacktool_sumskyfile.sql \
+	xcstacktool_revertsumskyfile_delete.sql \
+	xcstacktool_pendingcleanuprun.sql \
+	xcstacktool_pendingcleanupskyfile.sql \
+	xcstacktool_donecleanup.sql \
+	xcskytool_definebyquery_select.sql \
+	xcskytool_definebyquery_select_by_dg.sql \
+	xcskytool_definebyquery_inputs.sql \
+	xcskytool_definexccalrun.sql \
+	xcskytool_pendingxccalrun.sql \
+	xcskytool_revertxccal.sql \
+	xcskytool_xccalresult.sql \
+	xcskytool_export_run.sql \
+	xcskytool_export_input.sql \
+	xcskytool_export_result.sql \
+	xcskytool_export_xccalrun.sql \
+	xcskytool_export_xccalresult.sql \
+	xcskytool_inputs.sql \
+	xcskytool_todo.sql \
+	xcskytool_result.sql \
+	xcskytool_revert.sql \
+	xcskytool_revert_update.sql \
+	xcfftool_definebyquery_pt1.sql \
+	xcfftool_definebyquery_pt2.sql \
+	xcfftool_definebyquery_select_warps.sql \
+	xcfftool_result.sql \
+	xcfftool_revert.sql \
+	xcfftool_revertsummary.sql \
+	xcfftool_summaryinputs.sql \
+	xcfftool_summary.sql \
+	xcfftool_toadvance.sql \
+	xcfftool_todo.sql
Index: trunk/ippTools/share/pxadmin_create_pstamp_tables.sql
===================================================================
--- trunk/ippTools/share/pxadmin_create_pstamp_tables.sql	(revision 42837)
+++ trunk/ippTools/share/pxadmin_create_pstamp_tables.sql	(revision 42837)
@@ -0,0 +1,136 @@
+CREATE TABLE pstampDataStore (
+        ds_id BIGINT AUTO_INCREMENT,
+        state VARCHAR(64),
+        lastFileset VARCHAR(64),
+        timestamp  DATETIME,
+        label VARCHAR(64),
+        outProduct VARCHAR(64) UNIQUE,
+        uri VARCHAR(255),
+        pollInterval INTEGER DEFAULT 60,
+        need_magic TINYINT,
+        PRIMARY KEY(ds_id),
+        KEY(ds_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE pstampProject (
+        proj_id BIGINT AUTO_INCREMENT,
+        name VARCHAR(64) UNIQUE,
+        state VARCHAR(64),
+        dbname VARCHAR(64),
+        dvodb VARCHAR(64),
+        camera VARCHAR(64),
+        telescope VARCHAR(64),
+        need_magic TINYINT,
+        PRIMARY KEY(proj_id),
+        KEY(proj_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE pstampRequest (
+        req_id BIGINT AUTO_INCREMENT,
+        ds_id BIGINT,
+        state VARCHAR(64),
+        name VARCHAR(64) UNIQUE,
+        reqType VARCHAR(16),
+        label VARCHAR(64),
+        outProduct VARCHAR(64),
+        uri VARCHAR(255),
+        outdir VARCHAR(255),
+        username VARCHAR(255),
+        proj_id BIGINT,
+        registered datetime,
+        timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+        fault SMALLINT,
+        PRIMARY KEY(req_id),
+        KEY(req_id),
+        KEY(state),
+        KEY(fault),
+        KEY(label),
+        KEY(username)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE pstampJob (
+        job_id BIGINT AUTO_INCREMENT,
+        req_id BIGINT,
+        rownum VARCHAR(64),
+        state VARCHAR(64),
+        jobType VARCHAR(16),
+        fault SMALLINT,
+        exp_id BIGINT,
+        outputBase VARCHAR(255),
+        options BIGINT,
+        dep_id BIGINT,
+        fault_count INT,
+        parent_id BIGINT DEFAULT NULL,
+        is_parent TINYINT DEFAULT 0,
+        PRIMARY KEY(job_id, req_id),
+        KEY(job_id),
+        KEY(req_id),
+        KEY(state),
+        KEY(fault),
+        KEY(dep_id),
+        KEY(parent_id),
+        KEY(is_parent),
+        FOREIGN KEY(req_id) REFERENCES pstampRequest(req_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE pstampDependent (
+        dep_id BIGINT AUTO_INCREMENT,
+        state      VARCHAR(64),
+        stage      VARCHAR(64),
+        stage_id   BIGINT,
+        component  VARCHAR(64),
+        imagedb    VARCHAR(64),
+        outdir     VARCHAR(255),
+        rlabel     VARCHAR(64),
+        need_magic TINYINT,
+        fault      SMALLINT,
+        fault_count INT,
+        PRIMARY KEY(dep_id),
+        KEY(state),
+        KEY(stage),
+        KEY(stage_id),
+        KEY(component),
+        KEY(imagedb),
+        KEY(fault)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE pstampFile (
+    file_id BIGINT AUTO_INCREMENT,
+    job_id BIGINT NOT NULL,
+    path VARCHAR(255),
+    PRIMARY KEY(file_id),
+    KEY(job_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE pstampUserDomain (
+    domainName      VARCHAR(64),
+    accessLevel     INT,
+    defaultProduct  VARCHAR(255),
+    defaultLabel    VARCHAR(64),
+    PRIMARY KEY(domainName)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE pstampUser (
+    userName        VARCHAR(64),
+    domainName      VARCHAR(64),
+    accessLevel     INT,
+    defaultProduct  VARCHAR(255),
+    defaultLabel    VARCHAR(64),
+    PRIMARY KEY (userName, domainName)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE pstampAccessLevel (
+    proj_id         BIGINT,
+    accessLevel     INT,
+    mjd_min         FLOAT DEFAULT 0,
+    mjd_max         FLOAT DEFAULT 0,
+    PRIMARY KEY (proj_id, accessLevel),
+    UNIQUE KEY (proj_id, accessLevel),
+    FOREIGN KEY(proj_id) REFERENCES pstampProject(proj_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE pstampWebRequest (
+        num BIGINT AUTO_INCREMENT,
+        PRIMARY KEY(num)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
Index: trunk/ippTools/share/pxadmin_create_tables.sql
===================================================================
--- trunk/ippTools/share/pxadmin_create_tables.sql	(revision 42806)
+++ trunk/ippTools/share/pxadmin_create_tables.sql	(revision 42837)
@@ -394,5 +394,4 @@
     KEY(fault),
     KEY(quality),
-    KEY(data_state),
     FOREIGN KEY(chip_id, exp_id) REFERENCES chipRun(chip_id, exp_id),
     FOREIGN KEY(exp_id, class_id) REFERENCES rawImfile(exp_id, class_id)
@@ -558,4 +557,5 @@
     minidvodb_group VARCHAR(64),
     minidvodb_name VARCHAR(64),
+    minidvodb_host VARCHAR(64),
     PRIMARY KEY(add_id),
     KEY(add_id),
@@ -1414,140 +1414,4 @@
 ) ENGINE=innodb DEFAULT CHARSET=latin1;
 
-CREATE TABLE pstampDataStore (
-        ds_id BIGINT AUTO_INCREMENT,
-        state VARCHAR(64),
-        lastFileset VARCHAR(64),
-        timestamp  DATETIME,
-        label VARCHAR(64),
-        outProduct VARCHAR(64) UNIQUE,
-        uri VARCHAR(255),
-        pollInterval INTEGER DEFAULT 60,
-        need_magic TINYINT,
-        PRIMARY KEY(ds_id),
-        KEY(ds_id)
-) ENGINE=innodb DEFAULT CHARSET=latin1;
-
-CREATE TABLE pstampProject (
-        proj_id BIGINT AUTO_INCREMENT,
-        name VARCHAR(64) UNIQUE,
-        state VARCHAR(64),
-        dbname VARCHAR(64),
-        dvodb VARCHAR(64),
-        camera VARCHAR(64),
-        telescope VARCHAR(64),
-        need_magic TINYINT,
-        PRIMARY KEY(proj_id),
-        KEY(proj_id)
-) ENGINE=innodb DEFAULT CHARSET=latin1;
-
-CREATE TABLE pstampRequest (
-        req_id BIGINT AUTO_INCREMENT,
-        ds_id BIGINT,
-        state VARCHAR(64),
-        name VARCHAR(64) UNIQUE,
-        reqType VARCHAR(16),
-        label VARCHAR(64),
-        outProduct VARCHAR(64),
-        uri VARCHAR(255),
-        outdir VARCHAR(255),
-        username VARCHAR(255),
-        proj_id BIGINT,
-        registered datetime,
-        timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-        fault SMALLINT,
-        PRIMARY KEY(req_id),
-        KEY(req_id),
-        KEY(state),
-        KEY(fault),
-        KEY(label),
-        KEY(username)
-) ENGINE=innodb DEFAULT CHARSET=latin1;
-
-CREATE TABLE pstampJob (
-        job_id BIGINT AUTO_INCREMENT,
-        req_id BIGINT,
-        rownum VARCHAR(64),
-        state VARCHAR(64),
-        jobType VARCHAR(16),
-        fault SMALLINT,
-        exp_id BIGINT,
-        outputBase VARCHAR(255),
-        options BIGINT,
-        dep_id BIGINT,
-        fault_count INT,
-        parent_id BIGINT DEFAULT NULL,
-        is_parent TINYINT DEFAULT 0,
-        PRIMARY KEY(job_id, req_id),
-        KEY(job_id),
-        KEY(req_id),
-        KEY(state),
-        KEY(fault),
-        KEY(dep_id),
-        KEY(parent_id),
-        KEY(is_parent),
-        FOREIGN KEY(req_id) REFERENCES pstampRequest(req_id)
-) ENGINE=innodb DEFAULT CHARSET=latin1;
-
-CREATE TABLE pstampDependent (
-        dep_id BIGINT AUTO_INCREMENT,
-        state      VARCHAR(64),
-        stage      VARCHAR(64),
-        stage_id   BIGINT,
-        component  VARCHAR(64),
-        imagedb    VARCHAR(64),
-        outdir     VARCHAR(255),
-        rlabel     VARCHAR(64),
-        need_magic TINYINT,
-        fault      SMALLINT,
-        fault_count INT,
-        PRIMARY KEY(dep_id),
-        KEY(state),
-        KEY(stage),
-        KEY(stage_id),
-        KEY(component),
-        KEY(imagedb),
-        KEY(fault)
-) ENGINE=innodb DEFAULT CHARSET=latin1;
-
-CREATE TABLE pstampFile (
-    file_id BIGINT AUTO_INCREMENT,
-    job_id BIGINT NOT NULL,
-    path VARCHAR(255),
-    PRIMARY KEY(file_id),
-    KEY(job_id)
-) ENGINE=innodb DEFAULT CHARSET=latin1;
-
-CREATE TABLE pstampUserDomain (
-    domainName      VARCHAR(64),
-    accessLevel     INT,
-    defaultProduct  VARCHAR(255),
-    defaultLabel    VARCHAR(64),
-    PRIMARY KEY(domainName)
-) ENGINE=innodb DEFAULT CHARSET=latin1;
-
-CREATE TABLE pstampUser (
-    userName        VARCHAR(64),
-    domainName      VARCHAR(64),
-    accessLevel     INT,
-    defaultProduct  VARCHAR(255),
-    defaultLabel    VARCHAR(64),
-    PRIMARY KEY (userName, domainName)
-) ENGINE=innodb DEFAULT CHARSET=latin1;
-
-CREATE TABLE pstampAccessLevel (
-    proj_id         BIGINT,
-    accessLevel     INT,
-    mjd_min         FLOAT DEFAULT 0,
-    mjd_max         FLOAT DEFAULT 0,
-    PRIMARY KEY (proj_id, accessLevel),
-    UNIQUE KEY (proj_id, accessLevel),
-    FOREIGN KEY(proj_id) REFERENCES pstampProject(proj_id)
-) ENGINE=innodb DEFAULT CHARSET=latin1;
-
-CREATE TABLE pstampWebRequest (
-        num BIGINT AUTO_INCREMENT,
-        PRIMARY KEY(num)
-) ENGINE=innodb DEFAULT CHARSET=latin1;
-
 CREATE TABLE distTarget (
     target_id   BIGINT AUTO_INCREMENT,
@@ -1856,4 +1720,5 @@
     class_id VARCHAR(64) NOT NULL,     -- class (component) identifier
     path_base VARCHAR(255) NOT NULL,   -- root name for outputs
+    data_state VARCHAR(64),   
     magicked BIGINT,                   -- magic_id if magicked
     dtime_script FLOAT,                -- elapsed time for script
@@ -1905,4 +1770,5 @@
     skycell_id VARCHAR(64) NOT NULL,   -- skycell identifier
     path_base VARCHAR(255) NOT NULL,   -- root name for outputs
+    data_state VARCHAR(64),
     magicked BIGINT,                   -- magic_id if magicked
     dtime_script FLOAT,                -- elapsed time for script
@@ -2022,5 +1888,7 @@
        minidvodb_group VARCHAR(64) NOT NULL,
        minidvodb_path VARCHAR(255) NOT NULL,
+       minidvodb_host VARCHAR(64) NOT NULL,
        state VARCHAR(64) NOT NULL,
+       note VARCHAR(255) NOT NULL,
        creation_date TIMESTAMP,
        PRIMARY KEY(minidvodb_id), KEY(minidvodb_name), INDEX(minidvodb_id)
@@ -2168,5 +2036,5 @@
       quality SMALLINT NOT NULL,
       hostname VARCHAR(64) NOT NULL,
-      software_ver VARCHAR(64),
+      software_ver VARCHAR(16),
       fault SMALLINT NOT NULL,
       PRIMARY KEY(skycal_id),
@@ -2481,4 +2349,222 @@
 ) ENGINE=innodb DEFAULT CHARSET=latin1;
 
+CREATE TABLE xcstackCamera (
+        camera_id BIGINT AUTO_INCREMENT,
+        camera VARCHAR(255),
+        PRIMARY KEY(camera_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE xcstackRun (
+        xcstack_id BIGINT AUTO_INCREMENT,
+        state VARCHAR(64),
+        workdir VARCHAR(255),
+        label VARCHAR(64),
+        data_group VARCHAR(64),
+        dist_group VARCHAR(64),
+        reduction VARCHAR(64),
+        registered DATETIME,
+        skycell_id VARCHAR(64),
+        tess_id VARCHAR(64),
+        filter VARCHAR(64),
+        software_ver VARCHAR(16),
+        note VARCHAR(255),
+        PRIMARY KEY(xcstack_id),
+        KEY(xcstack_id),
+        KEY(state),
+        KEY(skycell_id),
+        KEY(tess_id),
+        KEY(label),
+        KEY(data_group),
+        KEY(dist_group)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE xcstackInputSkyfile (
+        xcstack_id BIGINT,
+        camera_id BIGINT,
+        stack_id BIGINT,
+        PRIMARY KEY(xcstack_id, camera_id, stack_id),
+	KEY(xcstack_id),
+	KEY(stack_id),
+        FOREIGN KEY(xcstack_id) REFERENCES xcstackRun(xcstack_id),
+        FOREIGN KEY(camera_id) REFERENCES xcstackCamera(camera_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE xcstackSumSkyfile (
+        xcstack_id BIGINT,
+        path_base VARCHAR(255),
+        bg DOUBLE,
+        bg_stdev DOUBLE,
+        dtime_stack FLOAT,
+        dtime_script FLOAT,
+        hostname VARCHAR(64),
+        good_frac FLOAT,
+        fault SMALLINT,
+        software_ver VARCHAR(16),
+        quality SMALLINT NOT NULL DEFAULT 0,
+        PRIMARY KEY(xcstack_id),
+        KEY(dtime_stack),
+        KEY(good_frac),
+        KEY(fault),
+        KEY(quality),
+        FOREIGN KEY(xcstack_id) REFERENCES xcstackRun(xcstack_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+-- A static sky analysis set
+CREATE TABLE xcskyRun (
+      xcsky_id BIGINT AUTO_INCREMENT, -- unique identifier
+      state VARCHAR(64) NOT NULL,  -- state of run (new, full, etc.)
+      workdir VARCHAR(255) NOT NULL, -- working directory
+      label VARCHAR(64),             -- processing label
+      data_group VARCHAR(64),        -- group for data
+      dist_group VARCHAR(64),        -- group for distribution
+      reduction VARCHAR(64),         -- reduction class (for altering recipe)
+      note VARCHAR(255),             -- note
+      registered TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- time run was registered
+      PRIMARY KEY(xcsky_id),
+      KEY(state),
+      KEY(label),
+      KEY(data_group),
+      KEY(dist_group)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+-- Inputs for static sky analysis
+CREATE TABLE xcskyInput (
+      xcsky_id BIGINT,             -- static sky identifier
+      xcstack_id BIGINT,          -- stack identifier
+      PRIMARY KEY(xcsky_id,xcstack_id),
+      KEY(xcstack_id),
+      FOREIGN KEY(xcsky_id) REFERENCES xcskyRun(xcsky_id),
+      FOREIGN KEY(xcstack_id) REFERENCES xcstackRun(xcstack_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+-- Result of static sky analysis
+CREATE TABLE xcskyResult (
+      xcsky_id BIGINT,             -- static sky identifier
+      path_base VARCHAR(255) NOT NULL, -- root name for outputs
+      dtime_phot FLOAT,                -- elapsed time for photometry
+      dtime_script FLOAT,              -- elapsed time for script
+      sources INT,                     -- number of sources
+      num_inputs INT,                  -- number of input images
+      hostname VARCHAR(64) NOT NULL,   -- host that executed script
+      good_frac FLOAT,                 -- good fraction of skycell
+      quality SMALLINT NOT NULL,       -- bad quality flag
+      fault SMALLINT NOT NULL,         -- fault code
+      PRIMARY KEY(xcsky_id),
+      KEY(good_frac),
+      KEY(fault),
+      KEY(quality),
+      FOREIGN KEY(xcsky_id) REFERENCES xcskyRun(xcsky_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE xccalRun (
+      xccal_id BIGINT AUTO_INCREMENT, -- unique identifier
+      xcsky_id BIGINT,
+      xcstack_id BIGINT,
+      state VARCHAR(64) NOT NULL,  -- state of run (new, full, etc.)
+      workdir VARCHAR(255) NOT NULL, -- working directory
+      label VARCHAR(64),             -- processing label
+      data_group VARCHAR(64),        -- group for data
+      dist_group VARCHAR(64),        -- group for distribution
+      reduction VARCHAR(64),         -- reduction class (for altering recipe)
+      registered TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- time run was registered
+      note VARCHAR(255),             -- note
+      PRIMARY KEY(xccal_id),
+      KEY(state),
+      KEY(label),
+      KEY(data_group),
+      KEY(dist_group),
+      KEY(xcsky_id),
+      KEY(xcstack_id),
+      FOREIGN KEY(xcsky_id) REFERENCES xcskyRun(xcsky_id),
+      FOREIGN KEY(xcstack_id) REFERENCES xcstackRun(xcstack_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+-- Result of sky calibration analysis
+CREATE TABLE xccalResult (
+      xccal_id BIGINT,
+      path_base VARCHAR(255) NOT NULL,
+      dtime_script FLOAT,
+      dtime_astrom FLOAT,
+      sigma_ra FLOAT,
+      sigma_dec FLOAT,
+      n_astrom INT,
+      n_detections INT,
+      n_extended INT,
+      n_forced INT,
+      zpt_obs FLOAT,
+      zpt_stdev FLOAT,
+      fwhm_major  FLOAT,
+      fwhm_minor  FLOAT,
+      quality SMALLINT NOT NULL,
+      hostname VARCHAR(64) NOT NULL,
+      software_ver VARCHAR(16),
+      fault SMALLINT NOT NULL,
+      PRIMARY KEY(xccal_id),
+      KEY(fault),
+      KEY(quality),
+      FOREIGN KEY(xccal_id) REFERENCES xccalRun(xccal_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE xcForceRun (
+    xcff_id         BIGINT NOT NULL AUTO_INCREMENT,
+    xccal_id        BIGINT, -- NOT a foreign key: reference to entry in xcamera db
+    sources_path_base VARCHAR(255),
+    state           VARCHAR(64),
+    workdir         VARCHAR(255),
+    label           VARCHAR(64),
+    skycell_id      VARCHAR(64),
+    tess_id         VARCHAR(64),
+    data_group      VARCHAR(64),
+    dist_group      VARCHAR(64),
+    note            VARCHAR(255),
+    reduction       VARCHAR(64),
+    registered TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- time run was registered
+    PRIMARY KEY(xcff_id),
+    KEY(state),
+    KEY(label),
+    KEY(data_group),
+    KEY(xccal_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE xcForceInput (
+    xcff_id              BIGINT,
+    warp_id              BIGINT,
+    camera_id            BIGINT,
+    PRIMARY KEY(xcff_id, warp_id, camera_id),
+    FOREIGN KEY(xcff_id)   REFERENCES xcForceRun(xcff_id),
+    FOREIGN KEY(warp_id)   REFERENCES warpRun(warp_id),
+    FOREIGN KEY(camera_id) REFERENCES xcstackCamera(camera_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE xcForceResult (
+    xcff_id         BIGINT,
+    warp_id         BIGINT,
+    path_base       VARCHAR(255) NOT NULL,
+    dtime_script    FLOAT,
+    quality         SMALLINT NOT NULL,
+    hostname        VARCHAR(64) NOT NULL,
+    software_ver    VARCHAR(16),
+    fault           SMALLINT NOT NULL,
+    PRIMARY KEY(xcff_id, warp_id),
+    KEY(fault),
+    KEY(quality),
+    FOREIGN KEY(xcff_id) REFERENCES xcForceRun(xcff_id),
+    FOREIGN KEY(warp_id) REFERENCES warpRun(warp_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+CREATE TABLE xcForceSummary (
+    xcff_id         BIGINT,
+    path_base       VARCHAR(255) NOT NULL,
+    dtime_script    FLOAT,
+    quality         SMALLINT NOT NULL,
+    hostname        VARCHAR(64) NOT NULL,
+    software_ver    VARCHAR(16),
+    fault           SMALLINT NOT NULL,
+    PRIMARY KEY(xcff_id),
+    KEY(fault),
+    KEY(quality),
+    FOREIGN KEY(xcff_id) REFERENCES xcForceRun(xcff_id)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
 -- These comment lines are here to avoid an empty query error.
 -- Another way to avoid that problem is to omit the semicolon above but I think that is untidy.
Index: trunk/ippTools/share/pxadmin_drop_tables.sql
===================================================================
--- trunk/ippTools/share/pxadmin_drop_tables.sql	(revision 42806)
+++ trunk/ippTools/share/pxadmin_drop_tables.sql	(revision 42837)
@@ -131,4 +131,17 @@
 DROP TABLE IF EXISTS fpcamProcessedExp;
 DROP TABLE IF EXISTS fpcamRun;
+DROP TABLE IF EXISTS xccalResult;
+DROP TABLE IF EXISTS xccalRun;
+DROP TABLE IF EXISTS xcForceInput;
+DROP TABLE IF EXISTS xcForceResult;
+DROP TABLE IF EXISTS xcForceRun;
+DROP TABLE IF EXISTS xcForceSummary;
+DROP TABLE IF EXISTS xcskyInput;
+DROP TABLE IF EXISTS xcskyResult;
+DROP TABLE IF EXISTS xcskyRun;
+DROP TABLE IF EXISTS xcstackCamera;
+DROP TABLE IF EXISTS xcstackInputSkyfile;
+DROP TABLE IF EXISTS xcstackRun;
+DROP TABLE IF EXISTS xcstackSumSkyfile;
 
 SET FOREIGN_KEY_CHECKS=1
Index: trunk/ippTools/share/xcfftool_definebyquery_pt1.sql
===================================================================
--- trunk/ippTools/share/xcfftool_definebyquery_pt1.sql	(revision 42837)
+++ trunk/ippTools/share/xcfftool_definebyquery_pt1.sql	(revision 42837)
@@ -0,0 +1,19 @@
+SELECT
+    xccalRun.xccal_id,
+    xccalResult.path_base,
+    xccalRun.data_group,
+    stackRun.tess_id,
+    stackRun.stack_id,
+    stackRun.skycell_id,
+    stackRun.filter
+FROM %s.xccalRun
+    JOIN %s.xccalResult         USING (xccal_id) -- needed for input sources path_base
+    JOIN %s.xcstackInputSkyfile USING (xcstack_id) -- exposes the stack_id
+    JOIN %s.xcstackCamera       USING (camera_id) -- select the camera matching this db
+    JOIN stackRun                      USING (stack_id) -- needed for filter
+    JOIN skycell                       USING (tess_id, skycell_id) -- needed to limit by coords
+
+-- this query is joined to part 2 which supplies WHERE conditions, but there may be an optional
+-- LEFT JOIN between the two parts for the -no-rerun case
+
+-- the 4 string formats are replaced with the xcamera database name
Index: trunk/ippTools/share/xcfftool_definebyquery_pt2.sql
===================================================================
--- trunk/ippTools/share/xcfftool_definebyquery_pt2.sql	(revision 42837)
+++ trunk/ippTools/share/xcfftool_definebyquery_pt2.sql	(revision 42837)
@@ -0,0 +1,10 @@
+WHERE 
+    -- the first string format below should be filled with the xcamera database
+    -- the second should be the name of the camera corresponding to the
+    -- database in which the tool is run
+    %s.xcstackCamera.camera_id = %lld 
+AND %s.xccalRun.state = 'full' 
+AND %s.xccalResult.quality = 0
+-- 'where' string is appended here
+-- for no-rerun, this also append:
+-- AND xcff_id IS NULL
Index: trunk/ippTools/share/xcfftool_definebyquery_select_warps.sql
===================================================================
--- trunk/ippTools/share/xcfftool_definebyquery_select_warps.sql	(revision 42837)
+++ trunk/ippTools/share/xcfftool_definebyquery_select_warps.sql	(revision 42837)
@@ -0,0 +1,8 @@
+SELECT warp_id
+FROM stackInputSkyfile
+JOIN warpRun using (warp_id)
+JOIN warpSkyfile using(warp_id, tess_id)
+WHERE (stack_id = %lld)
+AND   warpRun.state = 'full'
+AND   warpSkyfile.quality = 0
+AND   warpSkyfile.fault = 0
Index: trunk/ippTools/share/xcfftool_export_input.sql
===================================================================
--- trunk/ippTools/share/xcfftool_export_input.sql	(revision 42837)
+++ trunk/ippTools/share/xcfftool_export_input.sql	(revision 42837)
@@ -0,0 +1,3 @@
+SELECT
+    fullForceInput.*
+FROM fullForceInput
Index: trunk/ippTools/share/xcfftool_export_result.sql
===================================================================
--- trunk/ippTools/share/xcfftool_export_result.sql	(revision 42837)
+++ trunk/ippTools/share/xcfftool_export_result.sql	(revision 42837)
@@ -0,0 +1,3 @@
+SELECT
+    fullForceResult.*
+FROM fullForceResult
Index: trunk/ippTools/share/xcfftool_export_run.sql
===================================================================
--- trunk/ippTools/share/xcfftool_export_run.sql	(revision 42837)
+++ trunk/ippTools/share/xcfftool_export_run.sql	(revision 42837)
@@ -0,0 +1,3 @@
+SELECT
+    fullForceRun.*
+FROM fullForceRun
Index: trunk/ippTools/share/xcfftool_export_summary.sql
===================================================================
--- trunk/ippTools/share/xcfftool_export_summary.sql	(revision 42837)
+++ trunk/ippTools/share/xcfftool_export_summary.sql	(revision 42837)
@@ -0,0 +1,3 @@
+SELECT
+    fullForceSummary.*
+FROM fullForceSummary
Index: trunk/ippTools/share/xcfftool_result.sql
===================================================================
--- trunk/ippTools/share/xcfftool_result.sql	(revision 42837)
+++ trunk/ippTools/share/xcfftool_result.sql	(revision 42837)
@@ -0,0 +1,22 @@
+SELECT
+    xcForceResult.*,
+    xcForceRun.state,
+    xcForceRun.label,
+    xcForceRun.data_group,
+    xcForceRun.xccal_id,
+    xcForceRun.workdir,
+    xcForceRun.sources_path_base,
+    CONCAT_WS('.', warp_id, skycell_id) AS dist_component,
+    xcForceRun.tess_id,
+    xcForceRun.skycell_id,
+    rawExp.exp_id,
+    rawExp.exp_name,
+    rawExp.camera
+FROM xcForceRun
+    JOIN xcForceResult USING(xcff_id)
+    JOIN warpRun using(warp_id, tess_id)
+    JOIN warpSkyfile USING(warp_id, tess_id, skycell_id)
+    JOIN fakeRun using(fake_id)
+    JOIN camRun using(cam_id)
+    JOIN chipRun using(chip_id)
+    JOIN rawExp using(exp_id)
Index: trunk/ippTools/share/xcfftool_revert.sql
===================================================================
--- trunk/ippTools/share/xcfftool_revert.sql	(revision 42837)
+++ trunk/ippTools/share/xcfftool_revert.sql	(revision 42837)
@@ -0,0 +1,4 @@
+DELETE FROM xcForceResult
+USING xcForceResult, xcForceRun
+WHERE xcForceRun.xcff_id = xcForceResult.xcff_id
+    AND xcForceResult.fault != 0
Index: trunk/ippTools/share/xcfftool_revertsummary.sql
===================================================================
--- trunk/ippTools/share/xcfftool_revertsummary.sql	(revision 42837)
+++ trunk/ippTools/share/xcfftool_revertsummary.sql	(revision 42837)
@@ -0,0 +1,5 @@
+DELETE FROM xcForceSummary
+USING xcForceSummary, xcForceRun, skycell
+WHERE xcForceRun.xcff_id = xcForceSummary.xcff_id
+    AND xcForceSummary.fault != 0
+    AND xcForceRun.tess_id = skycell.tess_id AND xcForceRun.skycell_id =  skycell.skycell_id
Index: trunk/ippTools/share/xcfftool_summary.sql
===================================================================
--- trunk/ippTools/share/xcfftool_summary.sql	(revision 42837)
+++ trunk/ippTools/share/xcfftool_summary.sql	(revision 42837)
@@ -0,0 +1,15 @@
+SELECT DISTINCT
+    xcForceSummary.*,
+    xcForceRun.state,
+    xcForceRun.label,
+    xcForceRun.data_group,
+    xcForceRun.xccal_id,
+    xcForceRun.workdir,
+    xcForceRun.sources_path_base,
+    xcForceRun.tess_id,
+    xcForceRun.skycell_id,
+    'summary' AS dist_component
+FROM xcForceRun
+    JOIN xcForceSummary USING(xcff_id)
+    JOIN xcForceInput USING(xcff_id)
+    JOIN skycell USING (tess_id, skycell_id)
Index: trunk/ippTools/share/xcfftool_summaryinputs.sql
===================================================================
--- trunk/ippTools/share/xcfftool_summaryinputs.sql	(revision 42837)
+++ trunk/ippTools/share/xcfftool_summaryinputs.sql	(revision 42837)
@@ -0,0 +1,4 @@
+SELECT
+    fullForceResult.*
+FROM fullForceRun 
+    JOIN fullForceResult USING(ff_id)
Index: trunk/ippTools/share/xcfftool_toadvance.sql
===================================================================
--- trunk/ippTools/share/xcfftool_toadvance.sql	(revision 42837)
+++ trunk/ippTools/share/xcfftool_toadvance.sql	(revision 42837)
@@ -0,0 +1,15 @@
+SELECT
+    xcForceRun.xcff_id,
+    xcForceRun.tess_id,
+    xcForceRun.skycell_id,
+    xcForceRun.workdir,
+    xcForceRun.reduction
+FROM xcForceRun
+    JOIN xcForceInput USING(xcff_id)
+    LEFT JOIN xcForceResult USING(xcff_id, warp_id)
+    LEFT JOIN xcForceSummary USING(xcff_id)
+WHERE xcForceRun.state = 'new' AND xcForceSummary.xcff_id IS NULL
+    -- WHERE hook %s
+    GROUP BY xcff_id
+    HAVING COUNT(xcForceInput.warp_id) = COUNT(xcForceResult.warp_id)
+        AND SUM(xcForceResult.fault) = 0
Index: trunk/ippTools/share/xcfftool_todo.sql
===================================================================
--- trunk/ippTools/share/xcfftool_todo.sql	(revision 42837)
+++ trunk/ippTools/share/xcfftool_todo.sql	(revision 42837)
@@ -0,0 +1,12 @@
+SELECT
+    xcForceRun.*,
+    xcForceInput.warp_id,
+    warpSkyfile.path_base AS warp_path_base
+FROM xcForceRun
+    JOIN xcForceInput USING(xcff_id)
+    JOIN warpRun USING(warp_id, tess_id)
+    JOIN warpSkyfile USING(warp_id, tess_id, skycell_id)
+    LEFT JOIN xcForceResult USING(xcff_id, warp_id)
+WHERE xcForceRun.state = 'new'
+    AND xcForceResult.xcff_id IS NULL
+    AND warpSkyfile.data_state = 'full'
Index: trunk/ippTools/share/xcfftool_updaterun.sql
===================================================================
--- trunk/ippTools/share/xcfftool_updaterun.sql	(revision 42837)
+++ trunk/ippTools/share/xcfftool_updaterun.sql	(revision 42837)
@@ -0,0 +1,2 @@
+UPDATE xcForceRun
+JOIN skycell USING(tess_id, skycell_id);
Index: trunk/ippTools/share/xcskytool_definebyquery.sql
===================================================================
--- trunk/ippTools/share/xcskytool_definebyquery.sql	(revision 42837)
+++ trunk/ippTools/share/xcskytool_definebyquery.sql	(revision 42837)
@@ -0,0 +1,22 @@
+SELECT 
+       tess_id,	
+       skycell_id, 
+       num_filter 
+FROM (
+  SELECT
+      tess_id,
+      skycell_id,
+      COUNT(DISTINCT filter) AS num_filter
+  FROM xcstackRun
+  JOIN xcstackSumSkyfile USING(xcstack_id)
+  WHERE xcstackRun.state = 'full'
+      AND xcstackSumSkyfile.fault = 0
+      AND xcstackSumSkyfile.quality = 0
+  -- WHERE hook 1 %s 
+  -- restrict by xcstackSumSkyfile.good_frac, xcstackRun.skycell_id, xcstackRun.label
+  GROUP BY
+      tess_id,
+      skycell_id
+  ) AS TMP
+WHERE num_filter == %d
+
Index: trunk/ippTools/share/xcskytool_definebyquery_inputs.sql
===================================================================
--- trunk/ippTools/share/xcskytool_definebyquery_inputs.sql	(revision 42837)
+++ trunk/ippTools/share/xcskytool_definebyquery_inputs.sql	(revision 42837)
@@ -0,0 +1,16 @@
+  SELECT
+      MAX(xcstack_id) as xcstack_id,
+      tess_id,
+      skycell_id,
+      filter
+  FROM xcstackRun
+  JOIN xcstackSumSkyfile USING(xcstack_id)
+  JOIN skycell USING(tess_id, skycell_id)
+  WHERE xcstackRun.state = 'full'
+      AND xcstackSumSkyfile.fault = 0
+      AND xcstackSumSkyfile.quality = 0
+      AND xcstackRun.tess_id = '%s'
+      AND xcstackRun.skycell_id = '%s'
+  -- WHERE hook 1 %s
+  -- WHERE hook 2 %s
+  GROUP BY xcstackRun.filter
Index: trunk/ippTools/share/xcskytool_definebyquery_select.sql
===================================================================
--- trunk/ippTools/share/xcskytool_definebyquery_select.sql	(revision 42837)
+++ trunk/ippTools/share/xcskytool_definebyquery_select.sql	(revision 42837)
@@ -0,0 +1,41 @@
+SELECT DISTINCT
+       tess_id,	
+       skycell_id,
+       num_filter 
+FROM (
+  SELECT
+      tess_id,
+      skycell_id,
+      COUNT(DISTINCT filter) AS num_filter
+  FROM xcstackRun
+  JOIN xcstackSumSkyfile USING(xcstack_id)
+  JOIN skycell using(tess_id, skycell_id)
+  WHERE xcstackRun.state = 'full'
+      AND xcstackSumSkyfile.fault = 0
+      AND xcstackSumSkyfile.quality = 0
+  -- WHERE hook 1 %s 
+  -- restrict by selected filters, xcstackSumSkyfile.good_frac, xcstackRun.skycell_id, xcstackRun.label
+  GROUP BY
+      tess_id,
+      skycell_id
+  ) AS xcstacks
+LEFT JOIN (
+  SELECT DISTINCT xcsky_id,
+    MIN(xcstackRun.skycell_id) as skycell_id,
+    MIN(xcstackRun.tess_id) as tess_id,
+    MIN(xcstackRun.data_group) as data_group,
+    COUNT(xcstack_id) AS num_filter
+  FROM xcskyRun
+    JOIN xcskyInput USING(xcsky_id)
+    JOIN xcstackRun USING(xcstack_id)
+    JOIN xcstackSumSkyfile USING(xcstack_id)
+    JOIN skycell USING(tess_id, skycell_id)
+    WHERE 1
+    -- restrict by selected filters, xcstackSumSkyfile.good_frac, xcstackRun.skycell_id, xcstackRun.label
+    -- and unless -rerun is supplied that no existing xcskyRun exists
+    -- WHERE hook 2 %s
+  GROUP BY xcsky_id
+ ) AS ExistingRunsInputs
+USING(tess_id, skycell_id, num_filter)
+WHERE num_filter = %d
+-- WHERE hook 3 %s
Index: trunk/ippTools/share/xcskytool_definebyquery_select_by_dg.sql
===================================================================
--- trunk/ippTools/share/xcskytool_definebyquery_select_by_dg.sql	(revision 42837)
+++ trunk/ippTools/share/xcskytool_definebyquery_select_by_dg.sql	(revision 42837)
@@ -0,0 +1,43 @@
+SELECT DISTINCT
+       tess_id,	
+       skycell_id,
+       Xcstacks.data_group,
+       num_filter 
+FROM (
+  SELECT
+      tess_id,
+      skycell_id,
+      label,
+      data_group,
+      COUNT(DISTINCT filter) AS num_filter
+  FROM xcstackRun
+  JOIN xcstackSumSkyfile USING(xcstack_id)
+  WHERE xcstackRun.state = 'full'
+      AND xcstackSumSkyfile.fault = 0
+      AND xcstackSumSkyfile.quality = 0
+  -- WHERE hook 1 %s 
+  -- restrict by selected filters, xcstackSumSkyfile.good_frac, xcstackRun.skycell_id, xcstackRun.label
+  GROUP BY
+      tess_id,
+      skycell_id,
+      data_group
+  ) AS Xcstacks
+LEFT JOIN (
+  SELECT DISTINCT xcsky_id,
+    xcstackRun.skycell_id,
+    xcstackRun.tess_id,
+    xcstackRun.data_group,
+    COUNT(xcstack_id) AS num_filter
+  FROM xcskyRun
+    JOIN xcskyInput USING(xcsky_id)
+    JOIN xcstackRun USING(xcstack_id)
+    JOIN xcstackSumSkyfile USING(xcstack_id)
+    WHERE 1
+    -- WHERE hook 2 %s
+    -- restrict by selected filters, xcstackSumSkyfile.good_frac, xcstackRun.skycell_id, xcstackRun.label
+  GROUP BY xcsky_id
+ ) AS ExistingRunsInputs
+USING(tess_id, skycell_id, data_group, num_filter)
+WHERE num_filter = %d
+    AND Xcstacks.data_group = ExistingRunsInputs.data_group
+-- WHERE hook 3 %s
Index: trunk/ippTools/share/xcskytool_definexccalrun.sql
===================================================================
--- trunk/ippTools/share/xcskytool_definexccalrun.sql	(revision 42837)
+++ trunk/ippTools/share/xcskytool_definexccalrun.sql	(revision 42837)
@@ -0,0 +1,18 @@
+SELECT
+    xcskyRun.xcsky_id,
+    xcstackRun.xcstack_id,
+    skycell_id,
+    filter,
+    xcskyRun.workdir,
+    xcskyRun.label,
+    xcskyRun.data_group,
+    xcskyRun.dist_group
+FROM xcskyRun 
+    JOIN xcskyResult USING(xcsky_id)
+    JOIN xcskyInput USING(xcsky_id)
+    JOIN xcstackRun USING(xcstack_id)
+    JOIN xcstackSumSkyfile USING(xcstack_id)
+    JOIN skycell USING(tess_id, skycell_id)
+    LEFT JOIN xccalRun ON xcskyRun.xcsky_id = xccalRun.xcsky_id AND xcstackRun.xcstack_id = xccalRun.xcstack_id -- join hook %s
+WHERE xcskyRun.state = 'full'
+    AND xcskyResult.quality = 0
Index: trunk/ippTools/share/xcskytool_export_input.sql
===================================================================
--- trunk/ippTools/share/xcskytool_export_input.sql	(revision 42837)
+++ trunk/ippTools/share/xcskytool_export_input.sql	(revision 42837)
@@ -0,0 +1,1 @@
+SELECT xcskyInput.* FROM xcskyInput
Index: trunk/ippTools/share/xcskytool_export_result.sql
===================================================================
--- trunk/ippTools/share/xcskytool_export_result.sql	(revision 42837)
+++ trunk/ippTools/share/xcskytool_export_result.sql	(revision 42837)
@@ -0,0 +1,1 @@
+SELECT xcskyResult.* FROM xcskyResult
Index: trunk/ippTools/share/xcskytool_export_run.sql
===================================================================
--- trunk/ippTools/share/xcskytool_export_run.sql	(revision 42837)
+++ trunk/ippTools/share/xcskytool_export_run.sql	(revision 42837)
@@ -0,0 +1,1 @@
+SELECT xcskyRun.* from xcskyRun
Index: trunk/ippTools/share/xcskytool_export_xccalresult.sql
===================================================================
--- trunk/ippTools/share/xcskytool_export_xccalresult.sql	(revision 42837)
+++ trunk/ippTools/share/xcskytool_export_xccalresult.sql	(revision 42837)
@@ -0,0 +1,1 @@
+SELECT * from xccalResult
Index: trunk/ippTools/share/xcskytool_export_xccalrun.sql
===================================================================
--- trunk/ippTools/share/xcskytool_export_xccalrun.sql	(revision 42837)
+++ trunk/ippTools/share/xcskytool_export_xccalrun.sql	(revision 42837)
@@ -0,0 +1,1 @@
+SELECT * from xccalRun
Index: trunk/ippTools/share/xcskytool_inputs.sql
===================================================================
--- trunk/ippTools/share/xcskytool_inputs.sql	(revision 42837)
+++ trunk/ippTools/share/xcskytool_inputs.sql	(revision 42837)
@@ -0,0 +1,9 @@
+SELECT
+  xcskyRun.*,
+  xcskyInput.xcstack_id,
+  xcstackSumSkyfile.path_base,
+  xcstackRun.filter
+FROM xcskyRun
+JOIN xcskyInput USING(xcsky_id)
+JOIN xcstackSumSkyfile USING (xcstack_id)
+JOIN xcstackRun using(xcstack_id)
Index: trunk/ippTools/share/xcskytool_pendingxccalrun.sql
===================================================================
--- trunk/ippTools/share/xcskytool_pendingxccalrun.sql	(revision 42837)
+++ trunk/ippTools/share/xcskytool_pendingxccalrun.sql	(revision 42837)
@@ -0,0 +1,16 @@
+SELECT DISTINCT
+    xccalRun.*,
+    xcstackRun.tess_id,
+    xcstackRun.skycell_id,
+    xcstackRun.filter,
+    xcskyResult.path_base,
+    xcskyResult.num_inputs = 1 AS singlefilter
+FROM xccalRun
+JOIN xcskyRun USING (xcsky_id)
+JOIN xcskyResult USING(xcsky_id)
+JOIN xcstackRun USING (xcstack_id)
+JOIN xcstackInputSkyfile USING(xcstack_id)
+LEFT JOIN xccalResult USING(xccal_id)
+WHERE
+   ((xccalRun.state = 'new' AND xccalResult.xccal_id IS NULL)
+    OR (xccalRun.state ='update' and xcskyRun.state ='full' and xccalResult.fault = 0))
Index: trunk/ippTools/share/xcskytool_result.sql
===================================================================
--- trunk/ippTools/share/xcskytool_result.sql	(revision 42837)
+++ trunk/ippTools/share/xcskytool_result.sql	(revision 42837)
@@ -0,0 +1,18 @@
+SELECT
+    xcskyResult.*,
+    xcskyRun.state,
+    xcskyRun.workdir,
+    xcskyRun.label,
+    MIN(xcstackRun.tess_id) as tess_id, -- we implicitly assume that all xcstackRuns used for a xcskyRun have the same tess_id
+    MIN(xcstackRun.skycell_id) as skycell_id, -- and skycell_id 
+    count(xcstackRun.filter) AS num_filters,
+    TRUNCATE(MIN(skycell.radeg)/15., 4) as rahours,
+    MIN(skycell.radeg) as radeg,
+    MIN(skycell.decdeg) as decdeg,
+    MIN(skycell.glong) as glong,
+    MIN(skycell.glat) as glat
+FROM xcskyRun
+JOIN xcskyResult USING(xcsky_id)
+JOIN xcskyInput USING(xcsky_id)
+JOIN xcstackRun USING(xcstack_id)
+LEFT JOIN skycell USING(tess_id, skycell_id)
Index: trunk/ippTools/share/xcskytool_revert.sql
===================================================================
--- trunk/ippTools/share/xcskytool_revert.sql	(revision 42837)
+++ trunk/ippTools/share/xcskytool_revert.sql	(revision 42837)
@@ -0,0 +1,9 @@
+DELETE FROM xcskyResult
+USING xcskyResult, xcskyRun, xcskyInput, xcstackRun, skycell
+WHERE xcskyRun.xcsky_id = xcskyResult.xcsky_id
+    AND xcskyRun.xcsky_id = xcskyInput.xcsky_id
+    AND xcskyInput.xcstack_id = xcstackRun.xcstack_id
+    AND xcstackRun.tess_id = skycell.tess_id
+    AND xcstackRun.skycell_id = skycell.skycell_id
+    AND xcskyRun.state = 'new'
+    AND xcskyResult.fault != 0
Index: trunk/ippTools/share/xcskytool_revert_update.sql
===================================================================
--- trunk/ippTools/share/xcskytool_revert_update.sql	(revision 42837)
+++ trunk/ippTools/share/xcskytool_revert_update.sql	(revision 42837)
@@ -0,0 +1,11 @@
+UPDATE xcskyRun
+    JOIN xcskyResult USING(xcsky_id) JOIN xcskyInput USING(xcsky_id)
+    JOIN xcstackRun  USING(xcstack_id) JOIN skycell USING(tess_id, skycell_id)
+SET xcskyResult.fault = 0
+WHERE xcskyRun.xcsky_id = xcskyResult.xcsky_id
+    AND xcskyRun.xcsky_id = xcskyInput.xcsky_id
+    AND xcskyInput.xcstack_id = xcstackRun.xcstack_id
+    AND xcstackRun.tess_id = skycell.tess_id
+    AND xcstackRun.skycell_id = skycell.skycell_id
+    AND xcskyRun.state = 'update'
+    AND xcskyResult.fault != 0
Index: trunk/ippTools/share/xcskytool_revertxccal.sql
===================================================================
--- trunk/ippTools/share/xcskytool_revertxccal.sql	(revision 42837)
+++ trunk/ippTools/share/xcskytool_revertxccal.sql	(revision 42837)
@@ -0,0 +1,8 @@
+DELETE FROM xccalResult
+USING xccalResult, xccalRun, xcstackRun, skycell
+WHERE xccalRun.xccal_id = xccalResult.xccal_id
+    AND xcstackRun.xcstack_id = xccalRun.xcstack_id
+    AND xccalRun.state = 'new'
+    AND xccalResult.fault != 0
+    AND xcstackRun.tess_id = skycell.tess_id
+    AND xcstackRun.skycell_id = skycell.skycell_id
Index: trunk/ippTools/share/xcskytool_todo.sql
===================================================================
--- trunk/ippTools/share/xcskytool_todo.sql	(revision 42837)
+++ trunk/ippTools/share/xcskytool_todo.sql	(revision 42837)
@@ -0,0 +1,30 @@
+ -- we implicitly assume that all xcstackRuns used for a xcskyRun have the same tess_id and skycell_id 
+SELECT
+    xcskyRun.xcsky_id,
+    xcskyRun.workdir,
+    xcskyRun.reduction,
+    xcskyRun.label,
+    xcskyRun.state,
+    MIN(xcstackRun.tess_id) as tess_id,
+    MIN(xcstackRun.skycell_id) as skycell_id,
+    TRUNCATE(MIN(skycell.radeg)/15., 4) as rahours,
+    MIN(skycell.radeg) as radeg,
+    MIN(skycell.decdeg) as decdeg,
+    MIN(skycell.glong) as glong,
+    MIN(skycell.glat) as glat,
+    xcskyResult.path_base,
+    IFNULL(Label.priority, 10000) AS priority
+FROM xcskyRun
+JOIN xcskyInput USING (xcsky_id)
+JOIN xcstackRun USING (xcstack_id)
+JOIN skycell USING(tess_id, skycell_id)
+LEFT JOIN xcskyResult USING(xcsky_id)
+LEFT JOIN Label ON xcskyRun.label = Label.label
+WHERE
+   ((xcskyRun.state = 'new' AND xcskyResult.xcsky_id IS NULL)
+    OR (xcskyRun.state = 'update' AND xcskyResult.fault = 0))
+   AND (Label.active OR Label.active IS NULL)
+    -- WHERE hook %s
+GROUP BY xcsky_id
+HAVING SUM(IF(xcstackRun.state = 'full', 1, 0)) = COUNT(xcskyInput.xcsky_id)
+ORDER BY priority DESC, xcsky_id
Index: trunk/ippTools/share/xcskytool_xccalresult.sql
===================================================================
--- trunk/ippTools/share/xcskytool_xccalresult.sql	(revision 42837)
+++ trunk/ippTools/share/xcskytool_xccalresult.sql	(revision 42837)
@@ -0,0 +1,15 @@
+SELECT 
+    xccalResult.*,
+    xccalRun.xcstack_id,
+    xccalRun.workdir,
+    xcstackRun.filter,
+    xccalRun.state,
+    xccalRun.label,
+    xccalRun.data_group,
+    xccalRun.dist_group,
+    xccalRun.xcsky_id,
+    skycell.*
+FROM xccalRun
+JOIN xccalResult USING(xccal_id)
+JOIN xcstackRun USING(xcstack_id) 
+JOIN skycell USING(tess_id, skycell_id)
Index: trunk/ippTools/share/xcstacktool_addbyquery_select.sql
===================================================================
--- trunk/ippTools/share/xcstacktool_addbyquery_select.sql	(revision 42837)
+++ trunk/ippTools/share/xcstacktool_addbyquery_select.sql	(revision 42837)
@@ -0,0 +1,30 @@
+-- This is a query to get a list of skycells with warps to be stacked,
+-- along with the number of warps already in a stack.
+
+SELECT
+    TMP.xcstack_id,
+    TMP.stack_id,
+    TMP.tess_id,
+    TMP.skycell_id,
+    TMP.filter
+  FROM
+  (SELECT
+    xcstackRun.xcstack_id,
+    stackRun.stack_id,
+    stackRun.tess_id,
+    stackRun.skycell_id,
+    stackRun.filter
+    FROM %s.stackRun
+    JOIN %s.stackSumSkyfile USING (stack_id)
+    JOIN xcstackRun
+    ON ((stackRun.tess_id = xcstackRun.tess_id)
+    AND (stackRun.skycell_id = xcstackRun.skycell_id)
+    AND (stackRun.filter   = '%s')
+    AND (xcstackRun.filter = '%s'))
+-- WHERE HOOK:
+   WHERE %s
+  ) AS TMP
+    LEFT JOIN xcstackInputSkyfile
+    ON (TMP.xcstack_id = xcstackInputSkyfile.xcstack_id)
+    AND (camera_id = %d)
+    WHERE xcstackInputSkyfile.xcstack_id IS NULL
Index: trunk/ippTools/share/xcstacktool_addcamera.sql
===================================================================
--- trunk/ippTools/share/xcstacktool_addcamera.sql	(revision 42837)
+++ trunk/ippTools/share/xcstacktool_addcamera.sql	(revision 42837)
@@ -0,0 +1,3 @@
+
+INSERT INTO xcstackCamera
+ (camera_id, camera) VALUES (0, '@CAMERA@')
Index: trunk/ippTools/share/xcstacktool_definebyquery_select.sql
===================================================================
--- trunk/ippTools/share/xcstacktool_definebyquery_select.sql	(revision 42837)
+++ trunk/ippTools/share/xcstacktool_definebyquery_select.sql	(revision 42837)
@@ -0,0 +1,13 @@
+-- This is a query to get a list of skycells with warps to be stacked,
+-- along with the number of warps already in a stack.
+
+SELECT
+    xcstack_id,
+    stackRun.stack_id,
+    stackRun.tess_id,
+    stackRun.skycell_id,
+    stackRun.filter
+    FROM %s.stackRun
+    JOIN %s.stackSumSkyfile USING (stack_id)
+    LEFT JOIN
+    
Index: trunk/ippTools/share/xcstacktool_definebyquery_select_XC.sql
===================================================================
--- trunk/ippTools/share/xcstacktool_definebyquery_select_XC.sql	(revision 42837)
+++ trunk/ippTools/share/xcstacktool_definebyquery_select_XC.sql	(revision 42837)
@@ -0,0 +1,6 @@
+SELECT
+ xcstack_id,
+ stack_id
+ FROM xcstackRun
+ JOIN xcstackInputSkyfile USING (xcstack_id)
+
Index: trunk/ippTools/share/xcstacktool_donecleanup.sql
===================================================================
--- trunk/ippTools/share/xcstacktool_donecleanup.sql	(revision 42837)
+++ trunk/ippTools/share/xcstacktool_donecleanup.sql	(revision 42837)
@@ -0,0 +1,5 @@
+SELECT
+    xcstackRun.*
+FROM xcstackRun
+WHERE
+    xcstackRun.state = 'cleaned'
Index: trunk/ippTools/share/xcstacktool_inputskyfile.sql
===================================================================
--- trunk/ippTools/share/xcstacktool_inputskyfile.sql	(revision 42837)
+++ trunk/ippTools/share/xcstacktool_inputskyfile.sql	(revision 42837)
@@ -0,0 +1,7 @@
+SELECT
+  stackSumSkyfile.*
+  FROM xcstackInputSkyfile
+  JOIN %s.stackSumSkyfile
+  ON (xcstackInputSkyfile.stack_id = stackSumSkyfile.stack_id)
+  AND (xcstackInputSkyfile.camera_id = %d) where (xcstack_id = %d)
+    
Index: trunk/ippTools/share/xcstacktool_pendingcleanuprun.sql
===================================================================
--- trunk/ippTools/share/xcstacktool_pendingcleanuprun.sql	(revision 42837)
+++ trunk/ippTools/share/xcstacktool_pendingcleanuprun.sql	(revision 42837)
@@ -0,0 +1,9 @@
+-- this query only returns xcstackRun entries with a matching xcstackSumSkyfile
+SELECT
+    xcstackRun.xcstack_id,
+    xcstackRun.state,
+    xcstackSumSkyfile.path_base
+FROM xcstackRun
+JOIN xcstackSumSkyfile USING(xcstack_id)
+WHERE
+    (xcstackRun.state = 'goto_cleaned' OR xcstackRun.state = 'goto_scrubbed' OR xcstackRun.state = 'goto_purged')
Index: trunk/ippTools/share/xcstacktool_pendingcleanupskyfile.sql
===================================================================
--- trunk/ippTools/share/xcstacktool_pendingcleanupskyfile.sql	(revision 42837)
+++ trunk/ippTools/share/xcstacktool_pendingcleanupskyfile.sql	(revision 42837)
@@ -0,0 +1,11 @@
+SELECT
+    xcstackSumSkyfile.*,
+    xcstackRun.state,
+    xcstackRun.workdir,
+    xcstackRun.tess_id,
+    xcstackRun.skycell_id
+FROM xcstackRun
+JOIN xcstackSumSkyfile
+    USING(xcstack_id)
+WHERE
+    (xcstackRun.state = 'goto_cleaned' OR xcstackRun.state = 'goto_scrubbed' OR xcstackRun.state = 'goto_purged')
Index: trunk/ippTools/share/xcstacktool_revertsumskyfile_delete.sql
===================================================================
--- trunk/ippTools/share/xcstacktool_revertsumskyfile_delete.sql	(revision 42837)
+++ trunk/ippTools/share/xcstacktool_revertsumskyfile_delete.sql	(revision 42837)
@@ -0,0 +1,5 @@
+DELETE FROM xcstackSumSkyfile
+USING xcstackSumSkyfile, xcstackRun
+WHERE xcstackSumSkyfile.xcstack_id = xcstackRun.xcstack_id
+    AND xcstackRun.state = 'new'
+    AND fault != 0
Index: trunk/ippTools/share/xcstacktool_sumskyfile.sql
===================================================================
--- trunk/ippTools/share/xcstacktool_sumskyfile.sql	(revision 42837)
+++ trunk/ippTools/share/xcstacktool_sumskyfile.sql	(revision 42837)
@@ -0,0 +1,12 @@
+SELECT
+    xcstackSumSkyfile.*,
+    xcstackRun.state,
+    xcstackRun.tess_id,
+    xcstackRun.skycell_id,
+    xcstackRun.filter,
+    xcstackRun.label,
+    xcstackRun.data_group,
+    xcstackRun.workdir
+FROM xcstackRun
+JOIN xcstackSumSkyfile
+USING(xcstack_id)
Index: trunk/ippTools/share/xcstacktool_tosum.sql
===================================================================
--- trunk/ippTools/share/xcstacktool_tosum.sql	(revision 42837)
+++ trunk/ippTools/share/xcstacktool_tosum.sql	(revision 42837)
@@ -0,0 +1,15 @@
+SELECT
+    xcstackRun.xcstack_id,
+    xcstackRun.tess_id,
+    xcstackRun.skycell_id,
+    xcstackRun.workdir,
+    xcstackRun.reduction,
+    xcstackRun.label,
+    xcstackRun.state,
+    IFNULL(Label.priority, 10000) AS priority
+FROM xcstackRun
+LEFT JOIN xcstackSumSkyfile USING(xcstack_id)
+LEFT JOIN Label ON Label.label = xcstackRun.label
+WHERE
+    (xcstackRun.state = 'new' AND xcstackSumSkyfile.xcstack_id IS NULL)
+    AND (Label.active OR Label.active IS NULL)
Index: trunk/ippTools/src/Makefile.am
===================================================================
--- trunk/ippTools/src/Makefile.am	(revision 42806)
+++ trunk/ippTools/src/Makefile.am	(revision 42837)
@@ -24,4 +24,7 @@
 	stacktool \
 	staticskytool \
+	xcstacktool \
+	xcskytool \
+	xcfftool \
 	warptool \
 	receivetool \
@@ -82,4 +85,7 @@
 	regtool.h \
 	stacktool.h \
+	xcstacktool.h \
+	xcskytool.h \
+	xcfftool.h \
 	staticskytool.h \
 	warptool.h \
@@ -245,4 +251,22 @@
     staticskytoolConfig.c
 
+xcstacktool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+xcstacktool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+xcstacktool_SOURCES = \
+    xcstacktool.c \
+    xcstacktoolConfig.c
+
+xcskytool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+xcskytool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+xcskytool_SOURCES = \
+    xcskytool.c \
+    xcskytoolConfig.c
+
+xcfftool_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
+xcfftool_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
+xcfftool_SOURCES = \
+    xcfftool.c \
+    xcfftoolConfig.c
+
 pxadmin_CFLAGS = $(PSLIB_CFLAGS) $(PSMODULES_CFLAGS) $(IPPDB_CFLAGS)
 pxadmin_LDADD = $(PSLIB_LIBS) $(PSMODULES_LIBS) $(IPPDB_LIBS) libpxtools.la
Index: trunk/ippTools/src/addtool.c
===================================================================
--- trunk/ippTools/src/addtool.c	(revision 42806)
+++ trunk/ippTools/src/addtool.c	(revision 42837)
@@ -1694,8 +1694,8 @@
   // required
   PXOPT_LOOKUP_STR(minidvodb_group, config->args, "-set_minidvodb_group", true, false);
-  PXOPT_LOOKUP_STR(minidvodb_path, config->args, "-set_minidvodb_path", false, false);
+  PXOPT_LOOKUP_STR(minidvodb_path_raw, config->args, "-set_minidvodb_path", false, false);
   PXOPT_LOOKUP_STR(minidvodb_host, config->args, "-set_minidvodb_host", false, false);
   //optional
-  PXOPT_LOOKUP_STR(minidvodb_name, config->args, "-set_minidvodb_name", false, false);
+  PXOPT_LOOKUP_STR(minidvodb_name_raw, config->args, "-set_minidvodb_name", false, false);
   PXOPT_LOOKUP_STR(state, config->args, "-set_state", false, false);
   PXOPT_LOOKUP_STR(note,            config->args, "-set_note",            false, false);
@@ -1706,19 +1706,9 @@
   }
 
-//  psString minidvodbpath = "NULL";
-
-  // I don't know how to get around the complaints of minidvodb_path can't be null. this 'fixes' it, but someone smarter can fix it properly.
-//  if (minidvodb_path) {
-//    minidvodbpath = minidvodb_path;
-//  } else {
-//    psError(PS_ERR_UNKNOWN, false, "require minidvodb_path");
-//    return false;
-//  }
-  
   if (!minidvodbRunInsert(config->dbh,
 			  0, // job_id
-			  minidvodb_name,
+			  minidvodb_name_raw,
 			  minidvodb_group,
-			  minidvodb_path,
+			  minidvodb_path_raw,
 			  minidvodb_host,
 			  "new",
@@ -1741,26 +1731,48 @@
   printf("%" PRId64 "\n", minidvodb_id);
 
-  
-  if (!minidvodb_name) {
-    psStringAppend(&minidvodb_name, "%s.%" PRIu64,minidvodb_group,minidvodb_id);
-  }
-  if (minidvodb_path) {
-    psStringAppend(&minidvodb_path,"/%s",minidvodb_name);
-  }
-
-  psString query = NULL;
-
-  psStringAppend(&query, "UPDATE minidvodbRun SET minidvodb_path = '%s', minidvodb_name = '%s' where minidvodb_id = %" PRIu64";", minidvodb_path, minidvodb_name, minidvodb_id);
-
-  if (!p_psDBRunQuery(config->dbh, query)) {
-    psError(PS_ERR_UNKNOWN, false, "database error");
-    psFree(query);
-    return false;
-  }
-
-  if (!psDBCommit(config->dbh)) {
-    psError(PS_ERR_UNKNOWN, false, "database error");
-    return false;
-  }
+  // we created a new entry using the values supplied above for minidvodb_name_raw, minidvodb_host_raw.
+  // If minidvodb_name is not specified, it will be generated based on the value of minidvodb_group and
+  // the minidvodb_id of the new entry.  But this means we need to insert first, get the ID, then update.
+
+  bool doUpdate = false;
+
+  // Below: note that minidvodb_name_raw and minidvodb_path_raw are stored on config->args
+  // we need to work with a temporary external variable to avoid changing entries on the MD
+
+  // if minidvodb_name_raw is not specified above, create it from minidvodb_group
+  psString minidvodb_name = NULL;
+  if (minidvodb_name_raw) {
+    minidvodb_name = psStringCopy (minidvodb_name_raw);
+  } else {
+    psStringAppend(&minidvodb_name, "%s.%" PRIu64, minidvodb_group, minidvodb_id);
+    doUpdate = true;
+  }
+
+  // if minidvodb_path_raw IS specified above, update it with the minidvodb_name
+  psString minidvodb_path = NULL;
+  if (minidvodb_path_raw) {
+    psStringAppend(&minidvodb_path, "%s/%s", minidvodb_path_raw, minidvodb_name);
+    doUpdate = true;
+  }
+
+  if (doUpdate) {
+    psString query = NULL;
+
+    psStringAppend(&query, "UPDATE minidvodbRun SET minidvodb_path = '%s', minidvodb_name = '%s' where minidvodb_id = %" PRIu64";", minidvodb_path, minidvodb_name, minidvodb_id);
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+      psError(PS_ERR_UNKNOWN, false, "database error");
+      psFree(query);
+      return false;
+    }
+
+    if (!psDBCommit(config->dbh)) {
+      psError(PS_ERR_UNKNOWN, false, "database error");
+      return false;
+    }
+  }
+
+  psFree (minidvodb_name);
+  psFree (minidvodb_path);
 
   return true;
Index: trunk/ippTools/src/disttool.c
===================================================================
--- trunk/ippTools/src/disttool.c	(revision 42806)
+++ trunk/ippTools/src/disttool.c	(revision 42837)
@@ -765,8 +765,6 @@
     }
 
-    if (simple) {/* no option? */}
-
     if (pretend) {
-        if (!ippdbPrintMetadatas(stdout, output, "distRunsToUpdate", true)) {
+        if (!ippdbPrintMetadatas(stdout, output, "distRunsToUpdate", !simple)) {
             psError(PS_ERR_UNKNOWN, false, "failed to print array");
             psFree(output);
@@ -1652,4 +1650,5 @@
     PXOPT_LOOKUP_STR(state, config->args, "-set_state", false, false);
     PXOPT_LOOKUP_STR(comment, config->args, "-comment", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
 
     distTargetRow *row = distTargetRowAlloc(
@@ -1676,5 +1675,5 @@
     row->target_id = psDBLastInsertID(config->dbh);
 
-    if (!distTargetPrintObject(stdout, row, true)) {
+    if (!distTargetPrintObject(stdout, row, !simple)) {
             psError(PS_ERR_UNKNOWN, false, "failed to print object");
             psFree(row);
@@ -1815,4 +1814,5 @@
     PXOPT_LOOKUP_STR(last_fileset, config->args, "-last_fileset", false, false);
     PXOPT_LOOKUP_STR(state, config->args,        "-set_state", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args,      "-simple", false);
 
     // XXX: should we insure that these names do not contatin any whitespace?
@@ -1842,5 +1842,5 @@
     row->dest_id = psDBLastInsertID(config->dbh);
 
-    if (!rcDestinationPrintObject(stdout, row, true)) {
+    if (!rcDestinationPrintObject(stdout, row, !simple)) {
         psError(PS_ERR_UNKNOWN, false, "failed to print object");
         psFree(row);
Index: trunk/ippTools/src/disttoolConfig.c
===================================================================
--- trunk/ippTools/src/disttoolConfig.c	(revision 42806)
+++ trunk/ippTools/src/disttoolConfig.c	(revision 42837)
@@ -275,4 +275,5 @@
     psMetadataAddStr(definedestinationArgs, PS_LIST_TAIL, "-last_fileset",0, "define last_fileset", NULL);
     psMetadataAddStr(definedestinationArgs, PS_LIST_TAIL, "-set_state",   0, "define state", NULL);
+    psMetadataAddBool(definedestinationArgs, PS_LIST_TAIL, "-simple",  0, "use the simple output format", false);
 
     // -updatedestination
@@ -292,4 +293,5 @@
     psMetadataAddStr(definetargetArgs, PS_LIST_TAIL, "-set_state", 0, "define state", NULL);
     psMetadataAddStr(definetargetArgs, PS_LIST_TAIL, "-comment",   0, "define comment", NULL);
+    psMetadataAddBool(definetargetArgs, PS_LIST_TAIL, "-simple",  0, "use the simple output format", false);
 
     // -updatetarget
Index: trunk/ippTools/src/fftool.c
===================================================================
--- trunk/ippTools/src/fftool.c	(revision 42806)
+++ trunk/ippTools/src/fftool.c	(revision 42837)
@@ -104,5 +104,8 @@
 }
 
-
+// NOTE: this mode seems to be insufficiently constrained.  It first identifies the
+// set of stackRun/skycalRun entries that lack a fullForceRun match.  But the
+// second stage is selecting all warps only based on (tess_id, skycell_id, filter) without
+// restriction to those which contributed to the stack
 static bool definebyqueryMode(pxConfig *config)
 {
@@ -150,4 +153,6 @@
     }
     PXOPT_COPY_F32(config->args, warpWhereMD, "-select_good_frac_min",  "warpSkyfile.good_frac",   ">=");
+    // XXX the tess_id and filter below can never be used because they are removed above (for stack)
+    // these options should be something like -select_warp_tess_id and -select_warp_filter
     PXOPT_COPY_STR(config->args, warpWhereMD, "-select_tess_id",       "warpRun.tess_id",         "==");
     pxAddLabelSearchArgs(config, warpWhereMD, "-select_filter",        "rawExp.filter",          "LIKE");
@@ -223,4 +228,5 @@
     psString warpQueryTemplate = pxDataGet("fftool_definebyquery_select_warps.sql");
 
+    // NOTE: these allow the set of input warps to be restricted
     whereClause = psDBGenerateWhereConditionSQL(warpWhereMD, NULL);
     psStringAppend(&warpQueryTemplate, "\nAND %s", whereClause);
@@ -339,4 +345,7 @@
 }
 
+// NOTE: this mode and 'definebyquery' seem to be nearly identical except that
+// 'definebyquery' allows for restrictions on the input warps by label, data_group,
+// warp_id, tess_id, filter, good_frac
 static bool defineforstacksMode(pxConfig *config)
 {
Index: trunk/ippTools/src/pztool.c
===================================================================
--- trunk/ippTools/src/pztool.c	(revision 42806)
+++ trunk/ippTools/src/pztool.c	(revision 42837)
@@ -50,6 +50,5 @@
 static bool updatenewexpMode(pxConfig *config);
 
-// XXX EAM : 2021.05.18 : code for updatesummitExp was added but not finished.
-// XXX static bool updatesummitExpMode(pxConfig *config);
+static bool updatesummitExpMode(pxConfig *config);
 
 // static bool copydoneCompleteExp(pxConfig *config);
@@ -93,5 +92,5 @@
 	MODECASE(PZTOOL_MODE_UPDATEPZEXP, updatepzexpMode);
         MODECASE(PZTOOL_MODE_UPDATENEWEXP, updatenewexpMode);
-	// XXX MODECASE(PZTOOL_MODE_UPDATESUMMITEXP, updatesummitExpMode);
+	MODECASE(PZTOOL_MODE_UPDATESUMMITEXP, updatesummitExpMode);
         default:
             psAbort("invalid option (this should not happen)");
@@ -810,5 +809,4 @@
 }
 
-
 static bool updatecopiedMode(pxConfig *config)
 {
@@ -939,6 +937,5 @@
 }
 
-# if (0)
-static bool summitExpSetFault(pxConfig *config, const psS64 summit_id, const int fault)
+static bool summitExpSetFault(pxConfig *config, const psString exp_name, const psS64 summit_id, const int fault)
 {
     // check that state is a valid string value
@@ -949,7 +946,7 @@
   }
 
-    char *query = "UPDATE summitExp SET fault = %d WHERE summit_id = %ld";
-    if (!p_psDBRunQueryF(config->dbh, query, fault, summit_id)) {
-        psError(PS_ERR_UNKNOWN, false, "failed to change state for %lld", (long long) summit_id);
+    char *query = "UPDATE summitExp SET fault = %d WHERE summit_id = %ld AND exp_name = '%s'";
+    if (!p_psDBRunQueryF(config->dbh, query, fault, summit_id, exp_name)) {
+	psError(PS_ERR_UNKNOWN, false, "failed to change state for %s = %lld", exp_name, (long long) summit_id);
         return false;
     }
@@ -960,17 +957,16 @@
 static bool updatesummitExpMode(pxConfig *config)
 {
-  int fault;
-
+  // require both exp_name and summit_id as a double-check
   PS_ASSERT_PTR_NON_NULL(config, false);
   PXOPT_LOOKUP_S64(summit_id, config->args, "-summit_id", true, false);
-  PXOPT_LOOKUP_S32(state,    config->args, "-set_fault",true, false);
-
-  if (!summitExpSetFault(config,summit_id, fault)) {
-    psError(PS_ERR_UNKNOWN, false, "failed to change state for %lld", (long long) summit_id);
-    return false;
+  PXOPT_LOOKUP_STR(exp_name,  config->args, "-exp_name",  true, false);
+  PXOPT_LOOKUP_S32(fault,     config->args, "-set_fault", true, false);
+
+  if (!summitExpSetFault(config, exp_name, summit_id, fault)) {
+      psError(PS_ERR_UNKNOWN, false, "failed to change state for %s = %lld", exp_name, (long long) summit_id);
+      return false;
   }
   return true;
 }
-# endif  
   
 static bool updatepzexpMode(pxConfig *config)
Index: trunk/ippTools/src/pztoolConfig.c
===================================================================
--- trunk/ippTools/src/pztoolConfig.c	(revision 42806)
+++ trunk/ippTools/src/pztoolConfig.c	(revision 42837)
@@ -171,13 +171,9 @@
     psMetadataAddStr(updatepzexpArgs, PS_LIST_TAIL, "-set_state",  0,            "define new state (required)", NULL);
     
-# if (0)
     // -updatesummitexp
     psMetadata *updatesummitexpArgs = psMetadataAlloc();
     psMetadataAddS64(updatesummitexpArgs, PS_LIST_TAIL, "-summit_id", 0,     "define summit_id", 0);
     psMetadataAddStr(updatesummitexpArgs, PS_LIST_TAIL, "-exp_name",   0,            "search by exposure name (required)", NULL);
-    psMetadataAddStr(updatesummitexpArgs, PS_LIST_TAIL, "-inst",       0,            "search by camera (required)", NULL);
-    psMetadataAddStr(updatesummitexpArgs, PS_LIST_TAIL, "-telescope",  0,            "search by telescope (required)", NULL);
     psMetadataAddS32(updatesummitexpArgs, PS_LIST_TAIL, "-set_fault",  0,            "define new fault (required)", 0);
-# endif
     
     // -updatenewexp
Index: trunk/ippTools/src/stacktool.c
===================================================================
--- trunk/ippTools/src/stacktool.c	(revision 42806)
+++ trunk/ippTools/src/stacktool.c	(revision 42837)
@@ -496,7 +496,4 @@
             psFree(insert);
             psFree(list);
-            if (!psDBRollback(config->dbh)) {
-                psError(PS_ERR_UNKNOWN, false, "database error");
-            }
             return false;
         }
Index: trunk/ippTools/src/staticskytool.c
===================================================================
--- trunk/ippTools/src/staticskytool.c	(revision 42806)
+++ trunk/ippTools/src/staticskytool.c	(revision 42837)
@@ -1061,6 +1061,6 @@
     PXOPT_COPY_S64(config->args, whereMD, "-select_sky_id",        "staticskyRun.sky_id",       "==");
     PXOPT_COPY_S64(config->args, whereMD, "-select_stack_id",      "stackRun.stack_id",         "==");
-    PXOPT_COPY_STR(config->args, whereMD, "-select_skycell_id",    "stackRun.skycell_id",       "==");
     PXOPT_COPY_STR(config->args, whereMD, "-select_tess_id",       "stackRun.tess_id",          "==");
+    PXOPT_COPY_STR(config->args, whereMD, "-select_skycell_id",    "stackRun.skycell_id",       "LIKE");
     pxAddLabelSearchArgs(config, whereMD, "-select_filter",        "stackRun.filter",           "LIKE");
     PXOPT_COPY_F32(config->args, whereMD, "-select_good_frac_min", "stackSumSkyfile.good_frac", ">=");
Index: trunk/ippTools/src/xcfftool.c
===================================================================
--- trunk/ippTools/src/xcfftool.c	(revision 42837)
+++ trunk/ippTools/src/xcfftool.c	(revision 42837)
@@ -0,0 +1,1202 @@
+/*
+ * xcfftool.c
+ *
+ * Copyright (C) 2013 Institute for Astronomy, University of Hawaii
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVB_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include <ippdb.h>
+
+#include "pxtools.h"
+#include "pxspace.h"
+#include "xcfftool.h"
+
+static bool definebyqueryMode(pxConfig *config);
+static bool updaterunMode(pxConfig *config);
+static bool todoMode(pxConfig *config);
+static bool addresultMode(pxConfig *config);
+static bool resultMode(pxConfig *config);
+static bool revertMode(pxConfig *config);
+static bool updateresultMode(pxConfig *config);
+static bool toadvanceMode(pxConfig *config);
+static bool addsummaryMode(pxConfig *config);
+static bool revertsummaryMode(pxConfig *config);
+static bool updatesummaryMode(pxConfig *config);
+static bool summaryMode(pxConfig *config);
+static bool exportrunMode(pxConfig *config);
+static bool importrunMode(pxConfig *config);
+
+static bool setxcForceRunState(pxConfig *config, psS64 xcff_id, const char *state);
+static psS64 getCameraIDfromName(pxConfig *config, const char *dbname, const char *camera);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = xcfftoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(XCFFTOOL_MODE_DEFINEBYQUERY,     definebyqueryMode);
+        MODECASE(XCFFTOOL_MODE_UPDATERUN,         updaterunMode);
+        MODECASE(XCFFTOOL_MODE_TODO,              todoMode);
+        MODECASE(XCFFTOOL_MODE_ADDRESULT,         addresultMode);
+        MODECASE(XCFFTOOL_MODE_RESULT,            resultMode);
+        MODECASE(XCFFTOOL_MODE_REVERT,            revertMode);
+        MODECASE(XCFFTOOL_MODE_UPDATERESULT,      updateresultMode);
+        MODECASE(XCFFTOOL_MODE_TOADVANCE,         toadvanceMode);
+        MODECASE(XCFFTOOL_MODE_ADDSUMMARY,        addsummaryMode);
+        MODECASE(XCFFTOOL_MODE_REVERTSUMMARY,     revertsummaryMode);
+        MODECASE(XCFFTOOL_MODE_UPDATESUMMARY,     updatesummaryMode);
+        MODECASE(XCFFTOOL_MODE_SUMMARY,           summaryMode);
+        MODECASE(XCFFTOOL_MODE_EXPORTRUN,         exportrunMode);
+        MODECASE(XCFFTOOL_MODE_IMPORTRUN,         importrunMode);
+        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);
+}
+
+// NOTE: if I denormalize and populate (tess_id, skycell_id) in xcForceRun then I do not need
+// to join back to xcstackRun to get that info
+static bool definebyqueryMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // I need the name of the current working database
+    psMetadataItem *dbnameMD = pmConfigUserSite(config->modules, "DBNAME", PS_DATA_STRING);
+    char *dbname = dbnameMD->data.str;
+
+    // required:
+    PXOPT_LOOKUP_STR(workdir,     config->args, "-set_workdir", true, false);
+    PXOPT_LOOKUP_STR(label,       config->args, "-set_label", true, false);
+
+    // we have two relevant databases: the current working database (e.g., gpc1) and the cross-camera
+    // database used to generate the cross-camera stacks (e.g., xcamera).  the cross-camera database
+    // name is supplied with -xcamera as an argument.  the current working database is part of the
+    // database config info
+
+    // The xcamera must be supplied as a string, then the camera_id is selected
+    // from the table xcstackCamera.  
+    PXOPT_LOOKUP_STR(xcamera, config->args, "-xcamera", true, false);
+
+    // get camera_id for the working database from the xcamera database
+    psS64 camera_id = getCameraIDfromName(config, xcamera, dbname);
+    if (!camera_id) {
+	psError(PXTOOLS_ERR_SYS, false, "failed to find camera %s in database", dbname);
+	return false;
+    }
+    // get xcamera_id for the cross-camera database from the working database
+    psS64 xcamera_id = getCameraIDfromName(config, dbname, xcamera);
+    if (!xcamera_id) {
+	psError(PXTOOLS_ERR_SYS, false, "failed to find camera %s in database", xcamera);
+	return false;
+    }
+
+    // optional
+    PXOPT_LOOKUP_STR(data_group,  config->args, "-set_data_group", false, false);
+    PXOPT_LOOKUP_STR(dist_group,  config->args, "-set_dist_group", false, false);
+    PXOPT_LOOKUP_STR(note,        config->args, "-set_note", false, false);
+    PXOPT_LOOKUP_STR(reduction,   config->args, "-set_reduction", false, false);
+
+    // first, we need to select the xccalRun(s) which need to have xcForceRuns.  we can limit the selection based on
+    // qualities of the xccalRun or the xcstackRun.  There will be one xcForceRun for each
+    // identified xccalRun (there may be multiple xcForceRuns with different labels)
+
+    psMetadata *xccalWhereMD = psMetadataAlloc();
+    pxAddLabelSearchArgs(config, xccalWhereMD, "-select_xccal_label",      "xccalRun.label",      "LIKE");
+    pxAddLabelSearchArgs(config, xccalWhereMD, "-select_xccal_data_group", "xccalRun.data_group", "LIKE");
+    PXOPT_COPY_S64(config->args, xccalWhereMD, "-select_xccal_id",         "xccalRun.xccal_id",   "==");
+
+    if (!psListLength(xccalWhereMD->list)) {
+        psError(PXTOOLS_ERR_CONFIG, false, "xccal search parameters are required");
+        psFree(xccalWhereMD);
+        return false;
+    }
+
+    // the select query does not join to xcstackRun, but it does join to stackRun
+    PXOPT_COPY_STR(config->args, xccalWhereMD, "-select_skycell_id",    "stackRun.skycell_id",      "LIKE");
+    PXOPT_COPY_STR(config->args, xccalWhereMD, "-select_tess_id",       "stackRun.tess_id",         "==");
+    pxAddLabelSearchArgs(config, xccalWhereMD, "-select_filter",        "stackRun.filter",          "LIKE");
+    if (!pxskycellAddWhere(config, xccalWhereMD)) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to add skycell search arguments");
+        psFree(xccalWhereMD);
+        return false;
+    }
+
+    // once we have identified the xccalRun(s) for which we will generate the xcForceRuns, we
+    // need to select the input warps.  Normally, these would be all warps used to generate all
+    // stacks used to generate the xcstack skyfile associated with the xccalRun.  But we have
+    // the options to limit which warps we actually include.  
+
+    psMetadata *warpWhereMD = psMetadataAlloc();
+    pxAddLabelSearchArgs(config, warpWhereMD, "-select_warp_label",      "warpRun.label",           "LIKE");
+    pxAddLabelSearchArgs(config, warpWhereMD, "-select_warp_data_group", "warpRun.data_group",      "LIKE");
+    PXOPT_COPY_S64(config->args, warpWhereMD, "-select_warp_id",         "warpRun.warp_id",         "==");
+    PXOPT_COPY_F32(config->args, warpWhereMD, "-select_good_frac_min",   "warpSkyfile.good_frac",  ">=");
+    // it is necessary to limit the warps otherwise the warpQueryTemplate (line 282) will be ill-formed
+    if (!psListLength(warpWhereMD->list)) {
+        psError(PXTOOLS_ERR_CONFIG, false, "warp search parameters are required");
+        psFree(warpWhereMD);
+        psFree(xccalWhereMD);
+        return false;
+    }
+
+    PXOPT_LOOKUP_BOOL(rerun, config->args, "-rerun", false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_BOOL(pretend, config->args, "-pretend", false);
+
+    psString select = NULL;
+
+    psString query_pt1 = pxDataGet("xcfftool_definebyquery_pt1.sql");
+    if (!query_pt1) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        psFree(xccalWhereMD);
+        psFree(warpWhereMD);
+        return false;
+    }
+
+    // query_pt1 has 4 places where the xcamera database name must be supplied
+    psStringAppend (&select, query_pt1, xcamera, xcamera, xcamera, xcamera);
+    psFree (query_pt1);
+
+    if (!rerun) {
+	psStringAppend(&select, "LEFT JOIN xcForceRun ON (xcForceRun.xccal_id = %s.xccalRun.xccal_id) AND (xcForceRun.label = '%s')\n", xcamera, label);
+    }
+
+    psString query_pt2 = pxDataGet("xcfftool_definebyquery_pt2.sql");
+    if (!query_pt2) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        psFree(xccalWhereMD);
+        psFree(warpWhereMD);
+        psFree(select);
+        return false;
+    }
+
+    // query_pt2 has 3 places where the xcamera database name must be supplied and 1 for this camera
+    psStringAppend (&select, query_pt2, xcamera, (long long) camera_id, xcamera, xcamera);
+    psFree (query_pt2);
+
+    psString where = NULL;
+    psString whereClause = psDBGenerateWhereConditionSQL(xccalWhereMD, NULL);
+    psStringAppend(&select, "\n AND %s", whereClause);
+
+    psFree(whereClause);
+    psFree(xccalWhereMD);
+
+    // accept only the entries without a match in the target label
+    if (!rerun) psStringAppend(&select, "\n AND xcff_id IS NULL\n");
+
+    if (!p_psDBRunQuery(config->dbh, select)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(select);
+        return false;
+    }
+    psFree(select);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+        psFree(where);
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psWarning("xcfftool: no rows found");
+        psFree(output);
+        psFree(where);
+        return true;
+    }
+
+    if (pretend) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "toFullForce", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            psFree(where);
+            return false;
+        }
+        psFree(output);
+        psFree(where);
+        return true;
+    }
+
+    // this query selects all of the warps where were inputs to the given stackRun,
+    // with restrictions based on (quality, fault, warpRun.state, and other supplied conditions)
+    // there is no limit to the version of the warp analyss, to
+    psString warpQueryTemplate = pxDataGet("xcfftool_definebyquery_select_warps.sql");
+
+    // note the GROUP BY: if warpWhereMD is empty, this contruction
+    // will yield nonsense
+    whereClause = psDBGenerateWhereConditionSQL(warpWhereMD, NULL);
+    psStringAppend(&warpQueryTemplate, "\n AND %s GROUP BY warp_id", whereClause);
+
+    for (long i = 0; i < output->n; i++) {
+        psMetadata *row = output->data[i]; // Row from select
+        bool status;
+
+	if (!psDBTransaction(config->dbh)) {
+	    psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(output);
+	    return false;
+	}
+
+        // psS64 warp_id = psMetadataLookupS64(&status, row, "warp_id");
+        psS64 xccal_id = psMetadataLookupS64(&status, row, "xccal_id");
+        psS64 stack_id = psMetadataLookupS64(&status, row, "stack_id");
+        psString path_base = psMetadataLookupStr(&status, row, "path_base");
+        psString xccal_data_group = psMetadataLookupStr(&status, row, "data_group");
+        psString tess_id = psMetadataLookupStr(&status, row, "tess_id");
+        psString skycell_id = psMetadataLookupStr(&status, row, "skycell_id");
+
+        psString query = NULL;
+        psStringAppend(&query, warpQueryTemplate, (long long) stack_id);
+
+        if (!p_psDBRunQuery(config->dbh, query)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(query);
+            return false;
+        }
+        psFree(query);
+
+        // Find the warps for this skycell and filter combination
+        psArray *warpOutput = p_psDBFetchResult(config->dbh);
+        if (!warpOutput) {
+            psErrorCode err = psErrorCodeLast();
+            switch (err) {
+                case PS_ERR_DB_CLIENT:
+                    psError(PXTOOLS_ERR_SYS, false, "database error");
+                case PS_ERR_DB_SERVER:
+                    psError(PXTOOLS_ERR_PROG, false, "database error");
+                default:
+                    psError(PXTOOLS_ERR_PROG, false, "unknown error");
+            }
+            return false;
+        }
+        if (!psArrayLength(warpOutput)) {
+            // no warps for this xccal. Suprise?
+            psFree(warpOutput);
+            psFree(query);
+            continue;
+        }
+
+	// create an xcForceRun
+	if (!xcForceRunInsert(config->dbh,
+			      0x0,	     // xcff_id
+			      xccal_id,
+			      path_base,
+			      "new",	     // state
+			      workdir,
+			      label,
+			      skycell_id,
+			      tess_id,
+			      data_group ? data_group : (xccal_data_group ? xccal_data_group : label),
+			      dist_group,
+			      note,
+			      reduction,
+			      NULL        // registered
+		)
+	    ) {
+	    if (!psDBRollback(config->dbh)) {
+		psError(PS_ERR_UNKNOWN, false, "database error failed to rollback transaction");
+	    }
+	    psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(output);
+	    return false;
+	}
+
+        psS64 xcff_id = psDBLastInsertID(config->dbh);
+
+        for (int j = 0; j < warpOutput->n; j++) {
+            psMetadata *warpRow = warpOutput->data[j];
+            psS64 warp_id = psMetadataLookupS64(&status, warpRow, "warp_id");
+
+            if (!xcForceInputInsert(config->dbh, xcff_id, warp_id, xcamera_id)) {
+                if (!psDBRollback(config->dbh)) {
+                    psError(PS_ERR_UNKNOWN, false, "database error failed to rollback transaction");
+                }
+                psError(PS_ERR_UNKNOWN, false, "database error");
+                psFree(warpOutput);
+                psFree(output);
+                return false;
+            }
+        }
+
+	if (!psDBCommit(config->dbh)) {
+	    psError(PS_ERR_UNKNOWN, false, "database error");
+                psFree(warpOutput);
+	    psFree(output);
+	    return false;
+	}
+        psFree(warpOutput);
+    }
+    psFree(output);
+
+    return true;
+}
+
+static bool updaterunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-xcff_id",     "xcff_id",    "==");
+    PXOPT_COPY_STR(config->args, where, "-label",       "label",      "==");
+    PXOPT_COPY_STR(config->args, where, "-state",       "state",      "==");
+    PXOPT_COPY_STR(config->args, where, "-data_group",  "data_group", "==");
+    PXOPT_COPY_STR(config->args, where, "-dist_group",  "dist_group", "==");
+    PXOPT_COPY_STR(config->args, where, "-skycell_id",  "skycell_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-tess_id",     "tess_id",    "==");
+    if (!pxskycellAddWhere(config, where)) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to add skycell search arguments");
+        return false;
+    }
+    if (!psListLength(where->list)) {
+        psFree(where);
+        psError(PXTOOLS_ERR_CONFIG, false, "search parameters are required");
+        return false;
+    }
+
+    psString query = psStringCopy ("UPDATE xcForceRun JOIN skycell USING(tess_id, skycell_id)");
+
+    // pxUpdateRun gets parameters from config->args and updates
+    bool result = pxUpdateRun(config, where, &query, "xcForceRun", "xcff_id", "xcForceResult", true, false);
+    psFree(query);
+    psFree(where);
+
+    return result;
+}
+
+
+static bool todoMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *whereMD = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, whereMD,  "-xcff_id", "xcff_id", "==");
+    pxAddLabelSearchArgs (config, whereMD, "-label",   "xcForceRun.label", "==");
+    pxskycellAddWhere(config, whereMD);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("xcfftool_todo.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (!psListLength(whereMD->list)) {
+        psError(PXTOOLS_ERR_CONFIG, false, "search parameters are required");
+        psFree(whereMD);
+        return false;
+    }
+
+    psString whereClause = psDBGenerateWhereConditionSQL(whereMD, NULL);
+    psStringAppend(&query, "\n AND %s", whereClause);
+    psFree(whereClause);
+    psFree(whereMD);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("xcfftool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "xcForceRun", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+    return true;
+}
+
+static bool addresultMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    // required
+    PXOPT_LOOKUP_S64(xcff_id, config->args, "-xcff_id", true, false);
+    PXOPT_LOOKUP_S64(warp_id, config->args, "-warp_id", true, false);
+
+    // optional
+    PXOPT_LOOKUP_STR(path_base, config->args, "-path_base", false, false);
+    PXOPT_LOOKUP_STR(hostname, config->args, "-hostname", true, false);
+    PXOPT_LOOKUP_F32(dtime_script, config->args, "-dtime_script", false, false);
+    PXOPT_LOOKUP_STR(software_ver, config->args, "-software_ver", false, false);
+
+    // default values
+    PXOPT_LOOKUP_S16(fault, config->args, "-fault", false, false);
+    PXOPT_LOOKUP_S16(quality, config->args, "-quality", false, false);
+
+    // XXX not sure we need a transaction here...
+    if (!xcForceResultInsert(config->dbh,
+			       xcff_id,
+                               warp_id,
+                               path_base,
+                               dtime_script,
+                               quality,
+                               hostname,
+                               software_ver,
+                               fault
+          )) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool resultMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-xcff_id",    "xcForceRun.xcff_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-xccal_id",   "xcForceRun.xccal_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-warp_id",    "xcForceResult.warp_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, "-tess_id",    "xcForceRun.tess_id", "LIKE");
+    PXOPT_COPY_STR(config->args, where, "-skycell_id", "xcForceRun.skycell_id", "LIKE");
+    PXOPT_COPY_STR(config->args, where, "-label",      "xcForceRun.label", "==");
+    PXOPT_COPY_STR(config->args, where, "-data_group", "xcForceRun.data_group", "LIKE");
+    PXOPT_COPY_STR(config->args, where, "-filter",     "rawExp.filter", "LIKE");
+    PXOPT_COPY_S16(config->args, where, "-fault",      "xcForceResult.fault", "==");
+    PXOPT_COPY_S16(config->args, where, "-quality",    "xcForceResult.quality", "==");
+    pxskycellAddWhere(config, where);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("xcfftool_result.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " WHERE %s", whereClause);
+        psFree(whereClause);
+    } else {
+        psError(PXTOOLS_ERR_CONFIG, true, "search parameters or -all are required");
+        return false;
+    }
+
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, "\n%s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("xcfftool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        if (!ippdbPrintMetadatas(stdout, output, "xcForceResult", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool revertMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-xcff_id", "xcForceRun.xcff_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-warp_id", "xcForceResult.warp_id", "==");
+    pxAddLabelSearchArgs(config, where, "-label", "xcForceRun.label", "==");
+    PXOPT_COPY_S16(config->args, where, "-fault", "xcForceResult.fault", "==");
+
+    if (!pxskycellAddWhere(config, where)) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to add skycell search arguments");
+        return false;
+    }
+
+    if (!psListLength(where->list)) {
+        psFree(where);
+        psError(PXTOOLS_ERR_CONFIG, false, "search parameters are required");
+        return false;
+    }
+
+    // Delete product
+    psString delete = pxDataGet("xcfftool_revert.sql");
+    if (!delete) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&delete, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, delete)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(delete);
+        psFree(where);
+        return false;
+    }
+    psFree(delete);
+
+    int numRows = psDBAffectedRows(config->dbh); // Number of row affected
+    psLogMsg("xcfftool", PS_LOG_INFO, "Deleted %d rows", numRows);
+
+    psFree(where);
+    return true;
+}
+
+static bool updateresultMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_S16(fault, config->args, "-fault", true, false);
+    PXOPT_LOOKUP_S16(quality, config->args, "-quality", false, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-xcff_id", "xcff_id",   "==");
+    PXOPT_COPY_S64(config->args, where, "-warp_id", "warp_id",   "==");
+
+    if (!pxSetFaultCode(config->dbh, "xcForceResult", where, fault, quality)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to set set fault flag");
+        psFree (where);
+        return false;
+    }
+    psFree (where);
+    return true;
+}
+
+static bool toadvanceMode(pxConfig *config) {
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *whereMD = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, whereMD,  "-xcff_id", "xcff_id", "==");
+    pxAddLabelSearchArgs (config, whereMD, "-label", "xcForceRun.label", "==");
+    pxskycellAddWhere(config, whereMD);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("xcfftool_toadvance.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (!psListLength(whereMD->list)) {
+        psError(PXTOOLS_ERR_CONFIG, false, "search parameters are required");
+        psFree(whereMD);
+        return false;
+    }
+
+    psString whereClause = NULL;
+    psString temp = psDBGenerateWhereConditionSQL(whereMD, NULL);
+    psStringAppend(&whereClause, "\n AND %s", temp);
+    psFree(temp);
+    psFree(whereMD);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    // the where clause is required and is added to the query by the "WHERE hook" format string
+    if (!p_psDBRunQueryF(config->dbh, query, whereClause)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(whereClause);
+        psFree(query);
+        return false;
+    }
+    psFree(whereClause);
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("xcfftool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "xcForceRun", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+    return true;
+}
+
+static bool addsummaryMode(pxConfig *config) {
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    // required
+    PXOPT_LOOKUP_S64(xcff_id, config->args, "-xcff_id", true, false);
+
+    // optional
+    PXOPT_LOOKUP_STR(path_base, config->args, "-path_base", false, false);
+    PXOPT_LOOKUP_STR(hostname, config->args, "-hostname", true, false);
+    PXOPT_LOOKUP_F32(dtime_script, config->args, "-dtime_script", false, false);
+    PXOPT_LOOKUP_STR(software_ver, config->args, "-software_ver", false, false);
+
+    // default values
+    PXOPT_LOOKUP_S16(fault, config->args, "-fault", false, false);
+    PXOPT_LOOKUP_S16(quality, config->args, "-quality", false, false);
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!xcForceSummaryInsert(config->dbh,
+			       xcff_id,
+                               path_base,
+                               dtime_script,
+                               quality,
+                               hostname,
+                               software_ver,
+                               fault
+          )) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!fault) {
+        if (!setxcForceRunState(config, xcff_id, "full")) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to change staticskyRun state");
+            return false;
+        }
+    }
+
+    // point of no return
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool summaryMode(pxConfig *config) {
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-xcff_id",    "xcForceRun.xcff_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-xccal_id",   "xcForceRun.xccal_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-label",      "xcForceRun.label", "==");
+    PXOPT_COPY_STR(config->args, where, "-data_group", "xcForceRun.data_group", "LIKE");
+    PXOPT_COPY_STR(config->args, where, "-tess_id",    "xcForceRun.tess_id", "LIKE");
+    PXOPT_COPY_STR(config->args, where, "-skycell_id", "xcForceRun.skycell_id", "LIKE");
+    PXOPT_COPY_S16(config->args, where, "-fault",      "xcForceSummary.fault", "==");
+    pxskycellAddWhere(config, where);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("xcfftool_summary.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " WHERE %s", whereClause);
+        psFree(whereClause);
+    } else {
+        psError(PXTOOLS_ERR_CONFIG, true, "search parameters or -all are required");
+        return false;
+    }
+
+    psFree(where);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, "\n%s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("xcfftool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        if (!ippdbPrintMetadatas(stdout, output, "xcForceSummary", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool revertsummaryMode(pxConfig *config) {
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-xcff_id", "xcForceRun.xcff_id", "==");
+    pxAddLabelSearchArgs(config, where, "-label",   "xcForceRun.label", "==");
+    PXOPT_COPY_S16(config->args, where, "-fault",   "xcForceSummary.fault", "==");
+
+    if (!pxskycellAddWhere(config, where)) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to add skycell search arguments");
+        return false;
+    }
+
+    if (!psListLength(where->list)) {
+        psFree(where);
+        psError(PXTOOLS_ERR_CONFIG, false, "search parameters are required");
+        return false;
+    }
+
+    // Delete product
+    psString delete = pxDataGet("xcfftool_revertsummary.sql");
+    if (!delete) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&delete, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, delete)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(delete);
+        psFree(where);
+        return false;
+    }
+    psFree(delete);
+
+    int numRows = psDBAffectedRows(config->dbh); // Number of row affected
+    psLogMsg("xcfftool", PS_LOG_INFO, "Deleted %d rows", numRows);
+
+    psFree(where);
+    return true;
+}
+
+static bool updatesummaryMode(pxConfig *config) {
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_S16(fault, config->args, "-fault", true, false);
+    PXOPT_LOOKUP_S16(quality, config->args, "-quality", false, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-xcff_id",   "xcff_id",   "==");
+
+    if (!pxSetFaultCode(config->dbh, "xcForceSummary", where, fault, quality)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to set set fault flag");
+        psFree (where);
+        return false;
+    }
+    psFree (where);
+    return true;
+}
+
+bool exportrunMode(pxConfig *config)
+{
+    typedef struct ExportTable {
+	char tableName[80];
+	char tableSQL[80];
+    } ExportTable;
+
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    PXOPT_LOOKUP_STR(outfile, config->args, "-outfile", true,  false);
+    PXOPT_LOOKUP_U64(limit,   config->args, "-limit",   false, false);
+
+    FILE *f = fopen (outfile, "w");
+    if (f == NULL) {
+        psError(PS_ERR_UNKNOWN, false, "failed to open output file");
+        return false;
+    }
+
+    if (!pxExportVersion(config, f)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to write dbversion output file");
+        return false;
+    }
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-xcff_id", "xcff_id", "==");
+
+    ExportTable tables [] = {
+	{"xcForceRun",     "SELECT xcForceRun.*       FROM xcForceRun"},
+	{"xcForceInput",   "SELECT xcForceInput.*     FROM xcForceInput"},
+	{"xcForceResult",  "SELECT xcForceResult.*    FROM xcForceResult"},
+	{"xcForceSummary", "SELECT xcForceSummary.*   FROM xcForceSummary"},
+    };
+
+    int numTables = sizeof(tables)/sizeof(tables[0]);
+
+    for (int i=0; i < numTables; i++) {
+	psString query = psStringCopy(tables[i].tableSQL);
+
+	if (where && psListLength(where->list)) {
+	    psString whereClause = psDBGenerateWhereSQL(where, NULL);
+	    psStringAppend(&query, " %s", whereClause);
+	    psFree(whereClause);
+	}
+
+	// treat limit == 0 as "no limit"
+	if (limit) {
+	    psString limitString = psDBGenerateLimitSQL(limit);
+	    psStringAppend(&query, " %s", limitString);
+	    psFree(limitString);
+	}
+
+	if (!p_psDBRunQuery(config->dbh, query)) {
+	    psError(PS_ERR_UNKNOWN, false, "database error");
+	    psFree(query);
+	    return false;
+	}
+	psFree(query);
+
+	psArray *output = p_psDBFetchResult(config->dbh);
+	if (!output) {
+	    psError(PS_ERR_UNKNOWN, false, "database error");
+	    return false;
+	}
+	if (!psArrayLength(output)) {
+	    psError(PS_ERR_UNKNOWN, true, "no rows found");
+	    psFree(output);
+	    return false;
+	}
+
+	// we must write the export table in non-simple (true) format
+	if (!ippdbPrintMetadatas(f, output, tables[i].tableName, true)) {
+	    psError(PS_ERR_UNKNOWN, false, "failed to print array");
+	    psFree(output);
+	    return false;
+	}
+	psFree(output);
+    }
+
+    fclose (f);
+
+    return true;
+}
+
+// XXX this is coded in a very strange way
+bool importrunMode(pxConfig *config)
+{
+  unsigned int nFail;
+
+  int numImportTables = 3;
+
+  char tables[3] [80] = {"xcForceInput", "xcForceResult", "xcForceSummary"};
+
+  PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+  PXOPT_LOOKUP_STR(infile, config->args, "-infile", true,  false);
+
+  psMetadata *input = psMetadataConfigRead (NULL, &nFail, infile, false);
+
+  if (!pxCheckImportVersion(config, input)) {
+      psError(PS_ERR_UNKNOWN, false, "pxCheckImportVersion failed");
+      return false;
+  }
+
+  psMetadataItem *item = psMetadataLookup (input, "xcForceRun");
+  psAssert (item, "entry not in input?");
+  psAssert (item->type == PS_DATA_METADATA_MULTI, "entry not multi?");
+
+  psMetadataItem *entry = psListGet (item->data.list, 0);
+  assert (entry);
+  assert (entry->type == PS_DATA_METADATA);
+  xcForceRunRow *xcForceRun = xcForceRunObjectFromMetadata (entry->data.md);
+  xcForceRunInsertObject (config->dbh, xcForceRun);
+
+  // fprintf (stdout, "---- warp run ----\n");
+  // psMetadataPrint (stderr, entry->data.md, 1);
+
+  for (int i = 0; i < numImportTables; i++) {
+    item = psMetadataLookup (input, tables[i]);
+    psAssert (item, "entry not in input?");
+    psAssert (item->type == PS_DATA_METADATA_MULTI, "entry not multi?");
+
+    switch (i) {
+      case 0:
+        for (int i = 0; i < item->data.list->n; i++) {
+          entry = psListGet (item->data.list, i);
+          assert (entry);
+          assert (entry->type == PS_DATA_METADATA);
+          xcForceInputRow *xcForceInput = xcForceInputObjectFromMetadata (entry->data.md);
+          xcForceInputInsertObject (config->dbh, xcForceInput);
+
+          // fprintf (stdout, "---- row %d ----\n", i);
+          // psMetadataPrint (stderr, entry->data.md, 1);
+        }
+        break;
+
+      case 1:
+        for (int i = 0; i < item->data.list->n; i++) {
+          entry = psListGet (item->data.list, i);
+          assert (entry);
+          assert (entry->type == PS_DATA_METADATA);
+          xcForceResultRow *xcForceResult = xcForceResultObjectFromMetadata (entry->data.md);
+          xcForceResultInsertObject (config->dbh, xcForceResult);
+
+          // fprintf (stdout, "---- row %d ----\n", i);
+          // psMetadataPrint (stderr, entry->data.md, 1);
+        }
+        break;
+
+      case 2:
+        for (int i = 0; i < item->data.list->n; i++) {
+          entry = psListGet (item->data.list, i);
+          assert (entry);
+          assert (entry->type == PS_DATA_METADATA);
+          xcForceSummaryRow *xcForceSummary = xcForceSummaryObjectFromMetadata (entry->data.md);
+          xcForceSummaryInsertObject (config->dbh, xcForceSummary);
+
+          // fprintf (stdout, "---- row %d ----\n", i);
+          // psMetadataPrint (stderr, entry->data.md, 1);
+        }
+        break;
+    }
+  }
+  return true;
+}
+
+// This function returns the camera_id associated with the camera name.
+// We expect the camera to be defined, so an invalid name or a missing camera
+// results in a 0 return value which should be treated as an error.
+static psS64 getCameraIDfromName(pxConfig *config, const char *dbname, const char *camera)
+{
+    PS_ASSERT_PTR_NON_NULL(camera, false);
+
+    char *query = "SELECT camera_id FROM %s.xcstackCamera WHERE camera = '%s'";
+    if (!p_psDBRunQueryF(config->dbh, query, dbname, camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to select camera_id for camera %s from database %s", camera, dbname);
+        return 0;
+    }
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+        return false;
+    }
+
+    // we expect a value camera_id as a result
+    if (!psArrayLength(output)) {
+        psWarning("xcstacktool: no rows found");
+        psFree(output);
+        return false;
+    }
+
+    // we expect a single camera_id as a result
+    if (psArrayLength(output) > 1) {
+        psWarning("xcstacktool: too many camera?");
+        psFree(output);
+        return false;
+    }
+
+    // the query above selects stacks to be added to the xcstackRun
+    // for each stack, we need camera_id, stack_id, skycell_id, tess_id
+    psMetadata *row = output->data[0]; // Row from select
+    bool status;
+
+    // pull out the skycell_id, tess_id, filter
+    psS64 camera_id = psMetadataLookupS64(&status, row, "camera_id");
+    if (!status) {
+        psAbort("xcstacktool: camera_id not found in query result?");
+        psFree(output);
+        return false;
+    }
+
+    return camera_id;
+}
+
+static bool setxcForceRunState(pxConfig *config, psS64 xcff_id, const char *state)
+{
+    psString query = "UPDATE xcForceRun SET state = 'full' WHERE xcff_id = %" PRId64;
+    if (!p_psDBRunQueryF(config->dbh, query, xcff_id)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
Index: trunk/ippTools/src/xcfftool.h
===================================================================
--- trunk/ippTools/src/xcfftool.h	(revision 42837)
+++ trunk/ippTools/src/xcfftool.h	(revision 42837)
@@ -0,0 +1,45 @@
+/*
+ * xcfftool.h
+ *
+ * Copyright (C) 2013 IfA Hawaii
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef XCFFTOOL_H
+#define XCFFTOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    XCFFTOOL_MODE_NONE           = 0x0,
+    XCFFTOOL_MODE_DEFINEBYQUERY,
+    XCFFTOOL_MODE_UPDATERUN,
+    XCFFTOOL_MODE_TODO,
+    XCFFTOOL_MODE_ADDRESULT,
+    XCFFTOOL_MODE_RESULT,
+    XCFFTOOL_MODE_REVERT,
+    XCFFTOOL_MODE_UPDATERESULT,
+    XCFFTOOL_MODE_TOADVANCE,
+    XCFFTOOL_MODE_ADDSUMMARY,
+    XCFFTOOL_MODE_REVERTSUMMARY,
+    XCFFTOOL_MODE_UPDATESUMMARY,
+    XCFFTOOL_MODE_SUMMARY,
+    XCFFTOOL_MODE_EXPORTRUN,
+    XCFFTOOL_MODE_IMPORTRUN,
+} xcfftoolMode;
+
+pxConfig *xcfftoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // XCFFTOOL_H
Index: trunk/ippTools/src/xcfftoolConfig.c
===================================================================
--- trunk/ippTools/src/xcfftoolConfig.c	(revision 42837)
+++ trunk/ippTools/src/xcfftoolConfig.c	(revision 42837)
@@ -0,0 +1,245 @@
+/*
+ * xcfftool.c
+ *
+ * Copyright (C) 2013 Institute for Astronomy, University of Hawaii
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "xcfftool.h"
+
+pxConfig *xcfftoolConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(psErrorCodeLast(), false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    psTime *now = psTimeGetNow(PS_TIME_TAI);
+    (void) now;
+
+    // -definebyquery
+    psMetadata *definebyqueryArgs = psMetadataAlloc();
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-xcamera", 0, "specify the cross-camera database", false);
+
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-select_xccal_label", PS_META_DUPLICATE_OK, "search by xccal label (LIKE comparison, multiple OK)", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-select_xccal_data_group", PS_META_DUPLICATE_OK, "search by xccal data_group (LIKE comparison, multiple OK)", NULL);
+    psMetadataAddS64(definebyqueryArgs,  PS_LIST_TAIL, "-select_xccal_id", 0, "search by xccal ID", 0);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-select_warp_label", PS_META_DUPLICATE_OK, "limits warps by label (LIKE comparison, multiple OK)", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-select_warp_data_group", PS_META_DUPLICATE_OK, "limit warps by data_group (LIKE comparison, multiple OK)", NULL);
+    psMetadataAddS64(definebyqueryArgs,  PS_LIST_TAIL, "-select_warp_id", 0, "limit warps by warp ID", 0);
+    psMetadataAddF32(definebyqueryArgs,  PS_LIST_TAIL, "-select_good_frac_min", 0, "mimimum good_frac in warp", 0.0);
+
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-select_skycell_id", 0, "limit by skycell_id (LIKE comparision)", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-select_tess_id", 0, "limit by tess_id", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-select_filter", PS_META_DUPLICATE_OK, "limit by stack filter (LIKE comparison, multiple OK)", NULL);
+
+    pxskycellAddArguments(definebyqueryArgs);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-set_workdir", 0, "define workdir (required)", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-set_label", 0, "define label (required)", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-set_data_group", 0, "define data group", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-set_dist_group", 0, "define dist group", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-set_note", 0, "define note", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-set_reduction", 0, "define reduction", NULL);
+    psMetadataAddBool(definebyqueryArgs, PS_LIST_TAIL, "-rerun",  0, "queue new run even if one already exists for inputs", false);
+    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);
+
+    // -updaterun
+    psMetadata *updaterunArgs = psMetadataAlloc();
+    psMetadataAddS64(updaterunArgs, PS_LIST_TAIL, "-xcff_id",    0, "search by xcff ID", 0);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-label",      0, "search by label", 0);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-state",      0, "search by state", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-data_group", 0, "search by data_group", 0);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-dist_group", 0, "search by dist_group", 0);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-skycell_id", 0, "search by skycell_id", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-tess_id",    0, "search by tess_id", NULL);
+    pxskycellAddArguments(updaterunArgs);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_label", 0, "define new value for label", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_state", 0, "define new state", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_data_group", 0, "define new data_group", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_dist_group", 0, "define new dist_group", NULL);
+    psMetadataAddStr(updaterunArgs,  PS_LIST_TAIL, "-set_note", 0, "define note", NULL);
+
+    // -todo
+    psMetadata *todoArgs = psMetadataAlloc();
+    psMetadataAddS64(todoArgs, PS_LIST_TAIL, "-xcff_id", 0, "search by xcff ID", 0);
+    psMetadataAddStr(todoArgs, PS_LIST_TAIL, "-label", PS_META_DUPLICATE_OK, "search by label", NULL);
+    pxskycellAddArguments(todoArgs);
+    psMetadataAddU64(todoArgs, PS_LIST_TAIL, "-limit", 0, "limit result set to N items", 0);
+    psMetadataAddBool(todoArgs, PS_LIST_TAIL, "-simple", 0, "use the simple output format", false);
+
+    // -addresult
+    psMetadata *addresultArgs = psMetadataAlloc();
+    psMetadataAddS64(addresultArgs, PS_LIST_TAIL, "-xcff_id", 0, "define xcff ID (required)", 0);
+    psMetadataAddS64(addresultArgs, PS_LIST_TAIL, "-warp_id", 0, "define warp ID (required)", 0);
+    psMetadataAddStr(addresultArgs, PS_LIST_TAIL, "-path_base", 0, "define base output location(required)", 0);
+    psMetadataAddF32(addresultArgs, PS_LIST_TAIL, "-dtime_script", 0, "define elapsed time in script (seconds)", NAN);
+    psMetadataAddS16(addresultArgs, PS_LIST_TAIL, "-quality", 0, "set quality", 0);
+    psMetadataAddStr(addresultArgs, PS_LIST_TAIL, "-hostname", 0, "define hostname", 0);
+    psMetadataAddStr(addresultArgs, PS_LIST_TAIL, "-software_ver", 0, "define software version", 0);
+    psMetadataAddS16(addresultArgs, PS_LIST_TAIL, "-fault", 0, "set fault code", 0);
+
+    // -result
+    psMetadata *resultArgs= psMetadataAlloc();
+    psMetadataAddS64(resultArgs, PS_LIST_TAIL, "-xcff_id", 0, "search by xcforce ID", 0);
+    psMetadataAddS64(resultArgs, PS_LIST_TAIL, "-xccal_id", 0, "search by xccal ID", 0);
+    psMetadataAddS64(resultArgs, PS_LIST_TAIL, "-warp_id", 0, "search by warp ID", 0);
+    psMetadataAddS64(resultArgs, PS_LIST_TAIL, "-exp_id", 0, "search by exposure ID", 0);
+    psMetadataAddStr(resultArgs, PS_LIST_TAIL, "-exp_name", 0, "search by exposure name", 0);
+    psMetadataAddS64(resultArgs, PS_LIST_TAIL, "-skycal_id", 0, "search by skycal ID", 0);
+    psMetadataAddStr(resultArgs, PS_LIST_TAIL, "-tess_id", 0, "search by tess ID (LIKE comparison)", 0);
+    psMetadataAddStr(resultArgs, PS_LIST_TAIL, "-skycell_id", 0, "search by skycell ID (LIKE comparison)", 0);
+    psMetadataAddStr(resultArgs, PS_LIST_TAIL, "-label", 0, "search by label", NULL);
+    psMetadataAddStr(resultArgs, PS_LIST_TAIL, "-filter", 0, "search by rawExp filter", NULL);
+    psMetadataAddStr(resultArgs, PS_LIST_TAIL, "-data_group", 0, "search by data_group (LIKE comparison)", NULL);
+    pxskycellAddArguments(resultArgs);
+    psMetadataAddS16(resultArgs, PS_LIST_TAIL, "-fault", 0, "search by fault code", 0);
+    psMetadataAddS16(resultArgs, PS_LIST_TAIL, "-quality", 0, "search by quality value", 0);
+    psMetadataAddU64(resultArgs, PS_LIST_TAIL, "-limit", 0, "limit result set to N items", 0);
+    psMetadataAddBool(resultArgs, PS_LIST_TAIL, "-simple", 0, "use the simple output format", false);
+
+    // -revert
+    psMetadata *revertArgs= psMetadataAlloc();
+    psMetadataAddS64(revertArgs, PS_LIST_TAIL, "-xcff_id", 0, "search by xcff ID", 0);
+    psMetadataAddS64(revertArgs, PS_LIST_TAIL, "-warp_id", 0, "search by warp ID", 0);
+    psMetadataAddStr(revertArgs, PS_LIST_TAIL, "-label", PS_META_DUPLICATE_OK, "search by label", 0);
+    psMetadataAddS16(revertArgs, PS_LIST_TAIL, "-fault", 0, "search by fault code", 0);
+    pxskycellAddArguments(revertArgs);
+
+    // -updateresult
+    psMetadata *updateresultArgs = psMetadataAlloc();
+    psMetadataAddS64(updateresultArgs, PS_LIST_TAIL, "-xcff_id", 0, "define xcff ID (required)", 0);
+    psMetadataAddS64(updateresultArgs, PS_LIST_TAIL, "-warp_id", 0, "define warp ID (required)", 0);
+    psMetadataAddS16(updateresultArgs, PS_LIST_TAIL, "-fault", 0, "set fault code (required)", 0);
+    psMetadataAddS16(updateresultArgs, PS_LIST_TAIL, "-quality", 0, "set quality code", 0);
+
+    // -toadvance
+    psMetadata *toadvanceArgs = psMetadataAlloc();
+    psMetadataAddS64(toadvanceArgs, PS_LIST_TAIL, "-xcff_id", 0, "search by xcff ID", 0);
+    psMetadataAddStr(toadvanceArgs, PS_LIST_TAIL, "-label", PS_META_DUPLICATE_OK, "search by label", NULL);
+    pxskycellAddArguments(toadvanceArgs);
+    psMetadataAddU64(toadvanceArgs, PS_LIST_TAIL, "-limit", 0, "limit result set to N items", 0);
+    psMetadataAddBool(toadvanceArgs, PS_LIST_TAIL, "-simple", 0, "use the simple output format", false);
+
+    // -addsummary
+    psMetadata *addsummaryArgs = psMetadataAlloc();
+    psMetadataAddS64(addsummaryArgs, PS_LIST_TAIL, "-xcff_id", 0, "define xcff ID (required)", 0);
+    psMetadataAddStr(addsummaryArgs, PS_LIST_TAIL, "-path_base", 0, "define base output location(required)", 0);
+    psMetadataAddF32(addsummaryArgs, PS_LIST_TAIL, "-dtime_script", 0, "define elapsed time in script (seconds)", NAN);
+    psMetadataAddS16(addsummaryArgs, PS_LIST_TAIL, "-quality", 0, "set quality", 0);
+    psMetadataAddStr(addsummaryArgs, PS_LIST_TAIL, "-hostname", 0, "define hostname", 0);
+    psMetadataAddStr(addsummaryArgs, PS_LIST_TAIL, "-software_ver", 0, "define software version", 0);
+    psMetadataAddS16(addsummaryArgs, PS_LIST_TAIL, "-fault", 0, "set fault code", 0);
+
+    // -summary
+    psMetadata *summaryArgs= psMetadataAlloc();
+    psMetadataAddS64(summaryArgs, PS_LIST_TAIL, "-xcff_id", 0, "search by xcff ID", 0);
+    psMetadataAddS64(summaryArgs, PS_LIST_TAIL, "-xccal_id", 0, "search by skycal ID", 0);
+    psMetadataAddStr(summaryArgs, PS_LIST_TAIL, "-label", 0, "search by label", NULL);
+    psMetadataAddStr(summaryArgs, PS_LIST_TAIL, "-data_group", 0, "search by data_group (LIKE comparison)", NULL);
+    psMetadataAddStr(summaryArgs, PS_LIST_TAIL, "-tess_id", 0, "search by tess ID (LIKE comparison)", 0);
+    psMetadataAddStr(summaryArgs, PS_LIST_TAIL, "-skycell_id", 0, "search by skycell ID (LIKE comparison)", 0);
+    pxskycellAddArguments(summaryArgs);
+    psMetadataAddS16(summaryArgs, PS_LIST_TAIL, "-fault", 0, "search by fault code", 0);
+    psMetadataAddU64(summaryArgs, PS_LIST_TAIL, "-limit", 0, "limit result set to N items", 0);
+    psMetadataAddBool(summaryArgs, PS_LIST_TAIL, "-simple", 0, "use the simple output format", false);
+
+    // -revertsummary
+    psMetadata *revertsummaryArgs= psMetadataAlloc();
+    psMetadataAddS64(revertsummaryArgs, PS_LIST_TAIL, "-xcff_id", 0, "search by xcff ID", 0);
+    psMetadataAddStr(revertsummaryArgs, PS_LIST_TAIL, "-label", PS_META_DUPLICATE_OK, "search by label", 0);
+    psMetadataAddS16(revertsummaryArgs, PS_LIST_TAIL, "-fault", 0, "search by fault code", 0);
+    pxskycellAddArguments(revertsummaryArgs);
+
+    // -updatesummary
+    psMetadata *updatesummaryArgs = psMetadataAlloc();
+    psMetadataAddS64(updatesummaryArgs, PS_LIST_TAIL, "-xcff_id", 0, "define xcff ID (required)", 0);
+    psMetadataAddStr(updatesummaryArgs, PS_LIST_TAIL, "-path_base", 0, "define base output location", 0);
+    psMetadataAddF32(updatesummaryArgs, PS_LIST_TAIL, "-dtime_script", 0, "define elapsed time in script (seconds)", NAN);
+    psMetadataAddS16(updatesummaryArgs, PS_LIST_TAIL, "-quality", 0, "set quality", 0);
+    psMetadataAddStr(updatesummaryArgs, PS_LIST_TAIL, "-hostname", 0, "define hostname", 0);
+    psMetadataAddStr(updatesummaryArgs, PS_LIST_TAIL, "-software_ver", 0, "define software version", 0);
+    psMetadataAddS16(updatesummaryArgs, PS_LIST_TAIL, "-fault", 0, "set fault code", 0);
+
+    // -exportrun
+    psMetadata *exportrunArgs = psMetadataAlloc();
+    psMetadataAddS64(exportrunArgs, PS_LIST_TAIL, "-xcff_id", 0,          "export this xcff ID (required)", 0);
+    psMetadataAddStr(exportrunArgs, PS_LIST_TAIL, "-outfile", 0,          "export to this file (required)", NULL);
+    psMetadataAddU64(exportrunArgs, PS_LIST_TAIL, "-limit",   0,          "limit result set to N items", 0);
+//    psMetadataAddBool(exportrunArgs, PS_LIST_TAIL, "-clean",  0,          "export run in cleaned state", false);
+
+    // -importrun
+    psMetadata *importrunArgs = psMetadataAlloc();
+    psMetadataAddStr(importrunArgs, PS_LIST_TAIL, "-infile",  0,          "import from this file (required)", NULL);
+
+    psFree(now);
+
+    psMetadata *argSets = psMetadataAlloc();
+    psMetadata *modes   = psMetadataAlloc();
+
+    PXOPT_ADD_MODE("-definebyquery",   "Define a new xcff run",                             XCFFTOOL_MODE_DEFINEBYQUERY,   definebyqueryArgs);
+    PXOPT_ADD_MODE("-updaterun",       "Update a run",                                      XCFFTOOL_MODE_UPDATERUN,       updaterunArgs);
+    PXOPT_ADD_MODE("-todo",            "Get runs to do",                                    XCFFTOOL_MODE_TODO,            todoArgs);
+    PXOPT_ADD_MODE("-addresult",       "Add result for fullforce run on a warp",            XCFFTOOL_MODE_ADDRESULT,       addresultArgs);
+    PXOPT_ADD_MODE("-result",          "Get result fullforce run on a warp",                XCFFTOOL_MODE_RESULT,          resultArgs);
+    PXOPT_ADD_MODE("-revert",          "Revert failed fullforce run on a warp",             XCFFTOOL_MODE_REVERT,          revertArgs);
+    PXOPT_ADD_MODE("-updateresult",    "Update result for fullforce run on a warp",         XCFFTOOL_MODE_UPDATERESULT,    updateresultArgs);
+    PXOPT_ADD_MODE("-toadvance",       "list completed runs to summarize",                  XCFFTOOL_MODE_TOADVANCE,       toadvanceArgs);
+    PXOPT_ADD_MODE("-addsummary",      "insert summary result",                             XCFFTOOL_MODE_ADDSUMMARY,      addsummaryArgs);
+    PXOPT_ADD_MODE("-updatesummary",   "update summary result",                             XCFFTOOL_MODE_UPDATESUMMARY,   updatesummaryArgs);
+    PXOPT_ADD_MODE("-revertsummary",   "revert faulted summary",                            XCFFTOOL_MODE_REVERTSUMMARY,   revertsummaryArgs);
+    PXOPT_ADD_MODE("-summary",         "list summary results",                              XCFFTOOL_MODE_SUMMARY,         summaryArgs);
+    PXOPT_ADD_MODE("-exportrun",       "list summary results",                              XCFFTOOL_MODE_EXPORTRUN,       exportrunArgs);
+    PXOPT_ADD_MODE("-importrun",       "list summary results",                              XCFFTOOL_MODE_IMPORTRUN,       importrunArgs);
+
+    if (!pxGetOptions(stderr, argc, argv, config, modes, argSets)) {
+        psError(PS_ERR_UNKNOWN, true, "option parsing failed");
+        psFree(argSets);
+        psFree(modes);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+    psFree(modes);
+
+    // define Database handle, if used
+    // do this last so we don't setup a connection before CLI options are
+    // validated
+    config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    return config;
+}
Index: trunk/ippTools/src/xcskytool.c
===================================================================
--- trunk/ippTools/src/xcskytool.c	(revision 42837)
+++ trunk/ippTools/src/xcskytool.c	(revision 42837)
@@ -0,0 +1,1566 @@
+/*
+ * xcskytool.c
+ *
+ * Copyright (C) 2023 Eugene Magnier (based on staticskytool.c)
+ *
+ */
+
+#ifdef HAVB_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include <ippdb.h>
+
+#include "pxtools.h"
+#include "pxspace.h"
+#include "xcskytool.h"
+
+static bool definebyqueryMode(pxConfig *config);
+static bool updaterunMode(pxConfig *config);
+static bool inputsMode(pxConfig *config);
+static bool todoMode(pxConfig *config);
+static bool addresultMode(pxConfig *config);
+static bool resultMode(pxConfig *config);
+static bool revertMode(pxConfig *config);
+static bool updateresultMode(pxConfig *config);
+static bool exportrunMode(pxConfig *config);
+static bool importrunMode(pxConfig *config);
+
+static bool definexccalrunMode(pxConfig *config);
+static bool updatexccalrunMode(pxConfig *config);
+static bool pendingxccalrunMode(pxConfig *config);
+static bool addxccalresultMode(pxConfig *config);
+static bool xccalresultMode(pxConfig *config);
+static bool revertxccalresultMode(pxConfig *config);
+static bool updatexccalresultMode(pxConfig *config);
+static bool exportxccalrunMode(pxConfig *config);
+static bool importxccalrunMode(pxConfig *config);
+
+static bool setxcskyRunState(pxConfig *config, psS64 xcsky_id, const char *state);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    //    psAbort("oops");
+
+    pxConfig *config = xcskytoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(XCSKYTOOL_MODE_DEFINEBYQUERY,     definebyqueryMode);
+        MODECASE(XCSKYTOOL_MODE_UPDATERUN,         updaterunMode);
+        MODECASE(XCSKYTOOL_MODE_INPUTS,            inputsMode);
+        MODECASE(XCSKYTOOL_MODE_TODO,              todoMode);
+        MODECASE(XCSKYTOOL_MODE_ADDRESULT,         addresultMode);
+        MODECASE(XCSKYTOOL_MODE_RESULT,            resultMode);
+        MODECASE(XCSKYTOOL_MODE_REVERT,            revertMode);
+        MODECASE(XCSKYTOOL_MODE_UPDATERESULT,      updateresultMode);
+        MODECASE(XCSKYTOOL_MODE_EXPORTRUN,         exportrunMode);
+        MODECASE(XCSKYTOOL_MODE_IMPORTRUN,         importrunMode);
+        MODECASE(XCSKYTOOL_MODE_DEFINEXCCALRUN,    definexccalrunMode);
+        MODECASE(XCSKYTOOL_MODE_UPDATEXCCALRUN,    updatexccalrunMode);
+        MODECASE(XCSKYTOOL_MODE_PENDINGXCCALRUN,   pendingxccalrunMode);
+        MODECASE(XCSKYTOOL_MODE_ADDXCCALRESULT,    addxccalresultMode);
+        MODECASE(XCSKYTOOL_MODE_UPDATEXCCALRESULT, updatexccalresultMode);
+        MODECASE(XCSKYTOOL_MODE_REVERTXCCALRESULT, revertxccalresultMode);
+        MODECASE(XCSKYTOOL_MODE_XCCALRESULT,       xccalresultMode);
+        MODECASE(XCSKYTOOL_MODE_EXPORTXCCALRUN,    exportxccalrunMode);
+        MODECASE(XCSKYTOOL_MODE_IMPORTXCCALRUN,    importxccalrunMode);
+        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, false);
+    PXOPT_LOOKUP_STR(workdir,     config->args, "-set_workdir", true, false);
+    PXOPT_LOOKUP_STR(label,       config->args, "-set_label", true, false);
+
+    // optional
+    PXOPT_LOOKUP_STR(data_group,  config->args, "-set_data_group", false, false);
+    PXOPT_LOOKUP_STR(dist_group,  config->args, "-set_dist_group", false, false);
+    PXOPT_LOOKUP_STR(reduction,   config->args, "-set_reduction", false, false);
+    PXOPT_LOOKUP_STR(note,        config->args, "-set_note", false, false);
+    PXOPT_LOOKUP_TIME(registered, config->args, "-set_registered", false, false);
+    //    fprintf (stderr, "%s\n", registered);
+
+    psMetadata *whereMD = psMetadataAlloc();
+
+    PXOPT_COPY_S64(config->args, whereMD, "-select_xcstack_id",    "xcstackRun.xcstack_id",       "==");
+    PXOPT_COPY_STR(config->args, whereMD, "-select_tess_id",       "xcstackRun.tess_id",          "==");
+    PXOPT_COPY_STR(config->args, whereMD, "-select_skycell_id",    "xcstackRun.skycell_id",       "LIKE");
+    pxAddLabelSearchArgs(config, whereMD, "-select_filter",        "xcstackRun.filter",           "LIKE");
+    PXOPT_COPY_F32(config->args, whereMD, "-select_good_frac_min", "xcstackSumSkyfile.good_frac", ">=");
+    pxAddLabelSearchArgs(config, whereMD, "-select_label",         "xcstackRun.label",            "LIKE");
+    pxAddLabelSearchArgs(config, whereMD, "-select_data_group",    "xcstackRun.data_group",       "LIKE");
+    if (!pxskycellAddWhere(config, whereMD)) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to add skycell search arguments");
+        return false;
+    }
+
+    // find the number of requested filters:
+    psMetadataItem *filters = psMetadataLookup(config->args, "-select_filter");
+    psAssert (filters, "-select_filter must exist");
+    psAssert (filters->type == PS_DATA_METADATA_MULTI, "-select_filter should be a multi container");
+    psAssert (filters->data.list->n, "-select_filter should at least have a place-holder");
+
+# if (0)
+    // XXX 'filters' is a psMetadataItem of type MULTI.  Here is code to iterate over the list of items:
+    psListIterator *myIter = psListIteratorAlloc(filters->data.list, PS_LIST_HEAD, false);
+    psMetadataItem *myItem = NULL;
+    while ((myItem = psListGetAndIncrement (myIter))) {
+      fprintf (stderr, "type: %d\n", (int) myItem->type);
+    }
+# endif
+
+    int num_filter = filters->data.list->n;
+
+    // if -select_filter was not supplied, then the 'filters' container will have a single entry
+    // with a value of NULL
+    if (num_filter == 1) {
+      // psListIterator *myIter = psListIteratorAlloc(filters->data.list, PS_LIST_HEAD, false);
+      // psMetadataItem *myItem = psListGetAndIncrement (myIter);
+
+      psMetadataItem *myItem = psListGet (filters->data.list, 0);
+      psAssert (myItem->type == PS_DATA_STRING, "unexpected data type for -select_filter");
+      if (myItem->data.str == NULL) {
+        psError(PXTOOLS_ERR_CONFIG, false, "at least one -select_filter must be supplied");
+        return false;
+      }
+    }
+
+    PXOPT_LOOKUP_BOOL(group_by_data_group, config->args, "-group_by_data_group", false);
+    PXOPT_LOOKUP_BOOL(rerun, config->args, "-rerun", false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_BOOL(pretend, config->args, "-pretend", false);
+    PXOPT_LOOKUP_BOOL(check_inputs, config->args, "-check_inputs", false);
+
+    psString select;
+    if (group_by_data_group) {
+        select = pxDataGet("xcskytool_definebyquery_select_by_dg.sql");
+    } else {
+        select = pxDataGet("xcskytool_definebyquery_select.sql");
+    }
+    if (!select) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        psFree(whereMD);
+        return false;
+    }
+
+    if (!psListLength(whereMD->list)) {
+        psError(PXTOOLS_ERR_CONFIG, false, "search parameters are required");
+        psFree(whereMD);
+        return false;
+    }
+
+    // this 'where' is used for both xcskytool_definebyquery_select.sql and xcskytool_definebyquery_inputs.sql 
+    psString where = NULL;
+    psString whereClause = psDBGenerateWhereConditionSQL(whereMD, NULL);
+    psStringAppend(&where, "\nAND %s", whereClause);
+    psFree(whereClause);
+    psFree(whereMD);
+
+    psString where2 = NULL;
+    psString make_unique = NULL;
+    if (!rerun) {
+        psStringAppend(&where2, "\n %s\nAND xcskyRun.label = '%s'", where, label);
+        psStringAppend(&make_unique, "\nAND xcsky_id IS NULL");
+    }
+
+    if (!p_psDBRunQueryF(config->dbh, select, where, where2 ? where2 : where, num_filter, make_unique ? make_unique : "")) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(select);
+        return false;
+    }
+    psFree(select);
+    psFree(where2);
+    psFree(make_unique);
+
+    // we now have a list of (tess_id, skycell_id) that (potentially) meet out needs
+    // we now need to loop over all of these and for each pair, select the best set of
+    // inputs
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+        psFree(where);
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psWarning("xcskytool: no rows found");
+        psFree(output);
+        psFree(where);
+        return true;
+    }
+    if (pretend) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "xcskyInput", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            psFree(where);
+            return false;
+        }
+        psFree(output);
+        psFree(where);
+        return true;
+    }
+
+    psString inputsSQL = pxDataGet("xcskytool_definebyquery_inputs.sql");
+    if (!inputsSQL) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        psFree(where);
+        return false;
+    }
+
+    for (long i = 0; i < output->n; i++) {
+        psMetadata *row = output->data[i]; // Row from select
+        bool status;
+
+        // pull out the skycell_id, tess_id, filter
+        psString skycell_id = psMetadataLookupStr(&status, row, "skycell_id");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup skycell_id");
+            psFree(output);
+            psFree(inputsSQL);
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            return false;
+        }
+
+        psString tess_id = psMetadataLookupStr(&status, row, "tess_id");
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to lookup tess_id");
+            psFree(output);
+            psFree(inputsSQL);
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            return false;
+        }
+        psString by_data_group = NULL;
+        psString xcstack_data_group = NULL;
+        if (group_by_data_group) {
+            xcstack_data_group = psMetadataLookupStr(&status, row, "data_group");
+            if (!status) {
+                psError(PS_ERR_UNKNOWN, false, "failed to lookup data_group");
+                psFree(output);
+                psFree(inputsSQL);
+                if (!psDBRollback(config->dbh)) {
+                    psError(PS_ERR_UNKNOWN, false, "database error");
+                }
+                return false;
+            }
+            psStringAppend(&by_data_group, "\nAND xcstackRun.data_group = '%s'", xcstack_data_group);
+        }
+
+	// query for the inputs
+	if (!p_psDBRunQueryF(config->dbh, inputsSQL, tess_id, skycell_id, where, by_data_group ? by_data_group : "")) {
+	    psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(output);
+	    psFree(inputsSQL);
+	    return false;
+	}
+	
+	psArray *inputs = p_psDBFetchResult(config->dbh);
+	if (!inputs) {
+	    psErrorCode err = psErrorCodeLast();
+	    switch (err) {
+	      case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+	      case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+	      default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+	    }
+	    psFree(inputs);
+            psFree(output);
+            psFree(inputsSQL);
+	    return false;
+	}
+	if (!psArrayLength(inputs)) {
+	    psWarning("xcskytool ERROR: no rows found for known tess_id, skycell_id?");
+	    continue;
+	}
+
+	if (check_inputs) {
+	  // negative simple so the default is true
+	  if (!ippdbPrintMetadatas(stdout, inputs, "xcskyInput", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            psFree(where);
+            return false;
+	  }
+	  psFree(inputs);
+	  continue;
+	}
+
+	// XXX if we are unable to guarantee that all selected inputs have all and only the
+	// requested filters, see the code at the bottom of this file (ifdef'ed out for now)
+
+	// insert a new xcsky entry and find its new id
+	if (!psDBTransaction(config->dbh)) {
+	    psError(PS_ERR_UNKNOWN, false, "database error");
+	    psFree(inputs);
+            psFree(output);
+            psFree(inputsSQL);
+	    return false;
+	}
+
+	// create a xcskyRun
+	if (!xcskyRunInsert(config->dbh,
+				0x0,	     // xcsky_id
+				"new",	     // state
+				workdir,
+				label,
+				data_group ? data_group : (xcstack_data_group ? xcstack_data_group : label),
+				dist_group,
+				reduction,
+			    NULL, // XXX : broken like xcstacktool : is this due to the mysql version?
+				note
+		)
+	    ) {
+	    if (!psDBRollback(config->dbh)) {
+		psError(PS_ERR_UNKNOWN, false, "database error failed to rollback transaction");
+	    }
+	    psError(PS_ERR_UNKNOWN, false, "database error");
+	    psFree(inputs);
+            psFree(output);
+            psFree(inputsSQL);
+	    return false;
+	}
+
+	psS64 xcsky_id =  psDBLastInsertID(config->dbh);
+
+	// loop over the possible inputs and record only the valid ones
+	for (int j = 0; j < inputs->n; j++) {
+	    psMetadata *inputRow = inputs->data[j]; // Row from select
+	    
+	    // pull out the skycell_id, tess_id, filter
+	    psS64 xcstack_id = psMetadataLookupS64(&status, inputRow, "xcstack_id");
+	    psAssert(status, "failed to find xcstack_id?");
+	    
+	    // add a xcskyInput entry
+	    if (!xcskyInputInsert(config->dbh, xcsky_id, xcstack_id)) {
+		if (!psDBRollback(config->dbh)) {
+		    psError(PS_ERR_UNKNOWN, false, "database error failed to rollback transaction");
+		}
+		psError(PS_ERR_UNKNOWN, false, "database error");
+		psFree(inputs);
+		psFree(output);
+		psFree(inputsSQL);
+		return false;
+	    }
+	}
+	psFree (inputs);
+	
+	if (!psDBCommit(config->dbh)) {
+	    psError(PS_ERR_UNKNOWN, false, "database error");
+	    psFree(output);
+	    psFree(inputsSQL);
+	    return false;
+	}
+    }
+    psFree(inputsSQL);
+    psFree(output);
+    return true;
+}
+
+static bool updaterunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-xcsky_id",     "xcsky_id",            "==");
+    PXOPT_COPY_STR(config->args, where, "-label",       "xcskyRun.label",      "==");
+    PXOPT_COPY_STR(config->args, where, "-data_group",  "xcskyRun.data_group", "==");
+    PXOPT_COPY_STR(config->args, where, "-state",       "xcskyRun.state",      "==");
+    PXOPT_COPY_STR(config->args, where, "-tess_id",     "xcstackRun.tess_id",    "==");
+    PXOPT_COPY_STR(config->args, where, "-skycell_id",  "xcstackRun.skycell_id", "==");
+    if (!pxskycellAddWhere(config, where)) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to add skycell search arguments");
+        return false;
+    }
+    if (!psListLength(where->list)) {
+        psFree(where);
+        psError(PXTOOLS_ERR_CONFIG, false, "search parameters are required");
+        return false;
+    }
+
+    psString query = psStringCopy("UPDATE xcskyRun JOIN xcskyInput USING(xcsky_id) JOIN xcstackRun using(xcstack_id) JOIN skycell USING(tess_id, skycell_id)");
+
+    // pxUpdateRun gets parameters from config->args and updates
+    bool result = pxUpdateRun(config, where, &query, "xcskyRun", "xcsky_id", "xcskyResult", true, false);
+    psFree(query);
+    psFree(where);
+
+    return result;
+}
+
+static bool inputsMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // require at least an xcsky id (add better search options)
+    PXOPT_LOOKUP_S64(xcsky_id, config->args, "-xcsky_id", true, false);
+
+    psMetadata *where = psMetadataAlloc();
+    psMetadataAddS64 (where, PS_LIST_TAIL, "xcsky_id", 0, "==", xcsky_id);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("xcskytool_inputs.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, "xcskyInput");
+        psStringAppend(&query, " WHERE %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);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("xcskytool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "xcskyInput", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool todoMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *whereMD = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, whereMD, "-xcsky_id", "xcsky_id", "==");
+    pxAddLabelSearchArgs (config, whereMD, "-label", "xcskyRun.label", "==");
+    pxskycellAddWhere(config, whereMD);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("xcskytool_todo.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (!psListLength(whereMD->list)) {
+        psError(PXTOOLS_ERR_CONFIG, false, "search parameters are required");
+        psFree(whereMD);
+        return false;
+    }
+
+    psString where = NULL;
+    psString whereClause = psDBGenerateWhereConditionSQL(whereMD, NULL);
+    psStringAppend(&where, "\n AND %s", whereClause);
+    psFree(whereClause);
+    psFree(whereMD);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    // the where clause is required and matches the WHERE hook format string
+    if (!p_psDBRunQueryF(config->dbh, query, where)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("xcskytool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "xcskyResult", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+    return true;
+}
+
+static bool addresultMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required
+    PXOPT_LOOKUP_S64(xcsky_id, config->args, "-xcsky_id", true, false);
+
+    // optional
+    PXOPT_LOOKUP_STR(path_base, config->args, "-path_base", false, false);
+    PXOPT_LOOKUP_F32(dtime_phot, config->args, "-dtime_phot", false, false);
+    PXOPT_LOOKUP_F32(dtime_script, config->args, "-dtime_script", false, false);
+    PXOPT_LOOKUP_S32(sources, config->args, "-sources", false, false);
+    PXOPT_LOOKUP_STR(hostname, config->args, "-hostname", false, false);
+    PXOPT_LOOKUP_F32(good_frac, config->args, "-good_frac", false, false);
+    PXOPT_LOOKUP_S32(num_inputs, config->args, "-num_inputs", false, false);
+
+    // default values
+    PXOPT_LOOKUP_S16(fault, config->args, "-fault", false, false);
+    PXOPT_LOOKUP_S16(quality, config->args, "-quality", false, false);
+
+    // XXX not sure we need a transaction here...
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!xcskyResultInsert(config->dbh,
+			       xcsky_id,
+                               path_base,
+                               dtime_phot,
+                               dtime_script,
+                               sources,
+			       num_inputs,
+                               hostname,
+                               good_frac,
+                               fault,
+                               quality
+          )) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!fault) {
+        if (!setxcskyRunState(config, xcsky_id, "full")) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to change xcskyRun state");
+            return false;
+        }
+    }
+
+    // point of no return
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+// XXX what is this used by?  what filters are needed?
+static bool resultMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-xcsky_id",     "xcskyRun.xcsky_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-xcstack_id",   "xcskyInput.xcstack_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-label",        "xcskyRun.label", "==");
+    PXOPT_COPY_STR(config->args, where, "-data_group",   "xcskyRun.data_group", "LIKE");
+    PXOPT_COPY_STR(config->args, where, "-tess_id",      "xcstackRun.tess_id", "LIKE");
+    PXOPT_COPY_STR(config->args, where, "-skycell_id",   "xcstackRun.skycell_id", "LIKE");
+    PXOPT_COPY_S16(config->args, where, "-fault",        "xcskyResult.fault", "==");
+    pxskycellAddWhere(config, where);
+    PXOPT_LOOKUP_S32(num_filters, config->args, "-num_filters", false, false);
+
+    PXOPT_LOOKUP_BOOL(all, config->args, "-all", false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("xcskytool_result.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " WHERE %s", whereClause);
+        psFree(whereClause);
+    } else if (!all) {
+        psError(PXTOOLS_ERR_CONFIG, true, "search parameters or -all are required");
+        return false;
+    }
+
+    psFree(where);
+
+    psStringAppend(&query, "\nGROUP BY xcsky_id");
+    if (num_filters) {
+        psStringAppend(&query, "\nHAVING COUNT(filter) = %d", num_filters);
+    }
+        
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, "\n%s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("xcskytool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        if (!ippdbPrintMetadatas(stdout, output, "xcskyResult", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool revertMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-xcsky_id", "xcskyResult.xcsky_id", "==");
+    pxAddLabelSearchArgs(config, where, "-label", "xcskyRun.label", "==");
+    PXOPT_COPY_S16(config->args, where, "-fault", "xcskyResult.fault", "==");
+
+    if (!pxskycellAddWhere(config, where)) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to add skycell search arguments");
+        return false;
+    }
+
+    if (!psListLength(where->list) && !psMetadataLookupBool(NULL, config->args, "-all")) {
+        psFree(where);
+        psError(PXTOOLS_ERR_CONFIG, false, "search parameters are required");
+        return false;
+    }
+
+    psString delete = pxDataGet("xcskytool_revert.sql");
+    if (!delete) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        return false;
+    }
+    psString update = pxDataGet("xcskytool_revert_update.sql");
+    if (!update) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&delete, " AND %s", whereClause);
+        psStringAppend(&update, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, delete)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(delete);
+        psFree(where);
+        return false;
+    }
+    psFree(delete);
+
+    int numRows = psDBAffectedRows(config->dbh);
+    psLogMsg("xcskytool", PS_LOG_INFO, "Deleted %d rows", numRows);
+
+    if (!p_psDBRunQuery(config->dbh, update)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(delete);
+        psFree(where);
+        return false;
+    }
+    psFree(update);
+
+    numRows = psDBAffectedRows(config->dbh);
+    psLogMsg("xcskytool", PS_LOG_INFO, "Updated %d rows", numRows);
+
+    psFree(where);
+    return true;
+}
+
+static bool updateresultMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_S16(fault, config->args, "-fault", true, false);
+    PXOPT_LOOKUP_S16(quality, config->args, "-set_quality", false, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-xcsky_id",   "xcsky_id",   "==");
+
+    if (!pxSetFaultCode(config->dbh, "xcskyResult", where, fault, quality)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to set set fault flag");
+        psFree (where);
+        return false;
+    }
+    psFree (where);
+    return true;
+}
+
+bool exportrunMode(pxConfig *config)
+{
+  typedef struct ExportTable {
+    char tableName[80];
+    char sqlFilename[80];
+  } ExportTable;
+
+  int numExportTables = 3;
+
+  PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+  // XXX unused PXOPT_LOOKUP_S64(det_id, config->args, "-xcsky_id", true,  false);
+  PXOPT_LOOKUP_STR(outfile, config->args, "-outfile", true,  false);
+  PXOPT_LOOKUP_BOOL(clean,  config->args, "-clean",   false);
+  PXOPT_LOOKUP_U64(limit,   config->args, "-limit",   false, false);
+
+  FILE *f = fopen (outfile, "w");
+  if (f == NULL) {
+      psError(PS_ERR_UNKNOWN, false, "failed to open output file");
+      return false;
+  }
+
+  if (!pxExportVersion(config, f)) {
+    psError(PS_ERR_UNKNOWN, false, "failed to write dbversion output file");
+    return false;
+  }
+  psMetadata *where = psMetadataAlloc();
+  PXOPT_COPY_S64(config->args, where, "-xcsky_id", "xcsky_id", "==");
+
+  ExportTable tables [] = {
+    {"xcskyRun", "xcskytool_export_run.sql"},
+    {"xcskyInput", "xcskytool_export_input.sql"},
+    {"xcskyResult", "xcskytool_export_result.sql"},
+  };
+
+  for (int i=0; i < numExportTables; i++) {
+    psString query = pxDataGet(tables[i].sqlFilename);
+    if (!query) {
+      psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+      return false;
+    }
+
+    if (where && psListLength(where->list)) {
+      psString whereClause = psDBGenerateWhereSQL(where, NULL);
+      psStringAppend(&query, " %s", whereClause);
+      psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+      psString limitString = psDBGenerateLimitSQL(limit);
+      psStringAppend(&query, " %s", limitString);
+      psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+      psError(PS_ERR_UNKNOWN, false, "database error");
+      psFree(query);
+      return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+      psError(PS_ERR_UNKNOWN, false, "database error");
+      return false;
+    }
+    if (!psArrayLength(output)) {
+      psError(PS_ERR_UNKNOWN, true, "no rows found");
+      psFree(output);
+      return false;
+    }
+
+    if (clean) {
+        if (!strcmp(tables[i].tableName, "xcskyRun")) {
+            if (!pxSetStateCleaned("xcskyRun", "state", output)) {
+                psFree(output);
+                psError(PS_ERR_UNKNOWN, false, "pxSetStateClean failed for table %s",  tables[i].tableName);
+                return false;
+            }
+        }
+    }
+
+      // we must write the export table in non-simple (true) format
+    if (!ippdbPrintMetadatas(f, output, tables[i].tableName, true)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+  }
+
+  fclose (f);
+  return true;
+}
+
+bool importrunMode(pxConfig *config)
+{
+  return false;
+}
+
+bool exportxccalrunMode(pxConfig *config)
+{
+  typedef struct ExportTable {
+    char tableName[80];
+    char sqlFilename[80];
+  } ExportTable;
+
+  int numExportTables = 2;
+
+  PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+  // XXX unused PXOPT_LOOKUP_S64(det_id, config->args, "-xcsky_id", true,  false);
+  PXOPT_LOOKUP_STR(outfile, config->args, "-outfile", true,  false);
+  PXOPT_LOOKUP_BOOL(clean,  config->args, "-clean",   false);
+  PXOPT_LOOKUP_U64(limit,   config->args, "-limit",   false, false);
+
+  FILE *f = fopen (outfile, "w");
+  if (f == NULL) {
+      psError(PS_ERR_UNKNOWN, false, "failed to open output file");
+      return false;
+  }
+
+  if (!pxExportVersion(config, f)) {
+    psError(PS_ERR_UNKNOWN, false, "failed to write dbversion output file");
+    return false;
+  }
+  psMetadata *where = psMetadataAlloc();
+  PXOPT_COPY_S64(config->args, where, "-xccal_id", "xccal_id", "==");
+
+  ExportTable tables [] = {
+    {"xccalRun", "xcskytool_export_xccalrun.sql"},
+    {"xccalResult", "xcskytool_export_xccalresult.sql"},
+  };
+
+  for (int i = 0; i < numExportTables; i++) {
+    psString query = pxDataGet(tables[i].sqlFilename);
+    if (!query) {
+      psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+      return false;
+    }
+
+    if (where && psListLength(where->list)) {
+      psString whereClause = psDBGenerateWhereSQL(where, NULL);
+      psStringAppend(&query, " %s", whereClause);
+      psFree(whereClause);
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+      psString limitString = psDBGenerateLimitSQL(limit);
+      psStringAppend(&query, " %s", limitString);
+      psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+      psError(PS_ERR_UNKNOWN, false, "database error");
+      psFree(query);
+      return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+      psError(PS_ERR_UNKNOWN, false, "database error");
+      return false;
+    }
+    if (!psArrayLength(output)) {
+      psError(PS_ERR_UNKNOWN, true, "no rows found");
+      psFree(output);
+      return false;
+    }
+
+    if (clean) {
+        if (!strcmp(tables[i].tableName, "xccalRun")) {
+            if (!pxSetStateCleaned("xccalRun", "state", output)) {
+                psFree(output);
+                psError(PS_ERR_UNKNOWN, false, "pxSetStateClean failed for table %s",  tables[i].tableName);
+                return false;
+            }
+        }
+    }
+
+      // we must write the export table in non-simple (true) format
+    if (!ippdbPrintMetadatas(f, output, tables[i].tableName, true)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+  }
+
+  fclose (f);
+  return true;
+}
+
+bool importxccalrunMode(pxConfig *config)
+{
+  return false;
+}
+
+static bool setxcskyRunState(pxConfig *config, psS64 xcsky_id, const char *state)
+{
+    PS_ASSERT_PTR_NON_NULL(state, false);
+
+    // check that state is a valid string value
+    if (!pxIsValidState(state)) {
+        psError(PS_ERR_UNKNOWN, false, "invalid xcskyRun state: %s", state);
+        return false;
+    }
+
+    char *query = "UPDATE xcskyRun SET state = '%s' WHERE xcsky_id = %"PRId64;
+    if (!p_psDBRunQueryF(config->dbh, query, state, xcsky_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to change state for xcsky_id %"PRId64, xcsky_id);
+        return false;
+    }
+    return true;
+
+}
+
+static bool definexccalrunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required options
+    // none required. We get workdir, etc from xcskyRun if not provided
+
+    // optional
+    PXOPT_LOOKUP_STR(label,       config->args, "-set_label", false, false);
+    PXOPT_LOOKUP_STR(workdir,     config->args, "-set_workdir", false, false);
+    PXOPT_LOOKUP_STR(data_group,  config->args, "-set_data_group", false, false);
+    PXOPT_LOOKUP_STR(dist_group,  config->args, "-set_dist_group", false, false);
+    PXOPT_LOOKUP_STR(reduction,   config->args, "-set_reduction", false, false);
+    PXOPT_LOOKUP_STR(note,        config->args, "-set_note", false, false);
+    PXOPT_LOOKUP_TIME(registered, config->args, "-set_registered", false, false);
+
+    psMetadata *whereMD = psMetadataAlloc();
+
+    PXOPT_COPY_S64(config->args, whereMD, "-select_xcsky_id",      "xcskyRun.xcsky_id",           "==");
+    PXOPT_COPY_S64(config->args, whereMD, "-select_xcstack_id",    "xcstackRun.xcstack_id",       "==");
+    PXOPT_COPY_STR(config->args, whereMD, "-select_tess_id",       "xcstackRun.tess_id",          "==");
+    PXOPT_COPY_STR(config->args, whereMD, "-select_skycell_id",    "xcstackRun.skycell_id",       "LIKE");
+    pxAddLabelSearchArgs(config, whereMD, "-select_filter",        "xcstackRun.filter",           "LIKE");
+    PXOPT_COPY_F32(config->args, whereMD, "-select_good_frac_min", "xcstackSumSkyfile.good_frac", ">=");
+    pxAddLabelSearchArgs(config, whereMD, "-select_label",         "xcskyRun.label",              "LIKE");
+    pxAddLabelSearchArgs(config, whereMD, "-select_data_group",    "xcskyRun.data_group",         "LIKE");
+    if (!pxskycellAddWhere(config, whereMD)) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to add skycell search arguments");
+        return false;
+    }
+
+    PXOPT_LOOKUP_BOOL(rerun, config->args, "-rerun", false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_BOOL(pretend, config->args, "-pretend", false);
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+
+    psString query = pxDataGet("xcskytool_definexccalrun.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        psFree(whereMD);
+        return false;
+    }
+
+    if (!psListLength(whereMD->list)) {
+        psError(PXTOOLS_ERR_CONFIG, false, "search parameters are required");
+        psFree(whereMD);
+        return false;
+    }
+
+    psString whereClause = psDBGenerateWhereConditionSQL(whereMD, NULL);
+    psStringAppend(&query, "\nAND %s", whereClause);
+    psFree(whereClause);
+    psFree(whereMD);
+
+    psString labelHook = psStringCopy("");
+    if (!rerun) {
+        if (label)  {
+            psStringAppend(&labelHook, "\nAND xccalRun.label = '%s'", label);
+        }
+        psStringAppend(&query, "\nAND xccal_id IS NULL");
+    }
+
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, "\n%s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQueryF(config->dbh, query, labelHook)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(labelHook);
+        psFree(query);
+        return false;
+    }
+    psFree(labelHook);
+    psFree(query);
+
+    // we now have a list of (tess_id, skycell_id) that (potentially) meet out needs
+    // we now need to loop over all of these and for each pair, select the best set of
+    // inputs
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psWarning("xcskytool: no rows found");
+        psFree(output);
+        return true;
+    }
+    if (pretend) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "xccalRun", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+        psFree(output);
+        return true;
+    }
+
+    for (long i = 0; i < output->n; i++) {
+        psMetadata *row = output->data[i]; // Row from select
+        bool status;
+
+        psS64 xcsky_id = psMetadataLookupS64(&status, row, "xcsky_id");
+        psS64 xcstack_id = psMetadataLookupS64(&status, row, "xcstack_id");
+        psString sky_workdir = psMetadataLookupStr(&status, row, "workdir");
+        psString sky_label =  psMetadataLookupStr(&status, row, "label");
+        psString sky_data_group =  psMetadataLookupStr(&status, row, "data_group");
+        psString sky_dist_group =  psMetadataLookupStr(&status, row, "dist_group");
+
+	// create a xcskyRun
+	if (!xccalRunInsert(config->dbh,
+				0x0,	     // xccal_id
+                                xcsky_id,
+                                xcstack_id,
+				"new",	     // state
+				workdir ? workdir : sky_workdir,
+				label ? label : sky_label,
+				data_group ? data_group : sky_data_group,
+				dist_group ? dist_group : sky_dist_group,
+				reduction,
+				NULL,
+				note
+		)
+	    ) {
+	    psError(PS_ERR_UNKNOWN, false, "database error");
+            psFree(output);
+	    return false;
+	}
+
+    }
+    psFree(output);
+    return true;
+}
+
+static bool updatexccalrunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-xccal_id", "xccal_id",   "==");
+    PXOPT_COPY_S64(config->args, where, "-xcsky_id",   "xcsky_id",   "==");
+    PXOPT_COPY_S64(config->args, where, "-xcstack_id", "xcstack_id",   "==");
+    PXOPT_COPY_STR(config->args, where, "-skycell_id",  "xcstackRun.skycell_id",    "==");
+    PXOPT_COPY_STR(config->args, where, "-label",   "xccalRun.label",    "==");
+    PXOPT_COPY_STR(config->args, where, "-state",   "xccalRun.state",    "==");
+    if (!pxskycellAddWhere(config, where)) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to add skycell search arguments");
+        return false;
+    }
+    if (!psListLength(where->list)) {
+        psFree(where);
+        psError(PXTOOLS_ERR_CONFIG, false, "search parameters are required");
+        return false;
+    }
+
+    psString query = psStringCopy("UPDATE xccalRun JOIN xcstackRun using(xcstack_id) join skycell using(tess_id, skycell_id)");
+
+    // pxUpdateRun gets parameters from config->args and updates
+    bool result = pxUpdateRun(config, where, &query, "xccalRun", "xccal_id", "xccalResult", true, false);
+    psFree(query);
+    psFree(where);
+
+    return result;
+}
+
+static bool pendingxccalrunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *whereMD = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, whereMD, "-xccal_id", "xccal_id", "==");
+    PXOPT_COPY_S64(config->args, whereMD, "-xcsky_id", "xcsky_id", "==");
+    pxAddLabelSearchArgs (config, whereMD, "-label", "xccalRun.label", "==");
+    PXOPT_COPY_STR(config->args, whereMD, "-filter", "xcstackRun.filter", "LIKE");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("xcskytool_pendingxccalrun.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (!psListLength(whereMD->list)) {
+        psError(PXTOOLS_ERR_CONFIG, false, "search parameters are required");
+        psFree(whereMD);
+        return false;
+    }
+
+    psString whereClause = psDBGenerateWhereConditionSQL(whereMD, NULL);
+    psStringAppend(&query, "\n AND %s", whereClause);
+    psFree(whereClause);
+    psFree(whereMD);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, "\n%s", limitString);
+        psFree(limitString);
+    }
+
+    // the where clause is required and matches the WHERE hook format string
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("xcskytool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "pendingxccalRun", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+    return true;
+}
+
+static bool addxccalresultMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required
+    PXOPT_LOOKUP_S64(xccal_id, config->args, "-xccal_id", true, false);
+
+    // optional
+    PXOPT_LOOKUP_F32(sigma_ra, config->args,  "-sigma_ra", false, false);
+    PXOPT_LOOKUP_F32(sigma_dec, config->args, "-sigma_dec", false, false);
+
+    PXOPT_LOOKUP_F32(zpt_obs, config->args,   "-zpt_obs", false, false);
+    PXOPT_LOOKUP_F32(zpt_stdev, config->args, "-zpt_stdev", false, false);
+    PXOPT_LOOKUP_F32(fwhm_major, config->args, "-fwhm_major", false, false);
+    PXOPT_LOOKUP_F32(fwhm_minor, config->args, "-fwhm_minor", false, false);
+
+
+    PXOPT_LOOKUP_F32(dtime_script, config->args,   "-dtime_script", false, false);
+    PXOPT_LOOKUP_F32(dtime_astrom, config->args,   "-dtime_astrom", false, false);
+
+    PXOPT_LOOKUP_STR(hostname, config->args,    "-hostname", false, false);
+    PXOPT_LOOKUP_S32(n_astrom, config->args,    "-n_astrom", false, false);
+    PXOPT_LOOKUP_S32(n_detections, config->args,"-n_detections", false, false);
+    PXOPT_LOOKUP_S32(n_extended, config->args,  "-n_extended", false, false);
+    PXOPT_LOOKUP_S32(n_forced, config->args,    "-n_forced", false, false);
+
+    PXOPT_LOOKUP_STR(path_base, config->args,   "-path_base", false, false);
+    PXOPT_LOOKUP_S16(fault, config->args,       "-fault", false, false);
+    PXOPT_LOOKUP_S16(quality, config->args,     "-quality", false, false);
+
+    PXOPT_LOOKUP_STR(ver_pslib, config->args,   "-ver_pslib", false, false);
+    PXOPT_LOOKUP_STR(ver_psmodules, config->args, "-ver_psmodules", false, false);
+    PXOPT_LOOKUP_STR(ver_psphot, config->args,  "-ver_psphot", false, false);
+    PXOPT_LOOKUP_STR(ver_psastro, config->args, "-ver_psastro", false, false);
+    PXOPT_LOOKUP_STR(ver_ppstats, config->args, "-ver_ppstats", false, false);
+
+    psString software_ver = NULL;
+    if ((ver_pslib)&&(ver_psmodules)) {
+      software_ver = pxMergeCodeVersions(ver_pslib,ver_psmodules);
+    }
+    if (ver_psphot) {
+      software_ver = pxMergeCodeVersions(software_ver,ver_psphot);
+    }
+    if (ver_psastro) {
+      software_ver = pxMergeCodeVersions(software_ver,ver_psastro);
+    }
+    if (ver_ppstats) {
+      software_ver = pxMergeCodeVersions(software_ver,ver_ppstats);
+    }
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    xccalResultRow *row = xccalResultRowAlloc(
+        xccal_id,
+        path_base,
+        dtime_script,
+        dtime_astrom,
+        sigma_ra,
+        sigma_dec,
+        n_astrom,
+        n_detections,
+        n_extended,
+        n_forced,
+        zpt_obs,
+        zpt_stdev,
+        fwhm_major,
+        fwhm_minor,
+        quality,
+        software_ver,
+        hostname,
+        fault
+        );
+
+    if (!xccalResultInsertObject(config->dbh, row)) {
+        // rollback
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    if (fault) {
+        if (!psDBCommit(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+            return false;
+        }
+        return true;
+    }
+
+    psString query = "UPDATE xccalRun SET state = 'full' WHERE xccal_id = %" PRId64;
+    if (!p_psDBRunQueryF(config->dbh, query, xccal_id)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+static bool updatexccalresultMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_S16(fault, config->args, "-set_fault", true, false);
+    PXOPT_LOOKUP_S16(quality, config->args, "-set_quality", false, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-xccal_id",   "xccal_id",   "==");
+
+    if (!pxSetFaultCode(config->dbh, "xccalResult", where, fault, quality)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to set set fault flag");
+        psFree (where);
+        return false;
+    }
+    psFree (where);
+    return true;
+}
+static bool xccalresultMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-xccal_id",  "xccalRun.xccal_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-xcsky_id",     "xccalRun.xcsky_id", "==");
+    PXOPT_COPY_S64(config->args, where, "-xcstack_id",   "xcstackRun.xcstack_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-tess_id",    "xcstackRun.tess_id", "LIKE");
+    PXOPT_COPY_STR(config->args, where, "-skycell_id", "xcstackRun.skycell_id", "LIKE");
+    PXOPT_COPY_STR(config->args, where, "-filter",     "xcstackRun.filter", "LIKE");
+    PXOPT_COPY_STR(config->args, where, "-label",      "xccalRun.label", "==");
+    PXOPT_COPY_STR(config->args, where, "-data_group", "xccalRun.data_group", "LIKE");
+    PXOPT_COPY_S16(config->args, where, "-quality",     "xccalResult.quality", "==");
+    PXOPT_COPY_S16(config->args, where, "-fault",      "xccalResult.fault", "==");
+
+    if (!pxskycellAddWhere(config, where)) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to add skycell search arguments");
+        return false;
+    }
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("xcskytool_xccalresult.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (!psListLength(where->list)) {
+        psError(PXTOOLS_ERR_CONFIG, true, "search parameters are required");
+        return false;
+    }
+    psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+    psStringAppend(&query, " WHERE %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);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("xcskytool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (psArrayLength(output)) {
+        if (!ippdbPrintMetadatas(stdout, output, "xcskyResult", !simple)) {
+            psError(PS_ERR_UNKNOWN, false, "failed to print array");
+            psFree(output);
+            return false;
+        }
+    }
+
+    psFree(output);
+
+    return true;
+}
+static bool revertxccalresultMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-xccal_id", "xccalResult.xccal_id", "==");
+    pxAddLabelSearchArgs(config, where, "-label", "xccalRun.label", "==");
+    pxAddLabelSearchArgs(config, where, "-data_group", "xccalRun.data_group", "==");
+    pxAddLabelSearchArgs(config, where, "-filter", "xcstackRun.filter", "==");
+    PXOPT_COPY_S16(config->args, where, "-fault", "xccalResult.fault", "==");
+    if (!pxskycellAddWhere(config, where)) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to add skycell search arguments");
+        return false;
+    }
+
+    if (!psListLength(where->list) && !psMetadataLookupBool(NULL, config->args, "-all")) {
+        psFree(where);
+        psError(PXTOOLS_ERR_CONFIG, false, "search parameters are required");
+        return false;
+    }
+
+    // Delete product
+    psString delete = pxDataGet("xcskytool_revertxccal.sql");
+    if (!delete) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&delete, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, delete)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(delete);
+        psFree(where);
+        return false;
+    }
+    psFree(delete);
+
+    int numRows = psDBAffectedRows(config->dbh); // Number of row affected
+    psLogMsg("xcskytool", PS_LOG_INFO, "Deleted %d rows", numRows);
+
+    psFree(where);
+    return true;
+}
Index: trunk/ippTools/src/xcskytool.h
===================================================================
--- trunk/ippTools/src/xcskytool.h	(revision 42837)
+++ trunk/ippTools/src/xcskytool.h	(revision 42837)
@@ -0,0 +1,39 @@
+/*
+ * xcskytool.h
+ *
+ * Copyright (C) 2023  Eugene Magnier (based on staticskytool.h)
+ *
+ */
+
+#ifndef XCSKYTOOL_H
+#define XCSKYTOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    XCSKYTOOL_MODE_NONE           = 0x0,
+    XCSKYTOOL_MODE_DEFINEBYQUERY,
+    XCSKYTOOL_MODE_UPDATERUN,
+    XCSKYTOOL_MODE_INPUTS,
+    XCSKYTOOL_MODE_TODO,
+    XCSKYTOOL_MODE_ADDRESULT,
+    XCSKYTOOL_MODE_RESULT,
+    XCSKYTOOL_MODE_REVERT,
+    XCSKYTOOL_MODE_UPDATERESULT,
+    XCSKYTOOL_MODE_EXPORTRUN,
+    XCSKYTOOL_MODE_IMPORTRUN,
+
+    XCSKYTOOL_MODE_DEFINEXCCALRUN,
+    XCSKYTOOL_MODE_PENDINGXCCALRUN,
+    XCSKYTOOL_MODE_UPDATEXCCALRUN,
+    XCSKYTOOL_MODE_ADDXCCALRESULT,
+    XCSKYTOOL_MODE_UPDATEXCCALRESULT,
+    XCSKYTOOL_MODE_XCCALRESULT,
+    XCSKYTOOL_MODE_REVERTXCCALRESULT,
+    XCSKYTOOL_MODE_EXPORTXCCALRUN,
+    XCSKYTOOL_MODE_IMPORTXCCALRUN,
+} xcskytoolMode;
+
+pxConfig *xcskytoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // XCSKYTOOL_H
Index: trunk/ippTools/src/xcskytoolConfig.c
===================================================================
--- trunk/ippTools/src/xcskytoolConfig.c	(revision 42837)
+++ trunk/ippTools/src/xcskytoolConfig.c	(revision 42837)
@@ -0,0 +1,305 @@
+/*
+ * xcskytoolConfig.c
+ *
+ * Copyright (C) 2023 Eugene Magnier (based on staticskytoolConfig.c)
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "xcskytool.h"
+
+pxConfig *xcskytoolConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) { config = pxConfigAlloc(); }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(psErrorCodeLast(), false, "Can't find site configuration");
+        psFree(config);
+        return NULL;
+    }
+
+    psTime *now = psTimeGetNow(PS_TIME_TAI);
+
+    // -definebyquery
+    psMetadata *definebyqueryArgs = psMetadataAlloc();
+    psMetadataAddS64(definebyqueryArgs,  PS_LIST_TAIL, "-select_xcstack_id", 0, "search for xcstack_id", 0);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-select_skycell_id", 0, "search for skycell_id", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-select_tess_id", 0, "search for tess_id", NULL);
+    psMetadataAddF32(definebyqueryArgs,  PS_LIST_TAIL, "-select_good_frac_min", 0, "define min good_frac", NAN);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-select_label", PS_META_DUPLICATE_OK, "search by xcstackRun label (LIKE comparison, multiple OK)", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-select_data_group", PS_META_DUPLICATE_OK, "search by xcstackRun data_group (LIKE comparison, multiple OK)", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-select_filter", PS_META_DUPLICATE_OK, "search by filter (LIKE comparison, multiple OK)", NULL);
+    pxskycellAddArguments(definebyqueryArgs);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-set_workdir", 0, "define workdir (required)", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-set_label", 0, "define label (required)", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-set_data_group", 0, "define data group", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-set_dist_group", 0, "define dist group", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-set_note", 0, "define note", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-set_reduction", 0, "define reduction", NULL);
+    psMetadataAddTime(definebyqueryArgs, PS_LIST_TAIL, "-set_registered", 0, "time detrend run was registered", now);
+    psMetadataAddBool(definebyqueryArgs, PS_LIST_TAIL, "-group_by_data_group",  0, "queue unique run for each data_group", false);
+    psMetadataAddBool(definebyqueryArgs, PS_LIST_TAIL, "-rerun",  0, "queue new run even if one already exists for inputs", false);
+    psMetadataAddBool(definebyqueryArgs, PS_LIST_TAIL, "-pretend",  0, "do not actually modify the database", false);
+    psMetadataAddBool(definebyqueryArgs, PS_LIST_TAIL, "-check_inputs",  0, "list inputs, do not modify database", false);
+    psMetadataAddBool(definebyqueryArgs, PS_LIST_TAIL, "-simple", 0, "use the simple output format", false);
+
+    // -updaterun
+    psMetadata *updaterunArgs = psMetadataAlloc();
+    psMetadataAddS64(updaterunArgs, PS_LIST_TAIL, "-xcsky_id", 0, "search by xcsky ID", 0);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-state", 0, "search by state", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-label", 0, "search by label", 0);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-data_group", 0, "search by data_group", 0);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-tess_id", 0, "search by tess_id", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-skycell_id", 0, "search by skycell_id", NULL);
+    pxskycellAddArguments(updaterunArgs);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_label", 0, "define new value for label", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_state", 0, "define new state", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_data_group", 0, "define new data_group", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_dist_group", 0, "define new dist_group", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_note", 0, "define new note", NULL);
+
+    // -inputs
+    psMetadata *inputsArgs = psMetadataAlloc();
+    psMetadataAddS64(inputsArgs, PS_LIST_TAIL, "-xcsky_id", 0, "search by xcsky ID", 0);
+    psMetadataAddU64(inputsArgs, PS_LIST_TAIL, "-limit", 0, "limit result set to N items", 0);
+    psMetadataAddBool(inputsArgs, PS_LIST_TAIL, "-simple", 0, "use the simple output format", false);
+
+    // -todo
+    psMetadata *todoArgs = psMetadataAlloc();
+    psMetadataAddS64(todoArgs, PS_LIST_TAIL, "-xcsky_id", 0, "search by xcsky ID", 0);
+    psMetadataAddStr(todoArgs, PS_LIST_TAIL, "-label", PS_META_DUPLICATE_OK, "search by label", NULL);
+    pxskycellAddArguments(todoArgs);
+    psMetadataAddU64(todoArgs, PS_LIST_TAIL, "-limit", 0, "limit result set to N items", 0);
+    psMetadataAddBool(todoArgs, PS_LIST_TAIL, "-simple", 0, "use the simple output format", false);
+
+    // -addresult
+    psMetadata *addresultArgs = psMetadataAlloc();
+    psMetadataAddS64(addresultArgs, PS_LIST_TAIL, "-xcsky_id", 0, "define xcsky ID (required)", 0);
+    psMetadataAddStr(addresultArgs, PS_LIST_TAIL, "-path_base", 0, "define base output location", 0);
+    psMetadataAddF32(addresultArgs, PS_LIST_TAIL, "-dtime_phot", 0, "define photometry time", NAN);
+    psMetadataAddF32(addresultArgs, PS_LIST_TAIL, "-dtime_script", 0, "define elapsed time in script (seconds)", NAN);
+    psMetadataAddS32(addresultArgs, PS_LIST_TAIL, "-sources", 0, "number of sources", 0);
+    psMetadataAddS32(addresultArgs, PS_LIST_TAIL, "-num_inputs", 0, "number of inputs", 0);
+    psMetadataAddStr(addresultArgs, PS_LIST_TAIL, "-hostname", 0, "define hostname", 0);
+    psMetadataAddF32(addresultArgs, PS_LIST_TAIL, "-good_frac", 0, "define fraction of good pixels", NAN);
+    psMetadataAddS16(addresultArgs, PS_LIST_TAIL, "-quality", 0, "set quality", 0);
+    psMetadataAddS16(addresultArgs, PS_LIST_TAIL, "-fault", 0, "set fault code", 0);
+
+    // -result
+    psMetadata *resultArgs= psMetadataAlloc();
+    psMetadataAddS64(resultArgs, PS_LIST_TAIL, "-xcsky_id", 0, "search by xcsky ID", 0);
+    psMetadataAddStr(resultArgs, PS_LIST_TAIL, "-tess_id", 0, "search by tess ID (LIKE comparison)", 0);
+    psMetadataAddStr(resultArgs, PS_LIST_TAIL, "-skycell_id", 0, "search by skycell ID (LIKE comparison)", 0);
+    psMetadataAddStr(resultArgs, PS_LIST_TAIL, "-label", 0, "search by label", NULL);
+    psMetadataAddStr(resultArgs, PS_LIST_TAIL, "-data_group", 0, "search by data_group (LIKE comparison)", NULL);
+    psMetadataAddS16(resultArgs, PS_LIST_TAIL, "-num_filters", 0, "search by number of filters in the inputs", 0);
+    psMetadataAddS64(resultArgs, PS_LIST_TAIL, "-xcstack_id", 0, "search by input xcstack ID (if used num_filters will be wrong)", 0);
+    pxskycellAddArguments(resultArgs);
+    psMetadataAddS16(resultArgs, PS_LIST_TAIL, "-fault", 0, "search by fault code", 0);
+    psMetadataAddU64(resultArgs, PS_LIST_TAIL, "-limit", 0, "limit result set to N items", 0);
+    psMetadataAddBool(resultArgs, PS_LIST_TAIL, "-simple", 0, "use the simple output format", false);
+    psMetadataAddBool(resultArgs, PS_LIST_TAIL, "-all", 0, "allow no search terms", false);
+
+    // -revert
+    psMetadata *revertArgs= psMetadataAlloc();
+    psMetadataAddS64(revertArgs, PS_LIST_TAIL, "-xcsky_id", 0, "search by xcsky ID", 0);
+    psMetadataAddStr(revertArgs, PS_LIST_TAIL, "-label", PS_META_DUPLICATE_OK, "search by label", 0);
+    psMetadataAddS16(revertArgs, PS_LIST_TAIL, "-fault", 0, "search by fault code", 0);
+    pxskycellAddArguments(revertArgs);
+    psMetadataAddBool(revertArgs, PS_LIST_TAIL, "-all", 0, "allow no search terms", false);
+
+    // -updateresult
+    psMetadata *updateresultArgs = psMetadataAlloc();
+    psMetadataAddS64(updateresultArgs, PS_LIST_TAIL, "-xcsky_id", 0, "define xcsky ID (required)", 0);
+    psMetadataAddS16(updateresultArgs, PS_LIST_TAIL, "-fault", 0, "set fault code (required)", 0);
+    psMetadataAddS16(updateresultArgs, PS_LIST_TAIL, "-set_quality",  0, "set quality", 0);
+
+    // -exportrun
+    psMetadata *exportrunArgs = psMetadataAlloc();
+    psMetadataAddS64(exportrunArgs, PS_LIST_TAIL, "-xcsky_id", 0, "export this xcsky ID (required)", 0);
+    psMetadataAddStr(exportrunArgs, PS_LIST_TAIL, "-outfile", 0, "export to this file (required)", NULL);
+    psMetadataAddU64(exportrunArgs, PS_LIST_TAIL, "-limit", 0, "limit result set to N items", 0);
+    psMetadataAddBool(exportrunArgs, PS_LIST_TAIL, "-clean", 0, "mark tables as cleaned", false);
+
+    // -importrun
+    psMetadata *importrunArgs = psMetadataAlloc();
+    psMetadataAddStr(importrunArgs, PS_LIST_TAIL, "-infile", 0, "import from this file (required)", NULL);
+  
+    // -definexccalrun
+    psMetadata *definexccalrunArgs = psMetadataAlloc();
+    psMetadataAddS64(definexccalrunArgs,  PS_LIST_TAIL, "-select_xcsky_id", 0, "search for xcsky_id", 0);
+    psMetadataAddS64(definexccalrunArgs,  PS_LIST_TAIL, "-select_xcstack_id", 0, "search for xcstack_id", 0);
+    psMetadataAddStr(definexccalrunArgs,  PS_LIST_TAIL, "-select_skycell_id", 0, "search for skycell_id", NULL);
+    psMetadataAddStr(definexccalrunArgs,  PS_LIST_TAIL, "-select_tess_id", 0, "search for tess_id", NULL);
+    psMetadataAddF32(definexccalrunArgs,  PS_LIST_TAIL, "-select_good_frac_min", 0, "define min good_frac", NAN);
+    psMetadataAddStr(definexccalrunArgs,  PS_LIST_TAIL, "-select_label", PS_META_DUPLICATE_OK, "search by xcstackRun label (LIKE comparison, multiple OK)", NULL);
+    psMetadataAddStr(definexccalrunArgs,  PS_LIST_TAIL, "-select_data_group", PS_META_DUPLICATE_OK, "search by xcstackRun data_group (LIKE comparison, multiple OK)", NULL);
+    psMetadataAddStr(definexccalrunArgs,  PS_LIST_TAIL, "-select_filter", PS_META_DUPLICATE_OK, "search by filter (LIKE comparison, multiple OK)", NULL);
+    pxskycellAddArguments(definexccalrunArgs);
+    psMetadataAddStr(definexccalrunArgs,  PS_LIST_TAIL, "-set_workdir", 0, "define workdir", NULL);
+    psMetadataAddStr(definexccalrunArgs,  PS_LIST_TAIL, "-set_label", 0, "define label", NULL);
+    psMetadataAddStr(definexccalrunArgs,  PS_LIST_TAIL, "-set_data_group", 0, "define data group", NULL);
+    psMetadataAddStr(definexccalrunArgs,  PS_LIST_TAIL, "-set_dist_group", 0, "define dist group", NULL);
+    psMetadataAddStr(definexccalrunArgs,  PS_LIST_TAIL, "-set_note", 0, "define note", NULL);
+    psMetadataAddStr(definexccalrunArgs,  PS_LIST_TAIL, "-set_reduction", 0, "define reduction", NULL);
+    psMetadataAddTime(definexccalrunArgs, PS_LIST_TAIL, "-set_registered", 0, "time detrend run was registered", now);
+    psMetadataAddBool(definexccalrunArgs, PS_LIST_TAIL, "-rerun",  0, "queue new run even if one already exists for inputs", false);
+    psMetadataAddBool(definexccalrunArgs, PS_LIST_TAIL, "-pretend",  0, "do not actually modify the database", false);
+    psMetadataAddBool(definexccalrunArgs, PS_LIST_TAIL, "-check_inputs",  0, "list inputs, do not modify database", false);
+    psMetadataAddBool(definexccalrunArgs, PS_LIST_TAIL, "-simple", 0, "use the simple output format", false);
+    psMetadataAddU64(definexccalrunArgs, PS_LIST_TAIL, "-limit", 0, "limit result set to N items", 0);
+
+    // -updatexccalrun
+    psMetadata *updatexccalrunArgs = psMetadataAlloc();
+    psMetadataAddS64(updatexccalrunArgs, PS_LIST_TAIL, "-xccal_id", 0,    "search by xccal ID", 0);
+    psMetadataAddS64(updatexccalrunArgs, PS_LIST_TAIL, "-xcsky_id", 0,    "search by xcsky ID", 0);
+    psMetadataAddS64(updatexccalrunArgs, PS_LIST_TAIL, "-xcstack_id", 0,  "search by xcstack ID", 0);
+    psMetadataAddStr(updatexccalrunArgs, PS_LIST_TAIL, "-state", 0,       "search by state", NULL);
+    psMetadataAddStr(updatexccalrunArgs, PS_LIST_TAIL, "-skycell_id", 0,  "search by skycell_id", 0);
+    psMetadataAddStr(updatexccalrunArgs, PS_LIST_TAIL, "-label", 0,       "search by label", 0);
+    pxskycellAddArguments(updatexccalrunArgs);
+    psMetadataAddStr(updatexccalrunArgs, PS_LIST_TAIL, "-set_label", 0, "define new value for label", NULL);
+    psMetadataAddStr(updatexccalrunArgs, PS_LIST_TAIL, "-set_state", 0, "define new state", NULL);
+    psMetadataAddStr(updatexccalrunArgs, PS_LIST_TAIL, "-set_data_group", 0, "define new data_group", NULL);
+    psMetadataAddStr(updatexccalrunArgs, PS_LIST_TAIL, "-set_dist_group", 0, "define new dist_group", NULL);
+    psMetadataAddStr(updatexccalrunArgs, PS_LIST_TAIL, "-set_note", 0, "define new note", NULL);
+
+    // -pendingxccalrun
+    psMetadata *pendingxccalrunArgs = psMetadataAlloc();
+    psMetadataAddS64(pendingxccalrunArgs, PS_LIST_TAIL, "-xccal_id", 0, "search by xccal ID", 0);
+    psMetadataAddS64(pendingxccalrunArgs, PS_LIST_TAIL, "-xcsky_id", 0, "search by xcsky ID", 0);
+    psMetadataAddStr(pendingxccalrunArgs, PS_LIST_TAIL, "-label", PS_META_DUPLICATE_OK, "search by label", NULL);
+    psMetadataAddStr(pendingxccalrunArgs, PS_LIST_TAIL, "-filter", 0, "search by filter", NULL);
+    psMetadataAddU64(pendingxccalrunArgs, PS_LIST_TAIL, "-limit", 0, "limit result set to N items", 0);
+    psMetadataAddBool(pendingxccalrunArgs, PS_LIST_TAIL, "-simple", 0, "use the simple output format", false);
+
+    // -addxccalresult
+    psMetadata *addxccalresultArgs = psMetadataAlloc();
+    psMetadataAddS64(addxccalresultArgs, PS_LIST_TAIL, "-xccal_id", 0,     "define xccal_id (required)", 0);
+    psMetadataAddF32(addxccalresultArgs, PS_LIST_TAIL, "-sigma_ra", 0,     "define exposure E ra", NAN);
+    psMetadataAddF32(addxccalresultArgs, PS_LIST_TAIL, "-sigma_dec", 0,    "define exposure E dec", NAN);
+    psMetadataAddF32(addxccalresultArgs, PS_LIST_TAIL, "-zpt_obs", 0,      "define observed zero point", NAN);
+    psMetadataAddF32(addxccalresultArgs, PS_LIST_TAIL, "-zpt_stdev", 0,    "define observed zero point stdandard deviation", NAN);
+    psMetadataAddF32(addxccalresultArgs, PS_LIST_TAIL, "-fwhm_major", 0,   "define fwhm (major axis; pixels)", NAN);
+    psMetadataAddF32(addxccalresultArgs, PS_LIST_TAIL, "-fwhm_minor", 0,   "define fwhm (minor axis; pixels)", NAN);
+    psMetadataAddF32(addxccalresultArgs, PS_LIST_TAIL, "-dtime_script", 0, "define elapsed time in script (seconds)", NAN);
+    psMetadataAddF32(addxccalresultArgs, PS_LIST_TAIL, "-dtime_astrom", 0, "define elapsed time for astrometry (seconds)", NAN);
+
+    psMetadataAddStr(addxccalresultArgs, PS_LIST_TAIL, "-hostname", 0,            "define hostname", NULL);
+    psMetadataAddS32(addxccalresultArgs, PS_LIST_TAIL, "-n_astrom", 0,            "define number of astrometry reference objects", 0);
+    psMetadataAddS32(addxccalresultArgs, PS_LIST_TAIL, "-n_detections", 0,        "define number of detections", 0);
+    psMetadataAddS32(addxccalresultArgs, PS_LIST_TAIL, "-n_extended", 0,          "define number of extended detections", 0);
+    psMetadataAddS32(addxccalresultArgs, PS_LIST_TAIL, "-n_forced", 0,            "define number of forced detections", 0);
+
+    psMetadataAddStr(addxccalresultArgs, PS_LIST_TAIL, "-path_base", 0,            "define base output location", NULL);
+    psMetadataAddS16(addxccalresultArgs, PS_LIST_TAIL, "-fault",  0,            "set fault code", 0);
+    psMetadataAddS16(addxccalresultArgs, PS_LIST_TAIL, "-quality",  0,            "set quality", 0);
+
+    psMetadataAddStr(addxccalresultArgs, PS_LIST_TAIL, "-ver_pslib", 0, "define psLib version", NULL);
+    psMetadataAddStr(addxccalresultArgs, PS_LIST_TAIL, "-ver_psmodules", 0, "define psModules version", NULL);
+    psMetadataAddStr(addxccalresultArgs, PS_LIST_TAIL, "-ver_psphot", 0, "define psphot version", NULL);
+    psMetadataAddStr(addxccalresultArgs, PS_LIST_TAIL, "-ver_psastro", 0, "define psastro version", NULL);
+    psMetadataAddStr(addxccalresultArgs, PS_LIST_TAIL, "-ver_ppstats", 0, "define ppStats version", NULL);
+    psMetadataAddStr(addxccalresultArgs, PS_LIST_TAIL, "-ver_streaks", 0, "define streaksremove version", NULL);
+
+    // -revertxccal
+    psMetadata *revertxccalArgs= psMetadataAlloc();
+    psMetadataAddS64(revertxccalArgs, PS_LIST_TAIL,    "-xccal_id",   0, "search by xccal ID", 0);
+    psMetadataAddStr(revertxccalArgs, PS_LIST_TAIL,    "-label",      PS_META_DUPLICATE_OK, "search by label", 0);
+    psMetadataAddStr(revertxccalArgs, PS_LIST_TAIL,    "-data_group", PS_META_DUPLICATE_OK, "search by data_group", 0);
+    psMetadataAddStr(revertxccalArgs, PS_LIST_TAIL,    "-filter",     PS_META_DUPLICATE_OK, "search by filter", 0);
+    pxskycellAddArguments(revertxccalArgs);	       		     
+    psMetadataAddS16(revertxccalArgs, PS_LIST_TAIL,    "-fault",      0, "search by fault code", 0);
+    psMetadataAddBool(revertxccalArgs, PS_LIST_TAIL,   "-all",        0, "allow no search terms", false);
+
+    // -updatexccalresult
+    psMetadata *updatexccalresultArgs = psMetadataAlloc();
+    psMetadataAddS64(updatexccalresultArgs, PS_LIST_TAIL, "-xccal_id", 0, "define xccal ID (required)", 0);
+    psMetadataAddS16(updatexccalresultArgs, PS_LIST_TAIL, "-set_fault", 0, "set fault code (required)", 0);
+    psMetadataAddS16(updatexccalresultArgs, PS_LIST_TAIL, "-set_quality", 0, "set quality code", 0);
+
+    // -xccalresult
+    psMetadata *xccalresultArgs= psMetadataAlloc();
+    psMetadataAddS64(xccalresultArgs, PS_LIST_TAIL, "-xccal_id", 0, "search by xccal ID", 0);
+    psMetadataAddS64(xccalresultArgs, PS_LIST_TAIL, "-xcsky_id", 0, "search by xcsky ID", 0);
+    psMetadataAddS64(xccalresultArgs, PS_LIST_TAIL, "-xcstack_id", 0, "search by xcstack ID", 0);
+    psMetadataAddStr(xccalresultArgs, PS_LIST_TAIL, "-tess_id", 0, "search by tess ID (LIKE comparison)", 0);
+    psMetadataAddStr(xccalresultArgs, PS_LIST_TAIL, "-skycell_id", 0, "search by skycell ID (LIKE comparison)", 0);
+    psMetadataAddStr(xccalresultArgs, PS_LIST_TAIL, "-filter", 0, "search by filter (LIKE comparison)", 0);
+    psMetadataAddStr(xccalresultArgs, PS_LIST_TAIL, "-label", 0, "search by label (LIKE comparison)", NULL);
+    psMetadataAddStr(xccalresultArgs, PS_LIST_TAIL, "-data_group", 0, "search by data_group (LIKE comparison)", NULL);
+    psMetadataAddS16(xccalresultArgs, PS_LIST_TAIL, "-quality",  0, "search by quality", 0);
+    psMetadataAddS16(xccalresultArgs, PS_LIST_TAIL, "-fault", 0, "search by fault code", 0);
+    pxskycellAddArguments(xccalresultArgs);
+    psMetadataAddU64(xccalresultArgs, PS_LIST_TAIL, "-limit", 0, "limit xccalresult set to N items", 0);
+    psMetadataAddBool(xccalresultArgs, PS_LIST_TAIL, "-simple", 0, "use the simple output format", false);
+
+    // -exportxccalrun
+    psMetadata *exportxccalrunArgs = psMetadataAlloc();
+    psMetadataAddS64(exportxccalrunArgs, PS_LIST_TAIL, "-xccal_id", 0, "export this xccal ID (required)", 0);
+    psMetadataAddStr(exportxccalrunArgs, PS_LIST_TAIL, "-outfile", 0, "export to this file (required)", NULL);
+    psMetadataAddU64(exportxccalrunArgs, PS_LIST_TAIL, "-limit", 0, "limit result set to N items", 0);
+    psMetadataAddBool(exportxccalrunArgs, PS_LIST_TAIL, "-clean", 0, "mark tables as cleaned", false);
+
+    // -importrun
+    psMetadata *importxccalrunArgs = psMetadataAlloc();
+    psMetadataAddStr(importxccalrunArgs, PS_LIST_TAIL, "-infile", 0, "import from this file (required)", NULL);
+
+    psFree(now);
+
+    psMetadata *argSets = psMetadataAlloc();
+    psMetadata *modes   = psMetadataAlloc();
+
+    PXOPT_ADD_MODE("-definebyquery",   "Define a new run",         XCSKYTOOL_MODE_DEFINEBYQUERY,     definebyqueryArgs);
+    PXOPT_ADD_MODE("-updaterun",       "Update a run",             XCSKYTOOL_MODE_UPDATERUN,         updaterunArgs);
+    PXOPT_ADD_MODE("-inputs",          "Get inputs",               XCSKYTOOL_MODE_INPUTS,            inputsArgs);
+    PXOPT_ADD_MODE("-todo",            "Get runs to do",           XCSKYTOOL_MODE_TODO,              todoArgs);
+    PXOPT_ADD_MODE("-addresult",       "Add result of run",        XCSKYTOOL_MODE_ADDRESULT,         addresultArgs);
+    PXOPT_ADD_MODE("-result",          "Get result of run",        XCSKYTOOL_MODE_RESULT,            resultArgs);
+    PXOPT_ADD_MODE("-revert",          "Revert failed run",        XCSKYTOOL_MODE_REVERT,            revertArgs);
+    PXOPT_ADD_MODE("-updateresult",    "Update fault for run",     XCSKYTOOL_MODE_UPDATERESULT,      updateresultArgs);
+    PXOPT_ADD_MODE("-exportrun",       "Export run",               XCSKYTOOL_MODE_EXPORTRUN,         exportrunArgs);
+    PXOPT_ADD_MODE("-importrun",       "Import run",               XCSKYTOOL_MODE_IMPORTRUN,         importrunArgs);
+    PXOPT_ADD_MODE("-definexccalrun",  "Define a new xccalrun",    XCSKYTOOL_MODE_DEFINEXCCALRUN,    definexccalrunArgs);
+    PXOPT_ADD_MODE("-updatexccalrun",  "Update a xccalrun",        XCSKYTOOL_MODE_UPDATEXCCALRUN,    updatexccalrunArgs);
+    PXOPT_ADD_MODE("-pendingxccalrun", "Get xccal runs to do",     XCSKYTOOL_MODE_PENDINGXCCALRUN,   pendingxccalrunArgs);
+    PXOPT_ADD_MODE("-addxccalresult",  "add xccal result",         XCSKYTOOL_MODE_ADDXCCALRESULT,    addxccalresultArgs);
+    PXOPT_ADD_MODE("-revertxccal",     "revert faulted xccal run", XCSKYTOOL_MODE_REVERTXCCALRESULT, revertxccalArgs);
+    PXOPT_ADD_MODE("-updatexccal",     "revert faulted xccal run", XCSKYTOOL_MODE_UPDATEXCCALRESULT, updatexccalresultArgs);
+    PXOPT_ADD_MODE("-xccalresult",     "Get result of xccal run",  XCSKYTOOL_MODE_XCCALRESULT,       xccalresultArgs);
+    PXOPT_ADD_MODE("-exportxccalrun",  "Export xccal run",         XCSKYTOOL_MODE_EXPORTXCCALRUN,    exportxccalrunArgs);
+    PXOPT_ADD_MODE("-importxccalrun",  "Import xccal run",         XCSKYTOOL_MODE_IMPORTXCCALRUN,    importxccalrunArgs);
+
+    if (!pxGetOptions(stderr, argc, argv, config, modes, argSets)) {
+        psError(PS_ERR_UNKNOWN, true, "option parsing failed");
+        psFree(argSets);
+        psFree(modes);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+    psFree(modes);
+
+    // define Database handle, if used
+    // do this last so we don't setup a connection before CLI options are
+    // validated
+    config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    return config;
+}
Index: trunk/ippTools/src/xcstacktool.c
===================================================================
--- trunk/ippTools/src/xcstacktool.c	(revision 42837)
+++ trunk/ippTools/src/xcstacktool.c	(revision 42837)
@@ -0,0 +1,1544 @@
+/*
+ * xcstacktool.c
+ *
+ * Copyright (C) 2023 Eugene Magnier
+ * Based on stacktool.c (Joshua Hoblitt)
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVB_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include <ippdb.h>
+
+#include "pxtools.h"
+#include "xcstacktool.h"
+
+static bool definebyqueryMode(pxConfig *config);
+static bool addbyqueryMode(pxConfig *config);
+static bool updaterunMode(pxConfig *config);
+static bool tosumMode(pxConfig *config);
+static bool inputcamerasMode(pxConfig *config);
+static bool inputskyfileMode(pxConfig *config);
+static bool addsumskyfileMode(pxConfig *config);
+static bool sumskyfileMode(pxConfig *config);
+static bool revertsumskyfileMode(pxConfig *config);
+static bool updatesumskyfileMode(pxConfig *config);
+static bool pendingcleanuprunMode(pxConfig *config);
+static bool pendingcleanupskyfileMode(pxConfig *config);
+static bool donecleanupMode(pxConfig *config);
+static bool exportrunMode(pxConfig *config);
+static bool importrunMode(pxConfig *config);
+static bool addcameraMode(pxConfig *config);
+static bool getcameraMode(pxConfig *config);
+static bool listcamerasMode(pxConfig *config);
+
+static bool setxcstackRunState(pxConfig *config, psS64 xcstack_id, const char *state);
+static psS64 getCameraIDfromName(pxConfig *config, const char *camera);
+
+# define MODECASE(caseName, func) \
+    case caseName: \
+    if (!func(config)) { \
+        goto FAIL; \
+    } \
+    break;
+
+int main(int argc, char **argv)
+{
+    psLibInit(NULL);
+
+    pxConfig *config = xcstacktoolConfig(NULL, argc, argv);
+    if (!config) {
+        psError(PXTOOLS_ERR_CONFIG, false, "failed to configure");
+        goto FAIL;
+    }
+
+    switch (config->mode) {
+        MODECASE(XCSTACKTOOL_MODE_DEFINEBYQUERY,         definebyqueryMode);
+        MODECASE(XCSTACKTOOL_MODE_ADDBYQUERY,            addbyqueryMode);
+        MODECASE(XCSTACKTOOL_MODE_UPDATERUN,             updaterunMode);
+        MODECASE(XCSTACKTOOL_MODE_TOSUM,                 tosumMode);
+        MODECASE(XCSTACKTOOL_MODE_INPUTCAMERAS,          inputcamerasMode);
+        MODECASE(XCSTACKTOOL_MODE_INPUTSKYFILE,          inputskyfileMode);
+        MODECASE(XCSTACKTOOL_MODE_ADDSUMSKYFILE,         addsumskyfileMode);
+        MODECASE(XCSTACKTOOL_MODE_SUMSKYFILE,            sumskyfileMode);
+        MODECASE(XCSTACKTOOL_MODE_REVERTSUMSKYFILE,      revertsumskyfileMode);
+        MODECASE(XCSTACKTOOL_MODE_UPDATESUMSKYFILE,      updatesumskyfileMode);
+        MODECASE(XCSTACKTOOL_MODE_PENDINGCLEANUPRUN,     pendingcleanuprunMode);
+        MODECASE(XCSTACKTOOL_MODE_PENDINGCLEANUPSKYFILE, pendingcleanupskyfileMode);
+        MODECASE(XCSTACKTOOL_MODE_DONECLEANUP,           donecleanupMode);
+        MODECASE(XCSTACKTOOL_MODE_EXPORTRUN,             exportrunMode);
+        MODECASE(XCSTACKTOOL_MODE_IMPORTRUN,             importrunMode);
+        MODECASE(XCSTACKTOOL_MODE_ADDCAMERA,             addcameraMode);
+        MODECASE(XCSTACKTOOL_MODE_GETCAMERA,             getcameraMode);
+        MODECASE(XCSTACKTOOL_MODE_LISTCAMERAS,           listcamerasMode);
+        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);
+}
+
+// xcstack is only choosing input stacks.  options can include: 
+// camera, filter, skycell, tess, label, data_group, 
+static bool definebyqueryMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required options
+    PXOPT_LOOKUP_STR(workdir, config->args, "-set_workdir", true, false);
+
+    // The camera must be supplied as a string, then the camera_id is selected
+    // from the table xcstackCamera.
+    PXOPT_LOOKUP_STR(camera, config->args, "-xcamera", true, false);
+
+    // get this camera_id from the database:
+    psS64 camera_id = getCameraIDfromName(config, camera);
+    if (!camera_id) {
+      psError(PXTOOLS_ERR_SYS, false, "failed to find camera in database");
+      return false;
+    }
+
+    // define options for the output runs
+    PXOPT_LOOKUP_STR(label, config->args, "-set_label", false, false);
+    PXOPT_LOOKUP_STR(data_group, config->args, "-set_data_group", false, false);
+    PXOPT_LOOKUP_STR(dist_group, config->args, "-set_dist_group", false, false);
+    PXOPT_LOOKUP_STR(reduction, config->args, "-set_reduction", false, false);
+    PXOPT_LOOKUP_STR(note, config->args, "-set_note", false, false);
+    PXOPT_LOOKUP_TIME(registered, config->args, "-set_registered", false, false);
+
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_BOOL(pretend, config->args, "-pretend", false);
+
+    // we want to be able to queue the same input stacks for multiple output xcstackRuns,
+    // but we do not want to duplicate entries which already exist for the same
+    // label & data_group (or just data_group??).
+
+    // the possible input stacks are matched to already existing xcstacks with the same
+    // label & data_group and only ones which do NOT match (xcstack_id is NULL) are queued.
+
+    // In order to speed the query operations, the possible matched xcstacks are selected
+    // based on the tess_id, skycell_id, data_group restrictions supplied
+
+    // SUBSET TABLE SQL statement:
+
+    psMetadata *where_XC = psMetadataAlloc();
+
+    // also restrict the possible matched xcstackRuns based on these options:
+    pxAddLabelSearchArgs(config,  where_XC, "-select_tess_id",         "xcstackRun.tess_id", "==");
+    pxAddLabelSearchArgs(config,  where_XC, "-select_skycell_id",      "xcstackRun.skycell_id", "LIKE");
+
+    // generate the xcstackRun clause
+    if (!psListLength(where_XC->list)) {
+      psError(PXTOOLS_ERR_CONFIG, false, "search parameters are required (at least tess_id or skycell_id)");
+      psFree(where_XC);
+      return false;
+    }
+      
+    psString select_XC = pxDataGet("xcstacktool_definebyquery_select_XC.sql");
+    if (!select_XC) {
+      psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+      psFree(where_XC);
+      return false;
+    }
+      
+    psString whereClause_XC = psDBGenerateWhereConditionSQL(where_XC, NULL);
+    psStringAppend(&select_XC, " WHERE (xcstackInputSkyfile.camera_id = %d)\n AND %s", (int) camera_id, whereClause_XC);
+    psFree(whereClause_XC);
+
+    // add restrictions based on the output data_group and label
+    if (label)      { psStringAppend(&select_XC, "\n AND (xcstackRun.label      LIKE '%s')", label); }
+    if (data_group) { psStringAppend(&select_XC, "\n AND (xcstackRun.data_group LIKE '%s')", data_group); }
+
+    psFree(where_XC);
+
+    // MAIN SQL statement:
+
+    psMetadata *where = psMetadataAlloc();
+
+    // restrict the input stackRun based on these options:
+    pxAddLabelSearchArgs(config,  where, "-select_stack_id",           "stackRun.stack_id", "==");
+    pxAddLabelSearchArgs(config,  where, "-select_tess_id",            "stackRun.tess_id", "==");
+    pxAddLabelSearchArgs(config,  where, "-select_skycell_id",         "stackRun.skycell_id", "LIKE");
+
+    PXOPT_COPY_STR(config->args,  where, "-select_filter",             "stackRun.filter", "==");
+    pxAddLabelSearchArgs (config, where, "-select_label",              "stackRun.label", "LIKE");
+
+    PXOPT_COPY_F32(config->args,  where, "-select_good_frac_min",      "stackSumSkyfile.good_frac", ">=");
+    pxAddLabelSearchArgs(config,  where, "-select_data_group",         "stackRun.data_group", "LIKE");
+
+    if (!psListLength(where->list)) {
+      psError(PXTOOLS_ERR_CONFIG, false, "search parameters are required (at least tess_id or skycell_id)");
+      psFree(where);
+      return false;
+    }
+
+    psString select = pxDataGet("xcstacktool_definebyquery_select.sql");
+    if (!select) {
+      psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+      psFree(where);
+      return false;
+    }
+
+    // merge base select and select_XC
+    psStringAppend (&select, "(%s)\n AS xcstackRunData\n ON (stackRun.stack_id = xcstackRunData.stack_id)\n WHERE xcstack_id IS NULL", select_XC);
+    psFree (select_XC);
+
+    psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+    psStringAppend(&select, "\n AND %s", whereClause);
+    psFree(whereClause);
+    psFree(where);
+
+    // camera name is prepended to stackRun and stackSumSkyfile tables in query
+    if (!p_psDBRunQueryF(config->dbh, select, camera, camera)) {
+      psError(PS_ERR_UNKNOWN, false, "database error");
+      psFree(select);
+      return false;
+    }
+    psFree(select);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+      psErrorCode err = psErrorCodeLast();
+      switch (err) {
+	case PS_ERR_DB_CLIENT:
+	  psError(PXTOOLS_ERR_SYS, false, "database error");
+	case PS_ERR_DB_SERVER:
+	  psError(PXTOOLS_ERR_PROG, false, "database error");
+	default:
+	  psError(PXTOOLS_ERR_PROG, false, "unknown error");
+      }
+      return false;
+    }
+    if (!psArrayLength(output)) {
+      psWarning("xcstacktool: no rows found");
+      psFree(output);
+      return true;
+    }
+    if (pretend) {
+      // negative simple so the default is true
+      if (!ippdbPrintMetadatas(stdout, output, "xcstackSkycells", !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;
+    }
+
+    // used only if we catch an error
+    psString error_message = NULL;
+
+    // the query above selects stacks to be added to the xcstackRun
+    // for each stack, we need camera_id, stack_id, skycell_id, tess_id
+    psArray *list = psArrayAllocEmpty(16); // List of runs, to print
+    for (long i = 0; i < output->n; i++) {
+      psMetadata *row = output->data[i]; // Row from select
+      bool status;
+
+      bool got_error = false;
+
+      // pull out the skycell_id, tess_id, filter
+      psString skycell_id = psMetadataLookupStr(&status, row, "skycell_id");
+      if (!status) {
+	psStringAppend(&error_message, "skycell_id ");
+	got_error = true;
+      }
+
+      psString tess_id = psMetadataLookupStr(&status, row, "tess_id");
+      if (!status) {
+	psStringAppend(&error_message, "tess_id ");
+	got_error = true;
+      }
+
+      psString filter = psMetadataLookupStr(&status, row, "filter");
+      if (!status) {
+	psStringAppend(&error_message, "filter ");
+	got_error = true;
+      } 
+
+      psS64 stack_id = psMetadataLookupS64(&status, row, "stack_id");
+      if (!status) {
+	psStringAppend(&error_message, "skycell_id ");
+	got_error = true;
+      }
+
+      if (got_error) {
+	psError(PS_ERR_UNKNOWN, false, "failed to find: %s", error_message);
+	psFree(output);
+	psFree(list);
+	psFree(error_message);
+	if (!psDBRollback(config->dbh)) {
+	  psError(PS_ERR_UNKNOWN, false, "database error");
+	}
+	return false;
+      }
+
+      // create a new stackRun for this stack
+      xcstackRunRow *run = xcstackRunRowAlloc(
+					      0,                          // ID
+					      "wait",                     // state
+					      workdir,
+					      label,
+					      data_group ? data_group : label,
+					      dist_group,
+					      reduction,
+					      NULL, // should be 'registered'
+					      skycell_id,
+					      tess_id,
+					      filter,
+					      NULL, // software_ver : supplied by xcstackRunRowAlloc
+					      note);
+
+      if (!xcstackRunInsertObject(config->dbh, run)) {
+	if (!psDBRollback(config->dbh)) {
+	  psError(PS_ERR_UNKNOWN, false, "database error");
+	}
+	psError(PS_ERR_UNKNOWN, false, "database error (cannot insert xcstackRun entry)");
+	psFree(output);
+	psFree(list);
+	psFree(run);
+	psFree(error_message);
+	return false;
+      }
+
+      // figure out the new stack_id
+      psS64 xcstack_id = psDBLastInsertID(config->dbh);
+      run->xcstack_id = xcstack_id;
+
+      psArrayAdd(list, list->n, run);
+      psFree(run);
+
+      // now insert the xcstackInputSkyfile entry (only one allowed?)
+      xcstackInputSkyfileRow *inrow = xcstackInputSkyfileRowAlloc(run->xcstack_id, camera_id, stack_id);
+
+      if (!xcstackInputSkyfileInsertObject(config->dbh, inrow)) {
+	if (!psDBRollback(config->dbh)) {
+	  psError(PS_ERR_UNKNOWN, false, "database error");
+	}
+	psError(PS_ERR_UNKNOWN, false, "database error (cannot insert xcstackRun entry)");
+	psFree(output);
+	psFree(list);
+	psFree(inrow);
+	psFree(error_message);
+	return false;
+      }
+      psFree (inrow);
+    }
+    psFree(error_message);
+    psFree(output);
+
+    if (!psDBCommit(config->dbh)) {
+      psError(PS_ERR_UNKNOWN, false, "database error");
+      psFree(list);
+      return false;
+    }
+
+    if (!xcstackRunPrintObjects(stdout, list, !simple)) {
+      psError(PS_ERR_UNKNOWN, false, "failed to print object");
+      psFree(list);
+      return false;
+    }
+    psFree(list);
+    return true;
+}
+
+// This option selects stacks from the specified camera which match existing stacks listed
+// in xcstackRun (based on tess_id, skycell_id, filter) and identifies ones for which a
+// stack run for this camera is NOT listed in xcstackInputSkyfile. Since the filter names
+// for cameras may not match, the filter for the input camera and the xcstack filter must
+// both be specified
+static bool addbyqueryMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // The camera must be supplied as a string, then the camera_id is selected
+    // from the table xcstackCamera.
+    PXOPT_LOOKUP_STR(camera, config->args, "-xcamera", true, false);
+
+    // required options
+    PXOPT_LOOKUP_STR(infilter, config->args, "-select_instack_filter", true, false);
+    PXOPT_LOOKUP_STR(xcfilter, config->args, "-select_xcstack_filter", true, false);
+
+    // get this camera_id from the database:
+    psS64 camera_id = getCameraIDfromName(config, camera);
+    if (!camera_id) {
+	psError(PXTOOLS_ERR_SYS, false, "failed to find camera in database");
+	return false;
+    }
+
+    // output options (e.g., workdir, etc) for xcstack run are already defined so
+    // are not listed here
+
+    psMetadata *where = psMetadataAlloc();
+
+    // select based on properties of the input stacks
+    pxAddLabelSearchArgs (config, where, "-select_stack_id",           "stackRun.stack_id",   "==");
+    pxAddLabelSearchArgs (config, where, "-select_tess_id",            "stackRun.tess_id",    "LIKE");
+    pxAddLabelSearchArgs (config, where, "-select_skycell_id",         "stackRun.skycell_id", "LIKE");
+
+    // this forces at least one where condition to exist
+    PXOPT_COPY_F32(config->args,  where, "-select_good_frac_min",      "stackSumSkyfile.good_frac", ">=");
+
+    pxAddLabelSearchArgs (config, where, "-select_instack_label",      "stackRun.label",      "LIKE");
+    pxAddLabelSearchArgs (config, where, "-select_xcstack_label",      "xcstackRun.label",    "LIKE");
+
+    pxAddLabelSearchArgs(config,  where, "-select_instack_data_group", "stackRun.data_group", "LIKE");
+    pxAddLabelSearchArgs(config,  where, "-select_xcstack_data_group", "xcstackRun.data_group", "LIKE");
+    
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+    PXOPT_LOOKUP_BOOL(pretend, config->args, "-pretend", false);
+
+    if (!psListLength(where->list)) {
+	psError(PXTOOLS_ERR_CONFIG, false, "search parameters are required");
+	psFree(where);
+	return false;
+    }
+
+    psString select = pxDataGet("xcstacktool_addbyquery_select.sql");
+    if (!select) {
+	psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+	psFree(where);
+	return false;
+    }
+
+    // we are always guaranteed to have a where condition because of the -select_good_frac_min above
+    psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+    psFree(where);
+
+    if (!p_psDBRunQueryF(config->dbh, select, camera, camera, infilter, xcfilter, whereClause, (int) camera_id)) {
+	psError(PS_ERR_UNKNOWN, false, "database error");
+	psFree(select);
+	psFree(whereClause);
+	return false;
+    }
+    psFree(whereClause);
+    psFree(select);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+	  case PS_ERR_DB_CLIENT:
+	    psError(PXTOOLS_ERR_SYS, false, "database error");
+	  case PS_ERR_DB_SERVER:
+	    psError(PXTOOLS_ERR_PROG, false, "database error");
+	  default:
+	    psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psWarning("xcstacktool: no rows found");
+        psFree(output);
+        return true;
+    }
+    if (pretend) {
+        // negative simple so the default is true
+        if (!ippdbPrintMetadatas(stdout, output, "xcstackSkycells", !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;
+    }
+
+    // used only if we catch an error
+    psString error_message = NULL;
+
+    // the query above selects stacks to be added to the xcstackRun
+    // for each stack, we need camera_id, stack_id, skycell_id, tess_id
+    for (long i = 0; i < output->n; i++) {
+        psMetadata *row = output->data[i]; // Row from select
+        bool status;
+
+	bool got_error = false;
+
+        psS64 xcstack_id = psMetadataLookupS64(&status, row, "xcstack_id");
+        if (!status) {
+	    psStringAppend(&error_message, "xcstack_id ");
+	    got_error = true;
+        }
+
+        psS64 stack_id = psMetadataLookupS64(&status, row, "stack_id");
+        if (!status) {
+	    psStringAppend(&error_message, "stack_id ");
+	    got_error = true;
+        }
+
+	if (got_error) {
+            psError(PS_ERR_UNKNOWN, false, "failed to find: %s", error_message);
+            psFree(output);
+            psFree(error_message);
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            return false;
+        }
+
+	// now insert the xcstackInputSkyfile entry (only one allowed?)
+	xcstackInputSkyfileRow *inrow = xcstackInputSkyfileRowAlloc(xcstack_id, camera_id, stack_id);
+
+        if (!xcstackInputSkyfileInsertObject(config->dbh, inrow)) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "database error (cannot insert xcstackRun entry)");
+            psFree(output);
+            psFree(inrow);
+            psFree(error_message);
+            return false;
+        }
+	psFree (inrow);
+    }
+    psFree(error_message);
+    psFree(output);
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    return true;
+}
+
+static bool updaterunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-xcstack_id",  "xcstackRun.xcstack_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-label",       "xcstackRun.label",      "==");
+    PXOPT_COPY_STR(config->args, where, "-data_group",  "xcstackRun.data_group", "==");
+    PXOPT_COPY_STR(config->args, where, "-state",       "xcstackRun.state",      "==");
+
+    PXOPT_LOOKUP_S16(fault, config->args, "-fault", false, false);
+    
+    if (!psListLength(where->list)) {
+        psFree(where);
+        psError(PXTOOLS_ERR_CONFIG, false, "search parameters are required");
+        return false;
+    }
+
+    psString query = psStringCopy ("UPDATE xcstackRun");
+
+    if (fault) {
+        psStringAppend(&query, " JOIN xcstackSumSkyfile USING(xcstack_id)");
+        PXOPT_COPY_S16(config->args, where, "-fault", "xcstackSumSkyfile.fault", "==");
+    }
+    
+    // pxUpdateRun gets parameters from config->args and updates
+    bool result = pxUpdateRun(config, where, &query, "xcstackRun", "xcstack_id", "xcstackSumSkyfile", true, false);
+
+    psFree(query);
+    psFree(where);
+
+    return result;
+}
+
+static bool tosumMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-xcstack_id", "xcstack_id", "==");
+    pxAddLabelSearchArgs (config, where, "-label", "xcstackRun.label", "==");
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("xcstacktool_tosum.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    psString whereClause = psStringCopy(""); // WHERE conditions to add
+    if (psListLength(where->list)) {
+        psString new = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&whereClause, "\n AND %s", new);
+        psFree(new);
+    }
+    psFree(where);
+
+    psStringAppend(&query, "\nORDER by priority DESC, xcstack_id");
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQueryF(config->dbh, query, whereClause)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("xcstacktool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "xcstackSumSkyfile", !simple)) {
+	psError(PS_ERR_UNKNOWN, false, "failed to print array");
+	psFree(output);
+	return false;
+    }
+
+    psFree(output);
+    return true;
+}
+
+// return the input skycells needed to make this stack
+// unlike the single camera stacks, this mode is called
+// separately for each xcamera
+static bool inputcamerasMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // xcstack_id is required, need to add to the where explicitly
+    PXOPT_LOOKUP_S64(xcstack_id, config->args, "-xcstack_id", true, false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = NULL;
+    psStringAppend (&query, "SELECT camera from xcstackInputSkyfile JOIN xcstackCamera USING (camera_id) WHERE (xcstack_id = %"PRId64")", xcstack_id);
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS,  false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("xcstacktool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (!ippdbPrintMetadatas(stdout, output, "xcstackInputCameras", !simple)) {
+	psError(PS_ERR_UNKNOWN, false, "failed to print array");
+	psFree(output);
+	return false;
+    }
+
+    psFree(output);
+    return true;
+}
+
+// return the input skycells needed to make this stack
+// unlike the single camera stacks, this mode is called
+// separately for each xcamera
+static bool inputskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // xcstack_id is required, need to add to the where explicitly
+    PXOPT_LOOKUP_S64(xcstack_id, config->args, "-xcstack_id", true, false);
+
+    // The camera must be supplied as a string, then the camera_id is selected
+    // from the table xcstackCamera.
+    PXOPT_LOOKUP_STR(camera,    config->args, "-xcamera", true, false);  
+
+    // get this camera_id from the database:
+    psS64 camera_id = getCameraIDfromName(config, camera);
+    if (!camera_id) {
+	psError(PXTOOLS_ERR_SYS, false, "failed to find camera in database");
+	return false;
+    }
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("xcstacktool_inputskyfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // treat limit == 0 as "no limit"
+    if (limit) {
+        psString limitString = psDBGenerateLimitSQL(limit);
+        psStringAppend(&query, " %s", limitString);
+        psFree(limitString);
+    }
+
+    if (!p_psDBRunQueryF(config->dbh, query, camera, camera_id, xcstack_id)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(query);
+        return false;
+    }
+    psFree(query);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS,  false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("xcstacktool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (!ippdbPrintMetadatas(stdout, output, "xcstackInputSkyfile", !simple)) {
+	psError(PS_ERR_UNKNOWN, false, "failed to print array");
+	psFree(output);
+	return false;
+    }
+
+    psFree(output);
+    return true;
+}
+
+static bool addsumskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    // required
+    PXOPT_LOOKUP_S64(xcstack_id,    config->args, "-xcstack_id",    true, false);
+
+    // optional
+    PXOPT_LOOKUP_STR(path_base,     config->args, "-path_base",     false, false);
+    PXOPT_LOOKUP_F64(bg,            config->args, "-bg",            false, false);
+    PXOPT_LOOKUP_F64(bg_stdev,      config->args, "-bg_stdev",      false, false);
+    PXOPT_LOOKUP_F32(dtime_stack,   config->args, "-dtime_stack",   false, false);
+    PXOPT_LOOKUP_F32(dtime_script,  config->args, "-dtime_script",  false, false);
+    PXOPT_LOOKUP_STR(hostname,      config->args, "-hostname",      false, false);
+    PXOPT_LOOKUP_F32(good_frac,     config->args, "-good_frac",     false, false);
+
+    PXOPT_LOOKUP_STR(ver_pslib,     config->args, "-ver_pslib",     false, false);
+    PXOPT_LOOKUP_STR(ver_psmodules, config->args, "-ver_psmodules", false, false);
+    PXOPT_LOOKUP_STR(ver_ppstats,   config->args, "-ver_ppstats",   false, false);
+    PXOPT_LOOKUP_STR(ver_ppstack,   config->args, "-ver_ppstack",   false, false);
+    
+    psString software_ver = NULL;
+    if ((ver_pslib)&&(ver_psmodules)) {
+      software_ver = pxMergeCodeVersions(ver_pslib,ver_psmodules);
+    }
+    if (ver_ppstats) {
+      software_ver = pxMergeCodeVersions(software_ver,ver_ppstats);
+    }
+    if (ver_ppstack) {
+      software_ver = pxMergeCodeVersions(software_ver,ver_ppstack);
+    }
+
+    // default values
+    PXOPT_LOOKUP_S16(fault,   config->args, "-fault",   false, false);
+    PXOPT_LOOKUP_S16(quality, config->args, "-quality", false, false);
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!xcstackSumSkyfileInsert(config->dbh,
+				 xcstack_id,
+				 path_base,
+				 bg,
+				 bg_stdev,
+				 dtime_stack,
+				 dtime_script,
+				 hostname,
+				 good_frac,
+				 fault,
+				 software_ver,
+				 quality
+          )) {
+        if (!psDBRollback(config->dbh)) {
+            psError(PS_ERR_UNKNOWN, false, "database error");
+        }
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (fault == 0) {
+	// on success, set to full
+        if (!setxcstackRunState(config, xcstack_id, "full")) {
+            if (!psDBRollback(config->dbh)) {
+                psError(PS_ERR_UNKNOWN, false, "database error");
+            }
+            psError(PS_ERR_UNKNOWN, false, "failed to change xcstackRun's state");
+            return false;
+        }
+    }
+
+    // point of no return
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool sumskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-xcstack_id", "xcstackRun.xcstack_id", "==");
+    PXOPT_COPY_STR(config->args, where, "-state",      "xcstackRun.state", "==");
+    PXOPT_COPY_STR(config->args, where, "-tess_id",    "xcstackRun.tess_id", "LIKE");
+    PXOPT_COPY_STR(config->args, where, "-skycell_id", "xcstackRun.skycell_id", "LIKE");
+    PXOPT_COPY_STR(config->args, where, "-filter",     "xcstackRun.filter", "LIKE");
+    pxAddLabelSearchArgs(config, where, "-label",      "xcstackRun.label", "LIKE");
+    pxAddLabelSearchArgs(config, where, "-data_group", "xcstackRun.data_group", "LIKE");
+    PXOPT_COPY_S16(config->args, where, "-fault",      "xcstackSumSkyfile.fault", "==");
+
+    PXOPT_LOOKUP_BOOL(all, config->args, "-all", false);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psString query = pxDataGet("xcstacktool_sumskyfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&query, " WHERE %s", whereClause);
+        psFree(whereClause);
+    } else if (!all) {
+        psError(PXTOOLS_ERR_CONFIG, true, "search parameters or -all are required");
+        return false;
+    }
+
+    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);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("xcstacktool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    if (!ippdbPrintMetadatas(stdout, output, "xcstackSumSkyfile", !simple)) {
+	psError(PS_ERR_UNKNOWN, false, "failed to print array");
+	psFree(output);
+	return false;
+    }
+
+    psFree(output);
+    return true;
+}
+
+static bool revertsumskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_S64(config->args, where, "-xcstack_id", "xcstackSumSkyfile.xcstack_id", "==");
+    pxAddLabelSearchArgs(config, where, "-label",      "xcstackRun.label", "==");
+    PXOPT_COPY_S16(config->args, where, "-fault",      "xcstackSumSkyfile.fault", "==");
+
+    if (!psListLength(where->list)) {
+        psFree(where);
+        psError(PXTOOLS_ERR_CONFIG, false, "search parameters are required");
+        return false;
+    }
+
+    // Delete product
+    psString delete = pxDataGet("xcstacktool_revertsumskyfile_delete.sql");
+    if (!delete) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    if (psListLength(where->list)) {
+        psString whereClause = psDBGenerateWhereConditionSQL(where, NULL);
+        psStringAppend(&delete, " AND %s", whereClause);
+        psFree(whereClause);
+    }
+
+    if (!p_psDBRunQuery(config->dbh, delete)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        psFree(delete);
+        psFree(where);
+        return false;
+    }
+    psFree(delete);
+
+    int numRows = psDBAffectedRows(config->dbh); // Number of row affected
+    psLogMsg("xcstacktool", PS_LOG_INFO, "Deleted %d rows", numRows);
+
+    psFree(where);
+    return true;
+}
+
+// XXX fault can be set to any value but quality cannot be set to 0
+static bool updatesumskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    PXOPT_LOOKUP_S16(fault,      config->args, "-fault",       true,  false);
+    PXOPT_LOOKUP_S64(xcstack_id, config->args, "-xcstack_id",  true,  false);
+    PXOPT_LOOKUP_S16(quality,    config->args, "-set_quality", false, false);
+
+    psMetadata *where = psMetadataAlloc();
+    psMetadataAddS64 (where, PS_LIST_TAIL, "xcstack_id", 0, "==", xcstack_id);
+
+    if (!pxSetFaultCode(config->dbh, "xcstackSumSkyfile", where, fault, quality)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to set set fault flag");
+        psFree (where);
+        return false;
+    }
+    psFree (where);
+
+    return true;
+}
+
+static bool pendingcleanuprunMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psMetadata *where = psMetadataAlloc();
+    pxAddLabelSearchArgs (config, where, "-label", "xcstackRun.label", "==");
+    PXOPT_COPY_S64(config->args, where, "-xcstack_id", "xcstackRun.xcstack_id", "==");
+
+    psString query = pxDataGet("xcstacktool_pendingcleanuprun.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        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);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("xcstacktool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "xcstackPendingCleanupRun", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+    return true;
+}
+
+static bool pendingcleanupskyfileMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    PXOPT_LOOKUP_S64(xcstack_id, config->args, "-xcstack_id", true, false);
+    PXOPT_LOOKUP_U64(limit,      config->args, "-limit",      false, false);
+    PXOPT_LOOKUP_BOOL(simple,    config->args, "-simple",     false);
+
+    psMetadata *where = psMetadataAlloc();
+    psMetadataAddS64 (where, PS_LIST_TAIL, "xcstack_id", 0, "==", xcstack_id);
+    pxAddLabelSearchArgs (config, where, "-label", "xcstackRun.label", "==");
+
+    psString query = pxDataGet("xcstacktool_pendingcleanupskyfile.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        return false;
+    }
+
+    // should always be true: we require xcstack_id in the WHERE
+    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);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("xcstacktool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "xcstackPendingCleanupSkyfile", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+
+// XXX how is this mode used in practice?
+static bool donecleanupMode(pxConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    PXOPT_LOOKUP_U64(limit, config->args, "-limit", false, false);
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    psMetadata *where = psMetadataAlloc();
+    PXOPT_COPY_STR(config->args, where, "-label", "label", "==");
+
+    psString query = pxDataGet("xcstacktool_donecleanup.sql");
+    if (!query) {
+        psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+        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);
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+    if (!psArrayLength(output)) {
+        psTrace("xcstacktool", PS_LOG_INFO, "no rows found");
+        psFree(output);
+        return true;
+    }
+
+    // negative simple so the default is true
+    if (!ippdbPrintMetadatas(stdout, output, "xcstackDoneCleanup", !simple)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to print array");
+        psFree(output);
+        return false;
+    }
+
+    psFree(output);
+
+    return true;
+}
+
+static bool exportrunMode(pxConfig *config)
+{
+    typedef struct ExportTable {
+	char tableName[80];
+	char tableSQL[80];
+    } ExportTable;
+
+    int numExportTables = 3;
+
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    PXOPT_LOOKUP_S64(xcstack_id, config->args, "-xcstack_id", true,  false);
+    PXOPT_LOOKUP_STR(outfile, config->args, "-outfile", true,  false);
+    PXOPT_LOOKUP_BOOL(clean, config->args, "-clean", false);
+    PXOPT_LOOKUP_U64(limit,   config->args, "-limit",   false, false);
+
+    FILE *f = fopen (outfile, "w");
+    if (f == NULL) {
+	psError(PS_ERR_UNKNOWN, false, "failed to open output file");
+	return false;
+    }
+
+    if (!pxExportVersion(config, f)) {
+	psError(PS_ERR_UNKNOWN, false, "failed to write dbversion output file");
+	return false;
+    }
+    psMetadata *where = psMetadataAlloc();
+    psMetadataAddS64 (where, PS_LIST_TAIL, "xcstack_id", 0, "==", xcstack_id);
+
+    ExportTable tables [] = {
+	{"xcstackRun",          "SELECT xcstackRun.*          FROM xcstackRun"},
+	{"xcstackInputSkyfile", "SELECT xcstackInputSkyfile.* FROM xcstackInputSkyfile"},
+	{"xcstackSumSkyfile",   "SELECT xcstackSumSkyfile.*   FROM xcstackSumSkyfile"},
+    };
+
+    for (int i = 0; i < numExportTables; i++) {
+	psString query = psStringCopy(tables[i].tableSQL);
+
+	if (where && psListLength(where->list)) {
+	    psString whereClause = psDBGenerateWhereSQL(where, NULL);
+	    psStringAppend(&query, " %s", whereClause);
+	    psFree(whereClause);
+	}
+
+	// treat limit == 0 as "no limit"
+	if (limit) {
+	    psString limitString = psDBGenerateLimitSQL(limit);
+	    psStringAppend(&query, " %s", limitString);
+	    psFree(limitString);
+	}
+
+	if (!p_psDBRunQuery(config->dbh, query)) {
+	    psError(PS_ERR_UNKNOWN, false, "database error");
+	    psFree(query);
+	    return false;
+	}
+	psFree(query);
+
+	psArray *output = p_psDBFetchResult(config->dbh);
+	if (!output) {
+	    psError(PS_ERR_UNKNOWN, false, "database error");
+	    return false;
+	}
+	if (!psArrayLength(output)) {
+	    psError(PS_ERR_UNKNOWN, true, "no rows found");
+	    psFree(output);
+	    return false;
+	}
+
+	if (clean) {
+	    if (!strcmp(tables[i].tableName, "xcstackRun")) {
+		if (!pxSetStateCleaned("xcstackRun", "state", output)) {
+		    psFree(output);
+		    psError(PS_ERR_UNKNOWN, false, "pxSetStateClean failed for table %s",  tables[i].tableName);
+		    return false;
+		}
+	    }
+	}
+
+	// we must write the export table in non-simple (true) format
+	if (!ippdbPrintMetadatas(f, output, tables[i].tableName, true)) {
+	    psError(PS_ERR_UNKNOWN, false, "failed to print array");
+	    psFree(output);
+	    return false;
+	}
+
+	psFree(output);
+    }
+
+    fclose (f);
+
+    return true;
+}
+
+static bool importrunMode(pxConfig *config)
+{
+  unsigned int nFail;
+
+  int numImportTables = 2;
+
+  char tables[2][80] = {"xcstackInputSkyfile", "xcstackSumSkyfile"};
+
+  PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+  PXOPT_LOOKUP_STR(infile, config->args, "-infile", true,  false);
+
+  psMetadata *input = psMetadataConfigRead (NULL, &nFail, infile, false);
+
+  if (!pxCheckImportVersion(config, input)) {
+      psError(PS_ERR_UNKNOWN, false, "pxCheckImportVersion failed");
+      return false;
+  }
+  psMetadataItem *item = psMetadataLookup (input, "xcstackRun");
+  psAssert (item, "entry not in input?");
+  psAssert (item->type == PS_DATA_METADATA_MULTI, "entry not multi?");
+
+  psMetadataItem *entry = psListGet (item->data.list, 0);
+  assert (entry);
+  assert (entry->type == PS_DATA_METADATA);
+  xcstackRunRow *xcstackRun = xcstackRunObjectFromMetadata (entry->data.md);
+  xcstackRunInsertObject (config->dbh, xcstackRun);
+
+  for (int i = 0; i < numImportTables; i++) {
+    psMetadataItem *item = psMetadataLookup (input, tables[i]);
+    psAssert (item, "entry not in input?");
+    psAssert (item->type == PS_DATA_METADATA_MULTI, "entry not multi?");
+
+    switch (i) {
+      case 0:
+        for (int i = 0; i < item->data.list->n; i++) {
+          entry = psListGet (item->data.list, i);
+          assert (entry);
+          assert (entry->type == PS_DATA_METADATA);
+          xcstackInputSkyfileRow *xcstackInputSkyfile = xcstackInputSkyfileObjectFromMetadata (entry->data.md);
+          xcstackInputSkyfileInsertObject (config->dbh, xcstackInputSkyfile);
+        }
+        break;
+
+      case 1:
+        for (int i = 0; i < item->data.list->n; i++) {
+          entry = psListGet (item->data.list, i);
+          assert (entry);
+          assert (entry->type == PS_DATA_METADATA);
+          xcstackSumSkyfileRow *xcstackSumSkyfile = xcstackSumSkyfileObjectFromMetadata (entry->data.md);
+          xcstackSumSkyfileInsertObject (config->dbh, xcstackSumSkyfile);
+        }
+        break;
+    }
+  }
+
+  return true;
+}
+
+static bool addcameraMode(pxConfig *config) {
+
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    PXOPT_LOOKUP_STR(camera, config->args, "-xcamera", true,  false);
+
+    psString query = pxDataGet("xcstacktool_addcamera.sql");
+    if (!query) {
+	psError(PXTOOLS_ERR_SYS, false, "failed to retreive SQL statement");
+	return false;
+    }
+
+    psStringSubstitute(&query, camera, "@CAMERA@");
+
+    if (!psDBTransaction(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    if (!p_psDBRunQuery(config->dbh, query)) {
+	psError(PS_ERR_UNKNOWN,false, "database error");
+	return(NULL);
+    }
+
+    int camera_id = psDBLastInsertID(config->dbh);
+    psLogMsg("stacktool", PS_LOG_INFO, "Added new camera %s (ID = %d)", camera, camera_id);
+
+    if (!psDBCommit(config->dbh)) {
+        psError(PS_ERR_UNKNOWN, false, "database error");
+        return false;
+    }
+
+    return true;
+}
+
+static bool getcameraMode(pxConfig *config) {
+
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    PXOPT_LOOKUP_STR(camera, config->args, "-xcamera", true,  false);
+
+    psS64 camera_id = getCameraIDfromName(config, camera);
+
+    psLogMsg("stacktool", PS_LOG_INFO, "Camera %s = %d", camera, (int) camera_id);
+
+    return true;
+}
+
+static bool listcamerasMode(pxConfig *config) {
+
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    PXOPT_LOOKUP_BOOL(simple, config->args, "-simple", false);
+
+    char *query = "SELECT camera, camera_id FROM xcstackCamera";
+    if (!p_psDBRunQuery(config->dbh, query)) {
+	psError(PS_ERR_UNKNOWN, false, "failed to select cameras");
+        return 0;
+    }
+    
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+	  case PS_ERR_DB_CLIENT:
+	    psError(PXTOOLS_ERR_SYS, false, "database error");
+	  case PS_ERR_DB_SERVER:
+	    psError(PXTOOLS_ERR_PROG, false, "database error");
+	  default:
+	    psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+        return false;
+    }
+
+    // we expect a value camera_id as a result
+    if (!psArrayLength(output)) {
+        psWarning("xcstacktool: no cameras found");
+        psFree(output);
+        return false;
+    }
+    
+    if (!ippdbPrintMetadatas(stdout, output, "xcstackCamera", !simple)) {
+	psError(PS_ERR_UNKNOWN, false, "failed to print array");
+	psFree(output);
+	return false;
+    }
+
+# if (0)
+    // alternative
+    for (long i = 0; i < output->n; i++) {
+        psMetadata *row = output->data[i]; // Row from select
+        bool status;
+	
+	// pull out the skycell_id, tess_id, filter
+	psS64 camera_id = psMetadataLookupS64(&status, row, "camera_id");
+	if (!status) {
+	    psAbort("xcstacktool: camera_id not found in query result?");
+	    psFree(output);
+	    return false;
+	}
+	
+	// pull out the skycell_id, tess_id, filter
+	psString camera_name = psMetadataLookupStr(&status, row, "camera");
+	if (!status) {
+	    psAbort("xcstacktool: camera name not found in query result?");
+	    psFree(output);
+	    return false;
+	}
+	
+	psLogMsg("stacktool", PS_LOG_INFO, "Camera %s = %d", camera_name, (int) camera_id);
+    }
+# endif
+    return true;
+}
+
+static bool setxcstackRunState(pxConfig *config, psS64 xcstack_id, const char *state)
+{
+    PS_ASSERT_PTR_NON_NULL(state, false);
+
+    // check that state is a valid string value
+    if (!pxIsValidState(state)) {
+        psError(PS_ERR_UNKNOWN, false, "invalid xcstackRun state: %s", state);
+        return false;
+    }
+
+    char *query = "UPDATE xcstackRun SET state = '%s' WHERE xcstack_id = %"PRId64;
+    if (!p_psDBRunQueryF(config->dbh, query, state, xcstack_id)) {
+        psError(PS_ERR_UNKNOWN, false,
+                "failed to change state for xcstack_id %"PRId64, xcstack_id);
+        return false;
+    }
+
+    return true;
+}
+
+// This function returns the camera_id associated with the camera name.
+// We expect the camera to be defined, so an invalid name or a missing camera
+// results in a 0 return value which should be treated as an error.
+static psS64 getCameraIDfromName(pxConfig *config, const char *camera)
+{
+    PS_ASSERT_PTR_NON_NULL(camera, false);
+
+    char *query = "SELECT camera_id FROM xcstackCamera WHERE camera = '%s'";
+    if (!p_psDBRunQueryF(config->dbh, query, camera)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to select camera_id for camera %s", camera);
+        return 0;
+    }
+
+    psArray *output = p_psDBFetchResult(config->dbh);
+    if (!output) {
+        psErrorCode err = psErrorCodeLast();
+        switch (err) {
+            case PS_ERR_DB_CLIENT:
+                psError(PXTOOLS_ERR_SYS, false, "database error");
+            case PS_ERR_DB_SERVER:
+                psError(PXTOOLS_ERR_PROG, false, "database error");
+            default:
+                psError(PXTOOLS_ERR_PROG, false, "unknown error");
+        }
+        return false;
+    }
+
+    // we expect a value camera_id as a result
+    if (!psArrayLength(output)) {
+        psWarning("xcstacktool: no rows found");
+        psFree(output);
+        return false;
+    }
+
+    // we expect a single camera_id as a result
+    if (psArrayLength(output) > 1) {
+        psWarning("xcstacktool: too many camera?");
+        psFree(output);
+        return false;
+    }
+
+    // the query above selects stacks to be added to the xcstackRun
+    // for each stack, we need camera_id, stack_id, skycell_id, tess_id
+    psMetadata *row = output->data[0]; // Row from select
+    bool status;
+
+    // pull out the skycell_id, tess_id, filter
+    psS64 camera_id = psMetadataLookupS64(&status, row, "camera_id");
+    if (!status) {
+        psAbort("xcstacktool: camera_id not found in query result?");
+        psFree(output);
+        return false;
+    }
+
+    return camera_id;
+}
+
+
Index: trunk/ippTools/src/xcstacktool.h
===================================================================
--- trunk/ippTools/src/xcstacktool.h	(revision 42837)
+++ trunk/ippTools/src/xcstacktool.h	(revision 42837)
@@ -0,0 +1,49 @@
+/*
+ * xcstacktool.h
+ *
+ * Copyright (C) 2007  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef XCSTACKTOOL_H
+#define XCSTACKTOOL_H 1
+
+#include "pxtools.h"
+
+typedef enum {
+    XCSTACKTOOL_MODE_NONE           = 0x0,
+    XCSTACKTOOL_MODE_DEFINEBYQUERY,
+    XCSTACKTOOL_MODE_ADDBYQUERY,
+    XCSTACKTOOL_MODE_UPDATERUN,
+    XCSTACKTOOL_MODE_TOSUM,
+    XCSTACKTOOL_MODE_INPUTCAMERAS,
+    XCSTACKTOOL_MODE_INPUTSKYFILE,
+    XCSTACKTOOL_MODE_ADDSUMSKYFILE,
+    XCSTACKTOOL_MODE_SUMSKYFILE,
+    XCSTACKTOOL_MODE_REVERTSUMSKYFILE,
+    XCSTACKTOOL_MODE_PENDINGCLEANUPRUN,
+    XCSTACKTOOL_MODE_PENDINGCLEANUPSKYFILE,
+    XCSTACKTOOL_MODE_DONECLEANUP,
+    XCSTACKTOOL_MODE_UPDATESUMSKYFILE,
+    XCSTACKTOOL_MODE_EXPORTRUN,
+    XCSTACKTOOL_MODE_IMPORTRUN,
+    XCSTACKTOOL_MODE_ADDCAMERA,
+    XCSTACKTOOL_MODE_GETCAMERA,
+    XCSTACKTOOL_MODE_LISTCAMERAS
+} xcstacktoolMode;
+
+pxConfig *xcstacktoolConfig(pxConfig *config, int argc, char **argv);
+
+#endif // XCSTACKTOOL_H
Index: trunk/ippTools/src/xcstacktoolConfig.c
===================================================================
--- trunk/ippTools/src/xcstacktoolConfig.c	(revision 42837)
+++ trunk/ippTools/src/xcstacktoolConfig.c	(revision 42837)
@@ -0,0 +1,259 @@
+/*
+ * xcstacktoolConfig.c
+ *
+ * Copyright (C) 2007-2008  Joshua Hoblitt
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * program; see the file COPYING. If not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <psmodules.h>
+
+#include "pxtools.h"
+#include "xcstacktool.h"
+
+pxConfig *xcstacktoolConfig(pxConfig *config, int argc, char **argv)
+{
+    if (!config) {
+        config = pxConfigAlloc();
+    }
+
+    pmConfigReadParamsSet(false);
+
+    // setup site config
+    config->modules = pmConfigRead(&argc, argv, NULL);
+    if (!config->modules) {
+        psError(psErrorCodeLast(), 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, "-xcamera",           0, "camera source of input stacks", 0);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-set_workdir",       0, "define workdir (required)", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-set_label",         0, "define label", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-set_data_group",    0, "define data group", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-set_dist_group",    0, "define dist group", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-set_note",          0, "define note", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-set_reduction",     0, "define reduction", NULL);
+    psMetadataAddTime(definebyqueryArgs, PS_LIST_TAIL, "-set_registered",    0, "time detrend run was registered", now);
+    psMetadataAddS64(definebyqueryArgs,  PS_LIST_TAIL, "-select_stack_id",   PS_META_DUPLICATE_OK, "search for stack_id", 0);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-select_tess_id",    PS_META_DUPLICATE_OK, "search for tess_id", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-select_skycell_id", PS_META_DUPLICATE_OK, "search for skycell_id (LIKE comparison, multiple ok)", NULL);
+    psMetadataAddF32(definebyqueryArgs,  PS_LIST_TAIL, "-select_good_frac_min", 0, "define min good_frac", 0.0);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-select_filter",     0, "search by filter", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-select_label",      PS_META_DUPLICATE_OK, "search by warpRun label (LIKE comparison, multiple OK)", NULL);
+    psMetadataAddStr(definebyqueryArgs,  PS_LIST_TAIL, "-select_data_group", PS_META_DUPLICATE_OK , "search by warpRun data_group (LIKE comparison, multiple ok)", NULL);
+
+    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);
+
+    // -addbyquery
+    psMetadata *addbyqueryArgs = psMetadataAlloc();
+    psMetadataAddStr(addbyqueryArgs,  PS_LIST_TAIL, "-xcamera",                   0, "camera source of input stacks", 0);
+    psMetadataAddStr(addbyqueryArgs,  PS_LIST_TAIL, "-select_instack_filter",     0, "search by input stack filter", NULL);
+    psMetadataAddStr(addbyqueryArgs,  PS_LIST_TAIL, "-select_xcstack_filter",     0, "search by xcstack filter", NULL);
+    psMetadataAddS64(addbyqueryArgs,  PS_LIST_TAIL, "-select_stack_id",           PS_META_DUPLICATE_OK, "search for stack_id", 0);
+    psMetadataAddStr(addbyqueryArgs,  PS_LIST_TAIL, "-select_tess_id",            PS_META_DUPLICATE_OK, "search for tess_id", NULL);
+    psMetadataAddStr(addbyqueryArgs,  PS_LIST_TAIL, "-select_skycell_id",         PS_META_DUPLICATE_OK, "search for skycell_id (LIKE comparison, multiple ok)", NULL);
+    psMetadataAddF32(addbyqueryArgs,  PS_LIST_TAIL, "-select_good_frac_min",      0, "define min good_frac", 0.0);
+    psMetadataAddStr(addbyqueryArgs,  PS_LIST_TAIL, "-select_instack_label",      PS_META_DUPLICATE_OK, "search by label for input stack (LIKE comparison, multiple OK)", NULL);
+    psMetadataAddStr(addbyqueryArgs,  PS_LIST_TAIL, "-select_xcstack_label",      PS_META_DUPLICATE_OK, "search by label for xcstack (LIKE comparison, multiple OK)", NULL);
+    psMetadataAddStr(addbyqueryArgs,  PS_LIST_TAIL, "-select_instack_data_group", PS_META_DUPLICATE_OK , "search by data_group of input stack (LIKE comparison, multiple ok)", NULL);
+    psMetadataAddStr(addbyqueryArgs,  PS_LIST_TAIL, "-select_xcstack_data_group", PS_META_DUPLICATE_OK , "search by data_group of xcstack (LIKE comparison, multiple ok)", NULL);
+    psMetadataAddBool(addbyqueryArgs, PS_LIST_TAIL, "-pretend",  0, "do not actually modify the database", false);
+    psMetadataAddBool(addbyqueryArgs, PS_LIST_TAIL, "-simple", 0, "use the simple output format", false);
+
+    // -updaterun
+    psMetadata *updaterunArgs = psMetadataAlloc();
+    psMetadataAddS64(updaterunArgs, PS_LIST_TAIL, "-xcstack_id", 0,         "search by stack ID", 0);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-label", 0,            "search by label", 0);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-data_group", 0,       "search by data_group", 0);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-state", 0,            "search by state", NULL);
+    psMetadataAddS16(updaterunArgs, PS_LIST_TAIL, "-fault",  0,           "search by fault code", 0);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_label", 0,        "define new value for label", 0);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_state", 0,        "define new state", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_data_group", 0,   "define new data_group", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_dist_group", 0,   "define new dist_group", NULL);
+    psMetadataAddStr(updaterunArgs, PS_LIST_TAIL, "-set_note", 0,         "define new note", NULL);
+
+    psMetadataAddBool(updaterunArgs, PS_LIST_TAIL, "-pretend",  0, "show query but do not actually modify the database", false);
+
+    // -tosum
+    psMetadata *tosumArgs = psMetadataAlloc();
+    psMetadataAddS64(tosumArgs, PS_LIST_TAIL, "-xcstack_id", 0,            "search by xcstack ID", 0);
+    psMetadataAddStr(tosumArgs, PS_LIST_TAIL, "-label", PS_META_DUPLICATE_OK, "search by label", NULL);
+    psMetadataAddU64(tosumArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(tosumArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -inputcameras
+    psMetadata *inputcamerasArgs = psMetadataAlloc();
+    psMetadataAddS64 (inputcamerasArgs, PS_LIST_TAIL, "-xcstack_id", 0,        "search by xcstack ID (required)", 0);
+    psMetadataAddU64 (inputcamerasArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(inputcamerasArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+
+    // -inputskyfile: xcstack_id and camera_id are both required
+    psMetadata *inputskyfileArgs = psMetadataAlloc();
+    psMetadataAddS64(inputskyfileArgs, PS_LIST_TAIL, "-xcstack_id", 0,        "search by xcstack ID (required)", 0);
+    psMetadataAddStr(inputskyfileArgs, PS_LIST_TAIL, "-xcamera", 0,           "camera database to search (required)", 0);
+    psMetadataAddU64(inputskyfileArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+    psMetadataAddBool(inputskyfileArgs, PS_LIST_TAIL, "-simple",  0,          "use the simple output format", false);
+
+    // -addsumskyfile
+    psMetadata *addsumskyfileArgs = psMetadataAlloc();
+    psMetadataAddS64(addsumskyfileArgs, PS_LIST_TAIL, "-xcstack_id", 0,            "define stack ID (required)", 0);
+    psMetadataAddStr(addsumskyfileArgs, PS_LIST_TAIL, "-path_base", 0,            "define base output location", 0);
+    psMetadataAddF64(addsumskyfileArgs, PS_LIST_TAIL, "-bg",  0,            "define exposue background", NAN);
+    psMetadataAddF64(addsumskyfileArgs, PS_LIST_TAIL, "-bg_stdev",  0,            "define exposue background mean stdev", NAN);
+    psMetadataAddF32(addsumskyfileArgs, PS_LIST_TAIL, "-dtime_stack",  0,            "define elapsed processing time", NAN);
+    psMetadataAddF32(addsumskyfileArgs, PS_LIST_TAIL, "-dtime_script", 0, "define elapsed time in script (seconds)", NAN);
+
+    psMetadataAddStr(addsumskyfileArgs, PS_LIST_TAIL, "-hostname", 0,            "define hostname", 0);
+    psMetadataAddF32(addsumskyfileArgs, PS_LIST_TAIL, "-good_frac",  0,            "define %% of good pixels", NAN);
+    psMetadataAddS16(addsumskyfileArgs, PS_LIST_TAIL, "-fault",  0,            "set fault code", 0);
+    psMetadataAddS16(addsumskyfileArgs, PS_LIST_TAIL, "-quality",  0,            "set quality", 0);
+
+    psMetadataAddStr(addsumskyfileArgs, PS_LIST_TAIL, "-ver_pslib", 0, "define psLib version", NULL);
+    psMetadataAddStr(addsumskyfileArgs, PS_LIST_TAIL, "-ver_psmodules", 0, "define psModules version", NULL);
+    psMetadataAddStr(addsumskyfileArgs, PS_LIST_TAIL, "-ver_ppstats", 0, "define ppStats version", NULL);
+    psMetadataAddStr(addsumskyfileArgs, PS_LIST_TAIL, "-ver_ppstack", 0, "define ppStack version", NULL);
+
+    // -sumskyfile
+    psMetadata *sumskyfileArgs= psMetadataAlloc();
+    psMetadataAddS64(sumskyfileArgs, PS_LIST_TAIL, "-xcstack_id", 0,                    "search by xcstack ID", 0);
+    psMetadataAddStr(sumskyfileArgs, PS_LIST_TAIL, "-state", 0,                         "search by state", NULL);
+    psMetadataAddStr(sumskyfileArgs, PS_LIST_TAIL, "-tess_id", 0,                       "search by tess ID (LIKE comparison)", 0);
+    psMetadataAddStr(sumskyfileArgs, PS_LIST_TAIL, "-skycell_id", 0,                    "search by skycell ID (LIKE comparison)", 0);
+    psMetadataAddStr(sumskyfileArgs, PS_LIST_TAIL, "-filter", 0,                        "search by filter (LIKE comparison)", NULL);
+    psMetadataAddStr(sumskyfileArgs, PS_LIST_TAIL, "-label", PS_META_DUPLICATE_OK,      "search by label", NULL);
+    psMetadataAddStr(sumskyfileArgs, PS_LIST_TAIL, "-data_group", PS_META_DUPLICATE_OK, "search by data_group (LIKE comparison)", NULL);
+    psMetadataAddS16(sumskyfileArgs, PS_LIST_TAIL, "-fault",  0,                        "search by fault code", 0);
+    psMetadataAddU64(sumskyfileArgs, PS_LIST_TAIL, "-limit",  0,                        "limit result set to N items", 0);
+    psMetadataAddBool(sumskyfileArgs, PS_LIST_TAIL, "-simple",  0,                      "use the simple output format", false);
+    psMetadataAddBool(sumskyfileArgs, PS_LIST_TAIL, "-all",  0,                         "enable search without arguments", false);
+
+    // -revertsumskyfile
+    psMetadata *revertsumskyfileArgs= psMetadataAlloc();
+    psMetadataAddS64(revertsumskyfileArgs, PS_LIST_TAIL, "-xcstack_id", 0,            "search by xcstack ID", 0);
+    psMetadataAddStr(revertsumskyfileArgs, PS_LIST_TAIL, "-label", PS_META_DUPLICATE_OK, "search by label", 0);
+    psMetadataAddS16(revertsumskyfileArgs, PS_LIST_TAIL, "-fault",  0,            "search by fault code", 0);
+    psMetadataAddBool(revertsumskyfileArgs, PS_LIST_TAIL, "-all",  0, "allow no search terms", 0);
+
+    // -updatesumskyfile
+    psMetadata *updatesumskyfileArgs = psMetadataAlloc();
+    psMetadataAddS64(updatesumskyfileArgs, PS_LIST_TAIL, "-xcstack_id", 0,            "define xcstack ID (required)", 0);
+    psMetadataAddS16(updatesumskyfileArgs, PS_LIST_TAIL, "-fault", 0,                 "set fault code (required)", 0);
+    psMetadataAddS16(updatesumskyfileArgs, PS_LIST_TAIL, "-set_quality", 0,           "set quality", 0);
+    
+    // -pendingcleanuprun
+    psMetadata *pendingcleanuprunArgs = psMetadataAlloc();
+    psMetadataAddS64(pendingcleanuprunArgs, PS_LIST_TAIL, "-xcstack_id", 0,          "search by xcstack ID", 0);
+    psMetadataAddStr(pendingcleanuprunArgs, PS_LIST_TAIL, "-label", PS_META_DUPLICATE_OK, "list blocks for specified label", NULL);
+    psMetadataAddBool(pendingcleanuprunArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+    psMetadataAddU64(pendingcleanuprunArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+
+    // -pendingcleanupskyfile
+    psMetadata *pendingcleanupskyfileArgs = psMetadataAlloc();
+    psMetadataAddS64(pendingcleanupskyfileArgs, PS_LIST_TAIL, "-xcstack_id", 0,          "search by xcstack ID (required)", 0);
+    psMetadataAddStr(pendingcleanupskyfileArgs, PS_LIST_TAIL, "-label", PS_META_DUPLICATE_OK, "list blocks for specified label", NULL);
+    psMetadataAddBool(pendingcleanupskyfileArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+    psMetadataAddU64(pendingcleanupskyfileArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+
+    // -donecleanup
+    psMetadata *donecleanupArgs = psMetadataAlloc();
+    psMetadataAddStr(donecleanupArgs, PS_LIST_TAIL, "-label",  0,            "list blocks for specified label", NULL);
+    psMetadataAddBool(donecleanupArgs, PS_LIST_TAIL, "-simple",  0,            "use the simple output format", false);
+    psMetadataAddU64(donecleanupArgs, PS_LIST_TAIL, "-limit",  0,            "limit result set to N items", 0);
+
+    // -exportrun
+    psMetadata *exportrunArgs = psMetadataAlloc();
+    psMetadataAddS64(exportrunArgs, PS_LIST_TAIL, "-xcstack_id", 0,       "export this xcstack ID (required)", 0);
+    psMetadataAddStr(exportrunArgs, PS_LIST_TAIL, "-outfile", 0,          "export to this file (required)", NULL);
+    psMetadataAddU64(exportrunArgs, PS_LIST_TAIL, "-limit",   0,          "limit result set to N items", 0);
+    psMetadataAddBool(exportrunArgs, PS_LIST_TAIL, "-clean",  0,          "mark tables as cleaned", false);
+
+    // -importrun
+    psMetadata *importrunArgs = psMetadataAlloc();
+    psMetadataAddStr(importrunArgs, PS_LIST_TAIL, "-infile",  0,          "import from this file (required)", NULL);
+
+    // -addcamera
+    psMetadata *addcameraArgs = psMetadataAlloc();
+    psMetadataAddStr(addcameraArgs, PS_LIST_TAIL, "-xcamera",  0,      "camera which can supply stacks (required)", NULL);
+
+    // -getcamera
+    psMetadata *getcameraArgs = psMetadataAlloc();
+    psMetadataAddStr(getcameraArgs, PS_LIST_TAIL, "-xcamera",  0,      "get camera_id for this camera", NULL);
+
+    // -listcameras
+    psMetadata *listcamerasArgs = psMetadataAlloc();
+    psMetadataAddBool(listcamerasArgs, PS_LIST_TAIL, "-simple",  0,      "use the simple output format", NULL);
+
+    psFree(now);
+
+    psMetadata *argSets = psMetadataAlloc();
+    psMetadata *modes   = psMetadataAlloc();
+
+    PXOPT_ADD_MODE("-definebyquery",         "Define a new xcstackRun by searching for stacks from one camera", XCSTACKTOOL_MODE_DEFINEBYQUERY, definebyqueryArgs);
+    PXOPT_ADD_MODE("-addbyquery",            "Add stacks from another camera to existing stackRuns",            XCSTACKTOOL_MODE_ADDBYQUERY,    addbyqueryArgs);
+    PXOPT_ADD_MODE("-updaterun",             "",                                                                XCSTACKTOOL_MODE_UPDATERUN,             updaterunArgs);
+
+    PXOPT_ADD_MODE("-tosum",                 "",                                                                XCSTACKTOOL_MODE_TOSUM,                 tosumArgs);
+    PXOPT_ADD_MODE("-inputcameras",          "",                                                                XCSTACKTOOL_MODE_INPUTCAMERAS,          inputcamerasArgs);
+    PXOPT_ADD_MODE("-inputskyfile",          "",                                                                XCSTACKTOOL_MODE_INPUTSKYFILE,          inputskyfileArgs);
+
+    PXOPT_ADD_MODE("-addsumskyfile",         "",                                                                XCSTACKTOOL_MODE_ADDSUMSKYFILE,         addsumskyfileArgs);
+    PXOPT_ADD_MODE("-sumskyfile",            "list results of stackRun",                                        XCSTACKTOOL_MODE_SUMSKYFILE,            sumskyfileArgs);
+    PXOPT_ADD_MODE("-revertsumskyfile",      "",                                                                XCSTACKTOOL_MODE_REVERTSUMSKYFILE,      revertsumskyfileArgs);
+    PXOPT_ADD_MODE("-updatesumskyfile",      "",                                                                XCSTACKTOOL_MODE_UPDATESUMSKYFILE,      updatesumskyfileArgs);
+
+    PXOPT_ADD_MODE("-pendingcleanuprun",     "show runs that need to be cleaned up",                            XCSTACKTOOL_MODE_PENDINGCLEANUPRUN,     pendingcleanuprunArgs);
+    PXOPT_ADD_MODE("-pendingcleanupskyfile", "show runs that need to be cleaned up",                            XCSTACKTOOL_MODE_PENDINGCLEANUPSKYFILE, pendingcleanupskyfileArgs);
+    PXOPT_ADD_MODE("-donecleanup",           "show runs that have been cleaned",                                XCSTACKTOOL_MODE_DONECLEANUP,           donecleanupArgs);
+
+    PXOPT_ADD_MODE("-exportrun",             "export run for import on other database",                         XCSTACKTOOL_MODE_EXPORTRUN,             exportrunArgs);
+    PXOPT_ADD_MODE("-importrun",             "import run from metadata file",                                   XCSTACKTOOL_MODE_IMPORTRUN,             importrunArgs);
+
+    PXOPT_ADD_MODE("-addcamera",             "add camera which can supply stacks",                              XCSTACKTOOL_MODE_ADDCAMERA,             addcameraArgs);
+    PXOPT_ADD_MODE("-getcamera",             "add camera which can supply stacks",                              XCSTACKTOOL_MODE_GETCAMERA,             getcameraArgs);
+    PXOPT_ADD_MODE("-listcameras",           "list cameras which can supply stacks",                            XCSTACKTOOL_MODE_LISTCAMERAS,           listcamerasArgs);
+
+    if (!pxGetOptions(stderr, argc, argv, config, modes, argSets)) {
+        psError(PS_ERR_UNKNOWN, true, "option parsing failed");
+        psFree(argSets);
+        psFree(modes);
+        psFree(config);
+        return NULL;
+    }
+
+    psFree(argSets);
+    psFree(modes);
+
+    // define Database handle, if used
+    // do this last so we don't setup a connection before CLI options are
+    // validated
+    config->dbh = psMemIncrRefCounter(pmConfigDB(config->modules));
+    if (!config->dbh) {
+        psError(PS_ERR_UNKNOWN, false, "Can't configure database");
+        psFree(config);
+        return NULL;
+    }
+
+    return config;
+}
