/*
 * code.c
 *
 * Copyright (C) 2006  Joshua Hoblitt
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * program; see the file COPYING. If not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/*
 *
 * This file was generated by glueforge 1.04
 *
 * Do NOT directly edit this file.
 *
 */

#include <stdio.h>
#include <inttypes.h>

#include "ippdb.h"

#define PZDATASTORE_TABLE_NAME "pzDataStore"
#define SUMMITEXP_TABLE_NAME "summitExp"
#define SUMMITIMFILE_TABLE_NAME "summitImfile"
#define PZDOWNLOADEXP_TABLE_NAME "pzDownloadExp"
#define PZDOWNLOADIMFILE_TABLE_NAME "pzDownloadImfile"
#define NEWEXP_TABLE_NAME "newExp"
#define NEWIMFILE_TABLE_NAME "newImfile"
#define RAWEXP_TABLE_NAME "rawExp"
#define RAWIMFILE_TABLE_NAME "rawImfile"
#define GUIDEPENDINGEXP_TABLE_NAME "guidePendingExp"
#define CHIPRUN_TABLE_NAME "chipRun"
#define CHIPPROCESSEDIMFILE_TABLE_NAME "chipProcessedImfile"
#define CHIPMASK_TABLE_NAME "chipMask"
#define CAMRUN_TABLE_NAME "camRun"
#define CAMPROCESSEDEXP_TABLE_NAME "camProcessedExp"
#define CAMMASK_TABLE_NAME "camMask"
#define FAKERUN_TABLE_NAME "fakeRun"
#define FAKEPROCESSEDIMFILE_TABLE_NAME "fakeProcessedImfile"
#define FAKEMASK_TABLE_NAME "fakeMask"
#define WARPRUN_TABLE_NAME "warpRun"
#define WARPSKYCELLMAP_TABLE_NAME "warpSkyCellMap"
#define WARPSKYFILE_TABLE_NAME "warpSkyfile"
#define WARPMASK_TABLE_NAME "warpMask"
#define DIFFRUN_TABLE_NAME "diffRun"
#define DIFFINPUTSKYFILE_TABLE_NAME "diffInputSkyfile"
#define DIFFSKYFILE_TABLE_NAME "diffSkyfile"
#define STACKRUN_TABLE_NAME "stackRun"
#define STACKINPUTSKYFILE_TABLE_NAME "stackInputSkyfile"
#define STACKSUMSKYFILE_TABLE_NAME "stackSumSkyfile"
#define DETRUN_TABLE_NAME "detRun"
#define DETINPUTEXP_TABLE_NAME "detInputExp"
#define DETPROCESSEDIMFILE_TABLE_NAME "detProcessedImfile"
#define DETPROCESSEDEXP_TABLE_NAME "detProcessedExp"
#define DETSTACKEDIMFILE_TABLE_NAME "detStackedImfile"
#define DETNORMALIZEDSTATIMFILE_TABLE_NAME "detNormalizedStatImfile"
#define DETNORMALIZEDIMFILE_TABLE_NAME "detNormalizedImfile"
#define DETNORMALIZEDEXP_TABLE_NAME "detNormalizedExp"
#define DETRESIDIMFILE_TABLE_NAME "detResidImfile"
#define DETRESIDEXP_TABLE_NAME "detResidExp"
#define DETRUNSUMMARY_TABLE_NAME "detRunSummary"
#define DETREGISTEREDIMFILE_TABLE_NAME "detRegisteredImfile"
#define DETCORRECTEDEXP_TABLE_NAME "detCorrectedExp"
#define DETCORRECTEDIMFILE_TABLE_NAME "detCorrectedImfile"
#define MAGICRUN_TABLE_NAME "magicRun"
#define MAGICINPUTSKYFILE_TABLE_NAME "magicInputSkyfile"
#define MAGICTREE_TABLE_NAME "magicTree"
#define MAGICNODERESULT_TABLE_NAME "magicNodeResult"
#define MAGICMASK_TABLE_NAME "magicMask"
#define CALDB_TABLE_NAME "calDB"
#define CALRUN_TABLE_NAME "calRun"
#define FLATCORRRUN_TABLE_NAME "flatcorrRun"
#define FLATCORREXP_TABLE_NAME "flatcorrExp"
#define PSTAMPDATASTORE_TABLE_NAME "pstampDataStore"
#define PSTAMPREQUEST_TABLE_NAME "pstampRequest"
#define PSTAMPJOB_TABLE_NAME "pstampJob"
#define MAX_STRING_LENGTH 1024

psDB *ippdbInit(const char *host, const char *user, const char *passwd, const char *dbname, unsigned int port)
{
    return psDBInit(host, user, passwd, dbname, port);
}

void ippdbCleanup(psDB *dbh)
{
    psDBCleanup(dbh);
}

bool ippdbPrintMetadata(FILE *stream, psMetadata *md, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(md, false);

    psMetadata *clean = psMetadataCopy(NULL, md);

    if (!ippdbPrintMetadataRaw(stream, clean, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(clean);
        return false;
    }
    psFree(clean);

    return true;
}

bool ippdbPrintMetadataRaw(FILE *stream, psMetadata *md, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(md, false);

    if (mdcf) {
        psString str = psMetadataConfigFormat(md);
        if (!str) {
            psError(PS_ERR_UNKNOWN, false, "failed to format data into a string");
            psFree(str);
            return false;
        }
        fprintf(stream, "%s\n", str);
        psFree(str);

        return true;
    }

#define METADATAITEM_STRIFY_CASE(ptype, format, type) \
case ptype: \
    psStringAppend(&str, format, item->data.type); \
    break;

    // else
    // flatten the metadata into | separated values
    psString str = NULL;
    psMetadataItem *item = NULL;
    psMetadataIterator *iter = psMetadataIteratorAlloc(md, 0, NULL);
    while ((item = psMetadataGetAndIncrement(iter))) {
        switch (item->type) {
            METADATAITEM_STRIFY_CASE(PS_DATA_S8, "%hhd", S8);
            METADATAITEM_STRIFY_CASE(PS_DATA_S16, "%hd", S16);
            METADATAITEM_STRIFY_CASE(PS_DATA_S32, "%d", S32);
            METADATAITEM_STRIFY_CASE(PS_DATA_S64, "%" PRId64, S64);
            METADATAITEM_STRIFY_CASE(PS_DATA_U8, "%hhu", U8);
            METADATAITEM_STRIFY_CASE(PS_DATA_U16, "%hu", U16);
            METADATAITEM_STRIFY_CASE(PS_DATA_U32, "%u", U32);
            METADATAITEM_STRIFY_CASE(PS_DATA_U64, "%" PRIu64, U64);
            METADATAITEM_STRIFY_CASE(PS_DATA_F32, "%f", F32);
            METADATAITEM_STRIFY_CASE(PS_DATA_F64, "%f", F64);
            case PS_DATA_STRING:
                if (item->data.str) {
                    psString tmpStr = psStringCopy(item->data.str);
                    psStringSubstitute(&tmpStr, "_", " ");
                    psStringAppend(&str, "%s", tmpStr);
                    psFree(tmpStr);
                }
                break;
            case PS_DATA_BOOL:
                if (item->data.B) {
                    psStringAppend(&str, "T");
                } else {
                    psStringAppend(&str, "F");
                }
                break;
            case PS_DATA_METADATA:
                if (!ippdbPrintMetadataRaw(stream, item->data.md, mdcf)) {
                    psError(PS_ERR_UNKNOWN, false ,"failed to print nested metadata");
                }
                // a metadata is a special case. We don't want a | seperating
                // the metadata from any other output so we need to skip the
                // !->offEnd test
                continue;
            case PS_DATA_TIME:
               // pass through NULLs as "NULL"
                if (item->data.V) {
                    psString time = psTimeToISO(item->data.V);
                    psStringAppend(&str, "%s", time);
                psFree(time);
                } else {
                    psStringAppend(&str, "NULL");
                }
                break;
            default:
                psError(PS_ERR_UNKNOWN, true,"unsupported psMetadataItem type");
                psFree(iter);
                psFree(str);
                return false;
        }
        if (!iter->iter->offEnd) {
            psStringAppend(&str, " ");
        }
    }
    psFree(iter);

    // if we did nothing but handle recursive metadatas str will be NULL
    if (str) {
        fprintf(stream, "%s\n", str);
        psFree(str);
    }

    return true;
}

bool ippdbPrintMetadatas(FILE *stream, psArray *mds, const char *mdname, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(mds, false);
    PS_ASSERT_PTR_NON_NULL(mdname, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(mds); i++) {
        psMetadata *md = psMetadataCopy(NULL, mds->data[i]);
        if (!psMetadataAddMetadata(output, PS_LIST_TAIL, mdname, PS_META_DUPLICATE_OK, NULL, md)) {
            psError(PS_ERR_UNKNOWN, false, "failed to add a metadata item");
            psFree(md);
            psFree(output);
            return false;
        }

        psFree(md);
    }

    if (!ippdbPrintMetadata(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
        return false;
    }

    psFree(output);

    return true;
}

bool ippdbPrintMetadatasRaw(FILE *stream, psArray *mds, const char *mdname, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(mds, false);
    PS_ASSERT_PTR_NON_NULL(mdname, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(mds); i++) {
        psMetadata *md = mds->data[i];
        if (!psMetadataAddMetadata(output, PS_LIST_TAIL, mdname, PS_META_DUPLICATE_OK, NULL, md)) {
            psError(PS_ERR_UNKNOWN, false, "failed to add a metadata item");
            psFree(output);
            return false;
        }
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
        return false;
    }

    psFree(output);

    return true;
}

static void pzDataStoreRowFree(pzDataStoreRow *object);

pzDataStoreRow *pzDataStoreRowAlloc(const char *camera, const char *telescope, const char *uri, psTime* epoch)
{
    pzDataStoreRow  *_object;

    _object = psAlloc(sizeof(pzDataStoreRow));
    psMemSetDeallocator(_object, (psFreeFunc)pzDataStoreRowFree);

    _object->camera = psStringCopy(camera);
    _object->telescope = psStringCopy(telescope);
    _object->uri = psStringCopy(uri);
    _object->epoch = psTimeCopy(epoch);

    return _object;
}

static void pzDataStoreRowFree(pzDataStoreRow *object)
{
    psFree(object->camera);
    psFree(object->telescope);
    psFree(object->uri);
    psFree(object->epoch);
}

bool pzDataStoreCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, PZDATASTORE_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool pzDataStoreDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, PZDATASTORE_TABLE_NAME);
}

bool pzDataStoreInsert(psDB * dbh, const char *camera, const char *telescope, const char *uri, psTime* epoch)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, epoch)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, PZDATASTORE_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long pzDataStoreDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, PZDATASTORE_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from pzDataStore");
        return count;

        deleted += count;
    }

    return deleted;
}
bool pzDataStoreInsertObject(psDB *dbh, pzDataStoreRow *object)
{
    return pzDataStoreInsert(dbh, object->camera, object->telescope, object->uri, object->epoch);
}

bool pzDataStoreInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!pzDataStoreInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool pzDataStoreInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  PZDATASTORE_TABLE_NAME
    if (!psFitsMoveExtName(fits, PZDATASTORE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", PZDATASTORE_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, PZDATASTORE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool pzDataStoreSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, PZDATASTORE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, PZDATASTORE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *pzDataStoreMetadataFromObject(const pzDataStoreRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, object->camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, object->telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, object->epoch)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }


    return md;
}

pzDataStoreRow *pzDataStoreObjectFromMetadata(psMetadata *md)
{

bool status = false;
    char* camera = psMetadataLookupPtr(&status, md, "camera");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
        return false;
    }
    char* telescope = psMetadataLookupPtr(&status, md, "telescope");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    psTime* epoch = psMetadataLookupPtr(&status, md, "epoch");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item epoch");
        return false;
    }

    return pzDataStoreRowAlloc(camera, telescope, uri, epoch);
}
psArray *pzDataStoreSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, PZDATASTORE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        pzDataStoreRow *object = pzDataStoreObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool pzDataStoreDeleteObject(psDB *dbh, const pzDataStoreRow *object)
{
    psMetadata *where = pzDataStoreMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, PZDATASTORE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from pzDataStore");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "pzDataStoreRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long pzDataStoreDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        pzDataStoreRow *object = objects->data[i];
        psMetadata *where = pzDataStoreMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, PZDATASTORE_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from pzDataStore");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool pzDataStorePrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = pzDataStoreMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            PZDATASTORE_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool pzDataStorePrintObject(FILE *stream, pzDataStoreRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = pzDataStoreMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void summitExpRowFree(summitExpRow *object);

summitExpRow *summitExpRowAlloc(const char *exp_name, const char *camera, const char *telescope, psTime* dateobs, const char *exp_type, const char *uri, psS32 imfiles, psS16 fault, psTime* epoch)
{
    summitExpRow    *_object;

    _object = psAlloc(sizeof(summitExpRow));
    psMemSetDeallocator(_object, (psFreeFunc)summitExpRowFree);

    _object->exp_name = psStringCopy(exp_name);
    _object->camera = psStringCopy(camera);
    _object->telescope = psStringCopy(telescope);
    _object->dateobs = psTimeCopy(dateobs);
    _object->exp_type = psStringCopy(exp_type);
    _object->uri = psStringCopy(uri);
    _object->imfiles = imfiles;
    _object->fault = fault;
    _object->epoch = psTimeCopy(epoch);

    return _object;
}

static void summitExpRowFree(summitExpRow *object)
{
    psFree(object->exp_name);
    psFree(object->camera);
    psFree(object->telescope);
    psFree(object->dateobs);
    psFree(object->exp_type);
    psFree(object->uri);
    psFree(object->epoch);
}

bool summitExpCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_name", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_name");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dateobs", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "imfiles", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, SUMMITEXP_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool summitExpDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, SUMMITEXP_TABLE_NAME);
}

bool summitExpInsert(psDB * dbh, const char *exp_name, const char *camera, const char *telescope, psTime* dateobs, const char *exp_type, const char *uri, psS32 imfiles, psS16 fault, psTime* epoch)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_name", PS_DATA_STRING, NULL, exp_name)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_name");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dateobs", PS_DATA_TIME, NULL, dateobs)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, exp_type)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "imfiles", PS_DATA_S32, NULL, imfiles)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, epoch)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, SUMMITEXP_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long summitExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, SUMMITEXP_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from summitExp");
        return count;

        deleted += count;
    }

    return deleted;
}
bool summitExpInsertObject(psDB *dbh, summitExpRow *object)
{
    return summitExpInsert(dbh, object->exp_name, object->camera, object->telescope, object->dateobs, object->exp_type, object->uri, object->imfiles, object->fault, object->epoch);
}

bool summitExpInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!summitExpInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool summitExpInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  SUMMITEXP_TABLE_NAME
    if (!psFitsMoveExtName(fits, SUMMITEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", SUMMITEXP_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, SUMMITEXP_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool summitExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, SUMMITEXP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, SUMMITEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *summitExpMetadataFromObject(const summitExpRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_name", PS_DATA_STRING, NULL, object->exp_name)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_name");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, object->camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, object->telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dateobs", PS_DATA_TIME, NULL, object->dateobs)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, object->exp_type)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "imfiles", PS_DATA_S32, NULL, object->imfiles)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, object->epoch)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }


    return md;
}

summitExpRow *summitExpObjectFromMetadata(psMetadata *md)
{

bool status = false;
    char* exp_name = psMetadataLookupPtr(&status, md, "exp_name");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_name");
        return false;
    }
    char* camera = psMetadataLookupPtr(&status, md, "camera");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
        return false;
    }
    char* telescope = psMetadataLookupPtr(&status, md, "telescope");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
        return false;
    }
    psTime* dateobs = psMetadataLookupPtr(&status, md, "dateobs");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dateobs");
        return false;
    }
    char* exp_type = psMetadataLookupPtr(&status, md, "exp_type");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    psS32 imfiles = psMetadataLookupS32(&status, md, "imfiles");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }
    psTime* epoch = psMetadataLookupPtr(&status, md, "epoch");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item epoch");
        return false;
    }

    return summitExpRowAlloc(exp_name, camera, telescope, dateobs, exp_type, uri, imfiles, fault, epoch);
}
psArray *summitExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, SUMMITEXP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        summitExpRow *object = summitExpObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool summitExpDeleteObject(psDB *dbh, const summitExpRow *object)
{
    psMetadata *where = summitExpMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, SUMMITEXP_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from summitExp");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "summitExpRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long summitExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        summitExpRow *object = objects->data[i];
        psMetadata *where = summitExpMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, SUMMITEXP_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from summitExp");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool summitExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = summitExpMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            SUMMITEXP_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool summitExpPrintObject(FILE *stream, summitExpRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = summitExpMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void summitImfileRowFree(summitImfileRow *object);

summitImfileRow *summitImfileRowAlloc(const char *exp_name, const char *camera, const char *telescope, const char *file_id, psS32 bytes, const char *md5sum, const char *class, const char *class_id, const char *uri, psTime* epoch)
{
    summitImfileRow *_object;

    _object = psAlloc(sizeof(summitImfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)summitImfileRowFree);

    _object->exp_name = psStringCopy(exp_name);
    _object->camera = psStringCopy(camera);
    _object->telescope = psStringCopy(telescope);
    _object->file_id = psStringCopy(file_id);
    _object->bytes = bytes;
    _object->md5sum = psStringCopy(md5sum);
    _object->class = psStringCopy(class);
    _object->class_id = psStringCopy(class_id);
    _object->uri = psStringCopy(uri);
    _object->epoch = psTimeCopy(epoch);

    return _object;
}

static void summitImfileRowFree(summitImfileRow *object)
{
    psFree(object->exp_name);
    psFree(object->camera);
    psFree(object->telescope);
    psFree(object->file_id);
    psFree(object->md5sum);
    psFree(object->class);
    psFree(object->class_id);
    psFree(object->uri);
    psFree(object->epoch);
}

bool summitImfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_name", PS_DATA_STRING, "Primary Key fkey(exp_name, camera, telescope) ref summitExp(exp_name, camera, telescope)", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_name");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "file_id", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item file_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bytes", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bytes");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "md5sum", PS_DATA_STRING, NULL, "32")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item md5sum");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, SUMMITIMFILE_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool summitImfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, SUMMITIMFILE_TABLE_NAME);
}

bool summitImfileInsert(psDB * dbh, const char *exp_name, const char *camera, const char *telescope, const char *file_id, psS32 bytes, const char *md5sum, const char *class, const char *class_id, const char *uri, psTime* epoch)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_name", PS_DATA_STRING, NULL, exp_name)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_name");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "file_id", PS_DATA_STRING, NULL, file_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item file_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bytes", PS_DATA_S32, NULL, bytes)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bytes");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "md5sum", PS_DATA_STRING, NULL, md5sum)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item md5sum");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, NULL, class)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, epoch)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, SUMMITIMFILE_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long summitImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, SUMMITIMFILE_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from summitImfile");
        return count;

        deleted += count;
    }

    return deleted;
}
bool summitImfileInsertObject(psDB *dbh, summitImfileRow *object)
{
    return summitImfileInsert(dbh, object->exp_name, object->camera, object->telescope, object->file_id, object->bytes, object->md5sum, object->class, object->class_id, object->uri, object->epoch);
}

bool summitImfileInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!summitImfileInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool summitImfileInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  SUMMITIMFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, SUMMITIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", SUMMITIMFILE_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, SUMMITIMFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool summitImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, SUMMITIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, SUMMITIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *summitImfileMetadataFromObject(const summitImfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_name", PS_DATA_STRING, NULL, object->exp_name)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_name");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, object->camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, object->telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "file_id", PS_DATA_STRING, NULL, object->file_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item file_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bytes", PS_DATA_S32, NULL, object->bytes)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bytes");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "md5sum", PS_DATA_STRING, NULL, object->md5sum)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item md5sum");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, NULL, object->class)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, object->epoch)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }


    return md;
}

summitImfileRow *summitImfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    char* exp_name = psMetadataLookupPtr(&status, md, "exp_name");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_name");
        return false;
    }
    char* camera = psMetadataLookupPtr(&status, md, "camera");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
        return false;
    }
    char* telescope = psMetadataLookupPtr(&status, md, "telescope");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
        return false;
    }
    char* file_id = psMetadataLookupPtr(&status, md, "file_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item file_id");
        return false;
    }
    psS32 bytes = psMetadataLookupS32(&status, md, "bytes");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bytes");
        return false;
    }
    char* md5sum = psMetadataLookupPtr(&status, md, "md5sum");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item md5sum");
        return false;
    }
    char* class = psMetadataLookupPtr(&status, md, "class");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class");
        return false;
    }
    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    psTime* epoch = psMetadataLookupPtr(&status, md, "epoch");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item epoch");
        return false;
    }

    return summitImfileRowAlloc(exp_name, camera, telescope, file_id, bytes, md5sum, class, class_id, uri, epoch);
}
psArray *summitImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, SUMMITIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        summitImfileRow *object = summitImfileObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool summitImfileDeleteObject(psDB *dbh, const summitImfileRow *object)
{
    psMetadata *where = summitImfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, SUMMITIMFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from summitImfile");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "summitImfileRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long summitImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        summitImfileRow *object = objects->data[i];
        psMetadata *where = summitImfileMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, SUMMITIMFILE_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from summitImfile");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool summitImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = summitImfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            SUMMITIMFILE_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool summitImfilePrintObject(FILE *stream, summitImfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = summitImfileMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void pzDownloadExpRowFree(pzDownloadExpRow *object);

pzDownloadExpRow *pzDownloadExpRowAlloc(const char *exp_name, const char *camera, const char *telescope, const char *state, psTime* epoch)
{
    pzDownloadExpRow *_object;

    _object = psAlloc(sizeof(pzDownloadExpRow));
    psMemSetDeallocator(_object, (psFreeFunc)pzDownloadExpRowFree);

    _object->exp_name = psStringCopy(exp_name);
    _object->camera = psStringCopy(camera);
    _object->telescope = psStringCopy(telescope);
    _object->state = psStringCopy(state);
    _object->epoch = psTimeCopy(epoch);

    return _object;
}

static void pzDownloadExpRowFree(pzDownloadExpRow *object)
{
    psFree(object->exp_name);
    psFree(object->camera);
    psFree(object->telescope);
    psFree(object->state);
    psFree(object->epoch);
}

bool pzDownloadExpCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_name", PS_DATA_STRING, "Primary Key fkey(exp_name, camera, telescope) ref summitExp(exp_name, camera, telescope)", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_name");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, PZDOWNLOADEXP_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool pzDownloadExpDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, PZDOWNLOADEXP_TABLE_NAME);
}

bool pzDownloadExpInsert(psDB * dbh, const char *exp_name, const char *camera, const char *telescope, const char *state, psTime* epoch)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_name", PS_DATA_STRING, NULL, exp_name)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_name");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, epoch)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, PZDOWNLOADEXP_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long pzDownloadExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, PZDOWNLOADEXP_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from pzDownloadExp");
        return count;

        deleted += count;
    }

    return deleted;
}
bool pzDownloadExpInsertObject(psDB *dbh, pzDownloadExpRow *object)
{
    return pzDownloadExpInsert(dbh, object->exp_name, object->camera, object->telescope, object->state, object->epoch);
}

bool pzDownloadExpInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!pzDownloadExpInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool pzDownloadExpInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  PZDOWNLOADEXP_TABLE_NAME
    if (!psFitsMoveExtName(fits, PZDOWNLOADEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", PZDOWNLOADEXP_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, PZDOWNLOADEXP_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool pzDownloadExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, PZDOWNLOADEXP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, PZDOWNLOADEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *pzDownloadExpMetadataFromObject(const pzDownloadExpRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_name", PS_DATA_STRING, NULL, object->exp_name)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_name");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, object->camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, object->telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, object->state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, object->epoch)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }


    return md;
}

pzDownloadExpRow *pzDownloadExpObjectFromMetadata(psMetadata *md)
{

bool status = false;
    char* exp_name = psMetadataLookupPtr(&status, md, "exp_name");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_name");
        return false;
    }
    char* camera = psMetadataLookupPtr(&status, md, "camera");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
        return false;
    }
    char* telescope = psMetadataLookupPtr(&status, md, "telescope");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
        return false;
    }
    char* state = psMetadataLookupPtr(&status, md, "state");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item state");
        return false;
    }
    psTime* epoch = psMetadataLookupPtr(&status, md, "epoch");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item epoch");
        return false;
    }

    return pzDownloadExpRowAlloc(exp_name, camera, telescope, state, epoch);
}
psArray *pzDownloadExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, PZDOWNLOADEXP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        pzDownloadExpRow *object = pzDownloadExpObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool pzDownloadExpDeleteObject(psDB *dbh, const pzDownloadExpRow *object)
{
    psMetadata *where = pzDownloadExpMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, PZDOWNLOADEXP_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from pzDownloadExp");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "pzDownloadExpRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long pzDownloadExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        pzDownloadExpRow *object = objects->data[i];
        psMetadata *where = pzDownloadExpMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, PZDOWNLOADEXP_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from pzDownloadExp");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool pzDownloadExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = pzDownloadExpMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            PZDOWNLOADEXP_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool pzDownloadExpPrintObject(FILE *stream, pzDownloadExpRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = pzDownloadExpMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void pzDownloadImfileRowFree(pzDownloadImfileRow *object);

pzDownloadImfileRow *pzDownloadImfileRowAlloc(const char *exp_name, const char *camera, const char *telescope, const char *class, const char *class_id, const char *uri, psS16 fault, psTime* epoch)
{
    pzDownloadImfileRow *_object;

    _object = psAlloc(sizeof(pzDownloadImfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)pzDownloadImfileRowFree);

    _object->exp_name = psStringCopy(exp_name);
    _object->camera = psStringCopy(camera);
    _object->telescope = psStringCopy(telescope);
    _object->class = psStringCopy(class);
    _object->class_id = psStringCopy(class_id);
    _object->uri = psStringCopy(uri);
    _object->fault = fault;
    _object->epoch = psTimeCopy(epoch);

    return _object;
}

static void pzDownloadImfileRowFree(pzDownloadImfileRow *object)
{
    psFree(object->exp_name);
    psFree(object->camera);
    psFree(object->telescope);
    psFree(object->class);
    psFree(object->class_id);
    psFree(object->uri);
    psFree(object->epoch);
}

bool pzDownloadImfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_name", PS_DATA_STRING, "Primary Key fkey(exp_name, camera, telescope) ref pzDownloadExp(exp_name, camera, telescope)", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_name");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, "Primary Key fkey(exp_name, camera, telescope, class, class_id) ref summitImfile(exp_name, camera, telescope, class, class_id)", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, PZDOWNLOADIMFILE_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool pzDownloadImfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, PZDOWNLOADIMFILE_TABLE_NAME);
}

bool pzDownloadImfileInsert(psDB * dbh, const char *exp_name, const char *camera, const char *telescope, const char *class, const char *class_id, const char *uri, psS16 fault, psTime* epoch)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_name", PS_DATA_STRING, NULL, exp_name)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_name");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, NULL, class)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, epoch)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, PZDOWNLOADIMFILE_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long pzDownloadImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, PZDOWNLOADIMFILE_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from pzDownloadImfile");
        return count;

        deleted += count;
    }

    return deleted;
}
bool pzDownloadImfileInsertObject(psDB *dbh, pzDownloadImfileRow *object)
{
    return pzDownloadImfileInsert(dbh, object->exp_name, object->camera, object->telescope, object->class, object->class_id, object->uri, object->fault, object->epoch);
}

bool pzDownloadImfileInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!pzDownloadImfileInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool pzDownloadImfileInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  PZDOWNLOADIMFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, PZDOWNLOADIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", PZDOWNLOADIMFILE_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, PZDOWNLOADIMFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool pzDownloadImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, PZDOWNLOADIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, PZDOWNLOADIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *pzDownloadImfileMetadataFromObject(const pzDownloadImfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_name", PS_DATA_STRING, NULL, object->exp_name)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_name");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, object->camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, object->telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, NULL, object->class)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, object->epoch)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }


    return md;
}

pzDownloadImfileRow *pzDownloadImfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    char* exp_name = psMetadataLookupPtr(&status, md, "exp_name");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_name");
        return false;
    }
    char* camera = psMetadataLookupPtr(&status, md, "camera");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
        return false;
    }
    char* telescope = psMetadataLookupPtr(&status, md, "telescope");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
        return false;
    }
    char* class = psMetadataLookupPtr(&status, md, "class");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class");
        return false;
    }
    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }
    psTime* epoch = psMetadataLookupPtr(&status, md, "epoch");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item epoch");
        return false;
    }

    return pzDownloadImfileRowAlloc(exp_name, camera, telescope, class, class_id, uri, fault, epoch);
}
psArray *pzDownloadImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, PZDOWNLOADIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        pzDownloadImfileRow *object = pzDownloadImfileObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool pzDownloadImfileDeleteObject(psDB *dbh, const pzDownloadImfileRow *object)
{
    psMetadata *where = pzDownloadImfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, PZDOWNLOADIMFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from pzDownloadImfile");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "pzDownloadImfileRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long pzDownloadImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        pzDownloadImfileRow *object = objects->data[i];
        psMetadata *where = pzDownloadImfileMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, PZDOWNLOADIMFILE_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from pzDownloadImfile");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool pzDownloadImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = pzDownloadImfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            PZDOWNLOADIMFILE_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool pzDownloadImfilePrintObject(FILE *stream, pzDownloadImfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = pzDownloadImfileMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void newExpRowFree(newExpRow *object);

newExpRow *newExpRowAlloc(psS64 exp_id, const char *tmp_exp_name, const char *tmp_camera, const char *tmp_telescope, const char *state, const char *workdir, const char *workdir_state, const char *reduction, const char *dvodb, const char *tess_id, const char *end_stage, const char *label, psTime* epoch)
{
    newExpRow       *_object;

    _object = psAlloc(sizeof(newExpRow));
    psMemSetDeallocator(_object, (psFreeFunc)newExpRowFree);

    _object->exp_id = exp_id;
    _object->tmp_exp_name = psStringCopy(tmp_exp_name);
    _object->tmp_camera = psStringCopy(tmp_camera);
    _object->tmp_telescope = psStringCopy(tmp_telescope);
    _object->state = psStringCopy(state);
    _object->workdir = psStringCopy(workdir);
    _object->workdir_state = psStringCopy(workdir_state);
    _object->reduction = psStringCopy(reduction);
    _object->dvodb = psStringCopy(dvodb);
    _object->tess_id = psStringCopy(tess_id);
    _object->end_stage = psStringCopy(end_stage);
    _object->label = psStringCopy(label);
    _object->epoch = psTimeCopy(epoch);

    return _object;
}

static void newExpRowFree(newExpRow *object)
{
    psFree(object->tmp_exp_name);
    psFree(object->tmp_camera);
    psFree(object->tmp_telescope);
    psFree(object->state);
    psFree(object->workdir);
    psFree(object->workdir_state);
    psFree(object->reduction);
    psFree(object->dvodb);
    psFree(object->tess_id);
    psFree(object->end_stage);
    psFree(object->label);
    psFree(object->epoch);
}

bool newExpCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, "Primary Key AUTO_INCREMENT", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tmp_exp_name", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tmp_exp_name");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tmp_camera", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tmp_camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tmp_telescope", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tmp_telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, "destination for output files", "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir_state", PS_DATA_STRING, "key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir_state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "reduction", PS_DATA_STRING, "Reduction class", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item reduction");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "end_stage", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item end_stage");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, NEWEXP_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool newExpDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, NEWEXP_TABLE_NAME);
}

bool newExpInsert(psDB * dbh, psS64 exp_id, const char *tmp_exp_name, const char *tmp_camera, const char *tmp_telescope, const char *state, const char *workdir, const char *workdir_state, const char *reduction, const char *dvodb, const char *tess_id, const char *end_stage, const char *label, psTime* epoch)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tmp_exp_name", PS_DATA_STRING, NULL, tmp_exp_name)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tmp_exp_name");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tmp_camera", PS_DATA_STRING, NULL, tmp_camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tmp_camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tmp_telescope", PS_DATA_STRING, NULL, tmp_telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tmp_telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir_state", PS_DATA_STRING, NULL, workdir_state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir_state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "reduction", PS_DATA_STRING, NULL, reduction)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item reduction");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, dvodb)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, tess_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "end_stage", PS_DATA_STRING, NULL, end_stage)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item end_stage");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, label)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, epoch)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, NEWEXP_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long newExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, NEWEXP_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from newExp");
        return count;

        deleted += count;
    }

    return deleted;
}
bool newExpInsertObject(psDB *dbh, newExpRow *object)
{
    return newExpInsert(dbh, object->exp_id, object->tmp_exp_name, object->tmp_camera, object->tmp_telescope, object->state, object->workdir, object->workdir_state, object->reduction, object->dvodb, object->tess_id, object->end_stage, object->label, object->epoch);
}

bool newExpInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!newExpInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool newExpInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  NEWEXP_TABLE_NAME
    if (!psFitsMoveExtName(fits, NEWEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", NEWEXP_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, NEWEXP_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool newExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, NEWEXP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, NEWEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *newExpMetadataFromObject(const newExpRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, object->exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tmp_exp_name", PS_DATA_STRING, NULL, object->tmp_exp_name)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tmp_exp_name");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tmp_camera", PS_DATA_STRING, NULL, object->tmp_camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tmp_camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tmp_telescope", PS_DATA_STRING, NULL, object->tmp_telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tmp_telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, object->state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, object->workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir_state", PS_DATA_STRING, NULL, object->workdir_state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir_state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "reduction", PS_DATA_STRING, NULL, object->reduction)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item reduction");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, object->dvodb)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, object->tess_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "end_stage", PS_DATA_STRING, NULL, object->end_stage)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item end_stage");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, object->label)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, object->epoch)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }


    return md;
}

newExpRow *newExpObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 exp_id = psMetadataLookupS64(&status, md, "exp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
        return false;
    }
    char* tmp_exp_name = psMetadataLookupPtr(&status, md, "tmp_exp_name");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item tmp_exp_name");
        return false;
    }
    char* tmp_camera = psMetadataLookupPtr(&status, md, "tmp_camera");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item tmp_camera");
        return false;
    }
    char* tmp_telescope = psMetadataLookupPtr(&status, md, "tmp_telescope");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item tmp_telescope");
        return false;
    }
    char* state = psMetadataLookupPtr(&status, md, "state");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item state");
        return false;
    }
    char* workdir = psMetadataLookupPtr(&status, md, "workdir");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
        return false;
    }
    char* workdir_state = psMetadataLookupPtr(&status, md, "workdir_state");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir_state");
        return false;
    }
    char* reduction = psMetadataLookupPtr(&status, md, "reduction");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item reduction");
        return false;
    }
    char* dvodb = psMetadataLookupPtr(&status, md, "dvodb");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dvodb");
        return false;
    }
    char* tess_id = psMetadataLookupPtr(&status, md, "tess_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item tess_id");
        return false;
    }
    char* end_stage = psMetadataLookupPtr(&status, md, "end_stage");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item end_stage");
        return false;
    }
    char* label = psMetadataLookupPtr(&status, md, "label");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item label");
        return false;
    }
    psTime* epoch = psMetadataLookupPtr(&status, md, "epoch");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item epoch");
        return false;
    }

    return newExpRowAlloc(exp_id, tmp_exp_name, tmp_camera, tmp_telescope, state, workdir, workdir_state, reduction, dvodb, tess_id, end_stage, label, epoch);
}
psArray *newExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, NEWEXP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        newExpRow *object = newExpObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool newExpDeleteObject(psDB *dbh, const newExpRow *object)
{
    psMetadata *where = newExpMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, NEWEXP_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from newExp");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "newExpRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long newExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        newExpRow *object = objects->data[i];
        psMetadata *where = newExpMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, NEWEXP_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from newExp");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool newExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = newExpMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            NEWEXP_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool newExpPrintObject(FILE *stream, newExpRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = newExpMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void newImfileRowFree(newImfileRow *object);

newImfileRow *newImfileRowAlloc(psS64 exp_id, const char *tmp_class_id, const char *uri, psTime* epoch)
{
    newImfileRow    *_object;

    _object = psAlloc(sizeof(newImfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)newImfileRowFree);

    _object->exp_id = exp_id;
    _object->tmp_class_id = psStringCopy(tmp_class_id);
    _object->uri = psStringCopy(uri);
    _object->epoch = psTimeCopy(epoch);

    return _object;
}

static void newImfileRowFree(newImfileRow *object)
{
    psFree(object->tmp_class_id);
    psFree(object->uri);
    psFree(object->epoch);
}

bool newImfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, "Primary Key fkey(exp_id) ref newExp(exp_id)", 64)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tmp_class_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tmp_class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, NEWIMFILE_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool newImfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, NEWIMFILE_TABLE_NAME);
}

bool newImfileInsert(psDB * dbh, psS64 exp_id, const char *tmp_class_id, const char *uri, psTime* epoch)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tmp_class_id", PS_DATA_STRING, NULL, tmp_class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tmp_class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, epoch)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, NEWIMFILE_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long newImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, NEWIMFILE_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from newImfile");
        return count;

        deleted += count;
    }

    return deleted;
}
bool newImfileInsertObject(psDB *dbh, newImfileRow *object)
{
    return newImfileInsert(dbh, object->exp_id, object->tmp_class_id, object->uri, object->epoch);
}

bool newImfileInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!newImfileInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool newImfileInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  NEWIMFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, NEWIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", NEWIMFILE_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, NEWIMFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool newImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, NEWIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, NEWIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *newImfileMetadataFromObject(const newImfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, object->exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tmp_class_id", PS_DATA_STRING, NULL, object->tmp_class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tmp_class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, object->epoch)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }


    return md;
}

newImfileRow *newImfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 exp_id = psMetadataLookupS64(&status, md, "exp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
        return false;
    }
    char* tmp_class_id = psMetadataLookupPtr(&status, md, "tmp_class_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item tmp_class_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    psTime* epoch = psMetadataLookupPtr(&status, md, "epoch");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item epoch");
        return false;
    }

    return newImfileRowAlloc(exp_id, tmp_class_id, uri, epoch);
}
psArray *newImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, NEWIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        newImfileRow *object = newImfileObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool newImfileDeleteObject(psDB *dbh, const newImfileRow *object)
{
    psMetadata *where = newImfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, NEWIMFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from newImfile");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "newImfileRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long newImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        newImfileRow *object = objects->data[i];
        psMetadata *where = newImfileMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, NEWIMFILE_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from newImfile");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool newImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = newImfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            NEWIMFILE_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool newImfilePrintObject(FILE *stream, newImfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = newImfileMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void rawExpRowFree(rawExpRow *object);

rawExpRow *rawExpRowAlloc(psS64 exp_id, const char *exp_name, const char *camera, const char *telescope, psTime* dateobs, const char *exp_tag, const char *exp_type, const char *filelevel, const char *workdir, const char *reduction, const char *dvodb, const char *tess_id, const char *end_stage, const char *filter, const char *comment, psF32 airmass, psF64 ra, psF64 decl, psF32 exp_time, psF32 sat_pixel_frac, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 alt, psF64 az, psF32 ccd_temp, psF64 posang, psF32 m1_x, psF32 m1_y, psF32 m1_z, psF32 m1_tip, psF32 m1_tilt, psF32 m2_x, psF32 m2_y, psF32 m2_z, psF32 m2_tip, psF32 m2_tilt, psF32 env_temperature, psF32 env_humidity, psF32 env_wind_speed, psF32 env_wind_dir, psF32 teltemp_m1, psF32 teltemp_m1cell, psF32 teltemp_m2, psF32 teltemp_spider, psF32 teltemp_truss, psF32 teltemp_extra, psF32 pon_time, psF64 user_1, psF64 user_2, psF64 user_3, psF64 user_4, psF64 user_5, const char *object, psF32 solang, const char *hostname, psS16 fault, psTime* epoch)
{
    rawExpRow       *_object;

    _object = psAlloc(sizeof(rawExpRow));
    psMemSetDeallocator(_object, (psFreeFunc)rawExpRowFree);

    _object->exp_id = exp_id;
    _object->exp_name = psStringCopy(exp_name);
    _object->camera = psStringCopy(camera);
    _object->telescope = psStringCopy(telescope);
    _object->dateobs = psTimeCopy(dateobs);
    _object->exp_tag = psStringCopy(exp_tag);
    _object->exp_type = psStringCopy(exp_type);
    _object->filelevel = psStringCopy(filelevel);
    _object->workdir = psStringCopy(workdir);
    _object->reduction = psStringCopy(reduction);
    _object->dvodb = psStringCopy(dvodb);
    _object->tess_id = psStringCopy(tess_id);
    _object->end_stage = psStringCopy(end_stage);
    _object->filter = psStringCopy(filter);
    _object->comment = psStringCopy(comment);
    _object->airmass = airmass;
    _object->ra = ra;
    _object->decl = decl;
    _object->exp_time = exp_time;
    _object->sat_pixel_frac = sat_pixel_frac;
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->bg_mean_stdev = bg_mean_stdev;
    _object->alt = alt;
    _object->az = az;
    _object->ccd_temp = ccd_temp;
    _object->posang = posang;
    _object->m1_x = m1_x;
    _object->m1_y = m1_y;
    _object->m1_z = m1_z;
    _object->m1_tip = m1_tip;
    _object->m1_tilt = m1_tilt;
    _object->m2_x = m2_x;
    _object->m2_y = m2_y;
    _object->m2_z = m2_z;
    _object->m2_tip = m2_tip;
    _object->m2_tilt = m2_tilt;
    _object->env_temperature = env_temperature;
    _object->env_humidity = env_humidity;
    _object->env_wind_speed = env_wind_speed;
    _object->env_wind_dir = env_wind_dir;
    _object->teltemp_m1 = teltemp_m1;
    _object->teltemp_m1cell = teltemp_m1cell;
    _object->teltemp_m2 = teltemp_m2;
    _object->teltemp_spider = teltemp_spider;
    _object->teltemp_truss = teltemp_truss;
    _object->teltemp_extra = teltemp_extra;
    _object->pon_time = pon_time;
    _object->user_1 = user_1;
    _object->user_2 = user_2;
    _object->user_3 = user_3;
    _object->user_4 = user_4;
    _object->user_5 = user_5;
    _object->object = psStringCopy(object);
    _object->solang = solang;
    _object->hostname = psStringCopy(hostname);
    _object->fault = fault;
    _object->epoch = psTimeCopy(epoch);

    return _object;
}

static void rawExpRowFree(rawExpRow *object)
{
    psFree(object->exp_name);
    psFree(object->camera);
    psFree(object->telescope);
    psFree(object->dateobs);
    psFree(object->exp_tag);
    psFree(object->exp_type);
    psFree(object->filelevel);
    psFree(object->workdir);
    psFree(object->reduction);
    psFree(object->dvodb);
    psFree(object->tess_id);
    psFree(object->end_stage);
    psFree(object->filter);
    psFree(object->comment);
    psFree(object->object);
    psFree(object->hostname);
    psFree(object->epoch);
}

bool rawExpCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, "Primary Key fkey(exp_id) ref newExp(exp_id)", 64)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_name", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_name");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dateobs", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filelevel", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filelevel");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, "destination for output files", "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "reduction", PS_DATA_STRING, "Reduction class", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item reduction");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "end_stage", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item end_stage");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "comment", PS_DATA_STRING, NULL, "80")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item comment");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "sat_pixel_frac", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sat_pixel_frac");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "alt", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item alt");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "az", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item posang");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_x", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_x");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_y", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_y");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_z", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_z");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_tip", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_tip");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_tilt", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_tilt");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_x", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_x");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_y", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_y");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_z", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_z");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_tip", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_tip");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_tilt", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_tilt");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "env_temperature", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item env_temperature");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "env_humidity", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item env_humidity");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "env_wind_speed", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item env_wind_speed");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "env_wind_dir", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item env_wind_dir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_m1", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_m1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_m1cell", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_m1cell");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_m2", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_m2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_spider", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_spider");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_truss", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_truss");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_extra", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_extra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "pon_time", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item pon_time");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "object", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item object");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "solang", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item solang");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "hostname", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item hostname");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, RAWEXP_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool rawExpDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, RAWEXP_TABLE_NAME);
}

bool rawExpInsert(psDB * dbh, psS64 exp_id, const char *exp_name, const char *camera, const char *telescope, psTime* dateobs, const char *exp_tag, const char *exp_type, const char *filelevel, const char *workdir, const char *reduction, const char *dvodb, const char *tess_id, const char *end_stage, const char *filter, const char *comment, psF32 airmass, psF64 ra, psF64 decl, psF32 exp_time, psF32 sat_pixel_frac, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 alt, psF64 az, psF32 ccd_temp, psF64 posang, psF32 m1_x, psF32 m1_y, psF32 m1_z, psF32 m1_tip, psF32 m1_tilt, psF32 m2_x, psF32 m2_y, psF32 m2_z, psF32 m2_tip, psF32 m2_tilt, psF32 env_temperature, psF32 env_humidity, psF32 env_wind_speed, psF32 env_wind_dir, psF32 teltemp_m1, psF32 teltemp_m1cell, psF32 teltemp_m2, psF32 teltemp_spider, psF32 teltemp_truss, psF32 teltemp_extra, psF32 pon_time, psF64 user_1, psF64 user_2, psF64 user_3, psF64 user_4, psF64 user_5, const char *object, psF32 solang, const char *hostname, psS16 fault, psTime* epoch)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_name", PS_DATA_STRING, NULL, exp_name)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_name");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dateobs", PS_DATA_TIME, NULL, dateobs)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, exp_type)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filelevel", PS_DATA_STRING, NULL, filelevel)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filelevel");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "reduction", PS_DATA_STRING, NULL, reduction)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item reduction");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, dvodb)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, tess_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "end_stage", PS_DATA_STRING, NULL, end_stage)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item end_stage");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, filter)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "comment", PS_DATA_STRING, NULL, comment)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item comment");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass", PS_DATA_F32, NULL, airmass)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra", PS_DATA_F64, NULL, ra)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl", PS_DATA_F64, NULL, decl)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time", PS_DATA_F32, NULL, exp_time)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "sat_pixel_frac", PS_DATA_F32, NULL, sat_pixel_frac)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sat_pixel_frac");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "alt", PS_DATA_F64, NULL, alt)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item alt");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "az", PS_DATA_F64, NULL, az)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp", PS_DATA_F32, NULL, ccd_temp)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang", PS_DATA_F64, NULL, posang)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item posang");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_x", PS_DATA_F32, NULL, m1_x)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_x");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_y", PS_DATA_F32, NULL, m1_y)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_y");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_z", PS_DATA_F32, NULL, m1_z)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_z");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_tip", PS_DATA_F32, NULL, m1_tip)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_tip");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_tilt", PS_DATA_F32, NULL, m1_tilt)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_tilt");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_x", PS_DATA_F32, NULL, m2_x)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_x");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_y", PS_DATA_F32, NULL, m2_y)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_y");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_z", PS_DATA_F32, NULL, m2_z)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_z");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_tip", PS_DATA_F32, NULL, m2_tip)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_tip");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_tilt", PS_DATA_F32, NULL, m2_tilt)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_tilt");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "env_temperature", PS_DATA_F32, NULL, env_temperature)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item env_temperature");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "env_humidity", PS_DATA_F32, NULL, env_humidity)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item env_humidity");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "env_wind_speed", PS_DATA_F32, NULL, env_wind_speed)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item env_wind_speed");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "env_wind_dir", PS_DATA_F32, NULL, env_wind_dir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item env_wind_dir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_m1", PS_DATA_F32, NULL, teltemp_m1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_m1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_m1cell", PS_DATA_F32, NULL, teltemp_m1cell)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_m1cell");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_m2", PS_DATA_F32, NULL, teltemp_m2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_m2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_spider", PS_DATA_F32, NULL, teltemp_spider)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_spider");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_truss", PS_DATA_F32, NULL, teltemp_truss)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_truss");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_extra", PS_DATA_F32, NULL, teltemp_extra)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_extra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "pon_time", PS_DATA_F32, NULL, pon_time)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item pon_time");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, user_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, user_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, user_3)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, user_4)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, user_5)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "object", PS_DATA_STRING, NULL, object)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item object");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "solang", PS_DATA_F32, NULL, solang)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item solang");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "hostname", PS_DATA_STRING, NULL, hostname)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item hostname");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, epoch)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, RAWEXP_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long rawExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, RAWEXP_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from rawExp");
        return count;

        deleted += count;
    }

    return deleted;
}
bool rawExpInsertObject(psDB *dbh, rawExpRow *object)
{
    return rawExpInsert(dbh, object->exp_id, object->exp_name, object->camera, object->telescope, object->dateobs, object->exp_tag, object->exp_type, object->filelevel, object->workdir, object->reduction, object->dvodb, object->tess_id, object->end_stage, object->filter, object->comment, object->airmass, object->ra, object->decl, object->exp_time, object->sat_pixel_frac, object->bg, object->bg_stdev, object->bg_mean_stdev, object->alt, object->az, object->ccd_temp, object->posang, object->m1_x, object->m1_y, object->m1_z, object->m1_tip, object->m1_tilt, object->m2_x, object->m2_y, object->m2_z, object->m2_tip, object->m2_tilt, object->env_temperature, object->env_humidity, object->env_wind_speed, object->env_wind_dir, object->teltemp_m1, object->teltemp_m1cell, object->teltemp_m2, object->teltemp_spider, object->teltemp_truss, object->teltemp_extra, object->pon_time, object->user_1, object->user_2, object->user_3, object->user_4, object->user_5, object->object, object->solang, object->hostname, object->fault, object->epoch);
}

bool rawExpInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!rawExpInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool rawExpInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  RAWEXP_TABLE_NAME
    if (!psFitsMoveExtName(fits, RAWEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", RAWEXP_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, RAWEXP_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool rawExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, RAWEXP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, RAWEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *rawExpMetadataFromObject(const rawExpRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, object->exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_name", PS_DATA_STRING, NULL, object->exp_name)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_name");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, object->camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, object->telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dateobs", PS_DATA_TIME, NULL, object->dateobs)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, object->exp_type)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filelevel", PS_DATA_STRING, NULL, object->filelevel)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filelevel");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, object->workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "reduction", PS_DATA_STRING, NULL, object->reduction)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item reduction");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, object->dvodb)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, object->tess_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "end_stage", PS_DATA_STRING, NULL, object->end_stage)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item end_stage");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, object->filter)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "comment", PS_DATA_STRING, NULL, object->comment)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item comment");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass", PS_DATA_F32, NULL, object->airmass)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra", PS_DATA_F64, NULL, object->ra)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl", PS_DATA_F64, NULL, object->decl)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time", PS_DATA_F32, NULL, object->exp_time)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "sat_pixel_frac", PS_DATA_F32, NULL, object->sat_pixel_frac)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sat_pixel_frac");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "alt", PS_DATA_F64, NULL, object->alt)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item alt");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "az", PS_DATA_F64, NULL, object->az)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp", PS_DATA_F32, NULL, object->ccd_temp)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang", PS_DATA_F64, NULL, object->posang)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item posang");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_x", PS_DATA_F32, NULL, object->m1_x)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_x");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_y", PS_DATA_F32, NULL, object->m1_y)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_y");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_z", PS_DATA_F32, NULL, object->m1_z)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_z");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_tip", PS_DATA_F32, NULL, object->m1_tip)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_tip");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_tilt", PS_DATA_F32, NULL, object->m1_tilt)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_tilt");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_x", PS_DATA_F32, NULL, object->m2_x)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_x");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_y", PS_DATA_F32, NULL, object->m2_y)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_y");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_z", PS_DATA_F32, NULL, object->m2_z)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_z");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_tip", PS_DATA_F32, NULL, object->m2_tip)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_tip");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_tilt", PS_DATA_F32, NULL, object->m2_tilt)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_tilt");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "env_temperature", PS_DATA_F32, NULL, object->env_temperature)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item env_temperature");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "env_humidity", PS_DATA_F32, NULL, object->env_humidity)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item env_humidity");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "env_wind_speed", PS_DATA_F32, NULL, object->env_wind_speed)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item env_wind_speed");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "env_wind_dir", PS_DATA_F32, NULL, object->env_wind_dir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item env_wind_dir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_m1", PS_DATA_F32, NULL, object->teltemp_m1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_m1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_m1cell", PS_DATA_F32, NULL, object->teltemp_m1cell)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_m1cell");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_m2", PS_DATA_F32, NULL, object->teltemp_m2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_m2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_spider", PS_DATA_F32, NULL, object->teltemp_spider)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_spider");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_truss", PS_DATA_F32, NULL, object->teltemp_truss)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_truss");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_extra", PS_DATA_F32, NULL, object->teltemp_extra)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_extra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "pon_time", PS_DATA_F32, NULL, object->pon_time)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item pon_time");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, object->user_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, object->user_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, object->user_3)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, object->user_4)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, object->user_5)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "object", PS_DATA_STRING, NULL, object->object)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item object");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "solang", PS_DATA_F32, NULL, object->solang)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item solang");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "hostname", PS_DATA_STRING, NULL, object->hostname)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item hostname");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, object->epoch)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }


    return md;
}

rawExpRow *rawExpObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 exp_id = psMetadataLookupS64(&status, md, "exp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
        return false;
    }
    char* exp_name = psMetadataLookupPtr(&status, md, "exp_name");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_name");
        return false;
    }
    char* camera = psMetadataLookupPtr(&status, md, "camera");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
        return false;
    }
    char* telescope = psMetadataLookupPtr(&status, md, "telescope");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
        return false;
    }
    psTime* dateobs = psMetadataLookupPtr(&status, md, "dateobs");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dateobs");
        return false;
    }
    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
        return false;
    }
    char* exp_type = psMetadataLookupPtr(&status, md, "exp_type");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
        return false;
    }
    char* filelevel = psMetadataLookupPtr(&status, md, "filelevel");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filelevel");
        return false;
    }
    char* workdir = psMetadataLookupPtr(&status, md, "workdir");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
        return false;
    }
    char* reduction = psMetadataLookupPtr(&status, md, "reduction");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item reduction");
        return false;
    }
    char* dvodb = psMetadataLookupPtr(&status, md, "dvodb");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dvodb");
        return false;
    }
    char* tess_id = psMetadataLookupPtr(&status, md, "tess_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item tess_id");
        return false;
    }
    char* end_stage = psMetadataLookupPtr(&status, md, "end_stage");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item end_stage");
        return false;
    }
    char* filter = psMetadataLookupPtr(&status, md, "filter");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
        return false;
    }
    char* comment = psMetadataLookupPtr(&status, md, "comment");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item comment");
        return false;
    }
    psF32 airmass = psMetadataLookupF32(&status, md, "airmass");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass");
        return false;
    }
    psF64 ra = psMetadataLookupF64(&status, md, "ra");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
        return false;
    }
    psF64 decl = psMetadataLookupF64(&status, md, "decl");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
        return false;
    }
    psF32 exp_time = psMetadataLookupF32(&status, md, "exp_time");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time");
        return false;
    }
    psF32 sat_pixel_frac = psMetadataLookupF32(&status, md, "sat_pixel_frac");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sat_pixel_frac");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
        return false;
    }
    psF64 alt = psMetadataLookupF64(&status, md, "alt");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item alt");
        return false;
    }
    psF64 az = psMetadataLookupF64(&status, md, "az");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item az");
        return false;
    }
    psF32 ccd_temp = psMetadataLookupF32(&status, md, "ccd_temp");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ccd_temp");
        return false;
    }
    psF64 posang = psMetadataLookupF64(&status, md, "posang");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item posang");
        return false;
    }
    psF32 m1_x = psMetadataLookupF32(&status, md, "m1_x");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item m1_x");
        return false;
    }
    psF32 m1_y = psMetadataLookupF32(&status, md, "m1_y");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item m1_y");
        return false;
    }
    psF32 m1_z = psMetadataLookupF32(&status, md, "m1_z");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item m1_z");
        return false;
    }
    psF32 m1_tip = psMetadataLookupF32(&status, md, "m1_tip");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item m1_tip");
        return false;
    }
    psF32 m1_tilt = psMetadataLookupF32(&status, md, "m1_tilt");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item m1_tilt");
        return false;
    }
    psF32 m2_x = psMetadataLookupF32(&status, md, "m2_x");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item m2_x");
        return false;
    }
    psF32 m2_y = psMetadataLookupF32(&status, md, "m2_y");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item m2_y");
        return false;
    }
    psF32 m2_z = psMetadataLookupF32(&status, md, "m2_z");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item m2_z");
        return false;
    }
    psF32 m2_tip = psMetadataLookupF32(&status, md, "m2_tip");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item m2_tip");
        return false;
    }
    psF32 m2_tilt = psMetadataLookupF32(&status, md, "m2_tilt");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item m2_tilt");
        return false;
    }
    psF32 env_temperature = psMetadataLookupF32(&status, md, "env_temperature");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item env_temperature");
        return false;
    }
    psF32 env_humidity = psMetadataLookupF32(&status, md, "env_humidity");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item env_humidity");
        return false;
    }
    psF32 env_wind_speed = psMetadataLookupF32(&status, md, "env_wind_speed");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item env_wind_speed");
        return false;
    }
    psF32 env_wind_dir = psMetadataLookupF32(&status, md, "env_wind_dir");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item env_wind_dir");
        return false;
    }
    psF32 teltemp_m1 = psMetadataLookupF32(&status, md, "teltemp_m1");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item teltemp_m1");
        return false;
    }
    psF32 teltemp_m1cell = psMetadataLookupF32(&status, md, "teltemp_m1cell");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item teltemp_m1cell");
        return false;
    }
    psF32 teltemp_m2 = psMetadataLookupF32(&status, md, "teltemp_m2");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item teltemp_m2");
        return false;
    }
    psF32 teltemp_spider = psMetadataLookupF32(&status, md, "teltemp_spider");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item teltemp_spider");
        return false;
    }
    psF32 teltemp_truss = psMetadataLookupF32(&status, md, "teltemp_truss");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item teltemp_truss");
        return false;
    }
    psF32 teltemp_extra = psMetadataLookupF32(&status, md, "teltemp_extra");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item teltemp_extra");
        return false;
    }
    psF32 pon_time = psMetadataLookupF32(&status, md, "pon_time");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item pon_time");
        return false;
    }
    psF64 user_1 = psMetadataLookupF64(&status, md, "user_1");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_1");
        return false;
    }
    psF64 user_2 = psMetadataLookupF64(&status, md, "user_2");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_2");
        return false;
    }
    psF64 user_3 = psMetadataLookupF64(&status, md, "user_3");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_3");
        return false;
    }
    psF64 user_4 = psMetadataLookupF64(&status, md, "user_4");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_4");
        return false;
    }
    psF64 user_5 = psMetadataLookupF64(&status, md, "user_5");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_5");
        return false;
    }
    char* object = psMetadataLookupPtr(&status, md, "object");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item object");
        return false;
    }
    psF32 solang = psMetadataLookupF32(&status, md, "solang");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item solang");
        return false;
    }
    char* hostname = psMetadataLookupPtr(&status, md, "hostname");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item hostname");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }
    psTime* epoch = psMetadataLookupPtr(&status, md, "epoch");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item epoch");
        return false;
    }

    return rawExpRowAlloc(exp_id, exp_name, camera, telescope, dateobs, exp_tag, exp_type, filelevel, workdir, reduction, dvodb, tess_id, end_stage, filter, comment, airmass, ra, decl, exp_time, sat_pixel_frac, bg, bg_stdev, bg_mean_stdev, alt, az, ccd_temp, posang, m1_x, m1_y, m1_z, m1_tip, m1_tilt, m2_x, m2_y, m2_z, m2_tip, m2_tilt, env_temperature, env_humidity, env_wind_speed, env_wind_dir, teltemp_m1, teltemp_m1cell, teltemp_m2, teltemp_spider, teltemp_truss, teltemp_extra, pon_time, user_1, user_2, user_3, user_4, user_5, object, solang, hostname, fault, epoch);
}
psArray *rawExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, RAWEXP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        rawExpRow *object = rawExpObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool rawExpDeleteObject(psDB *dbh, const rawExpRow *object)
{
    psMetadata *where = rawExpMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, RAWEXP_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from rawExp");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "rawExpRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long rawExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        rawExpRow *object = objects->data[i];
        psMetadata *where = rawExpMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, RAWEXP_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from rawExp");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool rawExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = rawExpMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            RAWEXP_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool rawExpPrintObject(FILE *stream, rawExpRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = rawExpMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void rawImfileRowFree(rawImfileRow *object);

rawImfileRow *rawImfileRowAlloc(psS64 exp_id, const char *exp_name, const char *camera, const char *telescope, psTime* dateobs, const char *tmp_class_id, const char *class_id, const char *uri, const char *exp_type, const char *filelevel, const char *filter, const char *comment, psF32 airmass, psF64 ra, psF64 decl, psF32 exp_time, psF32 sat_pixel_frac, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 alt, psF64 az, psF32 ccd_temp, psF64 posang, psF32 m1_x, psF32 m1_y, psF32 m1_z, psF32 m1_tip, psF32 m1_tilt, psF32 m2_x, psF32 m2_y, psF32 m2_z, psF32 m2_tip, psF32 m2_tilt, psF32 env_temperature, psF32 env_humidity, psF32 env_wind_speed, psF32 env_wind_dir, psF32 teltemp_m1, psF32 teltemp_m1cell, psF32 teltemp_m2, psF32 teltemp_spider, psF32 teltemp_truss, psF32 teltemp_extra, psF32 pon_time, psF64 user_1, psF64 user_2, psF64 user_3, psF64 user_4, psF64 user_5, const char *object, const char *hostname, psS16 fault, psTime* epoch)
{
    rawImfileRow    *_object;

    _object = psAlloc(sizeof(rawImfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)rawImfileRowFree);

    _object->exp_id = exp_id;
    _object->exp_name = psStringCopy(exp_name);
    _object->camera = psStringCopy(camera);
    _object->telescope = psStringCopy(telescope);
    _object->dateobs = psTimeCopy(dateobs);
    _object->tmp_class_id = psStringCopy(tmp_class_id);
    _object->class_id = psStringCopy(class_id);
    _object->uri = psStringCopy(uri);
    _object->exp_type = psStringCopy(exp_type);
    _object->filelevel = psStringCopy(filelevel);
    _object->filter = psStringCopy(filter);
    _object->comment = psStringCopy(comment);
    _object->airmass = airmass;
    _object->ra = ra;
    _object->decl = decl;
    _object->exp_time = exp_time;
    _object->sat_pixel_frac = sat_pixel_frac;
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->bg_mean_stdev = bg_mean_stdev;
    _object->alt = alt;
    _object->az = az;
    _object->ccd_temp = ccd_temp;
    _object->posang = posang;
    _object->m1_x = m1_x;
    _object->m1_y = m1_y;
    _object->m1_z = m1_z;
    _object->m1_tip = m1_tip;
    _object->m1_tilt = m1_tilt;
    _object->m2_x = m2_x;
    _object->m2_y = m2_y;
    _object->m2_z = m2_z;
    _object->m2_tip = m2_tip;
    _object->m2_tilt = m2_tilt;
    _object->env_temperature = env_temperature;
    _object->env_humidity = env_humidity;
    _object->env_wind_speed = env_wind_speed;
    _object->env_wind_dir = env_wind_dir;
    _object->teltemp_m1 = teltemp_m1;
    _object->teltemp_m1cell = teltemp_m1cell;
    _object->teltemp_m2 = teltemp_m2;
    _object->teltemp_spider = teltemp_spider;
    _object->teltemp_truss = teltemp_truss;
    _object->teltemp_extra = teltemp_extra;
    _object->pon_time = pon_time;
    _object->user_1 = user_1;
    _object->user_2 = user_2;
    _object->user_3 = user_3;
    _object->user_4 = user_4;
    _object->user_5 = user_5;
    _object->object = psStringCopy(object);
    _object->hostname = psStringCopy(hostname);
    _object->fault = fault;
    _object->epoch = psTimeCopy(epoch);

    return _object;
}

static void rawImfileRowFree(rawImfileRow *object)
{
    psFree(object->exp_name);
    psFree(object->camera);
    psFree(object->telescope);
    psFree(object->dateobs);
    psFree(object->tmp_class_id);
    psFree(object->class_id);
    psFree(object->uri);
    psFree(object->exp_type);
    psFree(object->filelevel);
    psFree(object->filter);
    psFree(object->comment);
    psFree(object->object);
    psFree(object->hostname);
    psFree(object->epoch);
}

bool rawImfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, "Primary Key fkey(exp_id, tmp_class_id) ref newImfile(exp_id, tmp_class_id)", 64)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_name", PS_DATA_STRING, "UINDEX(exp_id, tmp_class_id)", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_name");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dateobs", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tmp_class_id", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tmp_class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filelevel", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filelevel");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "comment", PS_DATA_STRING, NULL, "80")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item comment");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "sat_pixel_frac", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sat_pixel_frac");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "alt", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item alt");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "az", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item posang");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_x", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_x");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_y", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_y");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_z", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_z");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_tip", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_tip");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_tilt", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_tilt");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_x", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_x");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_y", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_y");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_z", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_z");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_tip", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_tip");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_tilt", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_tilt");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "env_temperature", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item env_temperature");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "env_humidity", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item env_humidity");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "env_wind_speed", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item env_wind_speed");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "env_wind_dir", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item env_wind_dir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_m1", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_m1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_m1cell", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_m1cell");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_m2", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_m2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_spider", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_spider");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_truss", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_truss");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_extra", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_extra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "pon_time", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item pon_time");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "object", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item object");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "hostname", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item hostname");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, RAWIMFILE_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool rawImfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, RAWIMFILE_TABLE_NAME);
}

bool rawImfileInsert(psDB * dbh, psS64 exp_id, const char *exp_name, const char *camera, const char *telescope, psTime* dateobs, const char *tmp_class_id, const char *class_id, const char *uri, const char *exp_type, const char *filelevel, const char *filter, const char *comment, psF32 airmass, psF64 ra, psF64 decl, psF32 exp_time, psF32 sat_pixel_frac, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 alt, psF64 az, psF32 ccd_temp, psF64 posang, psF32 m1_x, psF32 m1_y, psF32 m1_z, psF32 m1_tip, psF32 m1_tilt, psF32 m2_x, psF32 m2_y, psF32 m2_z, psF32 m2_tip, psF32 m2_tilt, psF32 env_temperature, psF32 env_humidity, psF32 env_wind_speed, psF32 env_wind_dir, psF32 teltemp_m1, psF32 teltemp_m1cell, psF32 teltemp_m2, psF32 teltemp_spider, psF32 teltemp_truss, psF32 teltemp_extra, psF32 pon_time, psF64 user_1, psF64 user_2, psF64 user_3, psF64 user_4, psF64 user_5, const char *object, const char *hostname, psS16 fault, psTime* epoch)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_name", PS_DATA_STRING, NULL, exp_name)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_name");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dateobs", PS_DATA_TIME, NULL, dateobs)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tmp_class_id", PS_DATA_STRING, NULL, tmp_class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tmp_class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, exp_type)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filelevel", PS_DATA_STRING, NULL, filelevel)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filelevel");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, filter)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "comment", PS_DATA_STRING, NULL, comment)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item comment");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass", PS_DATA_F32, NULL, airmass)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra", PS_DATA_F64, NULL, ra)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl", PS_DATA_F64, NULL, decl)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time", PS_DATA_F32, NULL, exp_time)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "sat_pixel_frac", PS_DATA_F32, NULL, sat_pixel_frac)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sat_pixel_frac");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "alt", PS_DATA_F64, NULL, alt)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item alt");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "az", PS_DATA_F64, NULL, az)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp", PS_DATA_F32, NULL, ccd_temp)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang", PS_DATA_F64, NULL, posang)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item posang");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_x", PS_DATA_F32, NULL, m1_x)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_x");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_y", PS_DATA_F32, NULL, m1_y)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_y");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_z", PS_DATA_F32, NULL, m1_z)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_z");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_tip", PS_DATA_F32, NULL, m1_tip)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_tip");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_tilt", PS_DATA_F32, NULL, m1_tilt)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_tilt");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_x", PS_DATA_F32, NULL, m2_x)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_x");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_y", PS_DATA_F32, NULL, m2_y)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_y");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_z", PS_DATA_F32, NULL, m2_z)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_z");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_tip", PS_DATA_F32, NULL, m2_tip)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_tip");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_tilt", PS_DATA_F32, NULL, m2_tilt)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_tilt");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "env_temperature", PS_DATA_F32, NULL, env_temperature)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item env_temperature");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "env_humidity", PS_DATA_F32, NULL, env_humidity)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item env_humidity");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "env_wind_speed", PS_DATA_F32, NULL, env_wind_speed)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item env_wind_speed");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "env_wind_dir", PS_DATA_F32, NULL, env_wind_dir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item env_wind_dir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_m1", PS_DATA_F32, NULL, teltemp_m1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_m1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_m1cell", PS_DATA_F32, NULL, teltemp_m1cell)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_m1cell");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_m2", PS_DATA_F32, NULL, teltemp_m2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_m2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_spider", PS_DATA_F32, NULL, teltemp_spider)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_spider");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_truss", PS_DATA_F32, NULL, teltemp_truss)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_truss");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_extra", PS_DATA_F32, NULL, teltemp_extra)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_extra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "pon_time", PS_DATA_F32, NULL, pon_time)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item pon_time");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, user_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, user_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, user_3)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, user_4)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, user_5)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "object", PS_DATA_STRING, NULL, object)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item object");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "hostname", PS_DATA_STRING, NULL, hostname)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item hostname");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, epoch)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, RAWIMFILE_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long rawImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, RAWIMFILE_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from rawImfile");
        return count;

        deleted += count;
    }

    return deleted;
}
bool rawImfileInsertObject(psDB *dbh, rawImfileRow *object)
{
    return rawImfileInsert(dbh, object->exp_id, object->exp_name, object->camera, object->telescope, object->dateobs, object->tmp_class_id, object->class_id, object->uri, object->exp_type, object->filelevel, object->filter, object->comment, object->airmass, object->ra, object->decl, object->exp_time, object->sat_pixel_frac, object->bg, object->bg_stdev, object->bg_mean_stdev, object->alt, object->az, object->ccd_temp, object->posang, object->m1_x, object->m1_y, object->m1_z, object->m1_tip, object->m1_tilt, object->m2_x, object->m2_y, object->m2_z, object->m2_tip, object->m2_tilt, object->env_temperature, object->env_humidity, object->env_wind_speed, object->env_wind_dir, object->teltemp_m1, object->teltemp_m1cell, object->teltemp_m2, object->teltemp_spider, object->teltemp_truss, object->teltemp_extra, object->pon_time, object->user_1, object->user_2, object->user_3, object->user_4, object->user_5, object->object, object->hostname, object->fault, object->epoch);
}

bool rawImfileInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!rawImfileInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool rawImfileInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  RAWIMFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, RAWIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", RAWIMFILE_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, RAWIMFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool rawImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, RAWIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, RAWIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *rawImfileMetadataFromObject(const rawImfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, object->exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_name", PS_DATA_STRING, NULL, object->exp_name)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_name");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, object->camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, object->telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dateobs", PS_DATA_TIME, NULL, object->dateobs)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tmp_class_id", PS_DATA_STRING, NULL, object->tmp_class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tmp_class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, object->exp_type)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filelevel", PS_DATA_STRING, NULL, object->filelevel)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filelevel");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, object->filter)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "comment", PS_DATA_STRING, NULL, object->comment)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item comment");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass", PS_DATA_F32, NULL, object->airmass)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra", PS_DATA_F64, NULL, object->ra)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl", PS_DATA_F64, NULL, object->decl)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time", PS_DATA_F32, NULL, object->exp_time)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "sat_pixel_frac", PS_DATA_F32, NULL, object->sat_pixel_frac)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sat_pixel_frac");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "alt", PS_DATA_F64, NULL, object->alt)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item alt");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "az", PS_DATA_F64, NULL, object->az)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp", PS_DATA_F32, NULL, object->ccd_temp)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang", PS_DATA_F64, NULL, object->posang)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item posang");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_x", PS_DATA_F32, NULL, object->m1_x)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_x");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_y", PS_DATA_F32, NULL, object->m1_y)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_y");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_z", PS_DATA_F32, NULL, object->m1_z)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_z");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_tip", PS_DATA_F32, NULL, object->m1_tip)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_tip");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m1_tilt", PS_DATA_F32, NULL, object->m1_tilt)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m1_tilt");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_x", PS_DATA_F32, NULL, object->m2_x)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_x");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_y", PS_DATA_F32, NULL, object->m2_y)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_y");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_z", PS_DATA_F32, NULL, object->m2_z)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_z");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_tip", PS_DATA_F32, NULL, object->m2_tip)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_tip");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "m2_tilt", PS_DATA_F32, NULL, object->m2_tilt)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item m2_tilt");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "env_temperature", PS_DATA_F32, NULL, object->env_temperature)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item env_temperature");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "env_humidity", PS_DATA_F32, NULL, object->env_humidity)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item env_humidity");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "env_wind_speed", PS_DATA_F32, NULL, object->env_wind_speed)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item env_wind_speed");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "env_wind_dir", PS_DATA_F32, NULL, object->env_wind_dir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item env_wind_dir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_m1", PS_DATA_F32, NULL, object->teltemp_m1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_m1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_m1cell", PS_DATA_F32, NULL, object->teltemp_m1cell)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_m1cell");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_m2", PS_DATA_F32, NULL, object->teltemp_m2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_m2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_spider", PS_DATA_F32, NULL, object->teltemp_spider)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_spider");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_truss", PS_DATA_F32, NULL, object->teltemp_truss)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_truss");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "teltemp_extra", PS_DATA_F32, NULL, object->teltemp_extra)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item teltemp_extra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "pon_time", PS_DATA_F32, NULL, object->pon_time)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item pon_time");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, object->user_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, object->user_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, object->user_3)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, object->user_4)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, object->user_5)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "object", PS_DATA_STRING, NULL, object->object)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item object");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "hostname", PS_DATA_STRING, NULL, object->hostname)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item hostname");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, object->epoch)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }


    return md;
}

rawImfileRow *rawImfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 exp_id = psMetadataLookupS64(&status, md, "exp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
        return false;
    }
    char* exp_name = psMetadataLookupPtr(&status, md, "exp_name");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_name");
        return false;
    }
    char* camera = psMetadataLookupPtr(&status, md, "camera");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
        return false;
    }
    char* telescope = psMetadataLookupPtr(&status, md, "telescope");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
        return false;
    }
    psTime* dateobs = psMetadataLookupPtr(&status, md, "dateobs");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dateobs");
        return false;
    }
    char* tmp_class_id = psMetadataLookupPtr(&status, md, "tmp_class_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item tmp_class_id");
        return false;
    }
    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    char* exp_type = psMetadataLookupPtr(&status, md, "exp_type");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
        return false;
    }
    char* filelevel = psMetadataLookupPtr(&status, md, "filelevel");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filelevel");
        return false;
    }
    char* filter = psMetadataLookupPtr(&status, md, "filter");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
        return false;
    }
    char* comment = psMetadataLookupPtr(&status, md, "comment");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item comment");
        return false;
    }
    psF32 airmass = psMetadataLookupF32(&status, md, "airmass");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass");
        return false;
    }
    psF64 ra = psMetadataLookupF64(&status, md, "ra");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
        return false;
    }
    psF64 decl = psMetadataLookupF64(&status, md, "decl");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
        return false;
    }
    psF32 exp_time = psMetadataLookupF32(&status, md, "exp_time");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time");
        return false;
    }
    psF32 sat_pixel_frac = psMetadataLookupF32(&status, md, "sat_pixel_frac");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sat_pixel_frac");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
        return false;
    }
    psF64 alt = psMetadataLookupF64(&status, md, "alt");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item alt");
        return false;
    }
    psF64 az = psMetadataLookupF64(&status, md, "az");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item az");
        return false;
    }
    psF32 ccd_temp = psMetadataLookupF32(&status, md, "ccd_temp");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ccd_temp");
        return false;
    }
    psF64 posang = psMetadataLookupF64(&status, md, "posang");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item posang");
        return false;
    }
    psF32 m1_x = psMetadataLookupF32(&status, md, "m1_x");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item m1_x");
        return false;
    }
    psF32 m1_y = psMetadataLookupF32(&status, md, "m1_y");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item m1_y");
        return false;
    }
    psF32 m1_z = psMetadataLookupF32(&status, md, "m1_z");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item m1_z");
        return false;
    }
    psF32 m1_tip = psMetadataLookupF32(&status, md, "m1_tip");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item m1_tip");
        return false;
    }
    psF32 m1_tilt = psMetadataLookupF32(&status, md, "m1_tilt");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item m1_tilt");
        return false;
    }
    psF32 m2_x = psMetadataLookupF32(&status, md, "m2_x");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item m2_x");
        return false;
    }
    psF32 m2_y = psMetadataLookupF32(&status, md, "m2_y");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item m2_y");
        return false;
    }
    psF32 m2_z = psMetadataLookupF32(&status, md, "m2_z");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item m2_z");
        return false;
    }
    psF32 m2_tip = psMetadataLookupF32(&status, md, "m2_tip");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item m2_tip");
        return false;
    }
    psF32 m2_tilt = psMetadataLookupF32(&status, md, "m2_tilt");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item m2_tilt");
        return false;
    }
    psF32 env_temperature = psMetadataLookupF32(&status, md, "env_temperature");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item env_temperature");
        return false;
    }
    psF32 env_humidity = psMetadataLookupF32(&status, md, "env_humidity");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item env_humidity");
        return false;
    }
    psF32 env_wind_speed = psMetadataLookupF32(&status, md, "env_wind_speed");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item env_wind_speed");
        return false;
    }
    psF32 env_wind_dir = psMetadataLookupF32(&status, md, "env_wind_dir");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item env_wind_dir");
        return false;
    }
    psF32 teltemp_m1 = psMetadataLookupF32(&status, md, "teltemp_m1");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item teltemp_m1");
        return false;
    }
    psF32 teltemp_m1cell = psMetadataLookupF32(&status, md, "teltemp_m1cell");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item teltemp_m1cell");
        return false;
    }
    psF32 teltemp_m2 = psMetadataLookupF32(&status, md, "teltemp_m2");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item teltemp_m2");
        return false;
    }
    psF32 teltemp_spider = psMetadataLookupF32(&status, md, "teltemp_spider");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item teltemp_spider");
        return false;
    }
    psF32 teltemp_truss = psMetadataLookupF32(&status, md, "teltemp_truss");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item teltemp_truss");
        return false;
    }
    psF32 teltemp_extra = psMetadataLookupF32(&status, md, "teltemp_extra");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item teltemp_extra");
        return false;
    }
    psF32 pon_time = psMetadataLookupF32(&status, md, "pon_time");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item pon_time");
        return false;
    }
    psF64 user_1 = psMetadataLookupF64(&status, md, "user_1");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_1");
        return false;
    }
    psF64 user_2 = psMetadataLookupF64(&status, md, "user_2");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_2");
        return false;
    }
    psF64 user_3 = psMetadataLookupF64(&status, md, "user_3");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_3");
        return false;
    }
    psF64 user_4 = psMetadataLookupF64(&status, md, "user_4");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_4");
        return false;
    }
    psF64 user_5 = psMetadataLookupF64(&status, md, "user_5");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_5");
        return false;
    }
    char* object = psMetadataLookupPtr(&status, md, "object");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item object");
        return false;
    }
    char* hostname = psMetadataLookupPtr(&status, md, "hostname");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item hostname");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }
    psTime* epoch = psMetadataLookupPtr(&status, md, "epoch");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item epoch");
        return false;
    }

    return rawImfileRowAlloc(exp_id, exp_name, camera, telescope, dateobs, tmp_class_id, class_id, uri, exp_type, filelevel, filter, comment, airmass, ra, decl, exp_time, sat_pixel_frac, bg, bg_stdev, bg_mean_stdev, alt, az, ccd_temp, posang, m1_x, m1_y, m1_z, m1_tip, m1_tilt, m2_x, m2_y, m2_z, m2_tip, m2_tilt, env_temperature, env_humidity, env_wind_speed, env_wind_dir, teltemp_m1, teltemp_m1cell, teltemp_m2, teltemp_spider, teltemp_truss, teltemp_extra, pon_time, user_1, user_2, user_3, user_4, user_5, object, hostname, fault, epoch);
}
psArray *rawImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, RAWIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        rawImfileRow *object = rawImfileObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool rawImfileDeleteObject(psDB *dbh, const rawImfileRow *object)
{
    psMetadata *where = rawImfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, RAWIMFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from rawImfile");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "rawImfileRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long rawImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        rawImfileRow *object = objects->data[i];
        psMetadata *where = rawImfileMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, RAWIMFILE_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from rawImfile");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool rawImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = rawImfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            RAWIMFILE_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool rawImfilePrintObject(FILE *stream, rawImfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = rawImfileMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void guidePendingExpRowFree(guidePendingExpRow *object);

guidePendingExpRow *guidePendingExpRowAlloc(psS64 guide_id, psS64 exp_id, const char *recipe)
{
    guidePendingExpRow *_object;

    _object = psAlloc(sizeof(guidePendingExpRow));
    psMemSetDeallocator(_object, (psFreeFunc)guidePendingExpRowFree);

    _object->guide_id = guide_id;
    _object->exp_id = exp_id;
    _object->recipe = psStringCopy(recipe);

    return _object;
}

static void guidePendingExpRowFree(guidePendingExpRow *object)
{
    psFree(object->recipe);
}

bool guidePendingExpCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "guide_id", PS_DATA_S64, "Primary Key AUTO_INCREMENT", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item guide_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, "Key", 64)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, GUIDEPENDINGEXP_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool guidePendingExpDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, GUIDEPENDINGEXP_TABLE_NAME);
}

bool guidePendingExpInsert(psDB * dbh, psS64 guide_id, psS64 exp_id, const char *recipe)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "guide_id", PS_DATA_S64, NULL, guide_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item guide_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, GUIDEPENDINGEXP_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long guidePendingExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, GUIDEPENDINGEXP_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from guidePendingExp");
        return count;

        deleted += count;
    }

    return deleted;
}
bool guidePendingExpInsertObject(psDB *dbh, guidePendingExpRow *object)
{
    return guidePendingExpInsert(dbh, object->guide_id, object->exp_id, object->recipe);
}

bool guidePendingExpInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!guidePendingExpInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool guidePendingExpInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  GUIDEPENDINGEXP_TABLE_NAME
    if (!psFitsMoveExtName(fits, GUIDEPENDINGEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", GUIDEPENDINGEXP_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, GUIDEPENDINGEXP_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool guidePendingExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, GUIDEPENDINGEXP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, GUIDEPENDINGEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *guidePendingExpMetadataFromObject(const guidePendingExpRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "guide_id", PS_DATA_S64, NULL, object->guide_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item guide_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, object->exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, object->recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }


    return md;
}

guidePendingExpRow *guidePendingExpObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 guide_id = psMetadataLookupS64(&status, md, "guide_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item guide_id");
        return false;
    }
    psS64 exp_id = psMetadataLookupS64(&status, md, "exp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
        return false;
    }
    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
        return false;
    }

    return guidePendingExpRowAlloc(guide_id, exp_id, recipe);
}
psArray *guidePendingExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, GUIDEPENDINGEXP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        guidePendingExpRow *object = guidePendingExpObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool guidePendingExpDeleteObject(psDB *dbh, const guidePendingExpRow *object)
{
    psMetadata *where = guidePendingExpMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, GUIDEPENDINGEXP_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from guidePendingExp");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "guidePendingExpRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long guidePendingExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        guidePendingExpRow *object = objects->data[i];
        psMetadata *where = guidePendingExpMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, GUIDEPENDINGEXP_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from guidePendingExp");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool guidePendingExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = guidePendingExpMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            GUIDEPENDINGEXP_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool guidePendingExpPrintObject(FILE *stream, guidePendingExpRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = guidePendingExpMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void chipRunRowFree(chipRunRow *object);

chipRunRow *chipRunRowAlloc(psS64 chip_id, psS64 exp_id, const char *state, const char *workdir, const char *workdir_state, const char *label, const char *reduction, const char *expgroup, const char *dvodb, const char *tess_id, const char *end_stage)
{
    chipRunRow      *_object;

    _object = psAlloc(sizeof(chipRunRow));
    psMemSetDeallocator(_object, (psFreeFunc)chipRunRowFree);

    _object->chip_id = chip_id;
    _object->exp_id = exp_id;
    _object->state = psStringCopy(state);
    _object->workdir = psStringCopy(workdir);
    _object->workdir_state = psStringCopy(workdir_state);
    _object->label = psStringCopy(label);
    _object->reduction = psStringCopy(reduction);
    _object->expgroup = psStringCopy(expgroup);
    _object->dvodb = psStringCopy(dvodb);
    _object->tess_id = psStringCopy(tess_id);
    _object->end_stage = psStringCopy(end_stage);

    return _object;
}

static void chipRunRowFree(chipRunRow *object)
{
    psFree(object->state);
    psFree(object->workdir);
    psFree(object->workdir_state);
    psFree(object->label);
    psFree(object->reduction);
    psFree(object->expgroup);
    psFree(object->dvodb);
    psFree(object->tess_id);
    psFree(object->end_stage);
}

bool chipRunCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, "Primary Key AUTO_INCREMENT", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, "Key INDEX(chip_id, exp_id) fkey (exp_id) ref rawExp(exp_id)", 64)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir_state", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir_state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "reduction", PS_DATA_STRING, "Reduction class", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item reduction");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "expgroup", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item expgroup");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "end_stage", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item end_stage");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, CHIPRUN_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool chipRunDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, CHIPRUN_TABLE_NAME);
}

bool chipRunInsert(psDB * dbh, psS64 chip_id, psS64 exp_id, const char *state, const char *workdir, const char *workdir_state, const char *label, const char *reduction, const char *expgroup, const char *dvodb, const char *tess_id, const char *end_stage)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, NULL, chip_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir_state", PS_DATA_STRING, NULL, workdir_state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir_state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, label)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "reduction", PS_DATA_STRING, NULL, reduction)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item reduction");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "expgroup", PS_DATA_STRING, NULL, expgroup)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item expgroup");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, dvodb)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, tess_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "end_stage", PS_DATA_STRING, NULL, end_stage)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item end_stage");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, CHIPRUN_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long chipRunDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, CHIPRUN_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from chipRun");
        return count;

        deleted += count;
    }

    return deleted;
}
bool chipRunInsertObject(psDB *dbh, chipRunRow *object)
{
    return chipRunInsert(dbh, object->chip_id, object->exp_id, object->state, object->workdir, object->workdir_state, object->label, object->reduction, object->expgroup, object->dvodb, object->tess_id, object->end_stage);
}

bool chipRunInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!chipRunInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool chipRunInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  CHIPRUN_TABLE_NAME
    if (!psFitsMoveExtName(fits, CHIPRUN_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", CHIPRUN_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, CHIPRUN_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool chipRunSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, CHIPRUN_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, CHIPRUN_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *chipRunMetadataFromObject(const chipRunRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, NULL, object->chip_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, object->exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, object->state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, object->workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir_state", PS_DATA_STRING, NULL, object->workdir_state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir_state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, object->label)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "reduction", PS_DATA_STRING, NULL, object->reduction)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item reduction");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "expgroup", PS_DATA_STRING, NULL, object->expgroup)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item expgroup");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, object->dvodb)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, object->tess_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "end_stage", PS_DATA_STRING, NULL, object->end_stage)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item end_stage");
        psFree(md);
        return false;
    }


    return md;
}

chipRunRow *chipRunObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 chip_id = psMetadataLookupS64(&status, md, "chip_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item chip_id");
        return false;
    }
    psS64 exp_id = psMetadataLookupS64(&status, md, "exp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
        return false;
    }
    char* state = psMetadataLookupPtr(&status, md, "state");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item state");
        return false;
    }
    char* workdir = psMetadataLookupPtr(&status, md, "workdir");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
        return false;
    }
    char* workdir_state = psMetadataLookupPtr(&status, md, "workdir_state");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir_state");
        return false;
    }
    char* label = psMetadataLookupPtr(&status, md, "label");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item label");
        return false;
    }
    char* reduction = psMetadataLookupPtr(&status, md, "reduction");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item reduction");
        return false;
    }
    char* expgroup = psMetadataLookupPtr(&status, md, "expgroup");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item expgroup");
        return false;
    }
    char* dvodb = psMetadataLookupPtr(&status, md, "dvodb");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dvodb");
        return false;
    }
    char* tess_id = psMetadataLookupPtr(&status, md, "tess_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item tess_id");
        return false;
    }
    char* end_stage = psMetadataLookupPtr(&status, md, "end_stage");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item end_stage");
        return false;
    }

    return chipRunRowAlloc(chip_id, exp_id, state, workdir, workdir_state, label, reduction, expgroup, dvodb, tess_id, end_stage);
}
psArray *chipRunSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, CHIPRUN_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        chipRunRow *object = chipRunObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool chipRunDeleteObject(psDB *dbh, const chipRunRow *object)
{
    psMetadata *where = chipRunMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, CHIPRUN_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from chipRun");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "chipRunRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long chipRunDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        chipRunRow *object = objects->data[i];
        psMetadata *where = chipRunMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, CHIPRUN_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from chipRun");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool chipRunPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = chipRunMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            CHIPRUN_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool chipRunPrintObject(FILE *stream, chipRunRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = chipRunMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void chipProcessedImfileRowFree(chipProcessedImfileRow *object);

chipProcessedImfileRow *chipProcessedImfileRowAlloc(psS64 chip_id, psS64 exp_id, const char *class_id, const char *uri, psF32 bg, psF32 bg_stdev, psF32 bg_mean_stdev, psF32 bias, psF32 bias_stdev, psF32 fringe_0, psF32 fringe_1, psF32 fringe_2, psF32 sigma_ra, psF32 sigma_dec, psF32 ap_resid, psF32 ap_resid_stdev, psF32 zp_mean, psF32 zp_stdev, psF32 fwhm_major, psF32 fwhm_minor, psF32 dtime_detrend, psF32 dtime_photom, psF32 dtime_astrom, const char *hostname, psS32 n_stars, psS32 n_extended, psS32 n_cr, psS32 n_astrom, const char *path_base, psS16 fault)
{
    chipProcessedImfileRow *_object;

    _object = psAlloc(sizeof(chipProcessedImfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)chipProcessedImfileRowFree);

    _object->chip_id = chip_id;
    _object->exp_id = exp_id;
    _object->class_id = psStringCopy(class_id);
    _object->uri = psStringCopy(uri);
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->bg_mean_stdev = bg_mean_stdev;
    _object->bias = bias;
    _object->bias_stdev = bias_stdev;
    _object->fringe_0 = fringe_0;
    _object->fringe_1 = fringe_1;
    _object->fringe_2 = fringe_2;
    _object->sigma_ra = sigma_ra;
    _object->sigma_dec = sigma_dec;
    _object->ap_resid = ap_resid;
    _object->ap_resid_stdev = ap_resid_stdev;
    _object->zp_mean = zp_mean;
    _object->zp_stdev = zp_stdev;
    _object->fwhm_major = fwhm_major;
    _object->fwhm_minor = fwhm_minor;
    _object->dtime_detrend = dtime_detrend;
    _object->dtime_photom = dtime_photom;
    _object->dtime_astrom = dtime_astrom;
    _object->hostname = psStringCopy(hostname);
    _object->n_stars = n_stars;
    _object->n_extended = n_extended;
    _object->n_cr = n_cr;
    _object->n_astrom = n_astrom;
    _object->path_base = psStringCopy(path_base);
    _object->fault = fault;

    return _object;
}

static void chipProcessedImfileRowFree(chipProcessedImfileRow *object)
{
    psFree(object->class_id);
    psFree(object->uri);
    psFree(object->hostname);
    psFree(object->path_base);
}

bool chipProcessedImfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, "Primary Key fkey (chip_id, exp_id) ref chipRun(chip_id, exp_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, "Primary Key fkey (exp_id, class_id) ref rawImfile(exp_id, class_id)", 64)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bias", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bias");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bias_stdev", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bias_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_0", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_0");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_1", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_2", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "sigma_ra", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sigma_ra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "sigma_dec", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sigma_dec");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ap_resid", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ap_resid");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ap_resid_stdev", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ap_resid_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "zp_mean", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item zp_mean");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "zp_stdev", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item zp_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fwhm_major", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fwhm_major");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fwhm_minor", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fwhm_minor");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_detrend", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_detrend");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_photom", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_photom");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_astrom", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_astrom");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "hostname", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item hostname");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "n_stars", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item n_stars");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "n_extended", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item n_extended");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "n_cr", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item n_cr");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "n_astrom", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item n_astrom");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, CHIPPROCESSEDIMFILE_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool chipProcessedImfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, CHIPPROCESSEDIMFILE_TABLE_NAME);
}

bool chipProcessedImfileInsert(psDB * dbh, psS64 chip_id, psS64 exp_id, const char *class_id, const char *uri, psF32 bg, psF32 bg_stdev, psF32 bg_mean_stdev, psF32 bias, psF32 bias_stdev, psF32 fringe_0, psF32 fringe_1, psF32 fringe_2, psF32 sigma_ra, psF32 sigma_dec, psF32 ap_resid, psF32 ap_resid_stdev, psF32 zp_mean, psF32 zp_stdev, psF32 fwhm_major, psF32 fwhm_minor, psF32 dtime_detrend, psF32 dtime_photom, psF32 dtime_astrom, const char *hostname, psS32 n_stars, psS32 n_extended, psS32 n_cr, psS32 n_astrom, const char *path_base, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, NULL, chip_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F32, NULL, bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F32, NULL, bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F32, NULL, bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bias", PS_DATA_F32, NULL, bias)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bias");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bias_stdev", PS_DATA_F32, NULL, bias_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bias_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_0", PS_DATA_F32, NULL, fringe_0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_0");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_1", PS_DATA_F32, NULL, fringe_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_2", PS_DATA_F32, NULL, fringe_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "sigma_ra", PS_DATA_F32, NULL, sigma_ra)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sigma_ra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "sigma_dec", PS_DATA_F32, NULL, sigma_dec)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sigma_dec");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ap_resid", PS_DATA_F32, NULL, ap_resid)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ap_resid");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ap_resid_stdev", PS_DATA_F32, NULL, ap_resid_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ap_resid_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "zp_mean", PS_DATA_F32, NULL, zp_mean)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item zp_mean");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "zp_stdev", PS_DATA_F32, NULL, zp_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item zp_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fwhm_major", PS_DATA_F32, NULL, fwhm_major)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fwhm_major");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fwhm_minor", PS_DATA_F32, NULL, fwhm_minor)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fwhm_minor");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_detrend", PS_DATA_F32, NULL, dtime_detrend)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_detrend");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_photom", PS_DATA_F32, NULL, dtime_photom)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_photom");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_astrom", PS_DATA_F32, NULL, dtime_astrom)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_astrom");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "hostname", PS_DATA_STRING, NULL, hostname)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item hostname");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "n_stars", PS_DATA_S32, NULL, n_stars)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item n_stars");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "n_extended", PS_DATA_S32, NULL, n_extended)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item n_extended");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "n_cr", PS_DATA_S32, NULL, n_cr)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item n_cr");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "n_astrom", PS_DATA_S32, NULL, n_astrom)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item n_astrom");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, CHIPPROCESSEDIMFILE_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long chipProcessedImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, CHIPPROCESSEDIMFILE_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from chipProcessedImfile");
        return count;

        deleted += count;
    }

    return deleted;
}
bool chipProcessedImfileInsertObject(psDB *dbh, chipProcessedImfileRow *object)
{
    return chipProcessedImfileInsert(dbh, object->chip_id, object->exp_id, object->class_id, object->uri, object->bg, object->bg_stdev, object->bg_mean_stdev, object->bias, object->bias_stdev, object->fringe_0, object->fringe_1, object->fringe_2, object->sigma_ra, object->sigma_dec, object->ap_resid, object->ap_resid_stdev, object->zp_mean, object->zp_stdev, object->fwhm_major, object->fwhm_minor, object->dtime_detrend, object->dtime_photom, object->dtime_astrom, object->hostname, object->n_stars, object->n_extended, object->n_cr, object->n_astrom, object->path_base, object->fault);
}

bool chipProcessedImfileInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!chipProcessedImfileInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool chipProcessedImfileInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  CHIPPROCESSEDIMFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, CHIPPROCESSEDIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", CHIPPROCESSEDIMFILE_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, CHIPPROCESSEDIMFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool chipProcessedImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, CHIPPROCESSEDIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, CHIPPROCESSEDIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *chipProcessedImfileMetadataFromObject(const chipProcessedImfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, NULL, object->chip_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, object->exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F32, NULL, object->bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F32, NULL, object->bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F32, NULL, object->bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bias", PS_DATA_F32, NULL, object->bias)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bias");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bias_stdev", PS_DATA_F32, NULL, object->bias_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bias_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_0", PS_DATA_F32, NULL, object->fringe_0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_0");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_1", PS_DATA_F32, NULL, object->fringe_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_2", PS_DATA_F32, NULL, object->fringe_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "sigma_ra", PS_DATA_F32, NULL, object->sigma_ra)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sigma_ra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "sigma_dec", PS_DATA_F32, NULL, object->sigma_dec)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sigma_dec");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ap_resid", PS_DATA_F32, NULL, object->ap_resid)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ap_resid");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ap_resid_stdev", PS_DATA_F32, NULL, object->ap_resid_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ap_resid_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "zp_mean", PS_DATA_F32, NULL, object->zp_mean)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item zp_mean");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "zp_stdev", PS_DATA_F32, NULL, object->zp_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item zp_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fwhm_major", PS_DATA_F32, NULL, object->fwhm_major)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fwhm_major");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fwhm_minor", PS_DATA_F32, NULL, object->fwhm_minor)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fwhm_minor");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_detrend", PS_DATA_F32, NULL, object->dtime_detrend)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_detrend");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_photom", PS_DATA_F32, NULL, object->dtime_photom)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_photom");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_astrom", PS_DATA_F32, NULL, object->dtime_astrom)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_astrom");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "hostname", PS_DATA_STRING, NULL, object->hostname)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item hostname");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "n_stars", PS_DATA_S32, NULL, object->n_stars)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item n_stars");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "n_extended", PS_DATA_S32, NULL, object->n_extended)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item n_extended");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "n_cr", PS_DATA_S32, NULL, object->n_cr)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item n_cr");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "n_astrom", PS_DATA_S32, NULL, object->n_astrom)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item n_astrom");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, object->path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

chipProcessedImfileRow *chipProcessedImfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 chip_id = psMetadataLookupS64(&status, md, "chip_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item chip_id");
        return false;
    }
    psS64 exp_id = psMetadataLookupS64(&status, md, "exp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
        return false;
    }
    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    psF32 bg = psMetadataLookupF32(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF32 bg_stdev = psMetadataLookupF32(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF32 bg_mean_stdev = psMetadataLookupF32(&status, md, "bg_mean_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
        return false;
    }
    psF32 bias = psMetadataLookupF32(&status, md, "bias");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bias");
        return false;
    }
    psF32 bias_stdev = psMetadataLookupF32(&status, md, "bias_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bias_stdev");
        return false;
    }
    psF32 fringe_0 = psMetadataLookupF32(&status, md, "fringe_0");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fringe_0");
        return false;
    }
    psF32 fringe_1 = psMetadataLookupF32(&status, md, "fringe_1");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fringe_1");
        return false;
    }
    psF32 fringe_2 = psMetadataLookupF32(&status, md, "fringe_2");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fringe_2");
        return false;
    }
    psF32 sigma_ra = psMetadataLookupF32(&status, md, "sigma_ra");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sigma_ra");
        return false;
    }
    psF32 sigma_dec = psMetadataLookupF32(&status, md, "sigma_dec");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sigma_dec");
        return false;
    }
    psF32 ap_resid = psMetadataLookupF32(&status, md, "ap_resid");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ap_resid");
        return false;
    }
    psF32 ap_resid_stdev = psMetadataLookupF32(&status, md, "ap_resid_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ap_resid_stdev");
        return false;
    }
    psF32 zp_mean = psMetadataLookupF32(&status, md, "zp_mean");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item zp_mean");
        return false;
    }
    psF32 zp_stdev = psMetadataLookupF32(&status, md, "zp_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item zp_stdev");
        return false;
    }
    psF32 fwhm_major = psMetadataLookupF32(&status, md, "fwhm_major");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fwhm_major");
        return false;
    }
    psF32 fwhm_minor = psMetadataLookupF32(&status, md, "fwhm_minor");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fwhm_minor");
        return false;
    }
    psF32 dtime_detrend = psMetadataLookupF32(&status, md, "dtime_detrend");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dtime_detrend");
        return false;
    }
    psF32 dtime_photom = psMetadataLookupF32(&status, md, "dtime_photom");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dtime_photom");
        return false;
    }
    psF32 dtime_astrom = psMetadataLookupF32(&status, md, "dtime_astrom");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dtime_astrom");
        return false;
    }
    char* hostname = psMetadataLookupPtr(&status, md, "hostname");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item hostname");
        return false;
    }
    psS32 n_stars = psMetadataLookupS32(&status, md, "n_stars");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item n_stars");
        return false;
    }
    psS32 n_extended = psMetadataLookupS32(&status, md, "n_extended");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item n_extended");
        return false;
    }
    psS32 n_cr = psMetadataLookupS32(&status, md, "n_cr");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item n_cr");
        return false;
    }
    psS32 n_astrom = psMetadataLookupS32(&status, md, "n_astrom");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item n_astrom");
        return false;
    }
    char* path_base = psMetadataLookupPtr(&status, md, "path_base");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item path_base");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return chipProcessedImfileRowAlloc(chip_id, exp_id, class_id, uri, bg, bg_stdev, bg_mean_stdev, bias, bias_stdev, fringe_0, fringe_1, fringe_2, sigma_ra, sigma_dec, ap_resid, ap_resid_stdev, zp_mean, zp_stdev, fwhm_major, fwhm_minor, dtime_detrend, dtime_photom, dtime_astrom, hostname, n_stars, n_extended, n_cr, n_astrom, path_base, fault);
}
psArray *chipProcessedImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, CHIPPROCESSEDIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        chipProcessedImfileRow *object = chipProcessedImfileObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool chipProcessedImfileDeleteObject(psDB *dbh, const chipProcessedImfileRow *object)
{
    psMetadata *where = chipProcessedImfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, CHIPPROCESSEDIMFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from chipProcessedImfile");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "chipProcessedImfileRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long chipProcessedImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        chipProcessedImfileRow *object = objects->data[i];
        psMetadata *where = chipProcessedImfileMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, CHIPPROCESSEDIMFILE_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from chipProcessedImfile");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool chipProcessedImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = chipProcessedImfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            CHIPPROCESSEDIMFILE_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool chipProcessedImfilePrintObject(FILE *stream, chipProcessedImfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = chipProcessedImfileMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void chipMaskRowFree(chipMaskRow *object);

chipMaskRow *chipMaskRowAlloc(const char *label)
{
    chipMaskRow     *_object;

    _object = psAlloc(sizeof(chipMaskRow));
    psMemSetDeallocator(_object, (psFreeFunc)chipMaskRowFree);

    _object->label = psStringCopy(label);

    return _object;
}

static void chipMaskRowFree(chipMaskRow *object)
{
    psFree(object->label);
}

bool chipMaskCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, CHIPMASK_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool chipMaskDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, CHIPMASK_TABLE_NAME);
}

bool chipMaskInsert(psDB * dbh, const char *label)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, label)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, CHIPMASK_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long chipMaskDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, CHIPMASK_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from chipMask");
        return count;

        deleted += count;
    }

    return deleted;
}
bool chipMaskInsertObject(psDB *dbh, chipMaskRow *object)
{
    return chipMaskInsert(dbh, object->label);
}

bool chipMaskInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!chipMaskInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool chipMaskInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  CHIPMASK_TABLE_NAME
    if (!psFitsMoveExtName(fits, CHIPMASK_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", CHIPMASK_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, CHIPMASK_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool chipMaskSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, CHIPMASK_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, CHIPMASK_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *chipMaskMetadataFromObject(const chipMaskRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, object->label)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }


    return md;
}

chipMaskRow *chipMaskObjectFromMetadata(psMetadata *md)
{

bool status = false;
    char* label = psMetadataLookupPtr(&status, md, "label");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item label");
        return false;
    }

    return chipMaskRowAlloc(label);
}
psArray *chipMaskSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, CHIPMASK_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        chipMaskRow *object = chipMaskObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool chipMaskDeleteObject(psDB *dbh, const chipMaskRow *object)
{
    psMetadata *where = chipMaskMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, CHIPMASK_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from chipMask");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "chipMaskRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long chipMaskDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        chipMaskRow *object = objects->data[i];
        psMetadata *where = chipMaskMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, CHIPMASK_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from chipMask");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool chipMaskPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = chipMaskMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            CHIPMASK_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool chipMaskPrintObject(FILE *stream, chipMaskRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = chipMaskMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void camRunRowFree(camRunRow *object);

camRunRow *camRunRowAlloc(psS64 cam_id, psS64 chip_id, const char *state, const char *workdir, const char *workdir_state, const char *label, const char *reduction, const char *expgroup, const char *dvodb, const char *tess_id, const char *end_stage)
{
    camRunRow       *_object;

    _object = psAlloc(sizeof(camRunRow));
    psMemSetDeallocator(_object, (psFreeFunc)camRunRowFree);

    _object->cam_id = cam_id;
    _object->chip_id = chip_id;
    _object->state = psStringCopy(state);
    _object->workdir = psStringCopy(workdir);
    _object->workdir_state = psStringCopy(workdir_state);
    _object->label = psStringCopy(label);
    _object->reduction = psStringCopy(reduction);
    _object->expgroup = psStringCopy(expgroup);
    _object->dvodb = psStringCopy(dvodb);
    _object->tess_id = psStringCopy(tess_id);
    _object->end_stage = psStringCopy(end_stage);

    return _object;
}

static void camRunRowFree(camRunRow *object)
{
    psFree(object->state);
    psFree(object->workdir);
    psFree(object->workdir_state);
    psFree(object->label);
    psFree(object->reduction);
    psFree(object->expgroup);
    psFree(object->dvodb);
    psFree(object->tess_id);
    psFree(object->end_stage);
}

bool camRunCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "cam_id", PS_DATA_S64, "Primary Key AUTO_INCREMENT", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item cam_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, "Key INDEX(cam_id, chip_id) fkey(chip_id) ref chipRun(chip_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, "key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir_state", PS_DATA_STRING, "key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir_state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, "key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "reduction", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item reduction");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "expgroup", PS_DATA_STRING, "key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item expgroup");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "end_stage", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item end_stage");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, CAMRUN_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool camRunDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, CAMRUN_TABLE_NAME);
}

bool camRunInsert(psDB * dbh, psS64 cam_id, psS64 chip_id, const char *state, const char *workdir, const char *workdir_state, const char *label, const char *reduction, const char *expgroup, const char *dvodb, const char *tess_id, const char *end_stage)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "cam_id", PS_DATA_S64, NULL, cam_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item cam_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, NULL, chip_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir_state", PS_DATA_STRING, NULL, workdir_state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir_state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, label)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "reduction", PS_DATA_STRING, NULL, reduction)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item reduction");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "expgroup", PS_DATA_STRING, NULL, expgroup)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item expgroup");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, dvodb)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, tess_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "end_stage", PS_DATA_STRING, NULL, end_stage)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item end_stage");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, CAMRUN_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long camRunDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, CAMRUN_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from camRun");
        return count;

        deleted += count;
    }

    return deleted;
}
bool camRunInsertObject(psDB *dbh, camRunRow *object)
{
    return camRunInsert(dbh, object->cam_id, object->chip_id, object->state, object->workdir, object->workdir_state, object->label, object->reduction, object->expgroup, object->dvodb, object->tess_id, object->end_stage);
}

bool camRunInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!camRunInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool camRunInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  CAMRUN_TABLE_NAME
    if (!psFitsMoveExtName(fits, CAMRUN_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", CAMRUN_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, CAMRUN_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool camRunSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, CAMRUN_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, CAMRUN_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *camRunMetadataFromObject(const camRunRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "cam_id", PS_DATA_S64, NULL, object->cam_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item cam_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, NULL, object->chip_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, object->state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, object->workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir_state", PS_DATA_STRING, NULL, object->workdir_state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir_state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, object->label)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "reduction", PS_DATA_STRING, NULL, object->reduction)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item reduction");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "expgroup", PS_DATA_STRING, NULL, object->expgroup)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item expgroup");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, object->dvodb)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, object->tess_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "end_stage", PS_DATA_STRING, NULL, object->end_stage)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item end_stage");
        psFree(md);
        return false;
    }


    return md;
}

camRunRow *camRunObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 cam_id = psMetadataLookupS64(&status, md, "cam_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item cam_id");
        return false;
    }
    psS64 chip_id = psMetadataLookupS64(&status, md, "chip_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item chip_id");
        return false;
    }
    char* state = psMetadataLookupPtr(&status, md, "state");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item state");
        return false;
    }
    char* workdir = psMetadataLookupPtr(&status, md, "workdir");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
        return false;
    }
    char* workdir_state = psMetadataLookupPtr(&status, md, "workdir_state");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir_state");
        return false;
    }
    char* label = psMetadataLookupPtr(&status, md, "label");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item label");
        return false;
    }
    char* reduction = psMetadataLookupPtr(&status, md, "reduction");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item reduction");
        return false;
    }
    char* expgroup = psMetadataLookupPtr(&status, md, "expgroup");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item expgroup");
        return false;
    }
    char* dvodb = psMetadataLookupPtr(&status, md, "dvodb");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dvodb");
        return false;
    }
    char* tess_id = psMetadataLookupPtr(&status, md, "tess_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item tess_id");
        return false;
    }
    char* end_stage = psMetadataLookupPtr(&status, md, "end_stage");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item end_stage");
        return false;
    }

    return camRunRowAlloc(cam_id, chip_id, state, workdir, workdir_state, label, reduction, expgroup, dvodb, tess_id, end_stage);
}
psArray *camRunSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, CAMRUN_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        camRunRow *object = camRunObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool camRunDeleteObject(psDB *dbh, const camRunRow *object)
{
    psMetadata *where = camRunMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, CAMRUN_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from camRun");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "camRunRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long camRunDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        camRunRow *object = objects->data[i];
        psMetadata *where = camRunMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, CAMRUN_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from camRun");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool camRunPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = camRunMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            CAMRUN_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool camRunPrintObject(FILE *stream, camRunRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = camRunMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void camProcessedExpRowFree(camProcessedExpRow *object);

camProcessedExpRow *camProcessedExpRowAlloc(psS64 cam_id, const char *uri, psF32 bg, psF32 bg_stdev, psF32 bg_mean_stdev, psF32 bias, psF32 bias_stdev, psF32 fringe_0, psF32 fringe_1, psF32 fringe_2, psF32 sigma_ra, psF32 sigma_dec, psF32 ap_resid, psF32 ap_resid_stdev, psF32 zp_mean, psF32 zp_stdev, psF32 fwhm_major, psF32 fwhm_minor, psF32 dtime_detrend, psF32 dtime_photom, psF32 dtime_astrom, const char *hostname, psS32 n_stars, psS32 n_extended, psS32 n_cr, psS32 n_astrom, const char *path_base, psS16 fault)
{
    camProcessedExpRow *_object;

    _object = psAlloc(sizeof(camProcessedExpRow));
    psMemSetDeallocator(_object, (psFreeFunc)camProcessedExpRowFree);

    _object->cam_id = cam_id;
    _object->uri = psStringCopy(uri);
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->bg_mean_stdev = bg_mean_stdev;
    _object->bias = bias;
    _object->bias_stdev = bias_stdev;
    _object->fringe_0 = fringe_0;
    _object->fringe_1 = fringe_1;
    _object->fringe_2 = fringe_2;
    _object->sigma_ra = sigma_ra;
    _object->sigma_dec = sigma_dec;
    _object->ap_resid = ap_resid;
    _object->ap_resid_stdev = ap_resid_stdev;
    _object->zp_mean = zp_mean;
    _object->zp_stdev = zp_stdev;
    _object->fwhm_major = fwhm_major;
    _object->fwhm_minor = fwhm_minor;
    _object->dtime_detrend = dtime_detrend;
    _object->dtime_photom = dtime_photom;
    _object->dtime_astrom = dtime_astrom;
    _object->hostname = psStringCopy(hostname);
    _object->n_stars = n_stars;
    _object->n_extended = n_extended;
    _object->n_cr = n_cr;
    _object->n_astrom = n_astrom;
    _object->path_base = psStringCopy(path_base);
    _object->fault = fault;

    return _object;
}

static void camProcessedExpRowFree(camProcessedExpRow *object)
{
    psFree(object->uri);
    psFree(object->hostname);
    psFree(object->path_base);
}

bool camProcessedExpCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "cam_id", PS_DATA_S64, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item cam_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, "fkey(cam_id) ref camRun(cam_id)", "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bias", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bias");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bias_stdev", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bias_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_0", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_0");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_1", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_2", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "sigma_ra", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sigma_ra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "sigma_dec", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sigma_dec");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ap_resid", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ap_resid");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ap_resid_stdev", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ap_resid_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "zp_mean", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item zp_mean");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "zp_stdev", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item zp_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fwhm_major", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fwhm_major");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fwhm_minor", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fwhm_minor");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_detrend", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_detrend");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_photom", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_photom");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_astrom", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_astrom");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "hostname", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item hostname");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "n_stars", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item n_stars");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "n_extended", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item n_extended");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "n_cr", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item n_cr");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "n_astrom", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item n_astrom");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, CAMPROCESSEDEXP_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool camProcessedExpDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, CAMPROCESSEDEXP_TABLE_NAME);
}

bool camProcessedExpInsert(psDB * dbh, psS64 cam_id, const char *uri, psF32 bg, psF32 bg_stdev, psF32 bg_mean_stdev, psF32 bias, psF32 bias_stdev, psF32 fringe_0, psF32 fringe_1, psF32 fringe_2, psF32 sigma_ra, psF32 sigma_dec, psF32 ap_resid, psF32 ap_resid_stdev, psF32 zp_mean, psF32 zp_stdev, psF32 fwhm_major, psF32 fwhm_minor, psF32 dtime_detrend, psF32 dtime_photom, psF32 dtime_astrom, const char *hostname, psS32 n_stars, psS32 n_extended, psS32 n_cr, psS32 n_astrom, const char *path_base, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "cam_id", PS_DATA_S64, NULL, cam_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item cam_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F32, NULL, bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F32, NULL, bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F32, NULL, bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bias", PS_DATA_F32, NULL, bias)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bias");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bias_stdev", PS_DATA_F32, NULL, bias_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bias_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_0", PS_DATA_F32, NULL, fringe_0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_0");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_1", PS_DATA_F32, NULL, fringe_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_2", PS_DATA_F32, NULL, fringe_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "sigma_ra", PS_DATA_F32, NULL, sigma_ra)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sigma_ra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "sigma_dec", PS_DATA_F32, NULL, sigma_dec)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sigma_dec");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ap_resid", PS_DATA_F32, NULL, ap_resid)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ap_resid");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ap_resid_stdev", PS_DATA_F32, NULL, ap_resid_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ap_resid_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "zp_mean", PS_DATA_F32, NULL, zp_mean)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item zp_mean");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "zp_stdev", PS_DATA_F32, NULL, zp_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item zp_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fwhm_major", PS_DATA_F32, NULL, fwhm_major)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fwhm_major");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fwhm_minor", PS_DATA_F32, NULL, fwhm_minor)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fwhm_minor");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_detrend", PS_DATA_F32, NULL, dtime_detrend)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_detrend");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_photom", PS_DATA_F32, NULL, dtime_photom)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_photom");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_astrom", PS_DATA_F32, NULL, dtime_astrom)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_astrom");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "hostname", PS_DATA_STRING, NULL, hostname)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item hostname");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "n_stars", PS_DATA_S32, NULL, n_stars)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item n_stars");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "n_extended", PS_DATA_S32, NULL, n_extended)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item n_extended");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "n_cr", PS_DATA_S32, NULL, n_cr)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item n_cr");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "n_astrom", PS_DATA_S32, NULL, n_astrom)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item n_astrom");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, CAMPROCESSEDEXP_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long camProcessedExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, CAMPROCESSEDEXP_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from camProcessedExp");
        return count;

        deleted += count;
    }

    return deleted;
}
bool camProcessedExpInsertObject(psDB *dbh, camProcessedExpRow *object)
{
    return camProcessedExpInsert(dbh, object->cam_id, object->uri, object->bg, object->bg_stdev, object->bg_mean_stdev, object->bias, object->bias_stdev, object->fringe_0, object->fringe_1, object->fringe_2, object->sigma_ra, object->sigma_dec, object->ap_resid, object->ap_resid_stdev, object->zp_mean, object->zp_stdev, object->fwhm_major, object->fwhm_minor, object->dtime_detrend, object->dtime_photom, object->dtime_astrom, object->hostname, object->n_stars, object->n_extended, object->n_cr, object->n_astrom, object->path_base, object->fault);
}

bool camProcessedExpInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!camProcessedExpInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool camProcessedExpInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  CAMPROCESSEDEXP_TABLE_NAME
    if (!psFitsMoveExtName(fits, CAMPROCESSEDEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", CAMPROCESSEDEXP_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, CAMPROCESSEDEXP_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool camProcessedExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, CAMPROCESSEDEXP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, CAMPROCESSEDEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *camProcessedExpMetadataFromObject(const camProcessedExpRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "cam_id", PS_DATA_S64, NULL, object->cam_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item cam_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F32, NULL, object->bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F32, NULL, object->bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F32, NULL, object->bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bias", PS_DATA_F32, NULL, object->bias)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bias");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bias_stdev", PS_DATA_F32, NULL, object->bias_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bias_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_0", PS_DATA_F32, NULL, object->fringe_0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_0");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_1", PS_DATA_F32, NULL, object->fringe_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_2", PS_DATA_F32, NULL, object->fringe_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "sigma_ra", PS_DATA_F32, NULL, object->sigma_ra)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sigma_ra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "sigma_dec", PS_DATA_F32, NULL, object->sigma_dec)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sigma_dec");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ap_resid", PS_DATA_F32, NULL, object->ap_resid)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ap_resid");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ap_resid_stdev", PS_DATA_F32, NULL, object->ap_resid_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ap_resid_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "zp_mean", PS_DATA_F32, NULL, object->zp_mean)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item zp_mean");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "zp_stdev", PS_DATA_F32, NULL, object->zp_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item zp_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fwhm_major", PS_DATA_F32, NULL, object->fwhm_major)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fwhm_major");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fwhm_minor", PS_DATA_F32, NULL, object->fwhm_minor)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fwhm_minor");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_detrend", PS_DATA_F32, NULL, object->dtime_detrend)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_detrend");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_photom", PS_DATA_F32, NULL, object->dtime_photom)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_photom");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_astrom", PS_DATA_F32, NULL, object->dtime_astrom)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_astrom");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "hostname", PS_DATA_STRING, NULL, object->hostname)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item hostname");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "n_stars", PS_DATA_S32, NULL, object->n_stars)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item n_stars");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "n_extended", PS_DATA_S32, NULL, object->n_extended)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item n_extended");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "n_cr", PS_DATA_S32, NULL, object->n_cr)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item n_cr");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "n_astrom", PS_DATA_S32, NULL, object->n_astrom)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item n_astrom");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, object->path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

camProcessedExpRow *camProcessedExpObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 cam_id = psMetadataLookupS64(&status, md, "cam_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item cam_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    psF32 bg = psMetadataLookupF32(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF32 bg_stdev = psMetadataLookupF32(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF32 bg_mean_stdev = psMetadataLookupF32(&status, md, "bg_mean_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
        return false;
    }
    psF32 bias = psMetadataLookupF32(&status, md, "bias");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bias");
        return false;
    }
    psF32 bias_stdev = psMetadataLookupF32(&status, md, "bias_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bias_stdev");
        return false;
    }
    psF32 fringe_0 = psMetadataLookupF32(&status, md, "fringe_0");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fringe_0");
        return false;
    }
    psF32 fringe_1 = psMetadataLookupF32(&status, md, "fringe_1");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fringe_1");
        return false;
    }
    psF32 fringe_2 = psMetadataLookupF32(&status, md, "fringe_2");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fringe_2");
        return false;
    }
    psF32 sigma_ra = psMetadataLookupF32(&status, md, "sigma_ra");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sigma_ra");
        return false;
    }
    psF32 sigma_dec = psMetadataLookupF32(&status, md, "sigma_dec");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sigma_dec");
        return false;
    }
    psF32 ap_resid = psMetadataLookupF32(&status, md, "ap_resid");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ap_resid");
        return false;
    }
    psF32 ap_resid_stdev = psMetadataLookupF32(&status, md, "ap_resid_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ap_resid_stdev");
        return false;
    }
    psF32 zp_mean = psMetadataLookupF32(&status, md, "zp_mean");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item zp_mean");
        return false;
    }
    psF32 zp_stdev = psMetadataLookupF32(&status, md, "zp_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item zp_stdev");
        return false;
    }
    psF32 fwhm_major = psMetadataLookupF32(&status, md, "fwhm_major");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fwhm_major");
        return false;
    }
    psF32 fwhm_minor = psMetadataLookupF32(&status, md, "fwhm_minor");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fwhm_minor");
        return false;
    }
    psF32 dtime_detrend = psMetadataLookupF32(&status, md, "dtime_detrend");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dtime_detrend");
        return false;
    }
    psF32 dtime_photom = psMetadataLookupF32(&status, md, "dtime_photom");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dtime_photom");
        return false;
    }
    psF32 dtime_astrom = psMetadataLookupF32(&status, md, "dtime_astrom");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dtime_astrom");
        return false;
    }
    char* hostname = psMetadataLookupPtr(&status, md, "hostname");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item hostname");
        return false;
    }
    psS32 n_stars = psMetadataLookupS32(&status, md, "n_stars");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item n_stars");
        return false;
    }
    psS32 n_extended = psMetadataLookupS32(&status, md, "n_extended");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item n_extended");
        return false;
    }
    psS32 n_cr = psMetadataLookupS32(&status, md, "n_cr");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item n_cr");
        return false;
    }
    psS32 n_astrom = psMetadataLookupS32(&status, md, "n_astrom");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item n_astrom");
        return false;
    }
    char* path_base = psMetadataLookupPtr(&status, md, "path_base");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item path_base");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return camProcessedExpRowAlloc(cam_id, uri, bg, bg_stdev, bg_mean_stdev, bias, bias_stdev, fringe_0, fringe_1, fringe_2, sigma_ra, sigma_dec, ap_resid, ap_resid_stdev, zp_mean, zp_stdev, fwhm_major, fwhm_minor, dtime_detrend, dtime_photom, dtime_astrom, hostname, n_stars, n_extended, n_cr, n_astrom, path_base, fault);
}
psArray *camProcessedExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, CAMPROCESSEDEXP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        camProcessedExpRow *object = camProcessedExpObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool camProcessedExpDeleteObject(psDB *dbh, const camProcessedExpRow *object)
{
    psMetadata *where = camProcessedExpMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, CAMPROCESSEDEXP_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from camProcessedExp");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "camProcessedExpRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long camProcessedExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        camProcessedExpRow *object = objects->data[i];
        psMetadata *where = camProcessedExpMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, CAMPROCESSEDEXP_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from camProcessedExp");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool camProcessedExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = camProcessedExpMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            CAMPROCESSEDEXP_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool camProcessedExpPrintObject(FILE *stream, camProcessedExpRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = camProcessedExpMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void camMaskRowFree(camMaskRow *object);

camMaskRow *camMaskRowAlloc(const char *label)
{
    camMaskRow      *_object;

    _object = psAlloc(sizeof(camMaskRow));
    psMemSetDeallocator(_object, (psFreeFunc)camMaskRowFree);

    _object->label = psStringCopy(label);

    return _object;
}

static void camMaskRowFree(camMaskRow *object)
{
    psFree(object->label);
}

bool camMaskCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, CAMMASK_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool camMaskDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, CAMMASK_TABLE_NAME);
}

bool camMaskInsert(psDB * dbh, const char *label)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, label)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, CAMMASK_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long camMaskDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, CAMMASK_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from camMask");
        return count;

        deleted += count;
    }

    return deleted;
}
bool camMaskInsertObject(psDB *dbh, camMaskRow *object)
{
    return camMaskInsert(dbh, object->label);
}

bool camMaskInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!camMaskInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool camMaskInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  CAMMASK_TABLE_NAME
    if (!psFitsMoveExtName(fits, CAMMASK_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", CAMMASK_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, CAMMASK_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool camMaskSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, CAMMASK_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, CAMMASK_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *camMaskMetadataFromObject(const camMaskRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, object->label)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }


    return md;
}

camMaskRow *camMaskObjectFromMetadata(psMetadata *md)
{

bool status = false;
    char* label = psMetadataLookupPtr(&status, md, "label");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item label");
        return false;
    }

    return camMaskRowAlloc(label);
}
psArray *camMaskSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, CAMMASK_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        camMaskRow *object = camMaskObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool camMaskDeleteObject(psDB *dbh, const camMaskRow *object)
{
    psMetadata *where = camMaskMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, CAMMASK_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from camMask");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "camMaskRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long camMaskDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        camMaskRow *object = objects->data[i];
        psMetadata *where = camMaskMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, CAMMASK_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from camMask");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool camMaskPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = camMaskMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            CAMMASK_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool camMaskPrintObject(FILE *stream, camMaskRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = camMaskMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void fakeRunRowFree(fakeRunRow *object);

fakeRunRow *fakeRunRowAlloc(psS64 fake_id, psS64 cam_id, const char *state, const char *workdir, const char *label, const char *reduction, const char *expgroup, const char *dvodb, const char *tess_id, const char *end_stage, psTime* epoch)
{
    fakeRunRow      *_object;

    _object = psAlloc(sizeof(fakeRunRow));
    psMemSetDeallocator(_object, (psFreeFunc)fakeRunRowFree);

    _object->fake_id = fake_id;
    _object->cam_id = cam_id;
    _object->state = psStringCopy(state);
    _object->workdir = psStringCopy(workdir);
    _object->label = psStringCopy(label);
    _object->reduction = psStringCopy(reduction);
    _object->expgroup = psStringCopy(expgroup);
    _object->dvodb = psStringCopy(dvodb);
    _object->tess_id = psStringCopy(tess_id);
    _object->end_stage = psStringCopy(end_stage);
    _object->epoch = psTimeCopy(epoch);

    return _object;
}

static void fakeRunRowFree(fakeRunRow *object)
{
    psFree(object->state);
    psFree(object->workdir);
    psFree(object->label);
    psFree(object->reduction);
    psFree(object->expgroup);
    psFree(object->dvodb);
    psFree(object->tess_id);
    psFree(object->end_stage);
    psFree(object->epoch);
}

bool fakeRunCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fake_id", PS_DATA_S64, "Primary Key AUTO_INCREMENT", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fake_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "cam_id", PS_DATA_S64, "Key INDEX(fake_id, cam_id) fkey (cam_id) ref camRun(cam_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item cam_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "reduction", PS_DATA_STRING, "Reduction class", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item reduction");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "expgroup", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item expgroup");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "end_stage", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item end_stage");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, FAKERUN_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool fakeRunDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, FAKERUN_TABLE_NAME);
}

bool fakeRunInsert(psDB * dbh, psS64 fake_id, psS64 cam_id, const char *state, const char *workdir, const char *label, const char *reduction, const char *expgroup, const char *dvodb, const char *tess_id, const char *end_stage, psTime* epoch)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fake_id", PS_DATA_S64, NULL, fake_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fake_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "cam_id", PS_DATA_S64, NULL, cam_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item cam_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, label)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "reduction", PS_DATA_STRING, NULL, reduction)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item reduction");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "expgroup", PS_DATA_STRING, NULL, expgroup)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item expgroup");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, dvodb)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, tess_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "end_stage", PS_DATA_STRING, NULL, end_stage)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item end_stage");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, epoch)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, FAKERUN_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long fakeRunDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, FAKERUN_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from fakeRun");
        return count;

        deleted += count;
    }

    return deleted;
}
bool fakeRunInsertObject(psDB *dbh, fakeRunRow *object)
{
    return fakeRunInsert(dbh, object->fake_id, object->cam_id, object->state, object->workdir, object->label, object->reduction, object->expgroup, object->dvodb, object->tess_id, object->end_stage, object->epoch);
}

bool fakeRunInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!fakeRunInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool fakeRunInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  FAKERUN_TABLE_NAME
    if (!psFitsMoveExtName(fits, FAKERUN_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", FAKERUN_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, FAKERUN_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool fakeRunSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, FAKERUN_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, FAKERUN_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *fakeRunMetadataFromObject(const fakeRunRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fake_id", PS_DATA_S64, NULL, object->fake_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fake_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "cam_id", PS_DATA_S64, NULL, object->cam_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item cam_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, object->state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, object->workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, object->label)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "reduction", PS_DATA_STRING, NULL, object->reduction)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item reduction");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "expgroup", PS_DATA_STRING, NULL, object->expgroup)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item expgroup");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, object->dvodb)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, object->tess_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "end_stage", PS_DATA_STRING, NULL, object->end_stage)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item end_stage");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, object->epoch)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }


    return md;
}

fakeRunRow *fakeRunObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 fake_id = psMetadataLookupS64(&status, md, "fake_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fake_id");
        return false;
    }
    psS64 cam_id = psMetadataLookupS64(&status, md, "cam_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item cam_id");
        return false;
    }
    char* state = psMetadataLookupPtr(&status, md, "state");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item state");
        return false;
    }
    char* workdir = psMetadataLookupPtr(&status, md, "workdir");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
        return false;
    }
    char* label = psMetadataLookupPtr(&status, md, "label");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item label");
        return false;
    }
    char* reduction = psMetadataLookupPtr(&status, md, "reduction");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item reduction");
        return false;
    }
    char* expgroup = psMetadataLookupPtr(&status, md, "expgroup");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item expgroup");
        return false;
    }
    char* dvodb = psMetadataLookupPtr(&status, md, "dvodb");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dvodb");
        return false;
    }
    char* tess_id = psMetadataLookupPtr(&status, md, "tess_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item tess_id");
        return false;
    }
    char* end_stage = psMetadataLookupPtr(&status, md, "end_stage");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item end_stage");
        return false;
    }
    psTime* epoch = psMetadataLookupPtr(&status, md, "epoch");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item epoch");
        return false;
    }

    return fakeRunRowAlloc(fake_id, cam_id, state, workdir, label, reduction, expgroup, dvodb, tess_id, end_stage, epoch);
}
psArray *fakeRunSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, FAKERUN_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        fakeRunRow *object = fakeRunObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool fakeRunDeleteObject(psDB *dbh, const fakeRunRow *object)
{
    psMetadata *where = fakeRunMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, FAKERUN_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from fakeRun");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "fakeRunRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long fakeRunDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        fakeRunRow *object = objects->data[i];
        psMetadata *where = fakeRunMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, FAKERUN_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from fakeRun");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool fakeRunPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = fakeRunMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            FAKERUN_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool fakeRunPrintObject(FILE *stream, fakeRunRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = fakeRunMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void fakeProcessedImfileRowFree(fakeProcessedImfileRow *object);

fakeProcessedImfileRow *fakeProcessedImfileRowAlloc(psS64 fake_id, psS64 exp_id, const char *class_id, const char *uri, psF32 dtime_fake, const char *hostname, const char *path_base, psS16 fault, psTime* epoch)
{
    fakeProcessedImfileRow *_object;

    _object = psAlloc(sizeof(fakeProcessedImfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)fakeProcessedImfileRowFree);

    _object->fake_id = fake_id;
    _object->exp_id = exp_id;
    _object->class_id = psStringCopy(class_id);
    _object->uri = psStringCopy(uri);
    _object->dtime_fake = dtime_fake;
    _object->hostname = psStringCopy(hostname);
    _object->path_base = psStringCopy(path_base);
    _object->fault = fault;
    _object->epoch = psTimeCopy(epoch);

    return _object;
}

static void fakeProcessedImfileRowFree(fakeProcessedImfileRow *object)
{
    psFree(object->class_id);
    psFree(object->uri);
    psFree(object->hostname);
    psFree(object->path_base);
    psFree(object->epoch);
}

bool fakeProcessedImfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fake_id", PS_DATA_S64, "Primary Key fkey (fake_id) ref fakeRun(fake_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fake_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, "Primary Key fkey (exp_id, class_id) ref rawImfile(exp_id, class_id)", 64)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_fake", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_fake");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "hostname", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item hostname");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, FAKEPROCESSEDIMFILE_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool fakeProcessedImfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, FAKEPROCESSEDIMFILE_TABLE_NAME);
}

bool fakeProcessedImfileInsert(psDB * dbh, psS64 fake_id, psS64 exp_id, const char *class_id, const char *uri, psF32 dtime_fake, const char *hostname, const char *path_base, psS16 fault, psTime* epoch)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fake_id", PS_DATA_S64, NULL, fake_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fake_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_fake", PS_DATA_F32, NULL, dtime_fake)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_fake");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "hostname", PS_DATA_STRING, NULL, hostname)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item hostname");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, epoch)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, FAKEPROCESSEDIMFILE_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long fakeProcessedImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, FAKEPROCESSEDIMFILE_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from fakeProcessedImfile");
        return count;

        deleted += count;
    }

    return deleted;
}
bool fakeProcessedImfileInsertObject(psDB *dbh, fakeProcessedImfileRow *object)
{
    return fakeProcessedImfileInsert(dbh, object->fake_id, object->exp_id, object->class_id, object->uri, object->dtime_fake, object->hostname, object->path_base, object->fault, object->epoch);
}

bool fakeProcessedImfileInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!fakeProcessedImfileInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool fakeProcessedImfileInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  FAKEPROCESSEDIMFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, FAKEPROCESSEDIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", FAKEPROCESSEDIMFILE_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, FAKEPROCESSEDIMFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool fakeProcessedImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, FAKEPROCESSEDIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, FAKEPROCESSEDIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *fakeProcessedImfileMetadataFromObject(const fakeProcessedImfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fake_id", PS_DATA_S64, NULL, object->fake_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fake_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, object->exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_fake", PS_DATA_F32, NULL, object->dtime_fake)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_fake");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "hostname", PS_DATA_STRING, NULL, object->hostname)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item hostname");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, object->path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "epoch", PS_DATA_TIME, NULL, object->epoch)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item epoch");
        psFree(md);
        return false;
    }


    return md;
}

fakeProcessedImfileRow *fakeProcessedImfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 fake_id = psMetadataLookupS64(&status, md, "fake_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fake_id");
        return false;
    }
    psS64 exp_id = psMetadataLookupS64(&status, md, "exp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
        return false;
    }
    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    psF32 dtime_fake = psMetadataLookupF32(&status, md, "dtime_fake");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dtime_fake");
        return false;
    }
    char* hostname = psMetadataLookupPtr(&status, md, "hostname");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item hostname");
        return false;
    }
    char* path_base = psMetadataLookupPtr(&status, md, "path_base");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item path_base");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }
    psTime* epoch = psMetadataLookupPtr(&status, md, "epoch");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item epoch");
        return false;
    }

    return fakeProcessedImfileRowAlloc(fake_id, exp_id, class_id, uri, dtime_fake, hostname, path_base, fault, epoch);
}
psArray *fakeProcessedImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, FAKEPROCESSEDIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        fakeProcessedImfileRow *object = fakeProcessedImfileObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool fakeProcessedImfileDeleteObject(psDB *dbh, const fakeProcessedImfileRow *object)
{
    psMetadata *where = fakeProcessedImfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, FAKEPROCESSEDIMFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from fakeProcessedImfile");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "fakeProcessedImfileRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long fakeProcessedImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        fakeProcessedImfileRow *object = objects->data[i];
        psMetadata *where = fakeProcessedImfileMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, FAKEPROCESSEDIMFILE_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from fakeProcessedImfile");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool fakeProcessedImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = fakeProcessedImfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            FAKEPROCESSEDIMFILE_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool fakeProcessedImfilePrintObject(FILE *stream, fakeProcessedImfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = fakeProcessedImfileMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void fakeMaskRowFree(fakeMaskRow *object);

fakeMaskRow *fakeMaskRowAlloc(const char *label)
{
    fakeMaskRow     *_object;

    _object = psAlloc(sizeof(fakeMaskRow));
    psMemSetDeallocator(_object, (psFreeFunc)fakeMaskRowFree);

    _object->label = psStringCopy(label);

    return _object;
}

static void fakeMaskRowFree(fakeMaskRow *object)
{
    psFree(object->label);
}

bool fakeMaskCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, FAKEMASK_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool fakeMaskDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, FAKEMASK_TABLE_NAME);
}

bool fakeMaskInsert(psDB * dbh, const char *label)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, label)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, FAKEMASK_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long fakeMaskDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, FAKEMASK_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from fakeMask");
        return count;

        deleted += count;
    }

    return deleted;
}
bool fakeMaskInsertObject(psDB *dbh, fakeMaskRow *object)
{
    return fakeMaskInsert(dbh, object->label);
}

bool fakeMaskInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!fakeMaskInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool fakeMaskInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  FAKEMASK_TABLE_NAME
    if (!psFitsMoveExtName(fits, FAKEMASK_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", FAKEMASK_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, FAKEMASK_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool fakeMaskSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, FAKEMASK_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, FAKEMASK_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *fakeMaskMetadataFromObject(const fakeMaskRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, object->label)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }


    return md;
}

fakeMaskRow *fakeMaskObjectFromMetadata(psMetadata *md)
{

bool status = false;
    char* label = psMetadataLookupPtr(&status, md, "label");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item label");
        return false;
    }

    return fakeMaskRowAlloc(label);
}
psArray *fakeMaskSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, FAKEMASK_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        fakeMaskRow *object = fakeMaskObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool fakeMaskDeleteObject(psDB *dbh, const fakeMaskRow *object)
{
    psMetadata *where = fakeMaskMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, FAKEMASK_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from fakeMask");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "fakeMaskRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long fakeMaskDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        fakeMaskRow *object = objects->data[i];
        psMetadata *where = fakeMaskMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, FAKEMASK_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from fakeMask");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool fakeMaskPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = fakeMaskMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            FAKEMASK_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool fakeMaskPrintObject(FILE *stream, fakeMaskRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = fakeMaskMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void warpRunRowFree(warpRunRow *object);

warpRunRow *warpRunRowAlloc(psS64 warp_id, psS64 fake_id, const char *mode, const char *state, const char *workdir, const char *workdir_state, const char *label, const char *dvodb, const char *tess_id, const char *end_stage, psTime* registered, bool magiced)
{
    warpRunRow      *_object;

    _object = psAlloc(sizeof(warpRunRow));
    psMemSetDeallocator(_object, (psFreeFunc)warpRunRowFree);

    _object->warp_id = warp_id;
    _object->fake_id = fake_id;
    _object->mode = psStringCopy(mode);
    _object->state = psStringCopy(state);
    _object->workdir = psStringCopy(workdir);
    _object->workdir_state = psStringCopy(workdir_state);
    _object->label = psStringCopy(label);
    _object->dvodb = psStringCopy(dvodb);
    _object->tess_id = psStringCopy(tess_id);
    _object->end_stage = psStringCopy(end_stage);
    _object->registered = psTimeCopy(registered);
    _object->magiced = magiced;

    return _object;
}

static void warpRunRowFree(warpRunRow *object)
{
    psFree(object->mode);
    psFree(object->state);
    psFree(object->workdir);
    psFree(object->workdir_state);
    psFree(object->label);
    psFree(object->dvodb);
    psFree(object->tess_id);
    psFree(object->end_stage);
    psFree(object->registered);
}

bool warpRunCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, "Primary Key AUTO_INCREMENT", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fake_id", PS_DATA_S64, "Key INDEX(warp_id, fake_id) fkey(fake_id) ref camProcessedExp(fake_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fake_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "mode", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item mode");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir_state", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir_state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, "key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "end_stage", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item end_stage");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "magiced", PS_DATA_BOOL, "Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magiced");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, WARPRUN_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool warpRunDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, WARPRUN_TABLE_NAME);
}

bool warpRunInsert(psDB * dbh, psS64 warp_id, psS64 fake_id, const char *mode, const char *state, const char *workdir, const char *workdir_state, const char *label, const char *dvodb, const char *tess_id, const char *end_stage, psTime* registered, bool magiced)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, NULL, warp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fake_id", PS_DATA_S64, NULL, fake_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fake_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "mode", PS_DATA_STRING, NULL, mode)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item mode");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir_state", PS_DATA_STRING, NULL, workdir_state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir_state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, label)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, dvodb)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, tess_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "end_stage", PS_DATA_STRING, NULL, end_stage)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item end_stage");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, registered)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "magiced", PS_DATA_BOOL, NULL, magiced)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magiced");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, WARPRUN_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long warpRunDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, WARPRUN_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from warpRun");
        return count;

        deleted += count;
    }

    return deleted;
}
bool warpRunInsertObject(psDB *dbh, warpRunRow *object)
{
    return warpRunInsert(dbh, object->warp_id, object->fake_id, object->mode, object->state, object->workdir, object->workdir_state, object->label, object->dvodb, object->tess_id, object->end_stage, object->registered, object->magiced);
}

bool warpRunInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!warpRunInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool warpRunInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  WARPRUN_TABLE_NAME
    if (!psFitsMoveExtName(fits, WARPRUN_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", WARPRUN_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, WARPRUN_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool warpRunSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, WARPRUN_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, WARPRUN_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *warpRunMetadataFromObject(const warpRunRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, NULL, object->warp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fake_id", PS_DATA_S64, NULL, object->fake_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fake_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "mode", PS_DATA_STRING, NULL, object->mode)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item mode");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, object->state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, object->workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir_state", PS_DATA_STRING, NULL, object->workdir_state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir_state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, object->label)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, object->dvodb)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, object->tess_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "end_stage", PS_DATA_STRING, NULL, object->end_stage)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item end_stage");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, object->registered)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "magiced", PS_DATA_BOOL, NULL, object->magiced)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magiced");
        psFree(md);
        return false;
    }


    return md;
}

warpRunRow *warpRunObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 warp_id = psMetadataLookupS64(&status, md, "warp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item warp_id");
        return false;
    }
    psS64 fake_id = psMetadataLookupS64(&status, md, "fake_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fake_id");
        return false;
    }
    char* mode = psMetadataLookupPtr(&status, md, "mode");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item mode");
        return false;
    }
    char* state = psMetadataLookupPtr(&status, md, "state");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item state");
        return false;
    }
    char* workdir = psMetadataLookupPtr(&status, md, "workdir");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
        return false;
    }
    char* workdir_state = psMetadataLookupPtr(&status, md, "workdir_state");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir_state");
        return false;
    }
    char* label = psMetadataLookupPtr(&status, md, "label");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item label");
        return false;
    }
    char* dvodb = psMetadataLookupPtr(&status, md, "dvodb");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dvodb");
        return false;
    }
    char* tess_id = psMetadataLookupPtr(&status, md, "tess_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item tess_id");
        return false;
    }
    char* end_stage = psMetadataLookupPtr(&status, md, "end_stage");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item end_stage");
        return false;
    }
    psTime* registered = psMetadataLookupPtr(&status, md, "registered");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item registered");
        return false;
    }
    bool magiced = psMetadataLookupBool(&status, md, "magiced");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item magiced");
        return false;
    }

    return warpRunRowAlloc(warp_id, fake_id, mode, state, workdir, workdir_state, label, dvodb, tess_id, end_stage, registered, magiced);
}
psArray *warpRunSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, WARPRUN_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        warpRunRow *object = warpRunObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool warpRunDeleteObject(psDB *dbh, const warpRunRow *object)
{
    psMetadata *where = warpRunMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, WARPRUN_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from warpRun");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "warpRunRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long warpRunDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        warpRunRow *object = objects->data[i];
        psMetadata *where = warpRunMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, WARPRUN_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from warpRun");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool warpRunPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = warpRunMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            WARPRUN_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool warpRunPrintObject(FILE *stream, warpRunRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = warpRunMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void warpSkyCellMapRowFree(warpSkyCellMapRow *object);

warpSkyCellMapRow *warpSkyCellMapRowAlloc(psS64 warp_id, const char *skycell_id, const char *tess_id, const char *class_id, psS16 fault)
{
    warpSkyCellMapRow *_object;

    _object = psAlloc(sizeof(warpSkyCellMapRow));
    psMemSetDeallocator(_object, (psFreeFunc)warpSkyCellMapRowFree);

    _object->warp_id = warp_id;
    _object->skycell_id = psStringCopy(skycell_id);
    _object->tess_id = psStringCopy(tess_id);
    _object->class_id = psStringCopy(class_id);
    _object->fault = fault;

    return _object;
}

static void warpSkyCellMapRowFree(warpSkyCellMapRow *object)
{
    psFree(object->skycell_id);
    psFree(object->tess_id);
    psFree(object->class_id);
}

bool warpSkyCellMapCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, "Primary Key fkey(warp_id) ref warpRun(warp_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, WARPSKYCELLMAP_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool warpSkyCellMapDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, WARPSKYCELLMAP_TABLE_NAME);
}

bool warpSkyCellMapInsert(psDB * dbh, psS64 warp_id, const char *skycell_id, const char *tess_id, const char *class_id, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, NULL, warp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, NULL, skycell_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, tess_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, WARPSKYCELLMAP_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long warpSkyCellMapDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, WARPSKYCELLMAP_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from warpSkyCellMap");
        return count;

        deleted += count;
    }

    return deleted;
}
bool warpSkyCellMapInsertObject(psDB *dbh, warpSkyCellMapRow *object)
{
    return warpSkyCellMapInsert(dbh, object->warp_id, object->skycell_id, object->tess_id, object->class_id, object->fault);
}

bool warpSkyCellMapInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!warpSkyCellMapInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool warpSkyCellMapInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  WARPSKYCELLMAP_TABLE_NAME
    if (!psFitsMoveExtName(fits, WARPSKYCELLMAP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", WARPSKYCELLMAP_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, WARPSKYCELLMAP_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool warpSkyCellMapSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, WARPSKYCELLMAP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, WARPSKYCELLMAP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *warpSkyCellMapMetadataFromObject(const warpSkyCellMapRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, NULL, object->warp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, NULL, object->skycell_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, object->tess_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

warpSkyCellMapRow *warpSkyCellMapObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 warp_id = psMetadataLookupS64(&status, md, "warp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item warp_id");
        return false;
    }
    char* skycell_id = psMetadataLookupPtr(&status, md, "skycell_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item skycell_id");
        return false;
    }
    char* tess_id = psMetadataLookupPtr(&status, md, "tess_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item tess_id");
        return false;
    }
    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return warpSkyCellMapRowAlloc(warp_id, skycell_id, tess_id, class_id, fault);
}
psArray *warpSkyCellMapSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, WARPSKYCELLMAP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        warpSkyCellMapRow *object = warpSkyCellMapObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool warpSkyCellMapDeleteObject(psDB *dbh, const warpSkyCellMapRow *object)
{
    psMetadata *where = warpSkyCellMapMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, WARPSKYCELLMAP_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from warpSkyCellMap");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "warpSkyCellMapRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long warpSkyCellMapDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        warpSkyCellMapRow *object = objects->data[i];
        psMetadata *where = warpSkyCellMapMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, WARPSKYCELLMAP_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from warpSkyCellMap");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool warpSkyCellMapPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = warpSkyCellMapMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            WARPSKYCELLMAP_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool warpSkyCellMapPrintObject(FILE *stream, warpSkyCellMapRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = warpSkyCellMapMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void warpSkyfileRowFree(warpSkyfileRow *object);

warpSkyfileRow *warpSkyfileRowAlloc(psS64 warp_id, const char *skycell_id, const char *tess_id, const char *uri, const char *path_base, psF64 bg, psF64 bg_stdev, psF32 dtime_warp, const char *hostname, psF32 good_frac, psS32 xmin, psS32 xmax, psS32 ymin, psS32 ymax, bool ignored, psS16 fault)
{
    warpSkyfileRow  *_object;

    _object = psAlloc(sizeof(warpSkyfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)warpSkyfileRowFree);

    _object->warp_id = warp_id;
    _object->skycell_id = psStringCopy(skycell_id);
    _object->tess_id = psStringCopy(tess_id);
    _object->uri = psStringCopy(uri);
    _object->path_base = psStringCopy(path_base);
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->dtime_warp = dtime_warp;
    _object->hostname = psStringCopy(hostname);
    _object->good_frac = good_frac;
    _object->xmin = xmin;
    _object->xmax = xmax;
    _object->ymin = ymin;
    _object->ymax = ymax;
    _object->ignored = ignored;
    _object->fault = fault;

    return _object;
}

static void warpSkyfileRowFree(warpSkyfileRow *object)
{
    psFree(object->skycell_id);
    psFree(object->tess_id);
    psFree(object->uri);
    psFree(object->path_base);
    psFree(object->hostname);
}

bool warpSkyfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, "Primary Key fkey(warp_id, skycell_id, tess_id) ref warpSkyCellMap(warp_id, skycell_id, tess_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_warp", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_warp");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "hostname", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item hostname");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "good_frac", PS_DATA_F32, "Key", 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item good_frac");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "xmin", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item xmin");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "xmax", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item xmax");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ymin", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ymin");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ymax", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ymax");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ignored", PS_DATA_BOOL, "Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ignored");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, WARPSKYFILE_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool warpSkyfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, WARPSKYFILE_TABLE_NAME);
}

bool warpSkyfileInsert(psDB * dbh, psS64 warp_id, const char *skycell_id, const char *tess_id, const char *uri, const char *path_base, psF64 bg, psF64 bg_stdev, psF32 dtime_warp, const char *hostname, psF32 good_frac, psS32 xmin, psS32 xmax, psS32 ymin, psS32 ymax, bool ignored, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, NULL, warp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, NULL, skycell_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, tess_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_warp", PS_DATA_F32, NULL, dtime_warp)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_warp");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "hostname", PS_DATA_STRING, NULL, hostname)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item hostname");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "good_frac", PS_DATA_F32, NULL, good_frac)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item good_frac");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "xmin", PS_DATA_S32, NULL, xmin)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item xmin");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "xmax", PS_DATA_S32, NULL, xmax)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item xmax");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ymin", PS_DATA_S32, NULL, ymin)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ymin");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ymax", PS_DATA_S32, NULL, ymax)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ymax");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ignored", PS_DATA_BOOL, NULL, ignored)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ignored");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, WARPSKYFILE_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long warpSkyfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, WARPSKYFILE_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from warpSkyfile");
        return count;

        deleted += count;
    }

    return deleted;
}
bool warpSkyfileInsertObject(psDB *dbh, warpSkyfileRow *object)
{
    return warpSkyfileInsert(dbh, object->warp_id, object->skycell_id, object->tess_id, object->uri, object->path_base, object->bg, object->bg_stdev, object->dtime_warp, object->hostname, object->good_frac, object->xmin, object->xmax, object->ymin, object->ymax, object->ignored, object->fault);
}

bool warpSkyfileInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!warpSkyfileInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool warpSkyfileInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  WARPSKYFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, WARPSKYFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", WARPSKYFILE_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, WARPSKYFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool warpSkyfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, WARPSKYFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, WARPSKYFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *warpSkyfileMetadataFromObject(const warpSkyfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, NULL, object->warp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, NULL, object->skycell_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, object->tess_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, object->path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_warp", PS_DATA_F32, NULL, object->dtime_warp)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_warp");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "hostname", PS_DATA_STRING, NULL, object->hostname)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item hostname");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "good_frac", PS_DATA_F32, NULL, object->good_frac)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item good_frac");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "xmin", PS_DATA_S32, NULL, object->xmin)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item xmin");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "xmax", PS_DATA_S32, NULL, object->xmax)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item xmax");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ymin", PS_DATA_S32, NULL, object->ymin)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ymin");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ymax", PS_DATA_S32, NULL, object->ymax)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ymax");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ignored", PS_DATA_BOOL, NULL, object->ignored)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ignored");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

warpSkyfileRow *warpSkyfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 warp_id = psMetadataLookupS64(&status, md, "warp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item warp_id");
        return false;
    }
    char* skycell_id = psMetadataLookupPtr(&status, md, "skycell_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item skycell_id");
        return false;
    }
    char* tess_id = psMetadataLookupPtr(&status, md, "tess_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item tess_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    char* path_base = psMetadataLookupPtr(&status, md, "path_base");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item path_base");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF32 dtime_warp = psMetadataLookupF32(&status, md, "dtime_warp");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dtime_warp");
        return false;
    }
    char* hostname = psMetadataLookupPtr(&status, md, "hostname");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item hostname");
        return false;
    }
    psF32 good_frac = psMetadataLookupF32(&status, md, "good_frac");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item good_frac");
        return false;
    }
    psS32 xmin = psMetadataLookupS32(&status, md, "xmin");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item xmin");
        return false;
    }
    psS32 xmax = psMetadataLookupS32(&status, md, "xmax");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item xmax");
        return false;
    }
    psS32 ymin = psMetadataLookupS32(&status, md, "ymin");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ymin");
        return false;
    }
    psS32 ymax = psMetadataLookupS32(&status, md, "ymax");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ymax");
        return false;
    }
    bool ignored = psMetadataLookupBool(&status, md, "ignored");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ignored");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return warpSkyfileRowAlloc(warp_id, skycell_id, tess_id, uri, path_base, bg, bg_stdev, dtime_warp, hostname, good_frac, xmin, xmax, ymin, ymax, ignored, fault);
}
psArray *warpSkyfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, WARPSKYFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        warpSkyfileRow *object = warpSkyfileObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool warpSkyfileDeleteObject(psDB *dbh, const warpSkyfileRow *object)
{
    psMetadata *where = warpSkyfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, WARPSKYFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from warpSkyfile");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "warpSkyfileRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long warpSkyfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        warpSkyfileRow *object = objects->data[i];
        psMetadata *where = warpSkyfileMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, WARPSKYFILE_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from warpSkyfile");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool warpSkyfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = warpSkyfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            WARPSKYFILE_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool warpSkyfilePrintObject(FILE *stream, warpSkyfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = warpSkyfileMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void warpMaskRowFree(warpMaskRow *object);

warpMaskRow *warpMaskRowAlloc(const char *label)
{
    warpMaskRow     *_object;

    _object = psAlloc(sizeof(warpMaskRow));
    psMemSetDeallocator(_object, (psFreeFunc)warpMaskRowFree);

    _object->label = psStringCopy(label);

    return _object;
}

static void warpMaskRowFree(warpMaskRow *object)
{
    psFree(object->label);
}

bool warpMaskCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, WARPMASK_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool warpMaskDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, WARPMASK_TABLE_NAME);
}

bool warpMaskInsert(psDB * dbh, const char *label)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, label)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, WARPMASK_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long warpMaskDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, WARPMASK_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from warpMask");
        return count;

        deleted += count;
    }

    return deleted;
}
bool warpMaskInsertObject(psDB *dbh, warpMaskRow *object)
{
    return warpMaskInsert(dbh, object->label);
}

bool warpMaskInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!warpMaskInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool warpMaskInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  WARPMASK_TABLE_NAME
    if (!psFitsMoveExtName(fits, WARPMASK_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", WARPMASK_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, WARPMASK_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool warpMaskSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, WARPMASK_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, WARPMASK_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *warpMaskMetadataFromObject(const warpMaskRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, object->label)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }


    return md;
}

warpMaskRow *warpMaskObjectFromMetadata(psMetadata *md)
{

bool status = false;
    char* label = psMetadataLookupPtr(&status, md, "label");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item label");
        return false;
    }

    return warpMaskRowAlloc(label);
}
psArray *warpMaskSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, WARPMASK_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        warpMaskRow *object = warpMaskObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool warpMaskDeleteObject(psDB *dbh, const warpMaskRow *object)
{
    psMetadata *where = warpMaskMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, WARPMASK_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from warpMask");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "warpMaskRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long warpMaskDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        warpMaskRow *object = objects->data[i];
        psMetadata *where = warpMaskMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, WARPMASK_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from warpMask");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool warpMaskPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = warpMaskMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            WARPMASK_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool warpMaskPrintObject(FILE *stream, warpMaskRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = warpMaskMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void diffRunRowFree(diffRunRow *object);

diffRunRow *diffRunRowAlloc(psS64 diff_id, const char *state, const char *workdir, const char *dvodb, psTime* registered, const char *skycell_id, const char *tess_id)
{
    diffRunRow      *_object;

    _object = psAlloc(sizeof(diffRunRow));
    psMemSetDeallocator(_object, (psFreeFunc)diffRunRowFree);

    _object->diff_id = diff_id;
    _object->state = psStringCopy(state);
    _object->workdir = psStringCopy(workdir);
    _object->dvodb = psStringCopy(dvodb);
    _object->registered = psTimeCopy(registered);
    _object->skycell_id = psStringCopy(skycell_id);
    _object->tess_id = psStringCopy(tess_id);

    return _object;
}

static void diffRunRowFree(diffRunRow *object)
{
    psFree(object->state);
    psFree(object->workdir);
    psFree(object->dvodb);
    psFree(object->registered);
    psFree(object->skycell_id);
    psFree(object->tess_id);
}

bool diffRunCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "diff_id", PS_DATA_S64, "Primary Key AUTO_INCREMENT", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item diff_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, DIFFRUN_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool diffRunDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, DIFFRUN_TABLE_NAME);
}

bool diffRunInsert(psDB * dbh, psS64 diff_id, const char *state, const char *workdir, const char *dvodb, psTime* registered, const char *skycell_id, const char *tess_id)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "diff_id", PS_DATA_S64, NULL, diff_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item diff_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, dvodb)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, registered)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, NULL, skycell_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, tess_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, DIFFRUN_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long diffRunDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, DIFFRUN_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from diffRun");
        return count;

        deleted += count;
    }

    return deleted;
}
bool diffRunInsertObject(psDB *dbh, diffRunRow *object)
{
    return diffRunInsert(dbh, object->diff_id, object->state, object->workdir, object->dvodb, object->registered, object->skycell_id, object->tess_id);
}

bool diffRunInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!diffRunInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool diffRunInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  DIFFRUN_TABLE_NAME
    if (!psFitsMoveExtName(fits, DIFFRUN_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DIFFRUN_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, DIFFRUN_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool diffRunSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, DIFFRUN_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, DIFFRUN_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *diffRunMetadataFromObject(const diffRunRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "diff_id", PS_DATA_S64, NULL, object->diff_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item diff_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, object->state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, object->workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, object->dvodb)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, object->registered)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, NULL, object->skycell_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, object->tess_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }


    return md;
}

diffRunRow *diffRunObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 diff_id = psMetadataLookupS64(&status, md, "diff_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item diff_id");
        return false;
    }
    char* state = psMetadataLookupPtr(&status, md, "state");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item state");
        return false;
    }
    char* workdir = psMetadataLookupPtr(&status, md, "workdir");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
        return false;
    }
    char* dvodb = psMetadataLookupPtr(&status, md, "dvodb");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dvodb");
        return false;
    }
    psTime* registered = psMetadataLookupPtr(&status, md, "registered");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item registered");
        return false;
    }
    char* skycell_id = psMetadataLookupPtr(&status, md, "skycell_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item skycell_id");
        return false;
    }
    char* tess_id = psMetadataLookupPtr(&status, md, "tess_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item tess_id");
        return false;
    }

    return diffRunRowAlloc(diff_id, state, workdir, dvodb, registered, skycell_id, tess_id);
}
psArray *diffRunSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, DIFFRUN_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        diffRunRow *object = diffRunObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool diffRunDeleteObject(psDB *dbh, const diffRunRow *object)
{
    psMetadata *where = diffRunMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, DIFFRUN_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from diffRun");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "diffRunRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long diffRunDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        diffRunRow *object = objects->data[i];
        psMetadata *where = diffRunMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, DIFFRUN_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from diffRun");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool diffRunPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = diffRunMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            DIFFRUN_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool diffRunPrintObject(FILE *stream, diffRunRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = diffRunMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void diffInputSkyfileRowFree(diffInputSkyfileRow *object);

diffInputSkyfileRow *diffInputSkyfileRowAlloc(psS64 diff_id, bool template, psS64 stack_id, psS64 warp_id, const char *skycell_id, const char *tess_id, const char *kind)
{
    diffInputSkyfileRow *_object;

    _object = psAlloc(sizeof(diffInputSkyfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)diffInputSkyfileRowFree);

    _object->diff_id = diff_id;
    _object->template = template;
    _object->stack_id = stack_id;
    _object->warp_id = warp_id;
    _object->skycell_id = psStringCopy(skycell_id);
    _object->tess_id = psStringCopy(tess_id);
    _object->kind = psStringCopy(kind);

    return _object;
}

static void diffInputSkyfileRowFree(diffInputSkyfileRow *object)
{
    psFree(object->skycell_id);
    psFree(object->tess_id);
    psFree(object->kind);
}

bool diffInputSkyfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "diff_id", PS_DATA_S64, "Primary Key fkey(diff_id) ref diffRun(diff_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item diff_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "template", PS_DATA_BOOL, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item template");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "stack_id", PS_DATA_S64, "fkey(stack_id) ref stackSumSkyfile(stack_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item stack_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, "fkey(warp_id, skycell_id, tess_id) ref warpSkyfile(warp_id, skycell_id, tess_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "kind", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item kind");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, DIFFINPUTSKYFILE_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool diffInputSkyfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, DIFFINPUTSKYFILE_TABLE_NAME);
}

bool diffInputSkyfileInsert(psDB * dbh, psS64 diff_id, bool template, psS64 stack_id, psS64 warp_id, const char *skycell_id, const char *tess_id, const char *kind)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "diff_id", PS_DATA_S64, NULL, diff_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item diff_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "template", PS_DATA_BOOL, NULL, template)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item template");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "stack_id", PS_DATA_S64, NULL, stack_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item stack_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, NULL, warp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, NULL, skycell_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, tess_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "kind", PS_DATA_STRING, NULL, kind)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item kind");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, DIFFINPUTSKYFILE_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long diffInputSkyfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, DIFFINPUTSKYFILE_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from diffInputSkyfile");
        return count;

        deleted += count;
    }

    return deleted;
}
bool diffInputSkyfileInsertObject(psDB *dbh, diffInputSkyfileRow *object)
{
    return diffInputSkyfileInsert(dbh, object->diff_id, object->template, object->stack_id, object->warp_id, object->skycell_id, object->tess_id, object->kind);
}

bool diffInputSkyfileInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!diffInputSkyfileInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool diffInputSkyfileInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  DIFFINPUTSKYFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, DIFFINPUTSKYFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DIFFINPUTSKYFILE_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, DIFFINPUTSKYFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool diffInputSkyfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, DIFFINPUTSKYFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, DIFFINPUTSKYFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *diffInputSkyfileMetadataFromObject(const diffInputSkyfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "diff_id", PS_DATA_S64, NULL, object->diff_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item diff_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "template", PS_DATA_BOOL, NULL, object->template)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item template");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "stack_id", PS_DATA_S64, NULL, object->stack_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item stack_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, NULL, object->warp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, NULL, object->skycell_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, object->tess_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "kind", PS_DATA_STRING, NULL, object->kind)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item kind");
        psFree(md);
        return false;
    }


    return md;
}

diffInputSkyfileRow *diffInputSkyfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 diff_id = psMetadataLookupS64(&status, md, "diff_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item diff_id");
        return false;
    }
    bool template = psMetadataLookupBool(&status, md, "template");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item template");
        return false;
    }
    psS64 stack_id = psMetadataLookupS64(&status, md, "stack_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stack_id");
        return false;
    }
    psS64 warp_id = psMetadataLookupS64(&status, md, "warp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item warp_id");
        return false;
    }
    char* skycell_id = psMetadataLookupPtr(&status, md, "skycell_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item skycell_id");
        return false;
    }
    char* tess_id = psMetadataLookupPtr(&status, md, "tess_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item tess_id");
        return false;
    }
    char* kind = psMetadataLookupPtr(&status, md, "kind");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item kind");
        return false;
    }

    return diffInputSkyfileRowAlloc(diff_id, template, stack_id, warp_id, skycell_id, tess_id, kind);
}
psArray *diffInputSkyfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, DIFFINPUTSKYFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        diffInputSkyfileRow *object = diffInputSkyfileObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool diffInputSkyfileDeleteObject(psDB *dbh, const diffInputSkyfileRow *object)
{
    psMetadata *where = diffInputSkyfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, DIFFINPUTSKYFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from diffInputSkyfile");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "diffInputSkyfileRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long diffInputSkyfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        diffInputSkyfileRow *object = objects->data[i];
        psMetadata *where = diffInputSkyfileMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, DIFFINPUTSKYFILE_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from diffInputSkyfile");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool diffInputSkyfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = diffInputSkyfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            DIFFINPUTSKYFILE_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool diffInputSkyfilePrintObject(FILE *stream, diffInputSkyfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = diffInputSkyfileMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void diffSkyfileRowFree(diffSkyfileRow *object);

diffSkyfileRow *diffSkyfileRowAlloc(psS64 diff_id, const char *uri, const char *path_base, psF64 bg, psF64 bg_stdev, psS32 stamps_num, psF32 stamps_rms, psS32 sources, psF32 dtime_diff, const char *hostname, psF32 good_frac, psS16 fault)
{
    diffSkyfileRow  *_object;

    _object = psAlloc(sizeof(diffSkyfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)diffSkyfileRowFree);

    _object->diff_id = diff_id;
    _object->uri = psStringCopy(uri);
    _object->path_base = psStringCopy(path_base);
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->stamps_num = stamps_num;
    _object->stamps_rms = stamps_rms;
    _object->sources = sources;
    _object->dtime_diff = dtime_diff;
    _object->hostname = psStringCopy(hostname);
    _object->good_frac = good_frac;
    _object->fault = fault;

    return _object;
}

static void diffSkyfileRowFree(diffSkyfileRow *object)
{
    psFree(object->uri);
    psFree(object->path_base);
    psFree(object->hostname);
}

bool diffSkyfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "diff_id", PS_DATA_S64, "Primary Key fkey(diff_id) ref diffRun(diff_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item diff_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "stamps_num", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item stamps_num");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "stamps_rms", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item stamps_rms");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "sources", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sources");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_diff", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_diff");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "hostname", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item hostname");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "good_frac", PS_DATA_F32, "Key", 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item good_frac");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, DIFFSKYFILE_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool diffSkyfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, DIFFSKYFILE_TABLE_NAME);
}

bool diffSkyfileInsert(psDB * dbh, psS64 diff_id, const char *uri, const char *path_base, psF64 bg, psF64 bg_stdev, psS32 stamps_num, psF32 stamps_rms, psS32 sources, psF32 dtime_diff, const char *hostname, psF32 good_frac, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "diff_id", PS_DATA_S64, NULL, diff_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item diff_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "stamps_num", PS_DATA_S32, NULL, stamps_num)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item stamps_num");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "stamps_rms", PS_DATA_F32, NULL, stamps_rms)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item stamps_rms");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "sources", PS_DATA_S32, NULL, sources)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sources");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_diff", PS_DATA_F32, NULL, dtime_diff)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_diff");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "hostname", PS_DATA_STRING, NULL, hostname)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item hostname");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "good_frac", PS_DATA_F32, NULL, good_frac)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item good_frac");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, DIFFSKYFILE_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long diffSkyfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, DIFFSKYFILE_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from diffSkyfile");
        return count;

        deleted += count;
    }

    return deleted;
}
bool diffSkyfileInsertObject(psDB *dbh, diffSkyfileRow *object)
{
    return diffSkyfileInsert(dbh, object->diff_id, object->uri, object->path_base, object->bg, object->bg_stdev, object->stamps_num, object->stamps_rms, object->sources, object->dtime_diff, object->hostname, object->good_frac, object->fault);
}

bool diffSkyfileInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!diffSkyfileInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool diffSkyfileInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  DIFFSKYFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, DIFFSKYFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DIFFSKYFILE_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, DIFFSKYFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool diffSkyfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, DIFFSKYFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, DIFFSKYFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *diffSkyfileMetadataFromObject(const diffSkyfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "diff_id", PS_DATA_S64, NULL, object->diff_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item diff_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, object->path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "stamps_num", PS_DATA_S32, NULL, object->stamps_num)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item stamps_num");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "stamps_rms", PS_DATA_F32, NULL, object->stamps_rms)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item stamps_rms");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "sources", PS_DATA_S32, NULL, object->sources)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sources");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_diff", PS_DATA_F32, NULL, object->dtime_diff)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_diff");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "hostname", PS_DATA_STRING, NULL, object->hostname)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item hostname");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "good_frac", PS_DATA_F32, NULL, object->good_frac)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item good_frac");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

diffSkyfileRow *diffSkyfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 diff_id = psMetadataLookupS64(&status, md, "diff_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item diff_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    char* path_base = psMetadataLookupPtr(&status, md, "path_base");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item path_base");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psS32 stamps_num = psMetadataLookupS32(&status, md, "stamps_num");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stamps_num");
        return false;
    }
    psF32 stamps_rms = psMetadataLookupF32(&status, md, "stamps_rms");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stamps_rms");
        return false;
    }
    psS32 sources = psMetadataLookupS32(&status, md, "sources");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sources");
        return false;
    }
    psF32 dtime_diff = psMetadataLookupF32(&status, md, "dtime_diff");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dtime_diff");
        return false;
    }
    char* hostname = psMetadataLookupPtr(&status, md, "hostname");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item hostname");
        return false;
    }
    psF32 good_frac = psMetadataLookupF32(&status, md, "good_frac");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item good_frac");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return diffSkyfileRowAlloc(diff_id, uri, path_base, bg, bg_stdev, stamps_num, stamps_rms, sources, dtime_diff, hostname, good_frac, fault);
}
psArray *diffSkyfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, DIFFSKYFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        diffSkyfileRow *object = diffSkyfileObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool diffSkyfileDeleteObject(psDB *dbh, const diffSkyfileRow *object)
{
    psMetadata *where = diffSkyfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, DIFFSKYFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from diffSkyfile");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "diffSkyfileRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long diffSkyfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        diffSkyfileRow *object = objects->data[i];
        psMetadata *where = diffSkyfileMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, DIFFSKYFILE_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from diffSkyfile");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool diffSkyfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = diffSkyfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            DIFFSKYFILE_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool diffSkyfilePrintObject(FILE *stream, diffSkyfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = diffSkyfileMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void stackRunRowFree(stackRunRow *object);

stackRunRow *stackRunRowAlloc(psS64 stack_id, const char *state, const char *workdir, const char *dvodb, psTime* registered, const char *skycell_id, const char *tess_id, const char *filter)
{
    stackRunRow     *_object;

    _object = psAlloc(sizeof(stackRunRow));
    psMemSetDeallocator(_object, (psFreeFunc)stackRunRowFree);

    _object->stack_id = stack_id;
    _object->state = psStringCopy(state);
    _object->workdir = psStringCopy(workdir);
    _object->dvodb = psStringCopy(dvodb);
    _object->registered = psTimeCopy(registered);
    _object->skycell_id = psStringCopy(skycell_id);
    _object->tess_id = psStringCopy(tess_id);
    _object->filter = psStringCopy(filter);

    return _object;
}

static void stackRunRowFree(stackRunRow *object)
{
    psFree(object->state);
    psFree(object->workdir);
    psFree(object->dvodb);
    psFree(object->registered);
    psFree(object->skycell_id);
    psFree(object->tess_id);
    psFree(object->filter);
}

bool stackRunCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "stack_id", PS_DATA_S64, "Primary Key AUTO_INCREMENT", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item stack_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, STACKRUN_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool stackRunDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, STACKRUN_TABLE_NAME);
}

bool stackRunInsert(psDB * dbh, psS64 stack_id, const char *state, const char *workdir, const char *dvodb, psTime* registered, const char *skycell_id, const char *tess_id, const char *filter)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "stack_id", PS_DATA_S64, NULL, stack_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item stack_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, dvodb)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, registered)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, NULL, skycell_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, tess_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, filter)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, STACKRUN_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long stackRunDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, STACKRUN_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from stackRun");
        return count;

        deleted += count;
    }

    return deleted;
}
bool stackRunInsertObject(psDB *dbh, stackRunRow *object)
{
    return stackRunInsert(dbh, object->stack_id, object->state, object->workdir, object->dvodb, object->registered, object->skycell_id, object->tess_id, object->filter);
}

bool stackRunInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!stackRunInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool stackRunInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  STACKRUN_TABLE_NAME
    if (!psFitsMoveExtName(fits, STACKRUN_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", STACKRUN_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, STACKRUN_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool stackRunSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, STACKRUN_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, STACKRUN_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *stackRunMetadataFromObject(const stackRunRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "stack_id", PS_DATA_S64, NULL, object->stack_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item stack_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, object->state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, object->workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, object->dvodb)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, object->registered)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "skycell_id", PS_DATA_STRING, NULL, object->skycell_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item skycell_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "tess_id", PS_DATA_STRING, NULL, object->tess_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item tess_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, object->filter)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
        psFree(md);
        return false;
    }


    return md;
}

stackRunRow *stackRunObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 stack_id = psMetadataLookupS64(&status, md, "stack_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stack_id");
        return false;
    }
    char* state = psMetadataLookupPtr(&status, md, "state");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item state");
        return false;
    }
    char* workdir = psMetadataLookupPtr(&status, md, "workdir");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
        return false;
    }
    char* dvodb = psMetadataLookupPtr(&status, md, "dvodb");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dvodb");
        return false;
    }
    psTime* registered = psMetadataLookupPtr(&status, md, "registered");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item registered");
        return false;
    }
    char* skycell_id = psMetadataLookupPtr(&status, md, "skycell_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item skycell_id");
        return false;
    }
    char* tess_id = psMetadataLookupPtr(&status, md, "tess_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item tess_id");
        return false;
    }
    char* filter = psMetadataLookupPtr(&status, md, "filter");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
        return false;
    }

    return stackRunRowAlloc(stack_id, state, workdir, dvodb, registered, skycell_id, tess_id, filter);
}
psArray *stackRunSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, STACKRUN_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        stackRunRow *object = stackRunObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool stackRunDeleteObject(psDB *dbh, const stackRunRow *object)
{
    psMetadata *where = stackRunMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, STACKRUN_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from stackRun");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "stackRunRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long stackRunDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        stackRunRow *object = objects->data[i];
        psMetadata *where = stackRunMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, STACKRUN_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from stackRun");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool stackRunPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = stackRunMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            STACKRUN_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool stackRunPrintObject(FILE *stream, stackRunRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = stackRunMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void stackInputSkyfileRowFree(stackInputSkyfileRow *object);

stackInputSkyfileRow *stackInputSkyfileRowAlloc(psS64 stack_id, psS64 warp_id)
{
    stackInputSkyfileRow *_object;

    _object = psAlloc(sizeof(stackInputSkyfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)stackInputSkyfileRowFree);

    _object->stack_id = stack_id;
    _object->warp_id = warp_id;

    return _object;
}

static void stackInputSkyfileRowFree(stackInputSkyfileRow *object)
{
}

bool stackInputSkyfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "stack_id", PS_DATA_S64, "Primary Key fkey(stack_id) ref stackRun(stack_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item stack_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, "Primary Key fkey(warp_id) ref warpSkyfile(warp_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, STACKINPUTSKYFILE_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool stackInputSkyfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, STACKINPUTSKYFILE_TABLE_NAME);
}

bool stackInputSkyfileInsert(psDB * dbh, psS64 stack_id, psS64 warp_id)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "stack_id", PS_DATA_S64, NULL, stack_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item stack_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, NULL, warp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, STACKINPUTSKYFILE_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long stackInputSkyfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, STACKINPUTSKYFILE_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from stackInputSkyfile");
        return count;

        deleted += count;
    }

    return deleted;
}
bool stackInputSkyfileInsertObject(psDB *dbh, stackInputSkyfileRow *object)
{
    return stackInputSkyfileInsert(dbh, object->stack_id, object->warp_id);
}

bool stackInputSkyfileInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!stackInputSkyfileInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool stackInputSkyfileInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  STACKINPUTSKYFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, STACKINPUTSKYFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", STACKINPUTSKYFILE_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, STACKINPUTSKYFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool stackInputSkyfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, STACKINPUTSKYFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, STACKINPUTSKYFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *stackInputSkyfileMetadataFromObject(const stackInputSkyfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "stack_id", PS_DATA_S64, NULL, object->stack_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item stack_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "warp_id", PS_DATA_S64, NULL, object->warp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item warp_id");
        psFree(md);
        return false;
    }


    return md;
}

stackInputSkyfileRow *stackInputSkyfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 stack_id = psMetadataLookupS64(&status, md, "stack_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stack_id");
        return false;
    }
    psS64 warp_id = psMetadataLookupS64(&status, md, "warp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item warp_id");
        return false;
    }

    return stackInputSkyfileRowAlloc(stack_id, warp_id);
}
psArray *stackInputSkyfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, STACKINPUTSKYFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        stackInputSkyfileRow *object = stackInputSkyfileObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool stackInputSkyfileDeleteObject(psDB *dbh, const stackInputSkyfileRow *object)
{
    psMetadata *where = stackInputSkyfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, STACKINPUTSKYFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from stackInputSkyfile");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "stackInputSkyfileRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long stackInputSkyfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        stackInputSkyfileRow *object = objects->data[i];
        psMetadata *where = stackInputSkyfileMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, STACKINPUTSKYFILE_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from stackInputSkyfile");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool stackInputSkyfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = stackInputSkyfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            STACKINPUTSKYFILE_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool stackInputSkyfilePrintObject(FILE *stream, stackInputSkyfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = stackInputSkyfileMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void stackSumSkyfileRowFree(stackSumSkyfileRow *object);

stackSumSkyfileRow *stackSumSkyfileRowAlloc(psS64 stack_id, const char *uri, const char *path_base, psF64 bg, psF64 bg_stdev, psF32 dtime_stack, const char *hostname, psF32 good_frac, psS16 fault)
{
    stackSumSkyfileRow *_object;

    _object = psAlloc(sizeof(stackSumSkyfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)stackSumSkyfileRowFree);

    _object->stack_id = stack_id;
    _object->uri = psStringCopy(uri);
    _object->path_base = psStringCopy(path_base);
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->dtime_stack = dtime_stack;
    _object->hostname = psStringCopy(hostname);
    _object->good_frac = good_frac;
    _object->fault = fault;

    return _object;
}

static void stackSumSkyfileRowFree(stackSumSkyfileRow *object)
{
    psFree(object->uri);
    psFree(object->path_base);
    psFree(object->hostname);
}

bool stackSumSkyfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "stack_id", PS_DATA_S64, "Primary Key fkey(stack_id) ref stackRun(stack_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item stack_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_stack", PS_DATA_F32, "Key", 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_stack");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "hostname", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item hostname");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "good_frac", PS_DATA_F32, "Key", 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item good_frac");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, STACKSUMSKYFILE_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool stackSumSkyfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, STACKSUMSKYFILE_TABLE_NAME);
}

bool stackSumSkyfileInsert(psDB * dbh, psS64 stack_id, const char *uri, const char *path_base, psF64 bg, psF64 bg_stdev, psF32 dtime_stack, const char *hostname, psF32 good_frac, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "stack_id", PS_DATA_S64, NULL, stack_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item stack_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_stack", PS_DATA_F32, NULL, dtime_stack)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_stack");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "hostname", PS_DATA_STRING, NULL, hostname)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item hostname");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "good_frac", PS_DATA_F32, NULL, good_frac)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item good_frac");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, STACKSUMSKYFILE_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long stackSumSkyfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, STACKSUMSKYFILE_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from stackSumSkyfile");
        return count;

        deleted += count;
    }

    return deleted;
}
bool stackSumSkyfileInsertObject(psDB *dbh, stackSumSkyfileRow *object)
{
    return stackSumSkyfileInsert(dbh, object->stack_id, object->uri, object->path_base, object->bg, object->bg_stdev, object->dtime_stack, object->hostname, object->good_frac, object->fault);
}

bool stackSumSkyfileInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!stackSumSkyfileInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool stackSumSkyfileInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  STACKSUMSKYFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, STACKSUMSKYFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", STACKSUMSKYFILE_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, STACKSUMSKYFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool stackSumSkyfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, STACKSUMSKYFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, STACKSUMSKYFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *stackSumSkyfileMetadataFromObject(const stackSumSkyfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "stack_id", PS_DATA_S64, NULL, object->stack_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item stack_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, object->path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dtime_stack", PS_DATA_F32, NULL, object->dtime_stack)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dtime_stack");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "hostname", PS_DATA_STRING, NULL, object->hostname)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item hostname");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "good_frac", PS_DATA_F32, NULL, object->good_frac)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item good_frac");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

stackSumSkyfileRow *stackSumSkyfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 stack_id = psMetadataLookupS64(&status, md, "stack_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stack_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    char* path_base = psMetadataLookupPtr(&status, md, "path_base");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item path_base");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF32 dtime_stack = psMetadataLookupF32(&status, md, "dtime_stack");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dtime_stack");
        return false;
    }
    char* hostname = psMetadataLookupPtr(&status, md, "hostname");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item hostname");
        return false;
    }
    psF32 good_frac = psMetadataLookupF32(&status, md, "good_frac");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item good_frac");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return stackSumSkyfileRowAlloc(stack_id, uri, path_base, bg, bg_stdev, dtime_stack, hostname, good_frac, fault);
}
psArray *stackSumSkyfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, STACKSUMSKYFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        stackSumSkyfileRow *object = stackSumSkyfileObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool stackSumSkyfileDeleteObject(psDB *dbh, const stackSumSkyfileRow *object)
{
    psMetadata *where = stackSumSkyfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, STACKSUMSKYFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from stackSumSkyfile");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "stackSumSkyfileRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long stackSumSkyfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        stackSumSkyfileRow *object = objects->data[i];
        psMetadata *where = stackSumSkyfileMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, STACKSUMSKYFILE_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from stackSumSkyfile");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool stackSumSkyfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = stackSumSkyfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            STACKSUMSKYFILE_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool stackSumSkyfilePrintObject(FILE *stream, stackSumSkyfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = stackSumSkyfileMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void detRunRowFree(detRunRow *object);

detRunRow *detRunRowAlloc(psS64 det_id, psS32 iteration, const char *det_type, const char *mode, const char *state, const char *filelevel, const char *workdir, const char *camera, const char *telescope, const char *exp_type, const char *reduction, const char *filter, psF32 airmass_min, psF32 airmass_max, psF32 exp_time_min, psF32 exp_time_max, psF32 ccd_temp_min, psF32 ccd_temp_max, psF64 posang_min, psF64 posang_max, psTime* registered, psTime* time_begin, psTime* time_end, psTime* use_begin, psTime* use_end, psF32 solang_min, psF32 solang_max, const char *label, psS32 parent)
{
    detRunRow       *_object;

    _object = psAlloc(sizeof(detRunRow));
    psMemSetDeallocator(_object, (psFreeFunc)detRunRowFree);

    _object->det_id = det_id;
    _object->iteration = iteration;
    _object->det_type = psStringCopy(det_type);
    _object->mode = psStringCopy(mode);
    _object->state = psStringCopy(state);
    _object->filelevel = psStringCopy(filelevel);
    _object->workdir = psStringCopy(workdir);
    _object->camera = psStringCopy(camera);
    _object->telescope = psStringCopy(telescope);
    _object->exp_type = psStringCopy(exp_type);
    _object->reduction = psStringCopy(reduction);
    _object->filter = psStringCopy(filter);
    _object->airmass_min = airmass_min;
    _object->airmass_max = airmass_max;
    _object->exp_time_min = exp_time_min;
    _object->exp_time_max = exp_time_max;
    _object->ccd_temp_min = ccd_temp_min;
    _object->ccd_temp_max = ccd_temp_max;
    _object->posang_min = posang_min;
    _object->posang_max = posang_max;
    _object->registered = psTimeCopy(registered);
    _object->time_begin = psTimeCopy(time_begin);
    _object->time_end = psTimeCopy(time_end);
    _object->use_begin = psTimeCopy(use_begin);
    _object->use_end = psTimeCopy(use_end);
    _object->solang_min = solang_min;
    _object->solang_max = solang_max;
    _object->label = psStringCopy(label);
    _object->parent = parent;

    return _object;
}

static void detRunRowFree(detRunRow *object)
{
    psFree(object->det_type);
    psFree(object->mode);
    psFree(object->state);
    psFree(object->filelevel);
    psFree(object->workdir);
    psFree(object->camera);
    psFree(object->telescope);
    psFree(object->exp_type);
    psFree(object->reduction);
    psFree(object->filter);
    psFree(object->registered);
    psFree(object->time_begin);
    psFree(object->time_end);
    psFree(object->use_begin);
    psFree(object->use_end);
    psFree(object->label);
}

bool detRunCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key AUTO_INCREMENT", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Key INDEX(det_id, iteration)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_type", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "mode", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item mode");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filelevel", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filelevel");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, "destination for output files", "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, "XXX this should be dropped", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "reduction", PS_DATA_STRING, "Reduction clas", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item reduction");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass_min", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass_max", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time_min", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time_max", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp_min", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp_max", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang_min", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item posang_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang_max", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item posang_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "time_begin", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item time_begin");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "time_end", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item time_end");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "use_begin", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item use_begin");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "use_end", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item use_end");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "solang_min", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item solang_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "solang_max", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item solang_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, "key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "parent", PS_DATA_S32, "Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item parent");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, DETRUN_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool detRunDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, DETRUN_TABLE_NAME);
}

bool detRunInsert(psDB * dbh, psS64 det_id, psS32 iteration, const char *det_type, const char *mode, const char *state, const char *filelevel, const char *workdir, const char *camera, const char *telescope, const char *exp_type, const char *reduction, const char *filter, psF32 airmass_min, psF32 airmass_max, psF32 exp_time_min, psF32 exp_time_max, psF32 ccd_temp_min, psF32 ccd_temp_max, psF64 posang_min, psF64 posang_max, psTime* registered, psTime* time_begin, psTime* time_end, psTime* use_begin, psTime* use_end, psF32 solang_min, psF32 solang_max, const char *label, psS32 parent)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_type", PS_DATA_STRING, NULL, det_type)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "mode", PS_DATA_STRING, NULL, mode)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item mode");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filelevel", PS_DATA_STRING, NULL, filelevel)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filelevel");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, exp_type)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "reduction", PS_DATA_STRING, NULL, reduction)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item reduction");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, filter)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass_min", PS_DATA_F32, NULL, airmass_min)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass_max", PS_DATA_F32, NULL, airmass_max)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time_min", PS_DATA_F32, NULL, exp_time_min)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time_max", PS_DATA_F32, NULL, exp_time_max)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp_min", PS_DATA_F32, NULL, ccd_temp_min)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp_max", PS_DATA_F32, NULL, ccd_temp_max)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang_min", PS_DATA_F64, NULL, posang_min)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item posang_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang_max", PS_DATA_F64, NULL, posang_max)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item posang_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, registered)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "time_begin", PS_DATA_TIME, NULL, time_begin)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item time_begin");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "time_end", PS_DATA_TIME, NULL, time_end)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item time_end");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "use_begin", PS_DATA_TIME, NULL, use_begin)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item use_begin");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "use_end", PS_DATA_TIME, NULL, use_end)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item use_end");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "solang_min", PS_DATA_F32, NULL, solang_min)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item solang_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "solang_max", PS_DATA_F32, NULL, solang_max)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item solang_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, label)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "parent", PS_DATA_S32, NULL, parent)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item parent");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, DETRUN_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long detRunDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, DETRUN_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detRun");
        return count;

        deleted += count;
    }

    return deleted;
}
bool detRunInsertObject(psDB *dbh, detRunRow *object)
{
    return detRunInsert(dbh, object->det_id, object->iteration, object->det_type, object->mode, object->state, object->filelevel, object->workdir, object->camera, object->telescope, object->exp_type, object->reduction, object->filter, object->airmass_min, object->airmass_max, object->exp_time_min, object->exp_time_max, object->ccd_temp_min, object->ccd_temp_max, object->posang_min, object->posang_max, object->registered, object->time_begin, object->time_end, object->use_begin, object->use_end, object->solang_min, object->solang_max, object->label, object->parent);
}

bool detRunInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!detRunInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool detRunInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  DETRUN_TABLE_NAME
    if (!psFitsMoveExtName(fits, DETRUN_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETRUN_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, DETRUN_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool detRunSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, DETRUN_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, DETRUN_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *detRunMetadataFromObject(const detRunRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, object->iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_type", PS_DATA_STRING, NULL, object->det_type)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "mode", PS_DATA_STRING, NULL, object->mode)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item mode");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, object->state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filelevel", PS_DATA_STRING, NULL, object->filelevel)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filelevel");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, object->workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, object->camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, object->telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, object->exp_type)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "reduction", PS_DATA_STRING, NULL, object->reduction)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item reduction");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, object->filter)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass_min", PS_DATA_F32, NULL, object->airmass_min)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass_max", PS_DATA_F32, NULL, object->airmass_max)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time_min", PS_DATA_F32, NULL, object->exp_time_min)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time_max", PS_DATA_F32, NULL, object->exp_time_max)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp_min", PS_DATA_F32, NULL, object->ccd_temp_min)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp_max", PS_DATA_F32, NULL, object->ccd_temp_max)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang_min", PS_DATA_F64, NULL, object->posang_min)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item posang_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang_max", PS_DATA_F64, NULL, object->posang_max)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item posang_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, object->registered)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "time_begin", PS_DATA_TIME, NULL, object->time_begin)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item time_begin");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "time_end", PS_DATA_TIME, NULL, object->time_end)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item time_end");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "use_begin", PS_DATA_TIME, NULL, object->use_begin)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item use_begin");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "use_end", PS_DATA_TIME, NULL, object->use_end)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item use_end");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "solang_min", PS_DATA_F32, NULL, object->solang_min)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item solang_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "solang_max", PS_DATA_F32, NULL, object->solang_max)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item solang_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, object->label)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "parent", PS_DATA_S32, NULL, object->parent)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item parent");
        psFree(md);
        return false;
    }


    return md;
}

detRunRow *detRunObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
        return false;
    }
    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
        return false;
    }
    char* det_type = psMetadataLookupPtr(&status, md, "det_type");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_type");
        return false;
    }
    char* mode = psMetadataLookupPtr(&status, md, "mode");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item mode");
        return false;
    }
    char* state = psMetadataLookupPtr(&status, md, "state");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item state");
        return false;
    }
    char* filelevel = psMetadataLookupPtr(&status, md, "filelevel");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filelevel");
        return false;
    }
    char* workdir = psMetadataLookupPtr(&status, md, "workdir");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
        return false;
    }
    char* camera = psMetadataLookupPtr(&status, md, "camera");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
        return false;
    }
    char* telescope = psMetadataLookupPtr(&status, md, "telescope");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
        return false;
    }
    char* exp_type = psMetadataLookupPtr(&status, md, "exp_type");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
        return false;
    }
    char* reduction = psMetadataLookupPtr(&status, md, "reduction");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item reduction");
        return false;
    }
    char* filter = psMetadataLookupPtr(&status, md, "filter");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
        return false;
    }
    psF32 airmass_min = psMetadataLookupF32(&status, md, "airmass_min");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass_min");
        return false;
    }
    psF32 airmass_max = psMetadataLookupF32(&status, md, "airmass_max");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass_max");
        return false;
    }
    psF32 exp_time_min = psMetadataLookupF32(&status, md, "exp_time_min");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time_min");
        return false;
    }
    psF32 exp_time_max = psMetadataLookupF32(&status, md, "exp_time_max");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time_max");
        return false;
    }
    psF32 ccd_temp_min = psMetadataLookupF32(&status, md, "ccd_temp_min");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ccd_temp_min");
        return false;
    }
    psF32 ccd_temp_max = psMetadataLookupF32(&status, md, "ccd_temp_max");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ccd_temp_max");
        return false;
    }
    psF64 posang_min = psMetadataLookupF64(&status, md, "posang_min");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item posang_min");
        return false;
    }
    psF64 posang_max = psMetadataLookupF64(&status, md, "posang_max");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item posang_max");
        return false;
    }
    psTime* registered = psMetadataLookupPtr(&status, md, "registered");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item registered");
        return false;
    }
    psTime* time_begin = psMetadataLookupPtr(&status, md, "time_begin");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item time_begin");
        return false;
    }
    psTime* time_end = psMetadataLookupPtr(&status, md, "time_end");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item time_end");
        return false;
    }
    psTime* use_begin = psMetadataLookupPtr(&status, md, "use_begin");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item use_begin");
        return false;
    }
    psTime* use_end = psMetadataLookupPtr(&status, md, "use_end");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item use_end");
        return false;
    }
    psF32 solang_min = psMetadataLookupF32(&status, md, "solang_min");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item solang_min");
        return false;
    }
    psF32 solang_max = psMetadataLookupF32(&status, md, "solang_max");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item solang_max");
        return false;
    }
    char* label = psMetadataLookupPtr(&status, md, "label");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item label");
        return false;
    }
    psS32 parent = psMetadataLookupS32(&status, md, "parent");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item parent");
        return false;
    }

    return detRunRowAlloc(det_id, iteration, det_type, mode, state, filelevel, workdir, camera, telescope, exp_type, reduction, filter, airmass_min, airmass_max, exp_time_min, exp_time_max, ccd_temp_min, ccd_temp_max, posang_min, posang_max, registered, time_begin, time_end, use_begin, use_end, solang_min, solang_max, label, parent);
}
psArray *detRunSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, DETRUN_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detRunRow *object = detRunObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool detRunDeleteObject(psDB *dbh, const detRunRow *object)
{
    psMetadata *where = detRunMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, DETRUN_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detRun");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "detRunRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long detRunDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        detRunRow *object = objects->data[i];
        psMetadata *where = detRunMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, DETRUN_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detRun");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool detRunPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = detRunMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            DETRUN_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool detRunPrintObject(FILE *stream, detRunRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = detRunMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void detInputExpRowFree(detInputExpRow *object);

detInputExpRow *detInputExpRowAlloc(psS64 det_id, psS32 iteration, psS64 exp_id, bool include)
{
    detInputExpRow  *_object;

    _object = psAlloc(sizeof(detInputExpRow));
    psMemSetDeallocator(_object, (psFreeFunc)detInputExpRowFree);

    _object->det_id = det_id;
    _object->iteration = iteration;
    _object->exp_id = exp_id;
    _object->include = include;

    return _object;
}

static void detInputExpRowFree(detInputExpRow *object)
{
}

bool detInputExpCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key fkey(det_id) ref detRun(det_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key fkey(exp_id) ref rawExp(exp_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, "Primary Key INDEX(det_id, exp_id)", 64)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "include", PS_DATA_BOOL, "INDEX(det_id, iteration)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item include");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, DETINPUTEXP_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool detInputExpDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, DETINPUTEXP_TABLE_NAME);
}

bool detInputExpInsert(psDB * dbh, psS64 det_id, psS32 iteration, psS64 exp_id, bool include)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "include", PS_DATA_BOOL, NULL, include)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item include");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, DETINPUTEXP_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long detInputExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, DETINPUTEXP_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detInputExp");
        return count;

        deleted += count;
    }

    return deleted;
}
bool detInputExpInsertObject(psDB *dbh, detInputExpRow *object)
{
    return detInputExpInsert(dbh, object->det_id, object->iteration, object->exp_id, object->include);
}

bool detInputExpInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!detInputExpInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool detInputExpInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  DETINPUTEXP_TABLE_NAME
    if (!psFitsMoveExtName(fits, DETINPUTEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETINPUTEXP_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, DETINPUTEXP_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool detInputExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, DETINPUTEXP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, DETINPUTEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *detInputExpMetadataFromObject(const detInputExpRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, object->iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, object->exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "include", PS_DATA_BOOL, NULL, object->include)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item include");
        psFree(md);
        return false;
    }


    return md;
}

detInputExpRow *detInputExpObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
        return false;
    }
    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
        return false;
    }
    psS64 exp_id = psMetadataLookupS64(&status, md, "exp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
        return false;
    }
    bool include = psMetadataLookupBool(&status, md, "include");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item include");
        return false;
    }

    return detInputExpRowAlloc(det_id, iteration, exp_id, include);
}
psArray *detInputExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, DETINPUTEXP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detInputExpRow *object = detInputExpObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool detInputExpDeleteObject(psDB *dbh, const detInputExpRow *object)
{
    psMetadata *where = detInputExpMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, DETINPUTEXP_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detInputExp");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "detInputExpRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long detInputExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        detInputExpRow *object = objects->data[i];
        psMetadata *where = detInputExpMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, DETINPUTEXP_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detInputExp");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool detInputExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = detInputExpMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            DETINPUTEXP_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool detInputExpPrintObject(FILE *stream, detInputExpRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = detInputExpMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void detProcessedImfileRowFree(detProcessedImfileRow *object);

detProcessedImfileRow *detProcessedImfileRowAlloc(psS64 det_id, psS64 exp_id, const char *class_id, const char *uri, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 fringe_0, psF64 fringe_1, psF64 fringe_2, psF64 user_1, psF64 user_2, psF64 user_3, psF64 user_4, psF64 user_5, const char *path_base, psS16 fault)
{
    detProcessedImfileRow *_object;

    _object = psAlloc(sizeof(detProcessedImfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)detProcessedImfileRowFree);

    _object->det_id = det_id;
    _object->exp_id = exp_id;
    _object->class_id = psStringCopy(class_id);
    _object->uri = psStringCopy(uri);
    _object->recipe = psStringCopy(recipe);
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->bg_mean_stdev = bg_mean_stdev;
    _object->fringe_0 = fringe_0;
    _object->fringe_1 = fringe_1;
    _object->fringe_2 = fringe_2;
    _object->user_1 = user_1;
    _object->user_2 = user_2;
    _object->user_3 = user_3;
    _object->user_4 = user_4;
    _object->user_5 = user_5;
    _object->path_base = psStringCopy(path_base);
    _object->fault = fault;

    return _object;
}

static void detProcessedImfileRowFree(detProcessedImfileRow *object)
{
    psFree(object->class_id);
    psFree(object->uri);
    psFree(object->recipe);
    psFree(object->path_base);
}

bool detProcessedImfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key fkey(det_id, exp_id) ref detInputExp(det_id, exp_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, "Primary Key fkey(exp_id, class_id) ref rawImfile(exp_id, class_id)", 64)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key INDEX(det_id, class_id)", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, "INDEX(det_id, exp_id)", "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_0", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_0");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_1", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_2", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, DETPROCESSEDIMFILE_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool detProcessedImfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, DETPROCESSEDIMFILE_TABLE_NAME);
}

bool detProcessedImfileInsert(psDB * dbh, psS64 det_id, psS64 exp_id, const char *class_id, const char *uri, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 fringe_0, psF64 fringe_1, psF64 fringe_2, psF64 user_1, psF64 user_2, psF64 user_3, psF64 user_4, psF64 user_5, const char *path_base, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_0", PS_DATA_F64, NULL, fringe_0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_0");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_1", PS_DATA_F64, NULL, fringe_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_2", PS_DATA_F64, NULL, fringe_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, user_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, user_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, user_3)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, user_4)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, user_5)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, DETPROCESSEDIMFILE_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long detProcessedImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, DETPROCESSEDIMFILE_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detProcessedImfile");
        return count;

        deleted += count;
    }

    return deleted;
}
bool detProcessedImfileInsertObject(psDB *dbh, detProcessedImfileRow *object)
{
    return detProcessedImfileInsert(dbh, object->det_id, object->exp_id, object->class_id, object->uri, object->recipe, object->bg, object->bg_stdev, object->bg_mean_stdev, object->fringe_0, object->fringe_1, object->fringe_2, object->user_1, object->user_2, object->user_3, object->user_4, object->user_5, object->path_base, object->fault);
}

bool detProcessedImfileInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!detProcessedImfileInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool detProcessedImfileInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  DETPROCESSEDIMFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, DETPROCESSEDIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETPROCESSEDIMFILE_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, DETPROCESSEDIMFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool detProcessedImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, DETPROCESSEDIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, DETPROCESSEDIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *detProcessedImfileMetadataFromObject(const detProcessedImfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, object->exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, object->recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_0", PS_DATA_F64, NULL, object->fringe_0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_0");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_1", PS_DATA_F64, NULL, object->fringe_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_2", PS_DATA_F64, NULL, object->fringe_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, object->user_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, object->user_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, object->user_3)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, object->user_4)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, object->user_5)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, object->path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

detProcessedImfileRow *detProcessedImfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
        return false;
    }
    psS64 exp_id = psMetadataLookupS64(&status, md, "exp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
        return false;
    }
    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
        return false;
    }
    psF64 fringe_0 = psMetadataLookupF64(&status, md, "fringe_0");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fringe_0");
        return false;
    }
    psF64 fringe_1 = psMetadataLookupF64(&status, md, "fringe_1");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fringe_1");
        return false;
    }
    psF64 fringe_2 = psMetadataLookupF64(&status, md, "fringe_2");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fringe_2");
        return false;
    }
    psF64 user_1 = psMetadataLookupF64(&status, md, "user_1");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_1");
        return false;
    }
    psF64 user_2 = psMetadataLookupF64(&status, md, "user_2");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_2");
        return false;
    }
    psF64 user_3 = psMetadataLookupF64(&status, md, "user_3");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_3");
        return false;
    }
    psF64 user_4 = psMetadataLookupF64(&status, md, "user_4");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_4");
        return false;
    }
    psF64 user_5 = psMetadataLookupF64(&status, md, "user_5");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_5");
        return false;
    }
    char* path_base = psMetadataLookupPtr(&status, md, "path_base");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item path_base");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return detProcessedImfileRowAlloc(det_id, exp_id, class_id, uri, recipe, bg, bg_stdev, bg_mean_stdev, fringe_0, fringe_1, fringe_2, user_1, user_2, user_3, user_4, user_5, path_base, fault);
}
psArray *detProcessedImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, DETPROCESSEDIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detProcessedImfileRow *object = detProcessedImfileObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool detProcessedImfileDeleteObject(psDB *dbh, const detProcessedImfileRow *object)
{
    psMetadata *where = detProcessedImfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, DETPROCESSEDIMFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detProcessedImfile");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "detProcessedImfileRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long detProcessedImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        detProcessedImfileRow *object = objects->data[i];
        psMetadata *where = detProcessedImfileMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, DETPROCESSEDIMFILE_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detProcessedImfile");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool detProcessedImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = detProcessedImfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            DETPROCESSEDIMFILE_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool detProcessedImfilePrintObject(FILE *stream, detProcessedImfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = detProcessedImfileMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void detProcessedExpRowFree(detProcessedExpRow *object);

detProcessedExpRow *detProcessedExpRowAlloc(psS64 det_id, psS64 exp_id, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 fringe_0, psF64 fringe_1, psF64 fringe_2, psF64 user_1, psF64 user_2, psF64 user_3, psF64 user_4, psF64 user_5, const char *path_base, psS16 fault)
{
    detProcessedExpRow *_object;

    _object = psAlloc(sizeof(detProcessedExpRow));
    psMemSetDeallocator(_object, (psFreeFunc)detProcessedExpRowFree);

    _object->det_id = det_id;
    _object->exp_id = exp_id;
    _object->recipe = psStringCopy(recipe);
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->bg_mean_stdev = bg_mean_stdev;
    _object->fringe_0 = fringe_0;
    _object->fringe_1 = fringe_1;
    _object->fringe_2 = fringe_2;
    _object->user_1 = user_1;
    _object->user_2 = user_2;
    _object->user_3 = user_3;
    _object->user_4 = user_4;
    _object->user_5 = user_5;
    _object->path_base = psStringCopy(path_base);
    _object->fault = fault;

    return _object;
}

static void detProcessedExpRowFree(detProcessedExpRow *object)
{
    psFree(object->recipe);
    psFree(object->path_base);
}

bool detProcessedExpCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key fkey(det_id, exp_id) ref detInputExp(det_id, exp_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, "Primary Key fkey(det_id, exp_id) ref detProcessedImfile(det_id, exp_id)", 64)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_0", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_0");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_1", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_2", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, DETPROCESSEDEXP_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool detProcessedExpDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, DETPROCESSEDEXP_TABLE_NAME);
}

bool detProcessedExpInsert(psDB * dbh, psS64 det_id, psS64 exp_id, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 fringe_0, psF64 fringe_1, psF64 fringe_2, psF64 user_1, psF64 user_2, psF64 user_3, psF64 user_4, psF64 user_5, const char *path_base, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_0", PS_DATA_F64, NULL, fringe_0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_0");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_1", PS_DATA_F64, NULL, fringe_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_2", PS_DATA_F64, NULL, fringe_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, user_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, user_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, user_3)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, user_4)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, user_5)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, DETPROCESSEDEXP_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long detProcessedExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, DETPROCESSEDEXP_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detProcessedExp");
        return count;

        deleted += count;
    }

    return deleted;
}
bool detProcessedExpInsertObject(psDB *dbh, detProcessedExpRow *object)
{
    return detProcessedExpInsert(dbh, object->det_id, object->exp_id, object->recipe, object->bg, object->bg_stdev, object->bg_mean_stdev, object->fringe_0, object->fringe_1, object->fringe_2, object->user_1, object->user_2, object->user_3, object->user_4, object->user_5, object->path_base, object->fault);
}

bool detProcessedExpInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!detProcessedExpInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool detProcessedExpInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  DETPROCESSEDEXP_TABLE_NAME
    if (!psFitsMoveExtName(fits, DETPROCESSEDEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETPROCESSEDEXP_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, DETPROCESSEDEXP_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool detProcessedExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, DETPROCESSEDEXP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, DETPROCESSEDEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *detProcessedExpMetadataFromObject(const detProcessedExpRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, object->exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, object->recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_0", PS_DATA_F64, NULL, object->fringe_0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_0");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_1", PS_DATA_F64, NULL, object->fringe_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_2", PS_DATA_F64, NULL, object->fringe_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, object->user_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, object->user_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, object->user_3)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, object->user_4)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, object->user_5)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, object->path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

detProcessedExpRow *detProcessedExpObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
        return false;
    }
    psS64 exp_id = psMetadataLookupS64(&status, md, "exp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
        return false;
    }
    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
        return false;
    }
    psF64 fringe_0 = psMetadataLookupF64(&status, md, "fringe_0");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fringe_0");
        return false;
    }
    psF64 fringe_1 = psMetadataLookupF64(&status, md, "fringe_1");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fringe_1");
        return false;
    }
    psF64 fringe_2 = psMetadataLookupF64(&status, md, "fringe_2");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fringe_2");
        return false;
    }
    psF64 user_1 = psMetadataLookupF64(&status, md, "user_1");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_1");
        return false;
    }
    psF64 user_2 = psMetadataLookupF64(&status, md, "user_2");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_2");
        return false;
    }
    psF64 user_3 = psMetadataLookupF64(&status, md, "user_3");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_3");
        return false;
    }
    psF64 user_4 = psMetadataLookupF64(&status, md, "user_4");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_4");
        return false;
    }
    psF64 user_5 = psMetadataLookupF64(&status, md, "user_5");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_5");
        return false;
    }
    char* path_base = psMetadataLookupPtr(&status, md, "path_base");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item path_base");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return detProcessedExpRowAlloc(det_id, exp_id, recipe, bg, bg_stdev, bg_mean_stdev, fringe_0, fringe_1, fringe_2, user_1, user_2, user_3, user_4, user_5, path_base, fault);
}
psArray *detProcessedExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, DETPROCESSEDEXP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detProcessedExpRow *object = detProcessedExpObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool detProcessedExpDeleteObject(psDB *dbh, const detProcessedExpRow *object)
{
    psMetadata *where = detProcessedExpMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, DETPROCESSEDEXP_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detProcessedExp");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "detProcessedExpRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long detProcessedExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        detProcessedExpRow *object = objects->data[i];
        psMetadata *where = detProcessedExpMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, DETPROCESSEDEXP_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detProcessedExp");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool detProcessedExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = detProcessedExpMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            DETPROCESSEDEXP_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool detProcessedExpPrintObject(FILE *stream, detProcessedExpRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = detProcessedExpMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void detStackedImfileRowFree(detStackedImfileRow *object);

detStackedImfileRow *detStackedImfileRowAlloc(psS64 det_id, psS32 iteration, const char *class_id, const char *uri, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 user_1, psF64 user_2, psF64 user_3, psF64 user_4, psF64 user_5, psS16 fault)
{
    detStackedImfileRow *_object;

    _object = psAlloc(sizeof(detStackedImfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)detStackedImfileRowFree);

    _object->det_id = det_id;
    _object->iteration = iteration;
    _object->class_id = psStringCopy(class_id);
    _object->uri = psStringCopy(uri);
    _object->recipe = psStringCopy(recipe);
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->bg_mean_stdev = bg_mean_stdev;
    _object->user_1 = user_1;
    _object->user_2 = user_2;
    _object->user_3 = user_3;
    _object->user_4 = user_4;
    _object->user_5 = user_5;
    _object->fault = fault;

    return _object;
}

static void detStackedImfileRowFree(detStackedImfileRow *object)
{
    psFree(object->class_id);
    psFree(object->uri);
    psFree(object->recipe);
}

bool detStackedImfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key fkey(det_id, iteration) ref detInputExp(det_id, iteration)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key fkey(det_id, class_id) ref detProcessedImfile(det_id, class_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, DETSTACKEDIMFILE_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool detStackedImfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, DETSTACKEDIMFILE_TABLE_NAME);
}

bool detStackedImfileInsert(psDB * dbh, psS64 det_id, psS32 iteration, const char *class_id, const char *uri, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 user_1, psF64 user_2, psF64 user_3, psF64 user_4, psF64 user_5, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, user_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, user_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, user_3)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, user_4)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, user_5)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, DETSTACKEDIMFILE_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long detStackedImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, DETSTACKEDIMFILE_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detStackedImfile");
        return count;

        deleted += count;
    }

    return deleted;
}
bool detStackedImfileInsertObject(psDB *dbh, detStackedImfileRow *object)
{
    return detStackedImfileInsert(dbh, object->det_id, object->iteration, object->class_id, object->uri, object->recipe, object->bg, object->bg_stdev, object->bg_mean_stdev, object->user_1, object->user_2, object->user_3, object->user_4, object->user_5, object->fault);
}

bool detStackedImfileInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!detStackedImfileInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool detStackedImfileInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  DETSTACKEDIMFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, DETSTACKEDIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETSTACKEDIMFILE_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, DETSTACKEDIMFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool detStackedImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, DETSTACKEDIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, DETSTACKEDIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *detStackedImfileMetadataFromObject(const detStackedImfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, object->iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, object->recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, object->user_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, object->user_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, object->user_3)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, object->user_4)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, object->user_5)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

detStackedImfileRow *detStackedImfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
        return false;
    }
    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
        return false;
    }
    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
        return false;
    }
    psF64 user_1 = psMetadataLookupF64(&status, md, "user_1");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_1");
        return false;
    }
    psF64 user_2 = psMetadataLookupF64(&status, md, "user_2");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_2");
        return false;
    }
    psF64 user_3 = psMetadataLookupF64(&status, md, "user_3");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_3");
        return false;
    }
    psF64 user_4 = psMetadataLookupF64(&status, md, "user_4");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_4");
        return false;
    }
    psF64 user_5 = psMetadataLookupF64(&status, md, "user_5");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_5");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return detStackedImfileRowAlloc(det_id, iteration, class_id, uri, recipe, bg, bg_stdev, bg_mean_stdev, user_1, user_2, user_3, user_4, user_5, fault);
}
psArray *detStackedImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, DETSTACKEDIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detStackedImfileRow *object = detStackedImfileObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool detStackedImfileDeleteObject(psDB *dbh, const detStackedImfileRow *object)
{
    psMetadata *where = detStackedImfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, DETSTACKEDIMFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detStackedImfile");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "detStackedImfileRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long detStackedImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        detStackedImfileRow *object = objects->data[i];
        psMetadata *where = detStackedImfileMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, DETSTACKEDIMFILE_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detStackedImfile");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool detStackedImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = detStackedImfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            DETSTACKEDIMFILE_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool detStackedImfilePrintObject(FILE *stream, detStackedImfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = detStackedImfileMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void detNormalizedStatImfileRowFree(detNormalizedStatImfileRow *object);

detNormalizedStatImfileRow *detNormalizedStatImfileRowAlloc(psS64 det_id, psS32 iteration, const char *class_id, psF32 norm, psS16 fault)
{
    detNormalizedStatImfileRow *_object;

    _object = psAlloc(sizeof(detNormalizedStatImfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)detNormalizedStatImfileRowFree);

    _object->det_id = det_id;
    _object->iteration = iteration;
    _object->class_id = psStringCopy(class_id);
    _object->norm = norm;
    _object->fault = fault;

    return _object;
}

static void detNormalizedStatImfileRowFree(detNormalizedStatImfileRow *object)
{
    psFree(object->class_id);
}

bool detNormalizedStatImfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key fkey(det_id, iteration) ref detInputExp(det_id, iteration)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key fkey(det_id, iteration, class_id) ref detStackedImfile(det_id, iteration, class_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "norm", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item norm");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, DETNORMALIZEDSTATIMFILE_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool detNormalizedStatImfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, DETNORMALIZEDSTATIMFILE_TABLE_NAME);
}

bool detNormalizedStatImfileInsert(psDB * dbh, psS64 det_id, psS32 iteration, const char *class_id, psF32 norm, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "norm", PS_DATA_F32, NULL, norm)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item norm");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, DETNORMALIZEDSTATIMFILE_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long detNormalizedStatImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, DETNORMALIZEDSTATIMFILE_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detNormalizedStatImfile");
        return count;

        deleted += count;
    }

    return deleted;
}
bool detNormalizedStatImfileInsertObject(psDB *dbh, detNormalizedStatImfileRow *object)
{
    return detNormalizedStatImfileInsert(dbh, object->det_id, object->iteration, object->class_id, object->norm, object->fault);
}

bool detNormalizedStatImfileInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!detNormalizedStatImfileInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool detNormalizedStatImfileInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  DETNORMALIZEDSTATIMFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, DETNORMALIZEDSTATIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETNORMALIZEDSTATIMFILE_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, DETNORMALIZEDSTATIMFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool detNormalizedStatImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, DETNORMALIZEDSTATIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, DETNORMALIZEDSTATIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *detNormalizedStatImfileMetadataFromObject(const detNormalizedStatImfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, object->iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "norm", PS_DATA_F32, NULL, object->norm)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item norm");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

detNormalizedStatImfileRow *detNormalizedStatImfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
        return false;
    }
    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
        return false;
    }
    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
        return false;
    }
    psF32 norm = psMetadataLookupF32(&status, md, "norm");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item norm");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return detNormalizedStatImfileRowAlloc(det_id, iteration, class_id, norm, fault);
}
psArray *detNormalizedStatImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, DETNORMALIZEDSTATIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detNormalizedStatImfileRow *object = detNormalizedStatImfileObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool detNormalizedStatImfileDeleteObject(psDB *dbh, const detNormalizedStatImfileRow *object)
{
    psMetadata *where = detNormalizedStatImfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, DETNORMALIZEDSTATIMFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detNormalizedStatImfile");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "detNormalizedStatImfileRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long detNormalizedStatImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        detNormalizedStatImfileRow *object = objects->data[i];
        psMetadata *where = detNormalizedStatImfileMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, DETNORMALIZEDSTATIMFILE_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detNormalizedStatImfile");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool detNormalizedStatImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = detNormalizedStatImfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            DETNORMALIZEDSTATIMFILE_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool detNormalizedStatImfilePrintObject(FILE *stream, detNormalizedStatImfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = detNormalizedStatImfileMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void detNormalizedImfileRowFree(detNormalizedImfileRow *object);

detNormalizedImfileRow *detNormalizedImfileRowAlloc(psS64 det_id, psS32 iteration, const char *class_id, const char *uri, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 user_1, psF64 user_2, psF64 user_3, psF64 user_4, psF64 user_5, const char *path_base, psS16 fault)
{
    detNormalizedImfileRow *_object;

    _object = psAlloc(sizeof(detNormalizedImfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)detNormalizedImfileRowFree);

    _object->det_id = det_id;
    _object->iteration = iteration;
    _object->class_id = psStringCopy(class_id);
    _object->uri = psStringCopy(uri);
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->bg_mean_stdev = bg_mean_stdev;
    _object->user_1 = user_1;
    _object->user_2 = user_2;
    _object->user_3 = user_3;
    _object->user_4 = user_4;
    _object->user_5 = user_5;
    _object->path_base = psStringCopy(path_base);
    _object->fault = fault;

    return _object;
}

static void detNormalizedImfileRowFree(detNormalizedImfileRow *object)
{
    psFree(object->class_id);
    psFree(object->uri);
    psFree(object->path_base);
}

bool detNormalizedImfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key fkey(det_id) ref detInputExp(det_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key fkey(det_id, iteration, class_id) ref detNormalizedStatImfile(det_id, iteration, class_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key INDEX(det_id, iteration)", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool detNormalizedImfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, DETNORMALIZEDIMFILE_TABLE_NAME);
}

bool detNormalizedImfileInsert(psDB * dbh, psS64 det_id, psS32 iteration, const char *class_id, const char *uri, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 user_1, psF64 user_2, psF64 user_3, psF64 user_4, psF64 user_5, const char *path_base, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, user_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, user_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, user_3)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, user_4)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, user_5)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long detNormalizedImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detNormalizedImfile");
        return count;

        deleted += count;
    }

    return deleted;
}
bool detNormalizedImfileInsertObject(psDB *dbh, detNormalizedImfileRow *object)
{
    return detNormalizedImfileInsert(dbh, object->det_id, object->iteration, object->class_id, object->uri, object->bg, object->bg_stdev, object->bg_mean_stdev, object->user_1, object->user_2, object->user_3, object->user_4, object->user_5, object->path_base, object->fault);
}

bool detNormalizedImfileInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!detNormalizedImfileInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool detNormalizedImfileInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  DETNORMALIZEDIMFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, DETNORMALIZEDIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETNORMALIZEDIMFILE_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool detNormalizedImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, DETNORMALIZEDIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *detNormalizedImfileMetadataFromObject(const detNormalizedImfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, object->iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, object->user_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, object->user_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, object->user_3)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, object->user_4)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, object->user_5)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, object->path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

detNormalizedImfileRow *detNormalizedImfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
        return false;
    }
    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
        return false;
    }
    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
        return false;
    }
    psF64 user_1 = psMetadataLookupF64(&status, md, "user_1");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_1");
        return false;
    }
    psF64 user_2 = psMetadataLookupF64(&status, md, "user_2");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_2");
        return false;
    }
    psF64 user_3 = psMetadataLookupF64(&status, md, "user_3");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_3");
        return false;
    }
    psF64 user_4 = psMetadataLookupF64(&status, md, "user_4");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_4");
        return false;
    }
    psF64 user_5 = psMetadataLookupF64(&status, md, "user_5");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_5");
        return false;
    }
    char* path_base = psMetadataLookupPtr(&status, md, "path_base");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item path_base");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return detNormalizedImfileRowAlloc(det_id, iteration, class_id, uri, bg, bg_stdev, bg_mean_stdev, user_1, user_2, user_3, user_4, user_5, path_base, fault);
}
psArray *detNormalizedImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detNormalizedImfileRow *object = detNormalizedImfileObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool detNormalizedImfileDeleteObject(psDB *dbh, const detNormalizedImfileRow *object)
{
    psMetadata *where = detNormalizedImfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detNormalizedImfile");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "detNormalizedImfileRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long detNormalizedImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        detNormalizedImfileRow *object = objects->data[i];
        psMetadata *where = detNormalizedImfileMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, DETNORMALIZEDIMFILE_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detNormalizedImfile");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool detNormalizedImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = detNormalizedImfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            DETNORMALIZEDIMFILE_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool detNormalizedImfilePrintObject(FILE *stream, detNormalizedImfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = detNormalizedImfileMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void detNormalizedExpRowFree(detNormalizedExpRow *object);

detNormalizedExpRow *detNormalizedExpRowAlloc(psS64 det_id, psS32 iteration, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 user_1, psF64 user_2, psF64 user_3, psF64 user_4, psF64 user_5, const char *path_base, psS16 fault)
{
    detNormalizedExpRow *_object;

    _object = psAlloc(sizeof(detNormalizedExpRow));
    psMemSetDeallocator(_object, (psFreeFunc)detNormalizedExpRowFree);

    _object->det_id = det_id;
    _object->iteration = iteration;
    _object->recipe = psStringCopy(recipe);
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->bg_mean_stdev = bg_mean_stdev;
    _object->user_1 = user_1;
    _object->user_2 = user_2;
    _object->user_3 = user_3;
    _object->user_4 = user_4;
    _object->user_5 = user_5;
    _object->path_base = psStringCopy(path_base);
    _object->fault = fault;

    return _object;
}

static void detNormalizedExpRowFree(detNormalizedExpRow *object)
{
    psFree(object->recipe);
    psFree(object->path_base);
}

bool detNormalizedExpCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key fkey(det_id, iteration) ref detInputExp(det_id, iteration)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key fkey(det_id, iteration) ref detNormalizedImfile(det_id, iteration)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, DETNORMALIZEDEXP_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool detNormalizedExpDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, DETNORMALIZEDEXP_TABLE_NAME);
}

bool detNormalizedExpInsert(psDB * dbh, psS64 det_id, psS32 iteration, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 user_1, psF64 user_2, psF64 user_3, psF64 user_4, psF64 user_5, const char *path_base, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, user_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, user_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, user_3)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, user_4)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, user_5)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, DETNORMALIZEDEXP_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long detNormalizedExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, DETNORMALIZEDEXP_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detNormalizedExp");
        return count;

        deleted += count;
    }

    return deleted;
}
bool detNormalizedExpInsertObject(psDB *dbh, detNormalizedExpRow *object)
{
    return detNormalizedExpInsert(dbh, object->det_id, object->iteration, object->recipe, object->bg, object->bg_stdev, object->bg_mean_stdev, object->user_1, object->user_2, object->user_3, object->user_4, object->user_5, object->path_base, object->fault);
}

bool detNormalizedExpInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!detNormalizedExpInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool detNormalizedExpInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  DETNORMALIZEDEXP_TABLE_NAME
    if (!psFitsMoveExtName(fits, DETNORMALIZEDEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETNORMALIZEDEXP_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, DETNORMALIZEDEXP_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool detNormalizedExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, DETNORMALIZEDEXP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, DETNORMALIZEDEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *detNormalizedExpMetadataFromObject(const detNormalizedExpRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, object->iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, object->recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, object->user_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, object->user_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, object->user_3)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, object->user_4)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, object->user_5)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, object->path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

detNormalizedExpRow *detNormalizedExpObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
        return false;
    }
    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
        return false;
    }
    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
        return false;
    }
    psF64 user_1 = psMetadataLookupF64(&status, md, "user_1");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_1");
        return false;
    }
    psF64 user_2 = psMetadataLookupF64(&status, md, "user_2");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_2");
        return false;
    }
    psF64 user_3 = psMetadataLookupF64(&status, md, "user_3");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_3");
        return false;
    }
    psF64 user_4 = psMetadataLookupF64(&status, md, "user_4");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_4");
        return false;
    }
    psF64 user_5 = psMetadataLookupF64(&status, md, "user_5");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_5");
        return false;
    }
    char* path_base = psMetadataLookupPtr(&status, md, "path_base");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item path_base");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return detNormalizedExpRowAlloc(det_id, iteration, recipe, bg, bg_stdev, bg_mean_stdev, user_1, user_2, user_3, user_4, user_5, path_base, fault);
}
psArray *detNormalizedExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, DETNORMALIZEDEXP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detNormalizedExpRow *object = detNormalizedExpObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool detNormalizedExpDeleteObject(psDB *dbh, const detNormalizedExpRow *object)
{
    psMetadata *where = detNormalizedExpMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, DETNORMALIZEDEXP_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detNormalizedExp");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "detNormalizedExpRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long detNormalizedExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        detNormalizedExpRow *object = objects->data[i];
        psMetadata *where = detNormalizedExpMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, DETNORMALIZEDEXP_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detNormalizedExp");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool detNormalizedExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = detNormalizedExpMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            DETNORMALIZEDEXP_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool detNormalizedExpPrintObject(FILE *stream, detNormalizedExpRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = detNormalizedExpMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void detResidImfileRowFree(detResidImfileRow *object);

detResidImfileRow *detResidImfileRowAlloc(psS64 det_id, psS32 iteration, psS64 exp_id, const char *class_id, const char *uri, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 bg_skewness, psF64 bg_kurtosis, psF64 bin_stdev, psF64 fringe_0, psF64 fringe_1, psF64 fringe_2, psF64 fringe_resid_0, psF64 fringe_resid_1, psF64 fringe_resid_2, psF64 user_1, psF64 user_2, psF64 user_3, psF64 user_4, psF64 user_5, const char *path_base, psS16 fault)
{
    detResidImfileRow *_object;

    _object = psAlloc(sizeof(detResidImfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)detResidImfileRowFree);

    _object->det_id = det_id;
    _object->iteration = iteration;
    _object->exp_id = exp_id;
    _object->class_id = psStringCopy(class_id);
    _object->uri = psStringCopy(uri);
    _object->recipe = psStringCopy(recipe);
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->bg_mean_stdev = bg_mean_stdev;
    _object->bg_skewness = bg_skewness;
    _object->bg_kurtosis = bg_kurtosis;
    _object->bin_stdev = bin_stdev;
    _object->fringe_0 = fringe_0;
    _object->fringe_1 = fringe_1;
    _object->fringe_2 = fringe_2;
    _object->fringe_resid_0 = fringe_resid_0;
    _object->fringe_resid_1 = fringe_resid_1;
    _object->fringe_resid_2 = fringe_resid_2;
    _object->user_1 = user_1;
    _object->user_2 = user_2;
    _object->user_3 = user_3;
    _object->user_4 = user_4;
    _object->user_5 = user_5;
    _object->path_base = psStringCopy(path_base);
    _object->fault = fault;

    return _object;
}

static void detResidImfileRowFree(detResidImfileRow *object)
{
    psFree(object->class_id);
    psFree(object->uri);
    psFree(object->recipe);
    psFree(object->path_base);
}

bool detResidImfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key fkey(det_id, iteration, exp_id) ref detInputExp(det_id, iteration, exp_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key fkey(det_id, exp_id, class_id) ref detProcessedImfile(det_id, exp_id, class_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, "Primary Key fkey(det_id, iteration) ref detNormalizedExp(det_id, iteration)", 64)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key INDEX(det_id, iteration, exp_id)", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_skewness", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_skewness");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_kurtosis", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_kurtosis");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bin_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bin_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_0", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_0");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_1", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_2", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_resid_0", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_resid_0");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_resid_1", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_resid_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_resid_2", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_resid_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, DETRESIDIMFILE_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool detResidImfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, DETRESIDIMFILE_TABLE_NAME);
}

bool detResidImfileInsert(psDB * dbh, psS64 det_id, psS32 iteration, psS64 exp_id, const char *class_id, const char *uri, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 bg_skewness, psF64 bg_kurtosis, psF64 bin_stdev, psF64 fringe_0, psF64 fringe_1, psF64 fringe_2, psF64 fringe_resid_0, psF64 fringe_resid_1, psF64 fringe_resid_2, psF64 user_1, psF64 user_2, psF64 user_3, psF64 user_4, psF64 user_5, const char *path_base, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_skewness", PS_DATA_F64, NULL, bg_skewness)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_skewness");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_kurtosis", PS_DATA_F64, NULL, bg_kurtosis)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_kurtosis");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bin_stdev", PS_DATA_F64, NULL, bin_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bin_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_0", PS_DATA_F64, NULL, fringe_0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_0");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_1", PS_DATA_F64, NULL, fringe_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_2", PS_DATA_F64, NULL, fringe_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_resid_0", PS_DATA_F64, NULL, fringe_resid_0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_resid_0");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_resid_1", PS_DATA_F64, NULL, fringe_resid_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_resid_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_resid_2", PS_DATA_F64, NULL, fringe_resid_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_resid_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, user_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, user_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, user_3)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, user_4)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, user_5)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, DETRESIDIMFILE_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long detResidImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, DETRESIDIMFILE_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detResidImfile");
        return count;

        deleted += count;
    }

    return deleted;
}
bool detResidImfileInsertObject(psDB *dbh, detResidImfileRow *object)
{
    return detResidImfileInsert(dbh, object->det_id, object->iteration, object->exp_id, object->class_id, object->uri, object->recipe, object->bg, object->bg_stdev, object->bg_mean_stdev, object->bg_skewness, object->bg_kurtosis, object->bin_stdev, object->fringe_0, object->fringe_1, object->fringe_2, object->fringe_resid_0, object->fringe_resid_1, object->fringe_resid_2, object->user_1, object->user_2, object->user_3, object->user_4, object->user_5, object->path_base, object->fault);
}

bool detResidImfileInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!detResidImfileInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool detResidImfileInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  DETRESIDIMFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, DETRESIDIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETRESIDIMFILE_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, DETRESIDIMFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool detResidImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, DETRESIDIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, DETRESIDIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *detResidImfileMetadataFromObject(const detResidImfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, object->iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, object->exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, object->recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_skewness", PS_DATA_F64, NULL, object->bg_skewness)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_skewness");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_kurtosis", PS_DATA_F64, NULL, object->bg_kurtosis)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_kurtosis");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bin_stdev", PS_DATA_F64, NULL, object->bin_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bin_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_0", PS_DATA_F64, NULL, object->fringe_0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_0");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_1", PS_DATA_F64, NULL, object->fringe_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_2", PS_DATA_F64, NULL, object->fringe_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_resid_0", PS_DATA_F64, NULL, object->fringe_resid_0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_resid_0");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_resid_1", PS_DATA_F64, NULL, object->fringe_resid_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_resid_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_resid_2", PS_DATA_F64, NULL, object->fringe_resid_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_resid_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, object->user_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, object->user_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, object->user_3)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, object->user_4)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, object->user_5)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, object->path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

detResidImfileRow *detResidImfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
        return false;
    }
    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
        return false;
    }
    psS64 exp_id = psMetadataLookupS64(&status, md, "exp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
        return false;
    }
    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
        return false;
    }
    psF64 bg_skewness = psMetadataLookupF64(&status, md, "bg_skewness");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_skewness");
        return false;
    }
    psF64 bg_kurtosis = psMetadataLookupF64(&status, md, "bg_kurtosis");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_kurtosis");
        return false;
    }
    psF64 bin_stdev = psMetadataLookupF64(&status, md, "bin_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bin_stdev");
        return false;
    }
    psF64 fringe_0 = psMetadataLookupF64(&status, md, "fringe_0");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fringe_0");
        return false;
    }
    psF64 fringe_1 = psMetadataLookupF64(&status, md, "fringe_1");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fringe_1");
        return false;
    }
    psF64 fringe_2 = psMetadataLookupF64(&status, md, "fringe_2");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fringe_2");
        return false;
    }
    psF64 fringe_resid_0 = psMetadataLookupF64(&status, md, "fringe_resid_0");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fringe_resid_0");
        return false;
    }
    psF64 fringe_resid_1 = psMetadataLookupF64(&status, md, "fringe_resid_1");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fringe_resid_1");
        return false;
    }
    psF64 fringe_resid_2 = psMetadataLookupF64(&status, md, "fringe_resid_2");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fringe_resid_2");
        return false;
    }
    psF64 user_1 = psMetadataLookupF64(&status, md, "user_1");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_1");
        return false;
    }
    psF64 user_2 = psMetadataLookupF64(&status, md, "user_2");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_2");
        return false;
    }
    psF64 user_3 = psMetadataLookupF64(&status, md, "user_3");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_3");
        return false;
    }
    psF64 user_4 = psMetadataLookupF64(&status, md, "user_4");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_4");
        return false;
    }
    psF64 user_5 = psMetadataLookupF64(&status, md, "user_5");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_5");
        return false;
    }
    char* path_base = psMetadataLookupPtr(&status, md, "path_base");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item path_base");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return detResidImfileRowAlloc(det_id, iteration, exp_id, class_id, uri, recipe, bg, bg_stdev, bg_mean_stdev, bg_skewness, bg_kurtosis, bin_stdev, fringe_0, fringe_1, fringe_2, fringe_resid_0, fringe_resid_1, fringe_resid_2, user_1, user_2, user_3, user_4, user_5, path_base, fault);
}
psArray *detResidImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, DETRESIDIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detResidImfileRow *object = detResidImfileObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool detResidImfileDeleteObject(psDB *dbh, const detResidImfileRow *object)
{
    psMetadata *where = detResidImfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, DETRESIDIMFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detResidImfile");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "detResidImfileRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long detResidImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        detResidImfileRow *object = objects->data[i];
        psMetadata *where = detResidImfileMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, DETRESIDIMFILE_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detResidImfile");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool detResidImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = detResidImfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            DETRESIDIMFILE_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool detResidImfilePrintObject(FILE *stream, detResidImfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = detResidImfileMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void detResidExpRowFree(detResidExpRow *object);

detResidExpRow *detResidExpRowAlloc(psS64 det_id, psS32 iteration, psS64 exp_id, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 bg_skewness, psF64 bg_kurtosis, psF64 bin_stdev, psF64 fringe_0, psF64 fringe_1, psF64 fringe_2, psF64 fringe_resid_0, psF64 fringe_resid_1, psF64 fringe_resid_2, psF64 user_1, psF64 user_2, psF64 user_3, psF64 user_4, psF64 user_5, const char *path_base, bool accept, psS16 fault)
{
    detResidExpRow  *_object;

    _object = psAlloc(sizeof(detResidExpRow));
    psMemSetDeallocator(_object, (psFreeFunc)detResidExpRowFree);

    _object->det_id = det_id;
    _object->iteration = iteration;
    _object->exp_id = exp_id;
    _object->recipe = psStringCopy(recipe);
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->bg_mean_stdev = bg_mean_stdev;
    _object->bg_skewness = bg_skewness;
    _object->bg_kurtosis = bg_kurtosis;
    _object->bin_stdev = bin_stdev;
    _object->fringe_0 = fringe_0;
    _object->fringe_1 = fringe_1;
    _object->fringe_2 = fringe_2;
    _object->fringe_resid_0 = fringe_resid_0;
    _object->fringe_resid_1 = fringe_resid_1;
    _object->fringe_resid_2 = fringe_resid_2;
    _object->user_1 = user_1;
    _object->user_2 = user_2;
    _object->user_3 = user_3;
    _object->user_4 = user_4;
    _object->user_5 = user_5;
    _object->path_base = psStringCopy(path_base);
    _object->accept = accept;
    _object->fault = fault;

    return _object;
}

static void detResidExpRowFree(detResidExpRow *object)
{
    psFree(object->recipe);
    psFree(object->path_base);
}

bool detResidExpCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key fkey(det_id, iteration, exp_id) ref detInputExp(det_id, iteration, exp_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key fkey(det_id, iteration, exp_id) ref detResidImfile(det_id, iteration, exp_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, "Primary Key INDEX(det_id, iteration)", 64)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_skewness", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_skewness");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_kurtosis", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_kurtosis");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bin_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bin_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_0", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_0");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_1", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_2", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_resid_0", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_resid_0");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_resid_1", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_resid_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_resid_2", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_resid_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "accept", PS_DATA_BOOL, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item accept");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, DETRESIDEXP_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool detResidExpDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, DETRESIDEXP_TABLE_NAME);
}

bool detResidExpInsert(psDB * dbh, psS64 det_id, psS32 iteration, psS64 exp_id, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 bg_skewness, psF64 bg_kurtosis, psF64 bin_stdev, psF64 fringe_0, psF64 fringe_1, psF64 fringe_2, psF64 fringe_resid_0, psF64 fringe_resid_1, psF64 fringe_resid_2, psF64 user_1, psF64 user_2, psF64 user_3, psF64 user_4, psF64 user_5, const char *path_base, bool accept, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_skewness", PS_DATA_F64, NULL, bg_skewness)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_skewness");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_kurtosis", PS_DATA_F64, NULL, bg_kurtosis)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_kurtosis");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bin_stdev", PS_DATA_F64, NULL, bin_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bin_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_0", PS_DATA_F64, NULL, fringe_0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_0");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_1", PS_DATA_F64, NULL, fringe_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_2", PS_DATA_F64, NULL, fringe_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_resid_0", PS_DATA_F64, NULL, fringe_resid_0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_resid_0");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_resid_1", PS_DATA_F64, NULL, fringe_resid_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_resid_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_resid_2", PS_DATA_F64, NULL, fringe_resid_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_resid_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, user_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, user_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, user_3)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, user_4)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, user_5)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "accept", PS_DATA_BOOL, NULL, accept)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item accept");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, DETRESIDEXP_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long detResidExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, DETRESIDEXP_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detResidExp");
        return count;

        deleted += count;
    }

    return deleted;
}
bool detResidExpInsertObject(psDB *dbh, detResidExpRow *object)
{
    return detResidExpInsert(dbh, object->det_id, object->iteration, object->exp_id, object->recipe, object->bg, object->bg_stdev, object->bg_mean_stdev, object->bg_skewness, object->bg_kurtosis, object->bin_stdev, object->fringe_0, object->fringe_1, object->fringe_2, object->fringe_resid_0, object->fringe_resid_1, object->fringe_resid_2, object->user_1, object->user_2, object->user_3, object->user_4, object->user_5, object->path_base, object->accept, object->fault);
}

bool detResidExpInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!detResidExpInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool detResidExpInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  DETRESIDEXP_TABLE_NAME
    if (!psFitsMoveExtName(fits, DETRESIDEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETRESIDEXP_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, DETRESIDEXP_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool detResidExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, DETRESIDEXP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, DETRESIDEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *detResidExpMetadataFromObject(const detResidExpRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, object->iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, object->exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, object->recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_skewness", PS_DATA_F64, NULL, object->bg_skewness)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_skewness");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_kurtosis", PS_DATA_F64, NULL, object->bg_kurtosis)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_kurtosis");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bin_stdev", PS_DATA_F64, NULL, object->bin_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bin_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_0", PS_DATA_F64, NULL, object->fringe_0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_0");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_1", PS_DATA_F64, NULL, object->fringe_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_2", PS_DATA_F64, NULL, object->fringe_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_resid_0", PS_DATA_F64, NULL, object->fringe_resid_0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_resid_0");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_resid_1", PS_DATA_F64, NULL, object->fringe_resid_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_resid_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fringe_resid_2", PS_DATA_F64, NULL, object->fringe_resid_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fringe_resid_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, object->user_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, object->user_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, object->user_3)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, object->user_4)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, object->user_5)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, object->path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "accept", PS_DATA_BOOL, NULL, object->accept)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item accept");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

detResidExpRow *detResidExpObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
        return false;
    }
    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
        return false;
    }
    psS64 exp_id = psMetadataLookupS64(&status, md, "exp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
        return false;
    }
    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
        return false;
    }
    psF64 bg_skewness = psMetadataLookupF64(&status, md, "bg_skewness");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_skewness");
        return false;
    }
    psF64 bg_kurtosis = psMetadataLookupF64(&status, md, "bg_kurtosis");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_kurtosis");
        return false;
    }
    psF64 bin_stdev = psMetadataLookupF64(&status, md, "bin_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bin_stdev");
        return false;
    }
    psF64 fringe_0 = psMetadataLookupF64(&status, md, "fringe_0");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fringe_0");
        return false;
    }
    psF64 fringe_1 = psMetadataLookupF64(&status, md, "fringe_1");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fringe_1");
        return false;
    }
    psF64 fringe_2 = psMetadataLookupF64(&status, md, "fringe_2");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fringe_2");
        return false;
    }
    psF64 fringe_resid_0 = psMetadataLookupF64(&status, md, "fringe_resid_0");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fringe_resid_0");
        return false;
    }
    psF64 fringe_resid_1 = psMetadataLookupF64(&status, md, "fringe_resid_1");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fringe_resid_1");
        return false;
    }
    psF64 fringe_resid_2 = psMetadataLookupF64(&status, md, "fringe_resid_2");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fringe_resid_2");
        return false;
    }
    psF64 user_1 = psMetadataLookupF64(&status, md, "user_1");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_1");
        return false;
    }
    psF64 user_2 = psMetadataLookupF64(&status, md, "user_2");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_2");
        return false;
    }
    psF64 user_3 = psMetadataLookupF64(&status, md, "user_3");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_3");
        return false;
    }
    psF64 user_4 = psMetadataLookupF64(&status, md, "user_4");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_4");
        return false;
    }
    psF64 user_5 = psMetadataLookupF64(&status, md, "user_5");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_5");
        return false;
    }
    char* path_base = psMetadataLookupPtr(&status, md, "path_base");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item path_base");
        return false;
    }
    bool accept = psMetadataLookupBool(&status, md, "accept");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item accept");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return detResidExpRowAlloc(det_id, iteration, exp_id, recipe, bg, bg_stdev, bg_mean_stdev, bg_skewness, bg_kurtosis, bin_stdev, fringe_0, fringe_1, fringe_2, fringe_resid_0, fringe_resid_1, fringe_resid_2, user_1, user_2, user_3, user_4, user_5, path_base, accept, fault);
}
psArray *detResidExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, DETRESIDEXP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detResidExpRow *object = detResidExpObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool detResidExpDeleteObject(psDB *dbh, const detResidExpRow *object)
{
    psMetadata *where = detResidExpMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, DETRESIDEXP_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detResidExp");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "detResidExpRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long detResidExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        detResidExpRow *object = objects->data[i];
        psMetadata *where = detResidExpMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, DETRESIDEXP_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detResidExp");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool detResidExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = detResidExpMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            DETRESIDEXP_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool detResidExpPrintObject(FILE *stream, detResidExpRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = detResidExpMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void detRunSummaryRowFree(detRunSummaryRow *object);

detRunSummaryRow *detRunSummaryRowAlloc(psS64 det_id, psS32 iteration, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, bool accept, psS16 fault)
{
    detRunSummaryRow *_object;

    _object = psAlloc(sizeof(detRunSummaryRow));
    psMemSetDeallocator(_object, (psFreeFunc)detRunSummaryRowFree);

    _object->det_id = det_id;
    _object->iteration = iteration;
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->bg_mean_stdev = bg_mean_stdev;
    _object->accept = accept;
    _object->fault = fault;

    return _object;
}

static void detRunSummaryRowFree(detRunSummaryRow *object)
{
}

bool detRunSummaryCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key fkey(det_id, iteration) ref detInputExp(det_id, iteration)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key fkey(det_id, iteration) ref detResidExp(det_id, iteration)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "accept", PS_DATA_BOOL, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item accept");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, DETRUNSUMMARY_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool detRunSummaryDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, DETRUNSUMMARY_TABLE_NAME);
}

bool detRunSummaryInsert(psDB * dbh, psS64 det_id, psS32 iteration, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, bool accept, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "accept", PS_DATA_BOOL, NULL, accept)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item accept");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, DETRUNSUMMARY_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long detRunSummaryDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, DETRUNSUMMARY_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detRunSummary");
        return count;

        deleted += count;
    }

    return deleted;
}
bool detRunSummaryInsertObject(psDB *dbh, detRunSummaryRow *object)
{
    return detRunSummaryInsert(dbh, object->det_id, object->iteration, object->bg, object->bg_stdev, object->bg_mean_stdev, object->accept, object->fault);
}

bool detRunSummaryInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!detRunSummaryInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool detRunSummaryInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  DETRUNSUMMARY_TABLE_NAME
    if (!psFitsMoveExtName(fits, DETRUNSUMMARY_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETRUNSUMMARY_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, DETRUNSUMMARY_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool detRunSummarySelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, DETRUNSUMMARY_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, DETRUNSUMMARY_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *detRunSummaryMetadataFromObject(const detRunSummaryRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, object->iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "accept", PS_DATA_BOOL, NULL, object->accept)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item accept");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

detRunSummaryRow *detRunSummaryObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
        return false;
    }
    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
        return false;
    }
    bool accept = psMetadataLookupBool(&status, md, "accept");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item accept");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return detRunSummaryRowAlloc(det_id, iteration, bg, bg_stdev, bg_mean_stdev, accept, fault);
}
psArray *detRunSummarySelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, DETRUNSUMMARY_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detRunSummaryRow *object = detRunSummaryObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool detRunSummaryDeleteObject(psDB *dbh, const detRunSummaryRow *object)
{
    psMetadata *where = detRunSummaryMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, DETRUNSUMMARY_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detRunSummary");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "detRunSummaryRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long detRunSummaryDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        detRunSummaryRow *object = objects->data[i];
        psMetadata *where = detRunSummaryMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, DETRUNSUMMARY_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detRunSummary");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool detRunSummaryPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = detRunSummaryMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            DETRUNSUMMARY_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool detRunSummaryPrintObject(FILE *stream, detRunSummaryRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = detRunSummaryMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void detRegisteredImfileRowFree(detRegisteredImfileRow *object);

detRegisteredImfileRow *detRegisteredImfileRowAlloc(psS64 det_id, psS32 iteration, const char *class_id, const char *uri, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 user_1, psF64 user_2, psF64 user_3, psF64 user_4, psF64 user_5, const char *path_base, psS16 fault)
{
    detRegisteredImfileRow *_object;

    _object = psAlloc(sizeof(detRegisteredImfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)detRegisteredImfileRowFree);

    _object->det_id = det_id;
    _object->iteration = iteration;
    _object->class_id = psStringCopy(class_id);
    _object->uri = psStringCopy(uri);
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->bg_mean_stdev = bg_mean_stdev;
    _object->user_1 = user_1;
    _object->user_2 = user_2;
    _object->user_3 = user_3;
    _object->user_4 = user_4;
    _object->user_5 = user_5;
    _object->path_base = psStringCopy(path_base);
    _object->fault = fault;

    return _object;
}

static void detRegisteredImfileRowFree(detRegisteredImfileRow *object)
{
    psFree(object->class_id);
    psFree(object->uri);
    psFree(object->path_base);
}

bool detRegisteredImfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key fkey(det_id, iteration) ref detRun(det_id, iteration)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, DETREGISTEREDIMFILE_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool detRegisteredImfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, DETREGISTEREDIMFILE_TABLE_NAME);
}

bool detRegisteredImfileInsert(psDB * dbh, psS64 det_id, psS32 iteration, const char *class_id, const char *uri, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 user_1, psF64 user_2, psF64 user_3, psF64 user_4, psF64 user_5, const char *path_base, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, user_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, user_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, user_3)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, user_4)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, user_5)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, DETREGISTEREDIMFILE_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long detRegisteredImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, DETREGISTEREDIMFILE_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detRegisteredImfile");
        return count;

        deleted += count;
    }

    return deleted;
}
bool detRegisteredImfileInsertObject(psDB *dbh, detRegisteredImfileRow *object)
{
    return detRegisteredImfileInsert(dbh, object->det_id, object->iteration, object->class_id, object->uri, object->bg, object->bg_stdev, object->bg_mean_stdev, object->user_1, object->user_2, object->user_3, object->user_4, object->user_5, object->path_base, object->fault);
}

bool detRegisteredImfileInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!detRegisteredImfileInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool detRegisteredImfileInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  DETREGISTEREDIMFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, DETREGISTEREDIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETREGISTEREDIMFILE_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, DETREGISTEREDIMFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool detRegisteredImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, DETREGISTEREDIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, DETREGISTEREDIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *detRegisteredImfileMetadataFromObject(const detRegisteredImfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, object->iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_1", PS_DATA_F64, NULL, object->user_1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_2", PS_DATA_F64, NULL, object->user_2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_3", PS_DATA_F64, NULL, object->user_3)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_4", PS_DATA_F64, NULL, object->user_4)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "user_5", PS_DATA_F64, NULL, object->user_5)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item user_5");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, object->path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

detRegisteredImfileRow *detRegisteredImfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
        return false;
    }
    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
        return false;
    }
    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
        return false;
    }
    psF64 user_1 = psMetadataLookupF64(&status, md, "user_1");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_1");
        return false;
    }
    psF64 user_2 = psMetadataLookupF64(&status, md, "user_2");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_2");
        return false;
    }
    psF64 user_3 = psMetadataLookupF64(&status, md, "user_3");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_3");
        return false;
    }
    psF64 user_4 = psMetadataLookupF64(&status, md, "user_4");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_4");
        return false;
    }
    psF64 user_5 = psMetadataLookupF64(&status, md, "user_5");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item user_5");
        return false;
    }
    char* path_base = psMetadataLookupPtr(&status, md, "path_base");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item path_base");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return detRegisteredImfileRowAlloc(det_id, iteration, class_id, uri, bg, bg_stdev, bg_mean_stdev, user_1, user_2, user_3, user_4, user_5, path_base, fault);
}
psArray *detRegisteredImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, DETREGISTEREDIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detRegisteredImfileRow *object = detRegisteredImfileObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool detRegisteredImfileDeleteObject(psDB *dbh, const detRegisteredImfileRow *object)
{
    psMetadata *where = detRegisteredImfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, DETREGISTEREDIMFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detRegisteredImfile");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "detRegisteredImfileRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long detRegisteredImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        detRegisteredImfileRow *object = objects->data[i];
        psMetadata *where = detRegisteredImfileMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, DETREGISTEREDIMFILE_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detRegisteredImfile");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool detRegisteredImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = detRegisteredImfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            DETREGISTEREDIMFILE_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool detRegisteredImfilePrintObject(FILE *stream, detRegisteredImfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = detRegisteredImfileMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void detCorrectedExpRowFree(detCorrectedExpRow *object);

detCorrectedExpRow *detCorrectedExpRowAlloc(psS64 det_id, psS64 exp_id, const char *uri, psS64 corr_id, const char *corr_type, const char *recipe, const char *path_base, psS16 fault)
{
    detCorrectedExpRow *_object;

    _object = psAlloc(sizeof(detCorrectedExpRow));
    psMemSetDeallocator(_object, (psFreeFunc)detCorrectedExpRowFree);

    _object->det_id = det_id;
    _object->exp_id = exp_id;
    _object->uri = psStringCopy(uri);
    _object->corr_id = corr_id;
    _object->corr_type = psStringCopy(corr_type);
    _object->recipe = psStringCopy(recipe);
    _object->path_base = psStringCopy(path_base);
    _object->fault = fault;

    return _object;
}

static void detCorrectedExpRowFree(detCorrectedExpRow *object)
{
    psFree(object->uri);
    psFree(object->corr_type);
    psFree(object->recipe);
    psFree(object->path_base);
}

bool detCorrectedExpCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key fkey(det_id, exp_id) ref detInputExp(det_id, exp_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, "Primary Key fkey(exp_id, class_id) ref rawImfile(exp_id, class_id)", 64)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, "INDEX(det_id, exp_id)", "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "corr_id", PS_DATA_S64, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item corr_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "corr_type", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item corr_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, DETCORRECTEDEXP_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool detCorrectedExpDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, DETCORRECTEDEXP_TABLE_NAME);
}

bool detCorrectedExpInsert(psDB * dbh, psS64 det_id, psS64 exp_id, const char *uri, psS64 corr_id, const char *corr_type, const char *recipe, const char *path_base, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "corr_id", PS_DATA_S64, NULL, corr_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item corr_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "corr_type", PS_DATA_STRING, NULL, corr_type)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item corr_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, DETCORRECTEDEXP_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long detCorrectedExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, DETCORRECTEDEXP_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detCorrectedExp");
        return count;

        deleted += count;
    }

    return deleted;
}
bool detCorrectedExpInsertObject(psDB *dbh, detCorrectedExpRow *object)
{
    return detCorrectedExpInsert(dbh, object->det_id, object->exp_id, object->uri, object->corr_id, object->corr_type, object->recipe, object->path_base, object->fault);
}

bool detCorrectedExpInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!detCorrectedExpInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool detCorrectedExpInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  DETCORRECTEDEXP_TABLE_NAME
    if (!psFitsMoveExtName(fits, DETCORRECTEDEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETCORRECTEDEXP_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, DETCORRECTEDEXP_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool detCorrectedExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, DETCORRECTEDEXP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, DETCORRECTEDEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *detCorrectedExpMetadataFromObject(const detCorrectedExpRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, object->exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "corr_id", PS_DATA_S64, NULL, object->corr_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item corr_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "corr_type", PS_DATA_STRING, NULL, object->corr_type)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item corr_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, object->recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, object->path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

detCorrectedExpRow *detCorrectedExpObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
        return false;
    }
    psS64 exp_id = psMetadataLookupS64(&status, md, "exp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    psS64 corr_id = psMetadataLookupS64(&status, md, "corr_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item corr_id");
        return false;
    }
    char* corr_type = psMetadataLookupPtr(&status, md, "corr_type");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item corr_type");
        return false;
    }
    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
        return false;
    }
    char* path_base = psMetadataLookupPtr(&status, md, "path_base");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item path_base");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return detCorrectedExpRowAlloc(det_id, exp_id, uri, corr_id, corr_type, recipe, path_base, fault);
}
psArray *detCorrectedExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, DETCORRECTEDEXP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detCorrectedExpRow *object = detCorrectedExpObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool detCorrectedExpDeleteObject(psDB *dbh, const detCorrectedExpRow *object)
{
    psMetadata *where = detCorrectedExpMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, DETCORRECTEDEXP_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detCorrectedExp");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "detCorrectedExpRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long detCorrectedExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        detCorrectedExpRow *object = objects->data[i];
        psMetadata *where = detCorrectedExpMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, DETCORRECTEDEXP_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detCorrectedExp");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool detCorrectedExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = detCorrectedExpMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            DETCORRECTEDEXP_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool detCorrectedExpPrintObject(FILE *stream, detCorrectedExpRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = detCorrectedExpMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void detCorrectedImfileRowFree(detCorrectedImfileRow *object);

detCorrectedImfileRow *detCorrectedImfileRowAlloc(psS64 det_id, psS64 exp_id, const char *class_id, const char *uri, const char *path_base, psS16 fault)
{
    detCorrectedImfileRow *_object;

    _object = psAlloc(sizeof(detCorrectedImfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)detCorrectedImfileRowFree);

    _object->det_id = det_id;
    _object->exp_id = exp_id;
    _object->class_id = psStringCopy(class_id);
    _object->uri = psStringCopy(uri);
    _object->path_base = psStringCopy(path_base);
    _object->fault = fault;

    return _object;
}

static void detCorrectedImfileRowFree(detCorrectedImfileRow *object)
{
    psFree(object->class_id);
    psFree(object->uri);
    psFree(object->path_base);
}

bool detCorrectedImfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, "Primary Key fkey(det_id, exp_id) ref detInputExp(det_id, exp_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, "Primary Key fkey(exp_id, class_id) ref rawImfile(exp_id, class_id)", 64)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key INDEX(det_id, class_id)", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, "INDEX(det_id, exp_id)", "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key NOT NULL", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, DETCORRECTEDIMFILE_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool detCorrectedImfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, DETCORRECTEDIMFILE_TABLE_NAME);
}

bool detCorrectedImfileInsert(psDB * dbh, psS64 det_id, psS64 exp_id, const char *class_id, const char *uri, const char *path_base, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, DETCORRECTEDIMFILE_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long detCorrectedImfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, DETCORRECTEDIMFILE_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detCorrectedImfile");
        return count;

        deleted += count;
    }

    return deleted;
}
bool detCorrectedImfileInsertObject(psDB *dbh, detCorrectedImfileRow *object)
{
    return detCorrectedImfileInsert(dbh, object->det_id, object->exp_id, object->class_id, object->uri, object->path_base, object->fault);
}

bool detCorrectedImfileInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!detCorrectedImfileInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool detCorrectedImfileInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  DETCORRECTEDIMFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, DETCORRECTEDIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", DETCORRECTEDIMFILE_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, DETCORRECTEDIMFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool detCorrectedImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, DETCORRECTEDIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, DETCORRECTEDIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *detCorrectedImfileMetadataFromObject(const detCorrectedImfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S64, NULL, object->det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, object->exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "path_base", PS_DATA_STRING, NULL, object->path_base)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item path_base");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

detCorrectedImfileRow *detCorrectedImfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 det_id = psMetadataLookupS64(&status, md, "det_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
        return false;
    }
    psS64 exp_id = psMetadataLookupS64(&status, md, "exp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
        return false;
    }
    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    char* path_base = psMetadataLookupPtr(&status, md, "path_base");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item path_base");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return detCorrectedImfileRowAlloc(det_id, exp_id, class_id, uri, path_base, fault);
}
psArray *detCorrectedImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, DETCORRECTEDIMFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detCorrectedImfileRow *object = detCorrectedImfileObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool detCorrectedImfileDeleteObject(psDB *dbh, const detCorrectedImfileRow *object)
{
    psMetadata *where = detCorrectedImfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, DETCORRECTEDIMFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from detCorrectedImfile");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "detCorrectedImfileRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long detCorrectedImfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        detCorrectedImfileRow *object = objects->data[i];
        psMetadata *where = detCorrectedImfileMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, DETCORRECTEDIMFILE_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from detCorrectedImfile");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool detCorrectedImfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = detCorrectedImfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            DETCORRECTEDIMFILE_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool detCorrectedImfilePrintObject(FILE *stream, detCorrectedImfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = detCorrectedImfileMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void magicRunRowFree(magicRunRow *object);

magicRunRow *magicRunRowAlloc(psS64 magic_id, psS64 exp_id, const char *state, const char *workdir, const char *workdir_state, const char *label, const char *dvodb, psTime* registered, psS16 fault)
{
    magicRunRow     *_object;

    _object = psAlloc(sizeof(magicRunRow));
    psMemSetDeallocator(_object, (psFreeFunc)magicRunRowFree);

    _object->magic_id = magic_id;
    _object->exp_id = exp_id;
    _object->state = psStringCopy(state);
    _object->workdir = psStringCopy(workdir);
    _object->workdir_state = psStringCopy(workdir_state);
    _object->label = psStringCopy(label);
    _object->dvodb = psStringCopy(dvodb);
    _object->registered = psTimeCopy(registered);
    _object->fault = fault;

    return _object;
}

static void magicRunRowFree(magicRunRow *object)
{
    psFree(object->state);
    psFree(object->workdir);
    psFree(object->workdir_state);
    psFree(object->label);
    psFree(object->dvodb);
    psFree(object->registered);
}

bool magicRunCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "magic_id", PS_DATA_S64, "Primary Key AUTO_INCREMENT", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magic_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, "Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir_state", PS_DATA_STRING, "Key", "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir_state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, "key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, MAGICRUN_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool magicRunDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, MAGICRUN_TABLE_NAME);
}

bool magicRunInsert(psDB * dbh, psS64 magic_id, psS64 exp_id, const char *state, const char *workdir, const char *workdir_state, const char *label, const char *dvodb, psTime* registered, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "magic_id", PS_DATA_S64, NULL, magic_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magic_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir_state", PS_DATA_STRING, NULL, workdir_state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir_state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, label)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, dvodb)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, registered)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, MAGICRUN_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long magicRunDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, MAGICRUN_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from magicRun");
        return count;

        deleted += count;
    }

    return deleted;
}
bool magicRunInsertObject(psDB *dbh, magicRunRow *object)
{
    return magicRunInsert(dbh, object->magic_id, object->exp_id, object->state, object->workdir, object->workdir_state, object->label, object->dvodb, object->registered, object->fault);
}

bool magicRunInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!magicRunInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool magicRunInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  MAGICRUN_TABLE_NAME
    if (!psFitsMoveExtName(fits, MAGICRUN_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", MAGICRUN_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, MAGICRUN_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool magicRunSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, MAGICRUN_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, MAGICRUN_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *magicRunMetadataFromObject(const magicRunRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "magic_id", PS_DATA_S64, NULL, object->magic_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magic_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_S64, NULL, object->exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, object->state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, object->workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir_state", PS_DATA_STRING, NULL, object->workdir_state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir_state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, object->label)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, object->dvodb)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, object->registered)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

magicRunRow *magicRunObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 magic_id = psMetadataLookupS64(&status, md, "magic_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item magic_id");
        return false;
    }
    psS64 exp_id = psMetadataLookupS64(&status, md, "exp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
        return false;
    }
    char* state = psMetadataLookupPtr(&status, md, "state");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item state");
        return false;
    }
    char* workdir = psMetadataLookupPtr(&status, md, "workdir");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
        return false;
    }
    char* workdir_state = psMetadataLookupPtr(&status, md, "workdir_state");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir_state");
        return false;
    }
    char* label = psMetadataLookupPtr(&status, md, "label");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item label");
        return false;
    }
    char* dvodb = psMetadataLookupPtr(&status, md, "dvodb");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dvodb");
        return false;
    }
    psTime* registered = psMetadataLookupPtr(&status, md, "registered");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item registered");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return magicRunRowAlloc(magic_id, exp_id, state, workdir, workdir_state, label, dvodb, registered, fault);
}
psArray *magicRunSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, MAGICRUN_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        magicRunRow *object = magicRunObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool magicRunDeleteObject(psDB *dbh, const magicRunRow *object)
{
    psMetadata *where = magicRunMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, MAGICRUN_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from magicRun");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "magicRunRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long magicRunDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        magicRunRow *object = objects->data[i];
        psMetadata *where = magicRunMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, MAGICRUN_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from magicRun");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool magicRunPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = magicRunMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            MAGICRUN_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool magicRunPrintObject(FILE *stream, magicRunRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = magicRunMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void magicInputSkyfileRowFree(magicInputSkyfileRow *object);

magicInputSkyfileRow *magicInputSkyfileRowAlloc(psS64 magic_id, psS64 diff_id, const char *node)
{
    magicInputSkyfileRow *_object;

    _object = psAlloc(sizeof(magicInputSkyfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)magicInputSkyfileRowFree);

    _object->magic_id = magic_id;
    _object->diff_id = diff_id;
    _object->node = psStringCopy(node);

    return _object;
}

static void magicInputSkyfileRowFree(magicInputSkyfileRow *object)
{
    psFree(object->node);
}

bool magicInputSkyfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "magic_id", PS_DATA_S64, "Primary Key fkey(magic_id) ref magicRun(magic_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magic_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "diff_id", PS_DATA_S64, "Primary Key fkey(diff_id) ref diffRun(diff_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item diff_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "node", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item node");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, MAGICINPUTSKYFILE_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool magicInputSkyfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, MAGICINPUTSKYFILE_TABLE_NAME);
}

bool magicInputSkyfileInsert(psDB * dbh, psS64 magic_id, psS64 diff_id, const char *node)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "magic_id", PS_DATA_S64, NULL, magic_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magic_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "diff_id", PS_DATA_S64, NULL, diff_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item diff_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "node", PS_DATA_STRING, NULL, node)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item node");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, MAGICINPUTSKYFILE_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long magicInputSkyfileDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, MAGICINPUTSKYFILE_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from magicInputSkyfile");
        return count;

        deleted += count;
    }

    return deleted;
}
bool magicInputSkyfileInsertObject(psDB *dbh, magicInputSkyfileRow *object)
{
    return magicInputSkyfileInsert(dbh, object->magic_id, object->diff_id, object->node);
}

bool magicInputSkyfileInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!magicInputSkyfileInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool magicInputSkyfileInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  MAGICINPUTSKYFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, MAGICINPUTSKYFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", MAGICINPUTSKYFILE_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, MAGICINPUTSKYFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool magicInputSkyfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, MAGICINPUTSKYFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, MAGICINPUTSKYFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *magicInputSkyfileMetadataFromObject(const magicInputSkyfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "magic_id", PS_DATA_S64, NULL, object->magic_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magic_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "diff_id", PS_DATA_S64, NULL, object->diff_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item diff_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "node", PS_DATA_STRING, NULL, object->node)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item node");
        psFree(md);
        return false;
    }


    return md;
}

magicInputSkyfileRow *magicInputSkyfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 magic_id = psMetadataLookupS64(&status, md, "magic_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item magic_id");
        return false;
    }
    psS64 diff_id = psMetadataLookupS64(&status, md, "diff_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item diff_id");
        return false;
    }
    char* node = psMetadataLookupPtr(&status, md, "node");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item node");
        return false;
    }

    return magicInputSkyfileRowAlloc(magic_id, diff_id, node);
}
psArray *magicInputSkyfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, MAGICINPUTSKYFILE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        magicInputSkyfileRow *object = magicInputSkyfileObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool magicInputSkyfileDeleteObject(psDB *dbh, const magicInputSkyfileRow *object)
{
    psMetadata *where = magicInputSkyfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, MAGICINPUTSKYFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from magicInputSkyfile");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "magicInputSkyfileRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long magicInputSkyfileDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        magicInputSkyfileRow *object = objects->data[i];
        psMetadata *where = magicInputSkyfileMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, MAGICINPUTSKYFILE_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from magicInputSkyfile");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool magicInputSkyfilePrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = magicInputSkyfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            MAGICINPUTSKYFILE_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool magicInputSkyfilePrintObject(FILE *stream, magicInputSkyfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = magicInputSkyfileMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void magicTreeRowFree(magicTreeRow *object);

magicTreeRow *magicTreeRowAlloc(psS64 magic_id, const char *node, const char *dep)
{
    magicTreeRow    *_object;

    _object = psAlloc(sizeof(magicTreeRow));
    psMemSetDeallocator(_object, (psFreeFunc)magicTreeRowFree);

    _object->magic_id = magic_id;
    _object->node = psStringCopy(node);
    _object->dep = psStringCopy(dep);

    return _object;
}

static void magicTreeRowFree(magicTreeRow *object)
{
    psFree(object->node);
    psFree(object->dep);
}

bool magicTreeCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "magic_id", PS_DATA_S64, "Key fkey(magic_id) ref magicRun(magic_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magic_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "node", PS_DATA_STRING, "Key INDEX(magic_id, node)", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item node");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dep", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dep");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, MAGICTREE_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool magicTreeDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, MAGICTREE_TABLE_NAME);
}

bool magicTreeInsert(psDB * dbh, psS64 magic_id, const char *node, const char *dep)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "magic_id", PS_DATA_S64, NULL, magic_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magic_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "node", PS_DATA_STRING, NULL, node)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item node");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dep", PS_DATA_STRING, NULL, dep)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dep");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, MAGICTREE_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long magicTreeDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, MAGICTREE_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from magicTree");
        return count;

        deleted += count;
    }

    return deleted;
}
bool magicTreeInsertObject(psDB *dbh, magicTreeRow *object)
{
    return magicTreeInsert(dbh, object->magic_id, object->node, object->dep);
}

bool magicTreeInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!magicTreeInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool magicTreeInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  MAGICTREE_TABLE_NAME
    if (!psFitsMoveExtName(fits, MAGICTREE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", MAGICTREE_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, MAGICTREE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool magicTreeSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, MAGICTREE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, MAGICTREE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *magicTreeMetadataFromObject(const magicTreeRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "magic_id", PS_DATA_S64, NULL, object->magic_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magic_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "node", PS_DATA_STRING, NULL, object->node)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item node");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dep", PS_DATA_STRING, NULL, object->dep)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dep");
        psFree(md);
        return false;
    }


    return md;
}

magicTreeRow *magicTreeObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 magic_id = psMetadataLookupS64(&status, md, "magic_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item magic_id");
        return false;
    }
    char* node = psMetadataLookupPtr(&status, md, "node");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item node");
        return false;
    }
    char* dep = psMetadataLookupPtr(&status, md, "dep");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dep");
        return false;
    }

    return magicTreeRowAlloc(magic_id, node, dep);
}
psArray *magicTreeSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, MAGICTREE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        magicTreeRow *object = magicTreeObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool magicTreeDeleteObject(psDB *dbh, const magicTreeRow *object)
{
    psMetadata *where = magicTreeMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, MAGICTREE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from magicTree");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "magicTreeRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long magicTreeDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        magicTreeRow *object = objects->data[i];
        psMetadata *where = magicTreeMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, MAGICTREE_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from magicTree");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool magicTreePrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = magicTreeMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            MAGICTREE_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool magicTreePrintObject(FILE *stream, magicTreeRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = magicTreeMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void magicNodeResultRowFree(magicNodeResultRow *object);

magicNodeResultRow *magicNodeResultRowAlloc(psS64 magic_id, const char *node, const char *uri, psS16 fault)
{
    magicNodeResultRow *_object;

    _object = psAlloc(sizeof(magicNodeResultRow));
    psMemSetDeallocator(_object, (psFreeFunc)magicNodeResultRowFree);

    _object->magic_id = magic_id;
    _object->node = psStringCopy(node);
    _object->uri = psStringCopy(uri);
    _object->fault = fault;

    return _object;
}

static void magicNodeResultRowFree(magicNodeResultRow *object)
{
    psFree(object->node);
    psFree(object->uri);
}

bool magicNodeResultCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "magic_id", PS_DATA_S64, "Primary Key fkey(magic_id) ref magicRun(magic_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magic_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "node", PS_DATA_STRING, "Primary Key fkey(magic_id, node) ref magicTree(magic_id, node)", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item node");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, MAGICNODERESULT_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool magicNodeResultDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, MAGICNODERESULT_TABLE_NAME);
}

bool magicNodeResultInsert(psDB * dbh, psS64 magic_id, const char *node, const char *uri, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "magic_id", PS_DATA_S64, NULL, magic_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magic_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "node", PS_DATA_STRING, NULL, node)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item node");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, MAGICNODERESULT_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long magicNodeResultDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, MAGICNODERESULT_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from magicNodeResult");
        return count;

        deleted += count;
    }

    return deleted;
}
bool magicNodeResultInsertObject(psDB *dbh, magicNodeResultRow *object)
{
    return magicNodeResultInsert(dbh, object->magic_id, object->node, object->uri, object->fault);
}

bool magicNodeResultInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!magicNodeResultInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool magicNodeResultInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  MAGICNODERESULT_TABLE_NAME
    if (!psFitsMoveExtName(fits, MAGICNODERESULT_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", MAGICNODERESULT_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, MAGICNODERESULT_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool magicNodeResultSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, MAGICNODERESULT_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, MAGICNODERESULT_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *magicNodeResultMetadataFromObject(const magicNodeResultRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "magic_id", PS_DATA_S64, NULL, object->magic_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magic_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "node", PS_DATA_STRING, NULL, object->node)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item node");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

magicNodeResultRow *magicNodeResultObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 magic_id = psMetadataLookupS64(&status, md, "magic_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item magic_id");
        return false;
    }
    char* node = psMetadataLookupPtr(&status, md, "node");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item node");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return magicNodeResultRowAlloc(magic_id, node, uri, fault);
}
psArray *magicNodeResultSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, MAGICNODERESULT_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        magicNodeResultRow *object = magicNodeResultObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool magicNodeResultDeleteObject(psDB *dbh, const magicNodeResultRow *object)
{
    psMetadata *where = magicNodeResultMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, MAGICNODERESULT_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from magicNodeResult");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "magicNodeResultRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long magicNodeResultDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        magicNodeResultRow *object = objects->data[i];
        psMetadata *where = magicNodeResultMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, MAGICNODERESULT_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from magicNodeResult");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool magicNodeResultPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = magicNodeResultMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            MAGICNODERESULT_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool magicNodeResultPrintObject(FILE *stream, magicNodeResultRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = magicNodeResultMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void magicMaskRowFree(magicMaskRow *object);

magicMaskRow *magicMaskRowAlloc(psS64 magic_id, const char *uri, psS32 streaks, psS16 fault)
{
    magicMaskRow    *_object;

    _object = psAlloc(sizeof(magicMaskRow));
    psMemSetDeallocator(_object, (psFreeFunc)magicMaskRowFree);

    _object->magic_id = magic_id;
    _object->uri = psStringCopy(uri);
    _object->streaks = streaks;
    _object->fault = fault;

    return _object;
}

static void magicMaskRowFree(magicMaskRow *object)
{
    psFree(object->uri);
}

bool magicMaskCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "magic_id", PS_DATA_S64, "Primary Key fkey(magic_id) ref magicRun(magic_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magic_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "streaks", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item streaks");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, MAGICMASK_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool magicMaskDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, MAGICMASK_TABLE_NAME);
}

bool magicMaskInsert(psDB * dbh, psS64 magic_id, const char *uri, psS32 streaks, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "magic_id", PS_DATA_S64, NULL, magic_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magic_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "streaks", PS_DATA_S32, NULL, streaks)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item streaks");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, MAGICMASK_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long magicMaskDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, MAGICMASK_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from magicMask");
        return count;

        deleted += count;
    }

    return deleted;
}
bool magicMaskInsertObject(psDB *dbh, magicMaskRow *object)
{
    return magicMaskInsert(dbh, object->magic_id, object->uri, object->streaks, object->fault);
}

bool magicMaskInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!magicMaskInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool magicMaskInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  MAGICMASK_TABLE_NAME
    if (!psFitsMoveExtName(fits, MAGICMASK_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", MAGICMASK_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, MAGICMASK_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool magicMaskSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, MAGICMASK_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, MAGICMASK_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *magicMaskMetadataFromObject(const magicMaskRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "magic_id", PS_DATA_S64, NULL, object->magic_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magic_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "streaks", PS_DATA_S32, NULL, object->streaks)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item streaks");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

magicMaskRow *magicMaskObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 magic_id = psMetadataLookupS64(&status, md, "magic_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item magic_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    psS32 streaks = psMetadataLookupS32(&status, md, "streaks");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item streaks");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return magicMaskRowAlloc(magic_id, uri, streaks, fault);
}
psArray *magicMaskSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, MAGICMASK_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        magicMaskRow *object = magicMaskObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool magicMaskDeleteObject(psDB *dbh, const magicMaskRow *object)
{
    psMetadata *where = magicMaskMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, MAGICMASK_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from magicMask");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "magicMaskRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long magicMaskDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        magicMaskRow *object = objects->data[i];
        psMetadata *where = magicMaskMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, MAGICMASK_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from magicMask");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool magicMaskPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = magicMaskMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            MAGICMASK_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool magicMaskPrintObject(FILE *stream, magicMaskRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = magicMaskMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void calDBRowFree(calDBRow *object);

calDBRow *calDBRowAlloc(psS64 cal_id, const char *dvodb, const char *state)
{
    calDBRow        *_object;

    _object = psAlloc(sizeof(calDBRow));
    psMemSetDeallocator(_object, (psFreeFunc)calDBRowFree);

    _object->cal_id = cal_id;
    _object->dvodb = psStringCopy(dvodb);
    _object->state = psStringCopy(state);

    return _object;
}

static void calDBRowFree(calDBRow *object)
{
    psFree(object->dvodb);
    psFree(object->state);
}

bool calDBCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "cal_id", PS_DATA_S64, "Primary Key AUTO_INCREMENT", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item cal_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, CALDB_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool calDBDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, CALDB_TABLE_NAME);
}

bool calDBInsert(psDB * dbh, psS64 cal_id, const char *dvodb, const char *state)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "cal_id", PS_DATA_S64, NULL, cal_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item cal_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, dvodb)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, CALDB_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long calDBDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, CALDB_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from calDB");
        return count;

        deleted += count;
    }

    return deleted;
}
bool calDBInsertObject(psDB *dbh, calDBRow *object)
{
    return calDBInsert(dbh, object->cal_id, object->dvodb, object->state);
}

bool calDBInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!calDBInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool calDBInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  CALDB_TABLE_NAME
    if (!psFitsMoveExtName(fits, CALDB_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", CALDB_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, CALDB_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool calDBSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, CALDB_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, CALDB_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *calDBMetadataFromObject(const calDBRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "cal_id", PS_DATA_S64, NULL, object->cal_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item cal_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, object->dvodb)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, object->state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }


    return md;
}

calDBRow *calDBObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 cal_id = psMetadataLookupS64(&status, md, "cal_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item cal_id");
        return false;
    }
    char* dvodb = psMetadataLookupPtr(&status, md, "dvodb");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dvodb");
        return false;
    }
    char* state = psMetadataLookupPtr(&status, md, "state");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item state");
        return false;
    }

    return calDBRowAlloc(cal_id, dvodb, state);
}
psArray *calDBSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, CALDB_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        calDBRow *object = calDBObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool calDBDeleteObject(psDB *dbh, const calDBRow *object)
{
    psMetadata *where = calDBMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, CALDB_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from calDB");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "calDBRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long calDBDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        calDBRow *object = objects->data[i];
        psMetadata *where = calDBMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, CALDB_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from calDB");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool calDBPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = calDBMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            CALDB_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool calDBPrintObject(FILE *stream, calDBRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = calDBMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void calRunRowFree(calRunRow *object);

calRunRow *calRunRowAlloc(psS64 cal_id, const char *region, const char *last_step, const char *state)
{
    calRunRow       *_object;

    _object = psAlloc(sizeof(calRunRow));
    psMemSetDeallocator(_object, (psFreeFunc)calRunRowFree);

    _object->cal_id = cal_id;
    _object->region = psStringCopy(region);
    _object->last_step = psStringCopy(last_step);
    _object->state = psStringCopy(state);

    return _object;
}

static void calRunRowFree(calRunRow *object)
{
    psFree(object->region);
    psFree(object->last_step);
    psFree(object->state);
}

bool calRunCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "cal_id", PS_DATA_S64, "Primary Key AUTO_INCREMENT", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item cal_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "region", PS_DATA_STRING, "success or failure", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item region");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "last_step", PS_DATA_STRING, "fkey(cal_id) ref calDB(cal_id)", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item last_step");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, CALRUN_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool calRunDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, CALRUN_TABLE_NAME);
}

bool calRunInsert(psDB * dbh, psS64 cal_id, const char *region, const char *last_step, const char *state)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "cal_id", PS_DATA_S64, NULL, cal_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item cal_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "region", PS_DATA_STRING, NULL, region)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item region");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "last_step", PS_DATA_STRING, NULL, last_step)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item last_step");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, CALRUN_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long calRunDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, CALRUN_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from calRun");
        return count;

        deleted += count;
    }

    return deleted;
}
bool calRunInsertObject(psDB *dbh, calRunRow *object)
{
    return calRunInsert(dbh, object->cal_id, object->region, object->last_step, object->state);
}

bool calRunInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!calRunInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool calRunInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  CALRUN_TABLE_NAME
    if (!psFitsMoveExtName(fits, CALRUN_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", CALRUN_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, CALRUN_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool calRunSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, CALRUN_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, CALRUN_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *calRunMetadataFromObject(const calRunRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "cal_id", PS_DATA_S64, NULL, object->cal_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item cal_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "region", PS_DATA_STRING, NULL, object->region)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item region");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "last_step", PS_DATA_STRING, NULL, object->last_step)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item last_step");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, object->state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }


    return md;
}

calRunRow *calRunObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 cal_id = psMetadataLookupS64(&status, md, "cal_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item cal_id");
        return false;
    }
    char* region = psMetadataLookupPtr(&status, md, "region");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item region");
        return false;
    }
    char* last_step = psMetadataLookupPtr(&status, md, "last_step");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item last_step");
        return false;
    }
    char* state = psMetadataLookupPtr(&status, md, "state");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item state");
        return false;
    }

    return calRunRowAlloc(cal_id, region, last_step, state);
}
psArray *calRunSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, CALRUN_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        calRunRow *object = calRunObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool calRunDeleteObject(psDB *dbh, const calRunRow *object)
{
    psMetadata *where = calRunMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, CALRUN_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from calRun");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "calRunRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long calRunDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        calRunRow *object = objects->data[i];
        psMetadata *where = calRunMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, CALRUN_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from calRun");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool calRunPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = calRunMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            CALRUN_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool calRunPrintObject(FILE *stream, calRunRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = calRunMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void flatcorrRunRowFree(flatcorrRunRow *object);

flatcorrRunRow *flatcorrRunRowAlloc(psS64 corr_id, const char *dvodb, const char *filter, const char *state, const char *workdir, const char *label, const char *stats, const char *region)
{
    flatcorrRunRow  *_object;

    _object = psAlloc(sizeof(flatcorrRunRow));
    psMemSetDeallocator(_object, (psFreeFunc)flatcorrRunRowFree);

    _object->corr_id = corr_id;
    _object->dvodb = psStringCopy(dvodb);
    _object->filter = psStringCopy(filter);
    _object->state = psStringCopy(state);
    _object->workdir = psStringCopy(workdir);
    _object->label = psStringCopy(label);
    _object->stats = psStringCopy(stats);
    _object->region = psStringCopy(region);

    return _object;
}

static void flatcorrRunRowFree(flatcorrRunRow *object)
{
    psFree(object->dvodb);
    psFree(object->filter);
    psFree(object->state);
    psFree(object->workdir);
    psFree(object->label);
    psFree(object->stats);
    psFree(object->region);
}

bool flatcorrRunCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "corr_id", PS_DATA_S64, "Primary Key AUTO_INCREMENT", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item corr_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "stats", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "region", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item region");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, FLATCORRRUN_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool flatcorrRunDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, FLATCORRRUN_TABLE_NAME);
}

bool flatcorrRunInsert(psDB * dbh, psS64 corr_id, const char *dvodb, const char *filter, const char *state, const char *workdir, const char *label, const char *stats, const char *region)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "corr_id", PS_DATA_S64, NULL, corr_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item corr_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, dvodb)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, filter)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, label)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "stats", PS_DATA_STRING, NULL, stats)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "region", PS_DATA_STRING, NULL, region)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item region");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, FLATCORRRUN_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long flatcorrRunDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, FLATCORRRUN_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from flatcorrRun");
        return count;

        deleted += count;
    }

    return deleted;
}
bool flatcorrRunInsertObject(psDB *dbh, flatcorrRunRow *object)
{
    return flatcorrRunInsert(dbh, object->corr_id, object->dvodb, object->filter, object->state, object->workdir, object->label, object->stats, object->region);
}

bool flatcorrRunInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!flatcorrRunInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool flatcorrRunInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  FLATCORRRUN_TABLE_NAME
    if (!psFitsMoveExtName(fits, FLATCORRRUN_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", FLATCORRRUN_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, FLATCORRRUN_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool flatcorrRunSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, FLATCORRRUN_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, FLATCORRRUN_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *flatcorrRunMetadataFromObject(const flatcorrRunRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "corr_id", PS_DATA_S64, NULL, object->corr_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item corr_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dvodb", PS_DATA_STRING, NULL, object->dvodb)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dvodb");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, object->filter)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, object->state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, object->workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "label", PS_DATA_STRING, NULL, object->label)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item label");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "stats", PS_DATA_STRING, NULL, object->stats)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item stats");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "region", PS_DATA_STRING, NULL, object->region)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item region");
        psFree(md);
        return false;
    }


    return md;
}

flatcorrRunRow *flatcorrRunObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 corr_id = psMetadataLookupS64(&status, md, "corr_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item corr_id");
        return false;
    }
    char* dvodb = psMetadataLookupPtr(&status, md, "dvodb");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dvodb");
        return false;
    }
    char* filter = psMetadataLookupPtr(&status, md, "filter");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
        return false;
    }
    char* state = psMetadataLookupPtr(&status, md, "state");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item state");
        return false;
    }
    char* workdir = psMetadataLookupPtr(&status, md, "workdir");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
        return false;
    }
    char* label = psMetadataLookupPtr(&status, md, "label");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item label");
        return false;
    }
    char* stats = psMetadataLookupPtr(&status, md, "stats");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item stats");
        return false;
    }
    char* region = psMetadataLookupPtr(&status, md, "region");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item region");
        return false;
    }

    return flatcorrRunRowAlloc(corr_id, dvodb, filter, state, workdir, label, stats, region);
}
psArray *flatcorrRunSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, FLATCORRRUN_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        flatcorrRunRow *object = flatcorrRunObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool flatcorrRunDeleteObject(psDB *dbh, const flatcorrRunRow *object)
{
    psMetadata *where = flatcorrRunMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, FLATCORRRUN_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from flatcorrRun");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "flatcorrRunRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long flatcorrRunDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        flatcorrRunRow *object = objects->data[i];
        psMetadata *where = flatcorrRunMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, FLATCORRRUN_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from flatcorrRun");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool flatcorrRunPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = flatcorrRunMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            FLATCORRRUN_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool flatcorrRunPrintObject(FILE *stream, flatcorrRunRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = flatcorrRunMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void flatcorrExpRowFree(flatcorrExpRow *object);

flatcorrExpRow *flatcorrExpRowAlloc(psS64 corr_id, psS64 chip_id)
{
    flatcorrExpRow  *_object;

    _object = psAlloc(sizeof(flatcorrExpRow));
    psMemSetDeallocator(_object, (psFreeFunc)flatcorrExpRowFree);

    _object->corr_id = corr_id;
    _object->chip_id = chip_id;

    return _object;
}

static void flatcorrExpRowFree(flatcorrExpRow *object)
{
}

bool flatcorrExpCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "corr_id", PS_DATA_S64, "Primary Key fkey(corr_id) ref flatcorrRun(corr_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item corr_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, "Primary Key fkey(chip_id) ref chipRun(chip_id)", 64)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, FLATCORREXP_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool flatcorrExpDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, FLATCORREXP_TABLE_NAME);
}

bool flatcorrExpInsert(psDB * dbh, psS64 corr_id, psS64 chip_id)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "corr_id", PS_DATA_S64, NULL, corr_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item corr_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, NULL, chip_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, FLATCORREXP_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long flatcorrExpDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, FLATCORREXP_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from flatcorrExp");
        return count;

        deleted += count;
    }

    return deleted;
}
bool flatcorrExpInsertObject(psDB *dbh, flatcorrExpRow *object)
{
    return flatcorrExpInsert(dbh, object->corr_id, object->chip_id);
}

bool flatcorrExpInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!flatcorrExpInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool flatcorrExpInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  FLATCORREXP_TABLE_NAME
    if (!psFitsMoveExtName(fits, FLATCORREXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", FLATCORREXP_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, FLATCORREXP_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool flatcorrExpSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, FLATCORREXP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, FLATCORREXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *flatcorrExpMetadataFromObject(const flatcorrExpRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "corr_id", PS_DATA_S64, NULL, object->corr_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item corr_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "chip_id", PS_DATA_S64, NULL, object->chip_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item chip_id");
        psFree(md);
        return false;
    }


    return md;
}

flatcorrExpRow *flatcorrExpObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 corr_id = psMetadataLookupS64(&status, md, "corr_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item corr_id");
        return false;
    }
    psS64 chip_id = psMetadataLookupS64(&status, md, "chip_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item chip_id");
        return false;
    }

    return flatcorrExpRowAlloc(corr_id, chip_id);
}
psArray *flatcorrExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, FLATCORREXP_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        flatcorrExpRow *object = flatcorrExpObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool flatcorrExpDeleteObject(psDB *dbh, const flatcorrExpRow *object)
{
    psMetadata *where = flatcorrExpMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, FLATCORREXP_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from flatcorrExp");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "flatcorrExpRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long flatcorrExpDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        flatcorrExpRow *object = objects->data[i];
        psMetadata *where = flatcorrExpMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, FLATCORREXP_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from flatcorrExp");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool flatcorrExpPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = flatcorrExpMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            FLATCORREXP_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool flatcorrExpPrintObject(FILE *stream, flatcorrExpRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = flatcorrExpMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void pstampDataStoreRowFree(pstampDataStoreRow *object);

pstampDataStoreRow *pstampDataStoreRowAlloc(psS64 ds_id, const char *state, const char *lastFileset, const char *outProduct, const char *uri)
{
    pstampDataStoreRow *_object;

    _object = psAlloc(sizeof(pstampDataStoreRow));
    psMemSetDeallocator(_object, (psFreeFunc)pstampDataStoreRowFree);

    _object->ds_id = ds_id;
    _object->state = psStringCopy(state);
    _object->lastFileset = psStringCopy(lastFileset);
    _object->outProduct = psStringCopy(outProduct);
    _object->uri = psStringCopy(uri);

    return _object;
}

static void pstampDataStoreRowFree(pstampDataStoreRow *object)
{
    psFree(object->state);
    psFree(object->lastFileset);
    psFree(object->outProduct);
    psFree(object->uri);
}

bool pstampDataStoreCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ds_id", PS_DATA_S64, "Primary Key AUTO_INCREMENT", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ds_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "lastFileset", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item lastFileset");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "outProduct", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item outProduct");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, PSTAMPDATASTORE_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool pstampDataStoreDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, PSTAMPDATASTORE_TABLE_NAME);
}

bool pstampDataStoreInsert(psDB * dbh, psS64 ds_id, const char *state, const char *lastFileset, const char *outProduct, const char *uri)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ds_id", PS_DATA_S64, NULL, ds_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ds_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "lastFileset", PS_DATA_STRING, NULL, lastFileset)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item lastFileset");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "outProduct", PS_DATA_STRING, NULL, outProduct)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item outProduct");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, PSTAMPDATASTORE_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long pstampDataStoreDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, PSTAMPDATASTORE_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from pstampDataStore");
        return count;

        deleted += count;
    }

    return deleted;
}
bool pstampDataStoreInsertObject(psDB *dbh, pstampDataStoreRow *object)
{
    return pstampDataStoreInsert(dbh, object->ds_id, object->state, object->lastFileset, object->outProduct, object->uri);
}

bool pstampDataStoreInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!pstampDataStoreInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool pstampDataStoreInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  PSTAMPDATASTORE_TABLE_NAME
    if (!psFitsMoveExtName(fits, PSTAMPDATASTORE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", PSTAMPDATASTORE_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, PSTAMPDATASTORE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool pstampDataStoreSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, PSTAMPDATASTORE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, PSTAMPDATASTORE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *pstampDataStoreMetadataFromObject(const pstampDataStoreRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ds_id", PS_DATA_S64, NULL, object->ds_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ds_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, object->state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "lastFileset", PS_DATA_STRING, NULL, object->lastFileset)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item lastFileset");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "outProduct", PS_DATA_STRING, NULL, object->outProduct)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item outProduct");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }


    return md;
}

pstampDataStoreRow *pstampDataStoreObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 ds_id = psMetadataLookupS64(&status, md, "ds_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ds_id");
        return false;
    }
    char* state = psMetadataLookupPtr(&status, md, "state");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item state");
        return false;
    }
    char* lastFileset = psMetadataLookupPtr(&status, md, "lastFileset");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item lastFileset");
        return false;
    }
    char* outProduct = psMetadataLookupPtr(&status, md, "outProduct");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item outProduct");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }

    return pstampDataStoreRowAlloc(ds_id, state, lastFileset, outProduct, uri);
}
psArray *pstampDataStoreSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, PSTAMPDATASTORE_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        pstampDataStoreRow *object = pstampDataStoreObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool pstampDataStoreDeleteObject(psDB *dbh, const pstampDataStoreRow *object)
{
    psMetadata *where = pstampDataStoreMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, PSTAMPDATASTORE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from pstampDataStore");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "pstampDataStoreRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long pstampDataStoreDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        pstampDataStoreRow *object = objects->data[i];
        psMetadata *where = pstampDataStoreMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, PSTAMPDATASTORE_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from pstampDataStore");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool pstampDataStorePrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = pstampDataStoreMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            PSTAMPDATASTORE_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool pstampDataStorePrintObject(FILE *stream, pstampDataStoreRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = pstampDataStoreMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void pstampRequestRowFree(pstampRequestRow *object);

pstampRequestRow *pstampRequestRowAlloc(psS64 req_id, psS64 ds_id, const char *state, const char *name, const char *reqType, const char *outProduct, const char *uri, psS32 fault)
{
    pstampRequestRow *_object;

    _object = psAlloc(sizeof(pstampRequestRow));
    psMemSetDeallocator(_object, (psFreeFunc)pstampRequestRowFree);

    _object->req_id = req_id;
    _object->ds_id = ds_id;
    _object->state = psStringCopy(state);
    _object->name = psStringCopy(name);
    _object->reqType = psStringCopy(reqType);
    _object->outProduct = psStringCopy(outProduct);
    _object->uri = psStringCopy(uri);
    _object->fault = fault;

    return _object;
}

static void pstampRequestRowFree(pstampRequestRow *object)
{
    psFree(object->state);
    psFree(object->name);
    psFree(object->reqType);
    psFree(object->outProduct);
    psFree(object->uri);
}

bool pstampRequestCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "req_id", PS_DATA_S64, "Primary Key AUTO_INCREMENT", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item req_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ds_id", PS_DATA_S64, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ds_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "name", PS_DATA_STRING, "UNIQUE", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item name");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "reqType", PS_DATA_STRING, NULL, "16")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item reqType");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "outProduct", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item outProduct");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, PSTAMPREQUEST_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool pstampRequestDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, PSTAMPREQUEST_TABLE_NAME);
}

bool pstampRequestInsert(psDB * dbh, psS64 req_id, psS64 ds_id, const char *state, const char *name, const char *reqType, const char *outProduct, const char *uri, psS32 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "req_id", PS_DATA_S64, NULL, req_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item req_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ds_id", PS_DATA_S64, NULL, ds_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ds_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "name", PS_DATA_STRING, NULL, name)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item name");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "reqType", PS_DATA_STRING, NULL, reqType)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item reqType");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "outProduct", PS_DATA_STRING, NULL, outProduct)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item outProduct");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S32, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, PSTAMPREQUEST_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long pstampRequestDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, PSTAMPREQUEST_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from pstampRequest");
        return count;

        deleted += count;
    }

    return deleted;
}
bool pstampRequestInsertObject(psDB *dbh, pstampRequestRow *object)
{
    return pstampRequestInsert(dbh, object->req_id, object->ds_id, object->state, object->name, object->reqType, object->outProduct, object->uri, object->fault);
}

bool pstampRequestInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!pstampRequestInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool pstampRequestInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  PSTAMPREQUEST_TABLE_NAME
    if (!psFitsMoveExtName(fits, PSTAMPREQUEST_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", PSTAMPREQUEST_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, PSTAMPREQUEST_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool pstampRequestSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, PSTAMPREQUEST_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, PSTAMPREQUEST_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *pstampRequestMetadataFromObject(const pstampRequestRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "req_id", PS_DATA_S64, NULL, object->req_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item req_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ds_id", PS_DATA_S64, NULL, object->ds_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ds_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, object->state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "name", PS_DATA_STRING, NULL, object->name)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item name");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "reqType", PS_DATA_STRING, NULL, object->reqType)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item reqType");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "outProduct", PS_DATA_STRING, NULL, object->outProduct)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item outProduct");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S32, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

pstampRequestRow *pstampRequestObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 req_id = psMetadataLookupS64(&status, md, "req_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item req_id");
        return false;
    }
    psS64 ds_id = psMetadataLookupS64(&status, md, "ds_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ds_id");
        return false;
    }
    char* state = psMetadataLookupPtr(&status, md, "state");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item state");
        return false;
    }
    char* name = psMetadataLookupPtr(&status, md, "name");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item name");
        return false;
    }
    char* reqType = psMetadataLookupPtr(&status, md, "reqType");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item reqType");
        return false;
    }
    char* outProduct = psMetadataLookupPtr(&status, md, "outProduct");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item outProduct");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    psS32 fault = psMetadataLookupS32(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return pstampRequestRowAlloc(req_id, ds_id, state, name, reqType, outProduct, uri, fault);
}
psArray *pstampRequestSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, PSTAMPREQUEST_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        pstampRequestRow *object = pstampRequestObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool pstampRequestDeleteObject(psDB *dbh, const pstampRequestRow *object)
{
    psMetadata *where = pstampRequestMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, PSTAMPREQUEST_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from pstampRequest");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "pstampRequestRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long pstampRequestDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        pstampRequestRow *object = objects->data[i];
        psMetadata *where = pstampRequestMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, PSTAMPREQUEST_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from pstampRequest");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool pstampRequestPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = pstampRequestMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            PSTAMPREQUEST_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool pstampRequestPrintObject(FILE *stream, pstampRequestRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = pstampRequestMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
static void pstampJobRowFree(pstampJobRow *object);

pstampJobRow *pstampJobRowAlloc(psS64 job_id, psS64 req_id, const char *rownum, const char *state, const char *jobType, psS32 fault, const char *uri, const char *outputBase, const char *args)
{
    pstampJobRow    *_object;

    _object = psAlloc(sizeof(pstampJobRow));
    psMemSetDeallocator(_object, (psFreeFunc)pstampJobRowFree);

    _object->job_id = job_id;
    _object->req_id = req_id;
    _object->rownum = psStringCopy(rownum);
    _object->state = psStringCopy(state);
    _object->jobType = psStringCopy(jobType);
    _object->fault = fault;
    _object->uri = psStringCopy(uri);
    _object->outputBase = psStringCopy(outputBase);
    _object->args = psStringCopy(args);

    return _object;
}

static void pstampJobRowFree(pstampJobRow *object)
{
    psFree(object->rownum);
    psFree(object->state);
    psFree(object->jobType);
    psFree(object->uri);
    psFree(object->outputBase);
    psFree(object->args);
}

bool pstampJobCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "job_id", PS_DATA_S64, "Primary Key AUTO_INCREMENT", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item job_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "req_id", PS_DATA_S64, "Primary Key fkey(req_id) ref pstampRequest(req_id)", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item req_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "rownum", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item rownum");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "jobType", PS_DATA_STRING, NULL, "16")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item jobType");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "outputBase", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item outputBase");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "args", PS_DATA_STRING, NULL, "511")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item args");
        psFree(md);
        return false;
    }

    bool status = psDBCreateTable(dbh, PSTAMPJOB_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool pstampJobDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, PSTAMPJOB_TABLE_NAME);
}

bool pstampJobInsert(psDB * dbh, psS64 job_id, psS64 req_id, const char *rownum, const char *state, const char *jobType, psS32 fault, const char *uri, const char *outputBase, const char *args)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "job_id", PS_DATA_S64, NULL, job_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item job_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "req_id", PS_DATA_S64, NULL, req_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item req_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "rownum", PS_DATA_STRING, NULL, rownum)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item rownum");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "jobType", PS_DATA_STRING, NULL, jobType)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item jobType");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S32, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "outputBase", PS_DATA_STRING, NULL, outputBase)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item outputBase");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "args", PS_DATA_STRING, NULL, args)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item args");
        psFree(md);
        return false;
    }

    bool status = psDBInsertOneRow(dbh, PSTAMPJOB_TABLE_NAME, md);
    psFree(md);

    return status;
}

long long pstampJobDelete(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    long long       deleted = 0;

    long long count = psDBDeleteRows(dbh, PSTAMPJOB_TABLE_NAME, where, limit);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from pstampJob");
        return count;

        deleted += count;
    }

    return deleted;
}
bool pstampJobInsertObject(psDB *dbh, pstampJobRow *object)
{
    return pstampJobInsert(dbh, object->job_id, object->req_id, object->rownum, object->state, object->jobType, object->fault, object->uri, object->outputBase, object->args);
}

bool pstampJobInsertObjects(psDB *dbh, psArray *objects)
{
    for (long i = 0; i < psArrayLength(objects); i++) {
        if (!pstampJobInsertObject(dbh, objects->data[i])) {
            return false;
        }
    }

    return true;
}

bool pstampJobInsertFits(psDB *dbh, const psFits *fits)
{
    psArray         *rowSet;

    // move to (the first?) extension named  PSTAMPJOB_TABLE_NAME
    if (!psFitsMoveExtName(fits, PSTAMPJOB_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", PSTAMPJOB_TABLE_NAME);
        return false;
    }

    // check HDU type
    if (psFitsGetExtType(fits) != PS_FITS_TYPE_BINARY_TABLE)  {
        psError(PS_ERR_UNKNOWN, true, "FITS HDU type is not PS_FITS_TYPE_BINARY_TABLE");
        return false;
    }

    // read fits table
    rowSet = psFitsReadTable(fits);
    if (!rowSet) {
        psError(PS_ERR_UNKNOWN, true, "FITS read error or FITS table is empty");
        psFree(rowSet);
        return false;
    }

    if (!psDBInsertRows(dbh, PSTAMPJOB_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

bool pstampJobSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;

    rowSet = psDBSelectRows(dbh, PSTAMPJOB_TABLE_NAME, where, limit);
    if (!rowSet) {
        return false;
    }

    // output to fits
    if (!psFitsWriteTable(fits, NULL, rowSet, PSTAMPJOB_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, false, "FITS table write failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

psMetadata *pstampJobMetadataFromObject(const pstampJobRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "job_id", PS_DATA_S64, NULL, object->job_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item job_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "req_id", PS_DATA_S64, NULL, object->req_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item req_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "rownum", PS_DATA_STRING, NULL, object->rownum)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item rownum");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, NULL, object->state)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "jobType", PS_DATA_STRING, NULL, object->jobType)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item jobType");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S32, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "outputBase", PS_DATA_STRING, NULL, object->outputBase)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item outputBase");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "args", PS_DATA_STRING, NULL, object->args)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item args");
        psFree(md);
        return false;
    }


    return md;
}

pstampJobRow *pstampJobObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS64 job_id = psMetadataLookupS64(&status, md, "job_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item job_id");
        return false;
    }
    psS64 req_id = psMetadataLookupS64(&status, md, "req_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item req_id");
        return false;
    }
    char* rownum = psMetadataLookupPtr(&status, md, "rownum");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item rownum");
        return false;
    }
    char* state = psMetadataLookupPtr(&status, md, "state");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item state");
        return false;
    }
    char* jobType = psMetadataLookupPtr(&status, md, "jobType");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item jobType");
        return false;
    }
    psS32 fault = psMetadataLookupS32(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    char* outputBase = psMetadataLookupPtr(&status, md, "outputBase");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item outputBase");
        return false;
    }
    char* args = psMetadataLookupPtr(&status, md, "args");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item args");
        return false;
    }

    return pstampJobRowAlloc(job_id, req_id, rownum, state, jobType, fault, uri, outputBase, args);
}
psArray *pstampJobSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, PSTAMPJOB_TABLE_NAME, where, limit);
    if (!rowSet) {
        return NULL;
    }

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        pstampJobRow *object = pstampJobObjectFromMetadata(rowSet->data[i]);
        if (!object) {
            psFree(object);
            psFree(returnSet);
            psError(PS_ERR_UNKNOWN, false, "database error");
            return NULL;
        }
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool pstampJobDeleteObject(psDB *dbh, const pstampJobRow *object)
{
    psMetadata *where = pstampJobMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, PSTAMPJOB_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from pstampJob");
        return false;
    }
    if (count > 1) {
        // XXX should this be a psAbort() instead?  It is possible that
        // having an object match multiple rows was by design.
        psError(PS_ERR_UNKNOWN, true, "pstampJobRow object matched more then one row.  Check your database schema");
        return false;
    }

    return true;
}
long long pstampJobDeleteRowObjects(psDB *dbh, const psArray *objects, unsigned long long limit)
{
    long long       deleted = 0;

    for (long long i = 0; i < objects->n; i++) {
        pstampJobRow *object = objects->data[i];
        psMetadata *where = pstampJobMetadataFromObject(object);
        long long count = psDBDeleteRows(dbh, PSTAMPJOB_TABLE_NAME, where, limit);
        psFree(where);
        if (count < 0) {
            psError(PS_ERR_UNKNOWN, true, "failed to delete row from pstampJob");
            return count;
        }

        deleted += count;
    }

    return deleted;
}
bool pstampJobPrintObjects(FILE *stream, psArray *objects, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(objects, false);

    psMetadata *output = psMetadataAlloc();
    for (long i = 0; i < psArrayLength(objects); i++) {
        psMetadata *md = pstampJobMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            PSTAMPJOB_TABLE_NAME,
            PS_META_DUPLICATE_OK,
            NULL,
            md
        )) {
            psError(PS_ERR_UNKNOWN, false, "failed to add metadata");
            psFree(md);
            psFree(output);
            return false;
        }
        psFree(md);
    }

    if (!ippdbPrintMetadataRaw(stream, output, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(output);
    }
    psFree(output);

    return true;
}
bool pstampJobPrintObject(FILE *stream, pstampJobRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = pstampJobMetadataFromObject(object);

    if (!ippdbPrintMetadataRaw(stream, md, mdcf)) {
        psError(PS_ERR_UNKNOWN, false, "failed to print metadata");
        psFree(md);
    }

    psFree(md);

    return true;
}
