/*
 * 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 0.36
 *
 * Do NOT directly edit this file.
 *
 */

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

#include "ippdb.h"

#define EXPTAGCOUNTER_TABLE_NAME "expTagCounter"
#define SUMMITEXP_TABLE_NAME "summitExp"
#define SUMMITIMFILE_TABLE_NAME "summitImfile"
#define PZPENDINGEXP_TABLE_NAME "pzPendingExp"
#define PZPENDINGIMFILE_TABLE_NAME "pzPendingImfile"
#define PZDONEEXP_TABLE_NAME "pzDoneExp"
#define PZDONEIMFILE_TABLE_NAME "pzDoneImfile"
#define NEWEXP_TABLE_NAME "newExp"
#define NEWIMFILE_TABLE_NAME "newImfile"
#define RAWEXP_TABLE_NAME "rawExp"
#define RAWIMFILE_TABLE_NAME "rawImfile"
#define P1PENDINGEXP_TABLE_NAME "p1PendingExp"
#define P2PENDINGEXP_TABLE_NAME "p2PendingExp"
#define P2PENDINGIMFILE_TABLE_NAME "p2PendingImfile"
#define P2PROCESSEDEXP_TABLE_NAME "p2ProcessedExp"
#define P2MASK_TABLE_NAME "p2Mask"
#define P2PROCESSEDIMFILE_TABLE_NAME "p2ProcessedImfile"
#define P3PENDINGEXP_TABLE_NAME "p3PendingExp"
#define P3PROCESSEDEXP_TABLE_NAME "p3ProcessedExp"
#define P3MASK_TABLE_NAME "p3Mask"
#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 P4ARUN_TABLE_NAME "p4aRun"
#define P4AINPUTEXP_TABLE_NAME "p4aInputExp"
#define P4ASCFILE_TABLE_NAME "p4aScfile"
#define P4BRUN_TABLE_NAME "p4bRun"
#define P4BINPUTSCFILE_TABLE_NAME "p4bInputScfile"
#define P4BDIFFSCFILE_TABLE_NAME "p4bDiffScfile"
#define P4CRUN_TABLE_NAME "p4cRun"
#define P4CINPUTEXP_TABLE_NAME "p4cInputExp"
#define P4MAGICMASKIMFILE_TABLE_NAME "p4MagicMaskImfile"
#define SKYCELL_TABLE_NAME "skyCell"
#define SKYCELLMAP_TABLE_NAME "skyCellMap"
#define MAX_STRING_LENGTH 1024

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

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

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

    psMetadata *clean = psMetadataCopy(NULL, md);

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

    return true;
}

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

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

        return true;
    }

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

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

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

    return true;
}

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

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

        psFree(md);
    }

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

    psFree(output);

    return true;
}

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

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

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

    psFree(output);

    return true;
}

static void expTagCounterRowFree(expTagCounterRow *object);

expTagCounterRow *expTagCounterRowAlloc(psU64 counter)
{
    expTagCounterRow *_object;

    _object = psAlloc(sizeof(expTagCounterRow));
    psMemSetDeallocator(_object, (psFreeFunc)expTagCounterRowFree);

    _object->counter = counter;

    return _object;
}

static void expTagCounterRowFree(expTagCounterRow *object)
{
}

bool expTagCounterCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "counter", PS_DATA_U64, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item counter");
        psFree(md);
        return false;
    }

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

    psFree(md);

    return status;
}

bool expTagCounterDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, EXPTAGCOUNTER_TABLE_NAME);
}

bool expTagCounterInsert(psDB * dbh, psU64 counter)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "counter", PS_DATA_U64, NULL, counter)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item counter");
        psFree(md);
        return false;
    }

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool expTagCounterInsertObject(psDB *dbh, expTagCounterRow *object)
{
    return expTagCounterInsert(dbh, object->counter);
}

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

    return true;
}

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

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

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

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

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

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *expTagCounterMetadataFromObject(const expTagCounterRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "counter", PS_DATA_U64, NULL, object->counter)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item counter");
        psFree(md);
        return false;
    }


    return md;
}

expTagCounterRow *expTagCounterObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psU64 counter = psMetadataLookupU64(&status, md, "counter");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item counter");
        return false;
    }

    return expTagCounterRowAlloc(counter);
}
psArray *expTagCounterSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

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

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        expTagCounterRow *object = expTagCounterObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

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

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

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

        deleted += count;
    }

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

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

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

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

    psMetadata *md = expTagCounterMetadataFromObject(object);

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

    psFree(md);

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

summitExpRow *summitExpRowAlloc(const char *exp_id, const char *camera, const char *telescope, psTime* dateobs, const char *exp_type, const char *uri, psS32 imfiles)
{
    summitExpRow    *_object;

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

    _object->exp_id = psStringCopy(exp_id);
    _object->camera = psStringCopy(camera);
    _object->telescope = psStringCopy(telescope);
    _object->dateobs = psTimeCopy(dateobs);
    _object->exp_type = psStringCopy(exp_type);
    _object->uri = psStringCopy(uri);
    _object->imfiles = imfiles;

    return _object;
}

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

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

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

    psFree(md);

    return status;
}

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

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

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

    return status;
}

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

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

        deleted += count;
    }

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

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

    return true;
}

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

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

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

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

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

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

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


    return md;
}

summitExpRow *summitExpObjectFromMetadata(psMetadata *md)
{

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

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

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

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        summitExpRow *object = summitExpObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

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

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

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

        deleted += count;
    }

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

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

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

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

    psMetadata *md = summitExpMetadataFromObject(object);

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

    psFree(md);

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

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

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

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

    return _object;
}

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

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

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

    psFree(md);

    return status;
}

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

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

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

    return status;
}

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

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

        deleted += count;
    }

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

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

    return true;
}

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

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

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

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

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

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

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


    return md;
}

summitImfileRow *summitImfileObjectFromMetadata(psMetadata *md)
{

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

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

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

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        summitImfileRow *object = summitImfileObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

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

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

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

        deleted += count;
    }

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

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

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

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

    psMetadata *md = summitImfileMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void pzPendingExpRowFree(pzPendingExpRow *object);

pzPendingExpRow *pzPendingExpRowAlloc(const char *exp_id, const char *camera, const char *telescope)
{
    pzPendingExpRow *_object;

    _object = psAlloc(sizeof(pzPendingExpRow));
    psMemSetDeallocator(_object, (psFreeFunc)pzPendingExpRowFree);

    _object->exp_id = psStringCopy(exp_id);
    _object->camera = psStringCopy(camera);
    _object->telescope = psStringCopy(telescope);

    return _object;
}

static void pzPendingExpRowFree(pzPendingExpRow *object)
{
    psFree(object->exp_id);
    psFree(object->camera);
    psFree(object->telescope);
}

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

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

    psFree(md);

    return status;
}

bool pzPendingExpDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, PZPENDINGEXP_TABLE_NAME);
}

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

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool pzPendingExpInsertObject(psDB *dbh, pzPendingExpRow *object)
{
    return pzPendingExpInsert(dbh, object->exp_id, object->camera, object->telescope);
}

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

    return true;
}

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

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

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

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

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

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

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


    return md;
}

pzPendingExpRow *pzPendingExpObjectFromMetadata(psMetadata *md)
{

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

    return pzPendingExpRowAlloc(exp_id, camera, telescope);
}
psArray *pzPendingExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

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

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        pzPendingExpRow *object = pzPendingExpObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

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

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

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

        deleted += count;
    }

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

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

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

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

    psMetadata *md = pzPendingExpMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void pzPendingImfileRowFree(pzPendingImfileRow *object);

pzPendingImfileRow *pzPendingImfileRowAlloc(const char *exp_id, const char *camera, const char *telescope, const char *class, const char *class_id, const char *exp_tag)
{
    pzPendingImfileRow *_object;

    _object = psAlloc(sizeof(pzPendingImfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)pzPendingImfileRowFree);

    _object->exp_id = psStringCopy(exp_id);
    _object->camera = psStringCopy(camera);
    _object->telescope = psStringCopy(telescope);
    _object->class = psStringCopy(class);
    _object->class_id = psStringCopy(class_id);
    _object->exp_tag = psStringCopy(exp_tag);

    return _object;
}

static void pzPendingImfileRowFree(pzPendingImfileRow *object)
{
    psFree(object->exp_id);
    psFree(object->camera);
    psFree(object->telescope);
    psFree(object->class);
    psFree(object->class_id);
    psFree(object->exp_tag);
}

bool pzPendingImfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Unique Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }

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

    psFree(md);

    return status;
}

bool pzPendingImfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, PZPENDINGIMFILE_TABLE_NAME);
}

bool pzPendingImfileInsert(psDB * dbh, const char *exp_id, const char *camera, const char *telescope, const char *class, const char *class_id, const char *exp_tag)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, NULL, exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, NULL, class)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool pzPendingImfileInsertObject(psDB *dbh, pzPendingImfileRow *object)
{
    return pzPendingImfileInsert(dbh, object->exp_id, object->camera, object->telescope, object->class, object->class_id, object->exp_tag);
}

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

    return true;
}

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

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

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

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

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

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *pzPendingImfileMetadataFromObject(const pzPendingImfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, NULL, object->exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, object->camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, object->telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, NULL, object->class)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }


    return md;
}

pzPendingImfileRow *pzPendingImfileObjectFromMetadata(psMetadata *md)
{

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

    return pzPendingImfileRowAlloc(exp_id, camera, telescope, class, class_id, exp_tag);
}
psArray *pzPendingImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

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

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        pzPendingImfileRow *object = pzPendingImfileObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

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

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

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

        deleted += count;
    }

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

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

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

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

    psMetadata *md = pzPendingImfileMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void pzDoneExpRowFree(pzDoneExpRow *object);

pzDoneExpRow *pzDoneExpRowAlloc(const char *exp_id, const char *camera, const char *telescope)
{
    pzDoneExpRow    *_object;

    _object = psAlloc(sizeof(pzDoneExpRow));
    psMemSetDeallocator(_object, (psFreeFunc)pzDoneExpRowFree);

    _object->exp_id = psStringCopy(exp_id);
    _object->camera = psStringCopy(camera);
    _object->telescope = psStringCopy(telescope);

    return _object;
}

static void pzDoneExpRowFree(pzDoneExpRow *object)
{
    psFree(object->exp_id);
    psFree(object->camera);
    psFree(object->telescope);
}

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

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

    psFree(md);

    return status;
}

bool pzDoneExpDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, PZDONEEXP_TABLE_NAME);
}

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

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool pzDoneExpInsertObject(psDB *dbh, pzDoneExpRow *object)
{
    return pzDoneExpInsert(dbh, object->exp_id, object->camera, object->telescope);
}

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

    return true;
}

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

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

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

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

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

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

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


    return md;
}

pzDoneExpRow *pzDoneExpObjectFromMetadata(psMetadata *md)
{

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

    return pzDoneExpRowAlloc(exp_id, camera, telescope);
}
psArray *pzDoneExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

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

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        pzDoneExpRow *object = pzDoneExpObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

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

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

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

        deleted += count;
    }

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

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

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

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

    psMetadata *md = pzDoneExpMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void pzDoneImfileRowFree(pzDoneImfileRow *object);

pzDoneImfileRow *pzDoneImfileRowAlloc(const char *exp_id, const char *camera, const char *telescope, const char *class, const char *class_id, const char *exp_tag, const char *uri)
{
    pzDoneImfileRow *_object;

    _object = psAlloc(sizeof(pzDoneImfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)pzDoneImfileRowFree);

    _object->exp_id = psStringCopy(exp_id);
    _object->camera = psStringCopy(camera);
    _object->telescope = psStringCopy(telescope);
    _object->class = psStringCopy(class);
    _object->class_id = psStringCopy(class_id);
    _object->exp_tag = psStringCopy(exp_tag);
    _object->uri = psStringCopy(uri);

    return _object;
}

static void pzDoneImfileRowFree(pzDoneImfileRow *object)
{
    psFree(object->exp_id);
    psFree(object->camera);
    psFree(object->telescope);
    psFree(object->class);
    psFree(object->class_id);
    psFree(object->exp_tag);
    psFree(object->uri);
}

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

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

    psFree(md);

    return status;
}

bool pzDoneImfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, PZDONEIMFILE_TABLE_NAME);
}

bool pzDoneImfileInsert(psDB * dbh, const char *exp_id, const char *camera, const char *telescope, const char *class, const char *class_id, const char *exp_tag, const char *uri)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, NULL, exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, NULL, class)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool pzDoneImfileInsertObject(psDB *dbh, pzDoneImfileRow *object)
{
    return pzDoneImfileInsert(dbh, object->exp_id, object->camera, object->telescope, object->class, object->class_id, object->exp_tag, object->uri);
}

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

    return true;
}

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

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

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

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

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

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *pzDoneImfileMetadataFromObject(const pzDoneImfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, NULL, object->exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, object->camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, object->telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, NULL, object->class)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }


    return md;
}

pzDoneImfileRow *pzDoneImfileObjectFromMetadata(psMetadata *md)
{

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

    return pzDoneImfileRowAlloc(exp_id, camera, telescope, class, class_id, exp_tag, uri);
}
psArray *pzDoneImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

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

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        pzDoneImfileRow *object = pzDoneImfileObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

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

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

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

        deleted += count;
    }

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

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

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

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

    psMetadata *md = pzDoneImfileMetadataFromObject(object);

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

    psFree(md);

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

newExpRow *newExpRowAlloc(const char *exp_tag, const char *exp_id, const char *camera, const char *telescope, psS32 imfiles, const char *workdir)
{
    newExpRow       *_object;

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

    _object->exp_tag = psStringCopy(exp_tag);
    _object->exp_id = psStringCopy(exp_id);
    _object->camera = psStringCopy(camera);
    _object->telescope = psStringCopy(telescope);
    _object->imfiles = imfiles;
    _object->workdir = psStringCopy(workdir);

    return _object;
}

static void newExpRowFree(newExpRow *object)
{
    psFree(object->exp_tag);
    psFree(object->exp_id);
    psFree(object->camera);
    psFree(object->telescope);
    psFree(object->workdir);
}

bool newExpCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "imfiles", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, "destination for output files", "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }

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

    psFree(md);

    return status;
}

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

bool newExpInsert(psDB * dbh, const char *exp_tag, const char *exp_id, const char *camera, const char *telescope, psS32 imfiles, const char *workdir)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, NULL, exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "imfiles", PS_DATA_S32, NULL, imfiles)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool newExpInsertObject(psDB *dbh, newExpRow *object)
{
    return newExpInsert(dbh, object->exp_tag, object->exp_id, object->camera, object->telescope, object->imfiles, object->workdir);
}

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

    return true;
}

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

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

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

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

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

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *newExpMetadataFromObject(const newExpRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, NULL, object->exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, object->camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, object->telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "imfiles", PS_DATA_S32, NULL, object->imfiles)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, object->workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }


    return md;
}

newExpRow *newExpObjectFromMetadata(psMetadata *md)
{

bool status = false;
    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
        return false;
    }
    char* exp_id = psMetadataLookupPtr(&status, md, "exp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
        return false;
    }
    char* camera = psMetadataLookupPtr(&status, md, "camera");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
        return false;
    }
    char* telescope = psMetadataLookupPtr(&status, md, "telescope");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
        return false;
    }
    psS32 imfiles = psMetadataLookupS32(&status, md, "imfiles");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
        return false;
    }
    char* workdir = psMetadataLookupPtr(&status, md, "workdir");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
        return false;
    }

    return newExpRowAlloc(exp_tag, exp_id, camera, telescope, imfiles, workdir);
}
psArray *newExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

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

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        newExpRow *object = newExpObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

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

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

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

        deleted += count;
    }

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

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

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

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

    psMetadata *md = newExpMetadataFromObject(object);

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

    psFree(md);

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

newImfileRow *newImfileRowAlloc(const char *exp_tag, const char *class, const char *class_id, const char *uri)
{
    newImfileRow    *_object;

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

    _object->exp_tag = psStringCopy(exp_tag);
    _object->class = psStringCopy(class);
    _object->class_id = psStringCopy(class_id);
    _object->uri = psStringCopy(uri);

    return _object;
}

static void newImfileRowFree(newImfileRow *object)
{
    psFree(object->exp_tag);
    psFree(object->class);
    psFree(object->class_id);
    psFree(object->uri);
}

bool newImfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }

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

    psFree(md);

    return status;
}

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

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

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool newImfileInsertObject(psDB *dbh, newImfileRow *object)
{
    return newImfileInsert(dbh, object->exp_tag, object->class, object->class_id, object->uri);
}

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

    return true;
}

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

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

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

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

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

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

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


    return md;
}

newImfileRow *newImfileObjectFromMetadata(psMetadata *md)
{

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

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

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

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        newImfileRow *object = newImfileObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

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

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

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

        deleted += count;
    }

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

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

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

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

    psMetadata *md = newImfileMetadataFromObject(object);

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

    psFree(md);

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

rawExpRow *rawExpRowAlloc(const char *exp_tag, const char *exp_id, const char *camera, const char *telescope, psTime* dateobs, const char *exp_type, psS32 imfiles, const char *workdir, const char *filter, psF32 airmass, psF64 ra, psF64 decl, psF32 exp_time, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 alt, psF64 az, psF32 ccd_temp, psF64 posang, const char *object, psS16 fault)
{
    rawExpRow       *_object;

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

    _object->exp_tag = psStringCopy(exp_tag);
    _object->exp_id = psStringCopy(exp_id);
    _object->camera = psStringCopy(camera);
    _object->telescope = psStringCopy(telescope);
    _object->dateobs = psTimeCopy(dateobs);
    _object->exp_type = psStringCopy(exp_type);
    _object->imfiles = imfiles;
    _object->workdir = psStringCopy(workdir);
    _object->filter = psStringCopy(filter);
    _object->airmass = airmass;
    _object->ra = ra;
    _object->decl = decl;
    _object->exp_time = exp_time;
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->bg_mean_stdev = bg_mean_stdev;
    _object->alt = alt;
    _object->az = az;
    _object->ccd_temp = ccd_temp;
    _object->posang = posang;
    _object->object = psStringCopy(object);
    _object->fault = fault;

    return _object;
}

static void rawExpRowFree(rawExpRow *object)
{
    psFree(object->exp_tag);
    psFree(object->exp_id);
    psFree(object->camera);
    psFree(object->telescope);
    psFree(object->dateobs);
    psFree(object->exp_type);
    psFree(object->workdir);
    psFree(object->filter);
    psFree(object->object);
}

bool rawExpCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dateobs", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "imfiles", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, "destination for output files", "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "alt", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item alt");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "az", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item posang");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "object", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item object");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "NOT NULL", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

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

    psFree(md);

    return status;
}

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

bool rawExpInsert(psDB * dbh, const char *exp_tag, const char *exp_id, const char *camera, const char *telescope, psTime* dateobs, const char *exp_type, psS32 imfiles, const char *workdir, const char *filter, psF32 airmass, psF64 ra, psF64 decl, psF32 exp_time, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 alt, psF64 az, psF32 ccd_temp, psF64 posang, const char *object, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, NULL, exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dateobs", PS_DATA_TIME, NULL, dateobs)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, exp_type)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "imfiles", PS_DATA_S32, NULL, imfiles)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, filter)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass", PS_DATA_F32, NULL, airmass)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra", PS_DATA_F64, NULL, ra)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl", PS_DATA_F64, NULL, decl)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time", PS_DATA_F32, NULL, exp_time)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "alt", PS_DATA_F64, NULL, alt)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item alt");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "az", PS_DATA_F64, NULL, az)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp", PS_DATA_F32, NULL, ccd_temp)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang", PS_DATA_F64, NULL, posang)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item posang");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "object", PS_DATA_STRING, NULL, object)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item object");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool rawExpInsertObject(psDB *dbh, rawExpRow *object)
{
    return rawExpInsert(dbh, object->exp_tag, object->exp_id, object->camera, object->telescope, object->dateobs, object->exp_type, object->imfiles, object->workdir, object->filter, object->airmass, object->ra, object->decl, object->exp_time, object->bg, object->bg_stdev, object->bg_mean_stdev, object->alt, object->az, object->ccd_temp, object->posang, object->object, object->fault);
}

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

    return true;
}

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

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

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

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

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

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *rawExpMetadataFromObject(const rawExpRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_id", PS_DATA_STRING, NULL, object->exp_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, object->camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, object->telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dateobs", PS_DATA_TIME, NULL, object->dateobs)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, object->exp_type)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "imfiles", PS_DATA_S32, NULL, object->imfiles)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item imfiles");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, NULL, object->workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, object->filter)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass", PS_DATA_F32, NULL, object->airmass)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra", PS_DATA_F64, NULL, object->ra)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl", PS_DATA_F64, NULL, object->decl)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time", PS_DATA_F32, NULL, object->exp_time)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "alt", PS_DATA_F64, NULL, object->alt)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item alt");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "az", PS_DATA_F64, NULL, object->az)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp", PS_DATA_F32, NULL, object->ccd_temp)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang", PS_DATA_F64, NULL, object->posang)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item posang");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "object", PS_DATA_STRING, NULL, object->object)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item object");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

rawExpRow *rawExpObjectFromMetadata(psMetadata *md)
{

bool status = false;
    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
        return false;
    }
    char* exp_id = psMetadataLookupPtr(&status, md, "exp_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_id");
        return false;
    }
    char* camera = psMetadataLookupPtr(&status, md, "camera");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
        return false;
    }
    char* telescope = psMetadataLookupPtr(&status, md, "telescope");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
        return false;
    }
    psTime* dateobs = psMetadataLookupPtr(&status, md, "dateobs");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dateobs");
        return false;
    }
    char* exp_type = psMetadataLookupPtr(&status, md, "exp_type");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
        return false;
    }
    psS32 imfiles = psMetadataLookupS32(&status, md, "imfiles");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item imfiles");
        return false;
    }
    char* workdir = psMetadataLookupPtr(&status, md, "workdir");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
        return false;
    }
    char* filter = psMetadataLookupPtr(&status, md, "filter");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
        return false;
    }
    psF32 airmass = psMetadataLookupF32(&status, md, "airmass");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass");
        return false;
    }
    psF64 ra = psMetadataLookupF64(&status, md, "ra");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
        return false;
    }
    psF64 decl = psMetadataLookupF64(&status, md, "decl");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
        return false;
    }
    psF32 exp_time = psMetadataLookupF32(&status, md, "exp_time");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
        return false;
    }
    psF64 alt = psMetadataLookupF64(&status, md, "alt");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item alt");
        return false;
    }
    psF64 az = psMetadataLookupF64(&status, md, "az");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item az");
        return false;
    }
    psF32 ccd_temp = psMetadataLookupF32(&status, md, "ccd_temp");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ccd_temp");
        return false;
    }
    psF64 posang = psMetadataLookupF64(&status, md, "posang");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item posang");
        return false;
    }
    char* object = psMetadataLookupPtr(&status, md, "object");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item object");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return rawExpRowAlloc(exp_tag, exp_id, camera, telescope, dateobs, exp_type, imfiles, workdir, filter, airmass, ra, decl, exp_time, bg, bg_stdev, bg_mean_stdev, alt, az, ccd_temp, posang, object, fault);
}
psArray *rawExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

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

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        rawExpRow *object = rawExpObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

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

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

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

        deleted += count;
    }

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

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

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

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

    psMetadata *md = rawExpMetadataFromObject(object);

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

    psFree(md);

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

rawImfileRow *rawImfileRowAlloc(const char *exp_tag, const char *class, const char *class_id, const char *uri, const char *exp_type, const char *filter, psF32 airmass, psF64 ra, psF64 decl, psF32 exp_time, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 alt, psF64 az, psF32 ccd_temp, psF64 posang, const char *object, psTime* dateobs, psS16 fault)
{
    rawImfileRow    *_object;

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

    _object->exp_tag = psStringCopy(exp_tag);
    _object->class = psStringCopy(class);
    _object->class_id = psStringCopy(class_id);
    _object->uri = psStringCopy(uri);
    _object->exp_type = psStringCopy(exp_type);
    _object->filter = psStringCopy(filter);
    _object->airmass = airmass;
    _object->ra = ra;
    _object->decl = decl;
    _object->exp_time = exp_time;
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->bg_mean_stdev = bg_mean_stdev;
    _object->alt = alt;
    _object->az = az;
    _object->ccd_temp = ccd_temp;
    _object->posang = posang;
    _object->object = psStringCopy(object);
    _object->dateobs = psTimeCopy(dateobs);
    _object->fault = fault;

    return _object;
}

static void rawImfileRowFree(rawImfileRow *object)
{
    psFree(object->exp_tag);
    psFree(object->class);
    psFree(object->class_id);
    psFree(object->uri);
    psFree(object->exp_type);
    psFree(object->filter);
    psFree(object->object);
    psFree(object->dateobs);
}

bool rawImfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", PS_DATA_STRING, NULL, "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, "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, "filter", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "alt", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item alt");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "az", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item posang");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "object", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item object");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dateobs", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "NOT NULL", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

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

    psFree(md);

    return status;
}

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

bool rawImfileInsert(psDB * dbh, const char *exp_tag, const char *class, const char *class_id, const char *uri, const char *exp_type, const char *filter, psF32 airmass, psF64 ra, psF64 decl, psF32 exp_time, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF64 alt, psF64 az, psF32 ccd_temp, psF64 posang, const char *object, psTime* dateobs, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", 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, "exp_type", PS_DATA_STRING, NULL, exp_type)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, filter)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass", PS_DATA_F32, NULL, airmass)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra", PS_DATA_F64, NULL, ra)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl", PS_DATA_F64, NULL, decl)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time", PS_DATA_F32, NULL, exp_time)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "alt", PS_DATA_F64, NULL, alt)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item alt");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "az", PS_DATA_F64, NULL, az)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp", PS_DATA_F32, NULL, ccd_temp)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang", PS_DATA_F64, NULL, posang)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item posang");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "object", PS_DATA_STRING, NULL, object)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item object");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dateobs", PS_DATA_TIME, NULL, dateobs)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool rawImfileInsertObject(psDB *dbh, rawImfileRow *object)
{
    return rawImfileInsert(dbh, object->exp_tag, object->class, object->class_id, object->uri, object->exp_type, object->filter, object->airmass, object->ra, object->decl, object->exp_time, object->bg, object->bg_stdev, object->bg_mean_stdev, object->alt, object->az, object->ccd_temp, object->posang, object->object, object->dateobs, object->fault);
}

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

    return true;
}

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

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

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

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

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

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *rawImfileMetadataFromObject(const rawImfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class", 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, "exp_type", PS_DATA_STRING, NULL, object->exp_type)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, object->filter)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass", PS_DATA_F32, NULL, object->airmass)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra", PS_DATA_F64, NULL, object->ra)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl", PS_DATA_F64, NULL, object->decl)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item decl");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time", PS_DATA_F32, NULL, object->exp_time)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "alt", PS_DATA_F64, NULL, object->alt)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item alt");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "az", PS_DATA_F64, NULL, object->az)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item az");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp", PS_DATA_F32, NULL, object->ccd_temp)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang", PS_DATA_F64, NULL, object->posang)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item posang");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "object", PS_DATA_STRING, NULL, object->object)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item object");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "dateobs", PS_DATA_TIME, NULL, object->dateobs)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item dateobs");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

rawImfileRow *rawImfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
        return false;
    }
    char* class = 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;
    }
    char* exp_type = psMetadataLookupPtr(&status, md, "exp_type");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
        return false;
    }
    char* filter = psMetadataLookupPtr(&status, md, "filter");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
        return false;
    }
    psF32 airmass = psMetadataLookupF32(&status, md, "airmass");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass");
        return false;
    }
    psF64 ra = psMetadataLookupF64(&status, md, "ra");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra");
        return false;
    }
    psF64 decl = psMetadataLookupF64(&status, md, "decl");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl");
        return false;
    }
    psF32 exp_time = psMetadataLookupF32(&status, md, "exp_time");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
        return false;
    }
    psF64 alt = psMetadataLookupF64(&status, md, "alt");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item alt");
        return false;
    }
    psF64 az = psMetadataLookupF64(&status, md, "az");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item az");
        return false;
    }
    psF32 ccd_temp = psMetadataLookupF32(&status, md, "ccd_temp");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ccd_temp");
        return false;
    }
    psF64 posang = psMetadataLookupF64(&status, md, "posang");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item posang");
        return false;
    }
    char* object = psMetadataLookupPtr(&status, md, "object");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item object");
        return false;
    }
    psTime* dateobs = psMetadataLookupPtr(&status, md, "dateobs");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item dateobs");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return rawImfileRowAlloc(exp_tag, class, class_id, uri, exp_type, filter, airmass, ra, decl, exp_time, bg, bg_stdev, bg_mean_stdev, alt, az, ccd_temp, posang, object, dateobs, fault);
}
psArray *rawImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

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

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        rawImfileRow *object = rawImfileObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

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

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

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

        deleted += count;
    }

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

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

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

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

    psMetadata *md = rawImfileMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void p1PendingExpRowFree(p1PendingExpRow *object);

p1PendingExpRow *p1PendingExpRowAlloc(const char *exp_tag, const char *recipe, psS32 p1_version)
{
    p1PendingExpRow *_object;

    _object = psAlloc(sizeof(p1PendingExpRow));
    psMemSetDeallocator(_object, (psFreeFunc)p1PendingExpRowFree);

    _object->exp_tag = psStringCopy(exp_tag);
    _object->recipe = psStringCopy(recipe);
    _object->p1_version = p1_version;

    return _object;
}

static void p1PendingExpRowFree(p1PendingExpRow *object)
{
    psFree(object->exp_tag);
    psFree(object->recipe);
}

bool p1PendingExpCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "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, "p1_version", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
        psFree(md);
        return false;
    }

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

    psFree(md);

    return status;
}

bool p1PendingExpDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, P1PENDINGEXP_TABLE_NAME);
}

bool p1PendingExpInsert(psDB * dbh, const char *exp_tag, const char *recipe, psS32 p1_version)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "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, "p1_version", PS_DATA_S32, NULL, p1_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
        psFree(md);
        return false;
    }

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool p1PendingExpInsertObject(psDB *dbh, p1PendingExpRow *object)
{
    return p1PendingExpInsert(dbh, object->exp_tag, object->recipe, object->p1_version);
}

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

    return true;
}

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

    // move to (the first?) extension named  P1PENDINGEXP_TABLE_NAME
    if (!psFitsMoveExtName(fits, P1PENDINGEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P1PENDINGEXP_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, P1PENDINGEXP_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *p1PendingExpMetadataFromObject(const p1PendingExpRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "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, "p1_version", PS_DATA_S32, NULL, object->p1_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
        psFree(md);
        return false;
    }


    return md;
}

p1PendingExpRow *p1PendingExpObjectFromMetadata(psMetadata *md)
{

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

    return p1PendingExpRowAlloc(exp_tag, recipe, p1_version);
}
psArray *p1PendingExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, P1PENDINGEXP_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++) {
        p1PendingExpRow *object = p1PendingExpObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool p1PendingExpDeleteObject(psDB *dbh, const p1PendingExpRow *object)
{
    psMetadata *where = p1PendingExpMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, P1PENDINGEXP_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from p1PendingExp");
        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, "p1PendingExpRow object matched more then one row.  Check your database schema");
        return false;
    }

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

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

        deleted += count;
    }

    return deleted;
}
bool p1PendingExpPrintObjects(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 = p1PendingExpMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            P1PENDINGEXP_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 p1PendingExpPrintObject(FILE *stream, p1PendingExpRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = p1PendingExpMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void p2PendingExpRowFree(p2PendingExpRow *object);

p2PendingExpRow *p2PendingExpRowAlloc(const char *exp_tag, const char *recipe, psS32 p1_version, psS32 p2_version, const char *label)
{
    p2PendingExpRow *_object;

    _object = psAlloc(sizeof(p2PendingExpRow));
    psMemSetDeallocator(_object, (psFreeFunc)p2PendingExpRowFree);

    _object->exp_tag = psStringCopy(exp_tag);
    _object->recipe = psStringCopy(recipe);
    _object->p1_version = p1_version;
    _object->p2_version = p2_version;
    _object->label = psStringCopy(label);

    return _object;
}

static void p2PendingExpRowFree(p2PendingExpRow *object)
{
    psFree(object->exp_tag);
    psFree(object->recipe);
    psFree(object->label);
}

bool p2PendingExpCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "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, "p1_version", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p2_version", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
        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;
    }

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

    psFree(md);

    return status;
}

bool p2PendingExpDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, P2PENDINGEXP_TABLE_NAME);
}

bool p2PendingExpInsert(psDB * dbh, const char *exp_tag, const char *recipe, psS32 p1_version, psS32 p2_version, const char *label)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "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, "p1_version", PS_DATA_S32, NULL, p1_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p2_version", PS_DATA_S32, NULL, p2_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
        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;
    }

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool p2PendingExpInsertObject(psDB *dbh, p2PendingExpRow *object)
{
    return p2PendingExpInsert(dbh, object->exp_tag, object->recipe, object->p1_version, object->p2_version, object->label);
}

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

    return true;
}

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

    // move to (the first?) extension named  P2PENDINGEXP_TABLE_NAME
    if (!psFitsMoveExtName(fits, P2PENDINGEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P2PENDINGEXP_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, P2PENDINGEXP_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *p2PendingExpMetadataFromObject(const p2PendingExpRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "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, "p1_version", PS_DATA_S32, NULL, object->p1_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p2_version", PS_DATA_S32, NULL, object->p2_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
        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;
    }


    return md;
}

p2PendingExpRow *p2PendingExpObjectFromMetadata(psMetadata *md)
{

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

    return p2PendingExpRowAlloc(exp_tag, recipe, p1_version, p2_version, label);
}
psArray *p2PendingExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, P2PENDINGEXP_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++) {
        p2PendingExpRow *object = p2PendingExpObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool p2PendingExpDeleteObject(psDB *dbh, const p2PendingExpRow *object)
{
    psMetadata *where = p2PendingExpMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, P2PENDINGEXP_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from p2PendingExp");
        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, "p2PendingExpRow object matched more then one row.  Check your database schema");
        return false;
    }

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

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

        deleted += count;
    }

    return deleted;
}
bool p2PendingExpPrintObjects(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 = p2PendingExpMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            P2PENDINGEXP_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 p2PendingExpPrintObject(FILE *stream, p2PendingExpRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = p2PendingExpMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void p2PendingImfileRowFree(p2PendingImfileRow *object);

p2PendingImfileRow *p2PendingImfileRowAlloc(const char *exp_tag, const char *class_id, const char *uri, const char *recipe, psS32 p1_version, psS32 p2_version)
{
    p2PendingImfileRow *_object;

    _object = psAlloc(sizeof(p2PendingImfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)p2PendingImfileRowFree);

    _object->exp_tag = psStringCopy(exp_tag);
    _object->class_id = psStringCopy(class_id);
    _object->uri = psStringCopy(uri);
    _object->recipe = psStringCopy(recipe);
    _object->p1_version = p1_version;
    _object->p2_version = p2_version;

    return _object;
}

static void p2PendingImfileRowFree(p2PendingImfileRow *object)
{
    psFree(object->exp_tag);
    psFree(object->class_id);
    psFree(object->uri);
    psFree(object->recipe);
}

bool p2PendingImfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "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, "p1_version", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p2_version", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
        psFree(md);
        return false;
    }

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

    psFree(md);

    return status;
}

bool p2PendingImfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, P2PENDINGIMFILE_TABLE_NAME);
}

bool p2PendingImfileInsert(psDB * dbh, const char *exp_tag, const char *class_id, const char *uri, const char *recipe, psS32 p1_version, psS32 p2_version)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "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, "p1_version", PS_DATA_S32, NULL, p1_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p2_version", PS_DATA_S32, NULL, p2_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
        psFree(md);
        return false;
    }

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool p2PendingImfileInsertObject(psDB *dbh, p2PendingImfileRow *object)
{
    return p2PendingImfileInsert(dbh, object->exp_tag, object->class_id, object->uri, object->recipe, object->p1_version, object->p2_version);
}

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

    return true;
}

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

    // move to (the first?) extension named  P2PENDINGIMFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, P2PENDINGIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P2PENDINGIMFILE_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, P2PENDINGIMFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *p2PendingImfileMetadataFromObject(const p2PendingImfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "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, "p1_version", PS_DATA_S32, NULL, object->p1_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p2_version", PS_DATA_S32, NULL, object->p2_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
        psFree(md);
        return false;
    }


    return md;
}

p2PendingImfileRow *p2PendingImfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
        return false;
    }
    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
        return false;
    }
    psS32 p1_version = psMetadataLookupS32(&status, md, "p1_version");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p1_version");
        return false;
    }
    psS32 p2_version = psMetadataLookupS32(&status, md, "p2_version");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p2_version");
        return false;
    }

    return p2PendingImfileRowAlloc(exp_tag, class_id, uri, recipe, p1_version, p2_version);
}
psArray *p2PendingImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, P2PENDINGIMFILE_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++) {
        p2PendingImfileRow *object = p2PendingImfileObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool p2PendingImfileDeleteObject(psDB *dbh, const p2PendingImfileRow *object)
{
    psMetadata *where = p2PendingImfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, P2PENDINGIMFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from p2PendingImfile");
        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, "p2PendingImfileRow object matched more then one row.  Check your database schema");
        return false;
    }

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

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

        deleted += count;
    }

    return deleted;
}
bool p2PendingImfilePrintObjects(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 = p2PendingImfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            P2PENDINGIMFILE_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 p2PendingImfilePrintObject(FILE *stream, p2PendingImfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = p2PendingImfileMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void p2ProcessedExpRowFree(p2ProcessedExpRow *object);

p2ProcessedExpRow *p2ProcessedExpRowAlloc(const char *exp_tag, psS32 p1_version, psS32 p2_version, const char *label)
{
    p2ProcessedExpRow *_object;

    _object = psAlloc(sizeof(p2ProcessedExpRow));
    psMemSetDeallocator(_object, (psFreeFunc)p2ProcessedExpRowFree);

    _object->exp_tag = psStringCopy(exp_tag);
    _object->p1_version = p1_version;
    _object->p2_version = p2_version;
    _object->label = psStringCopy(label);

    return _object;
}

static void p2ProcessedExpRowFree(p2ProcessedExpRow *object)
{
    psFree(object->exp_tag);
    psFree(object->label);
}

bool p2ProcessedExpCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p1_version", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p2_version", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
        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;
    }

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

    psFree(md);

    return status;
}

bool p2ProcessedExpDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, P2PROCESSEDEXP_TABLE_NAME);
}

bool p2ProcessedExpInsert(psDB * dbh, const char *exp_tag, psS32 p1_version, psS32 p2_version, const char *label)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p1_version", PS_DATA_S32, NULL, p1_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p2_version", PS_DATA_S32, NULL, p2_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
        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;
    }

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool p2ProcessedExpInsertObject(psDB *dbh, p2ProcessedExpRow *object)
{
    return p2ProcessedExpInsert(dbh, object->exp_tag, object->p1_version, object->p2_version, object->label);
}

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

    return true;
}

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

    // move to (the first?) extension named  P2PROCESSEDEXP_TABLE_NAME
    if (!psFitsMoveExtName(fits, P2PROCESSEDEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P2PROCESSEDEXP_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, P2PROCESSEDEXP_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *p2ProcessedExpMetadataFromObject(const p2ProcessedExpRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p1_version", PS_DATA_S32, NULL, object->p1_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p2_version", PS_DATA_S32, NULL, object->p2_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
        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;
    }


    return md;
}

p2ProcessedExpRow *p2ProcessedExpObjectFromMetadata(psMetadata *md)
{

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

    return p2ProcessedExpRowAlloc(exp_tag, p1_version, p2_version, label);
}
psArray *p2ProcessedExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, P2PROCESSEDEXP_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++) {
        p2ProcessedExpRow *object = p2ProcessedExpObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool p2ProcessedExpDeleteObject(psDB *dbh, const p2ProcessedExpRow *object)
{
    psMetadata *where = p2ProcessedExpMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, P2PROCESSEDEXP_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from p2ProcessedExp");
        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, "p2ProcessedExpRow object matched more then one row.  Check your database schema");
        return false;
    }

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

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

        deleted += count;
    }

    return deleted;
}
bool p2ProcessedExpPrintObjects(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 = p2ProcessedExpMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            P2PROCESSEDEXP_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 p2ProcessedExpPrintObject(FILE *stream, p2ProcessedExpRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = p2ProcessedExpMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void p2MaskRowFree(p2MaskRow *object);

p2MaskRow *p2MaskRowAlloc(const char *label)
{
    p2MaskRow       *_object;

    _object = psAlloc(sizeof(p2MaskRow));
    psMemSetDeallocator(_object, (psFreeFunc)p2MaskRowFree);

    _object->label = psStringCopy(label);

    return _object;
}

static void p2MaskRowFree(p2MaskRow *object)
{
    psFree(object->label);
}

bool p2MaskCreateTable(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, P2MASK_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool p2MaskDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, P2MASK_TABLE_NAME);
}

bool p2MaskInsert(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, P2MASK_TABLE_NAME, md);
    psFree(md);

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool p2MaskInsertObject(psDB *dbh, p2MaskRow *object)
{
    return p2MaskInsert(dbh, object->label);
}

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

    return true;
}

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

    // move to (the first?) extension named  P2MASK_TABLE_NAME
    if (!psFitsMoveExtName(fits, P2MASK_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P2MASK_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, P2MASK_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *p2MaskMetadataFromObject(const p2MaskRow *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;
}

p2MaskRow *p2MaskObjectFromMetadata(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 p2MaskRowAlloc(label);
}
psArray *p2MaskSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, P2MASK_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++) {
        p2MaskRow *object = p2MaskObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool p2MaskDeleteObject(psDB *dbh, const p2MaskRow *object)
{
    psMetadata *where = p2MaskMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, P2MASK_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from p2Mask");
        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, "p2MaskRow object matched more then one row.  Check your database schema");
        return false;
    }

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

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

        deleted += count;
    }

    return deleted;
}
bool p2MaskPrintObjects(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 = p2MaskMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            P2MASK_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 p2MaskPrintObject(FILE *stream, p2MaskRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = p2MaskMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void p2ProcessedImfileRowFree(p2ProcessedImfileRow *object);

p2ProcessedImfileRow *p2ProcessedImfileRowAlloc(const char *exp_tag, const char *class_id, const char *uri, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *b1_uri, const char *b2_uri, psS32 p1_version, psS32 p2_version, psS16 fault)
{
    p2ProcessedImfileRow *_object;

    _object = psAlloc(sizeof(p2ProcessedImfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)p2ProcessedImfileRowFree);

    _object->exp_tag = psStringCopy(exp_tag);
    _object->class_id = psStringCopy(class_id);
    _object->uri = psStringCopy(uri);
    _object->recipe = psStringCopy(recipe);
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->bg_mean_stdev = bg_mean_stdev;
    _object->b1_uri = psStringCopy(b1_uri);
    _object->b2_uri = psStringCopy(b2_uri);
    _object->p1_version = p1_version;
    _object->p2_version = p2_version;
    _object->fault = fault;

    return _object;
}

static void p2ProcessedImfileRowFree(p2ProcessedImfileRow *object)
{
    psFree(object->exp_tag);
    psFree(object->class_id);
    psFree(object->uri);
    psFree(object->recipe);
    psFree(object->b1_uri);
    psFree(object->b2_uri);
}

bool p2ProcessedImfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "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, "b1_uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b2_uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p1_version", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p2_version", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "NOT NULL", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

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

    psFree(md);

    return status;
}

bool p2ProcessedImfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, P2PROCESSEDIMFILE_TABLE_NAME);
}

bool p2ProcessedImfileInsert(psDB * dbh, const char *exp_tag, const char *class_id, const char *uri, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *b1_uri, const char *b2_uri, psS32 p1_version, psS32 p2_version, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "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, "b1_uri", PS_DATA_STRING, NULL, b1_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b2_uri", PS_DATA_STRING, NULL, b2_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p1_version", PS_DATA_S32, NULL, p1_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p2_version", PS_DATA_S32, NULL, p2_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
        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, P2PROCESSEDIMFILE_TABLE_NAME, md);
    psFree(md);

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool p2ProcessedImfileInsertObject(psDB *dbh, p2ProcessedImfileRow *object)
{
    return p2ProcessedImfileInsert(dbh, object->exp_tag, object->class_id, object->uri, object->recipe, object->bg, object->bg_stdev, object->bg_mean_stdev, object->b1_uri, object->b2_uri, object->p1_version, object->p2_version, object->fault);
}

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

    return true;
}

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

    // move to (the first?) extension named  P2PROCESSEDIMFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, P2PROCESSEDIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P2PROCESSEDIMFILE_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, P2PROCESSEDIMFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *p2ProcessedImfileMetadataFromObject(const p2ProcessedImfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "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, "b1_uri", PS_DATA_STRING, NULL, object->b1_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b2_uri", PS_DATA_STRING, NULL, object->b2_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p1_version", PS_DATA_S32, NULL, object->p1_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p1_version");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p2_version", PS_DATA_S32, NULL, object->p2_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
        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;
}

p2ProcessedImfileRow *p2ProcessedImfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
        return false;
    }
    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
        return false;
    }
    char* b1_uri = psMetadataLookupPtr(&status, md, "b1_uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item b1_uri");
        return false;
    }
    char* b2_uri = psMetadataLookupPtr(&status, md, "b2_uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item b2_uri");
        return false;
    }
    psS32 p1_version = psMetadataLookupS32(&status, md, "p1_version");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p1_version");
        return false;
    }
    psS32 p2_version = psMetadataLookupS32(&status, md, "p2_version");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p2_version");
        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 p2ProcessedImfileRowAlloc(exp_tag, class_id, uri, recipe, bg, bg_stdev, bg_mean_stdev, b1_uri, b2_uri, p1_version, p2_version, fault);
}
psArray *p2ProcessedImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, P2PROCESSEDIMFILE_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++) {
        p2ProcessedImfileRow *object = p2ProcessedImfileObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool p2ProcessedImfileDeleteObject(psDB *dbh, const p2ProcessedImfileRow *object)
{
    psMetadata *where = p2ProcessedImfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, P2PROCESSEDIMFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from p2ProcessedImfile");
        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, "p2ProcessedImfileRow object matched more then one row.  Check your database schema");
        return false;
    }

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

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

        deleted += count;
    }

    return deleted;
}
bool p2ProcessedImfilePrintObjects(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 = p2ProcessedImfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            P2PROCESSEDIMFILE_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 p2ProcessedImfilePrintObject(FILE *stream, p2ProcessedImfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = p2ProcessedImfileMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void p3PendingExpRowFree(p3PendingExpRow *object);

p3PendingExpRow *p3PendingExpRowAlloc(const char *exp_tag, psS32 p2_version, psS32 p3_version, const char *label)
{
    p3PendingExpRow *_object;

    _object = psAlloc(sizeof(p3PendingExpRow));
    psMemSetDeallocator(_object, (psFreeFunc)p3PendingExpRowFree);

    _object->exp_tag = psStringCopy(exp_tag);
    _object->p2_version = p2_version;
    _object->p3_version = p3_version;
    _object->label = psStringCopy(label);

    return _object;
}

static void p3PendingExpRowFree(p3PendingExpRow *object)
{
    psFree(object->exp_tag);
    psFree(object->label);
}

bool p3PendingExpCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p2_version", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p3_version", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
        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;
    }

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

    psFree(md);

    return status;
}

bool p3PendingExpDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, P3PENDINGEXP_TABLE_NAME);
}

bool p3PendingExpInsert(psDB * dbh, const char *exp_tag, psS32 p2_version, psS32 p3_version, const char *label)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p2_version", PS_DATA_S32, NULL, p2_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p3_version", PS_DATA_S32, NULL, p3_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
        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;
    }

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool p3PendingExpInsertObject(psDB *dbh, p3PendingExpRow *object)
{
    return p3PendingExpInsert(dbh, object->exp_tag, object->p2_version, object->p3_version, object->label);
}

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

    return true;
}

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

    // move to (the first?) extension named  P3PENDINGEXP_TABLE_NAME
    if (!psFitsMoveExtName(fits, P3PENDINGEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P3PENDINGEXP_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, P3PENDINGEXP_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *p3PendingExpMetadataFromObject(const p3PendingExpRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p2_version", PS_DATA_S32, NULL, object->p2_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p3_version", PS_DATA_S32, NULL, object->p3_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
        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;
    }


    return md;
}

p3PendingExpRow *p3PendingExpObjectFromMetadata(psMetadata *md)
{

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

    return p3PendingExpRowAlloc(exp_tag, p2_version, p3_version, label);
}
psArray *p3PendingExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, P3PENDINGEXP_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++) {
        p3PendingExpRow *object = p3PendingExpObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool p3PendingExpDeleteObject(psDB *dbh, const p3PendingExpRow *object)
{
    psMetadata *where = p3PendingExpMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, P3PENDINGEXP_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from p3PendingExp");
        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, "p3PendingExpRow object matched more then one row.  Check your database schema");
        return false;
    }

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

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

        deleted += count;
    }

    return deleted;
}
bool p3PendingExpPrintObjects(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 = p3PendingExpMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            P3PENDINGEXP_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 p3PendingExpPrintObject(FILE *stream, p3PendingExpRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = p3PendingExpMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void p3ProcessedExpRowFree(p3ProcessedExpRow *object);

p3ProcessedExpRow *p3ProcessedExpRowAlloc(const char *exp_tag, const char *uri, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF32 sigma_ra, psF32 sigma_dec, psS32 nastro, const char *b1_uri, const char *b2_uri, psF32 zp_mean, psF32 zp_stdev, psS32 p2_version, psS32 p3_version, const char *label, psS16 fault)
{
    p3ProcessedExpRow *_object;

    _object = psAlloc(sizeof(p3ProcessedExpRow));
    psMemSetDeallocator(_object, (psFreeFunc)p3ProcessedExpRowFree);

    _object->exp_tag = psStringCopy(exp_tag);
    _object->uri = psStringCopy(uri);
    _object->recipe = psStringCopy(recipe);
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->bg_mean_stdev = bg_mean_stdev;
    _object->sigma_ra = sigma_ra;
    _object->sigma_dec = sigma_dec;
    _object->nastro = nastro;
    _object->b1_uri = psStringCopy(b1_uri);
    _object->b2_uri = psStringCopy(b2_uri);
    _object->zp_mean = zp_mean;
    _object->zp_stdev = zp_stdev;
    _object->p2_version = p2_version;
    _object->p3_version = p3_version;
    _object->label = psStringCopy(label);
    _object->fault = fault;

    return _object;
}

static void p3ProcessedExpRowFree(p3ProcessedExpRow *object)
{
    psFree(object->exp_tag);
    psFree(object->uri);
    psFree(object->recipe);
    psFree(object->b1_uri);
    psFree(object->b2_uri);
    psFree(object->label);
}

bool p3ProcessedExpCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "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, "sigma_ra", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sigma_ra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "sigma_dec", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sigma_dec");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "nastro", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item nastro");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b1_uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b2_uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_uri");
        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, "p2_version", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p3_version", PS_DATA_S32, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
        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, "fault", PS_DATA_S16, "NOT NULL", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

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

    psFree(md);

    return status;
}

bool p3ProcessedExpDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, P3PROCESSEDEXP_TABLE_NAME);
}

bool p3ProcessedExpInsert(psDB * dbh, const char *exp_tag, const char *uri, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psF32 sigma_ra, psF32 sigma_dec, psS32 nastro, const char *b1_uri, const char *b2_uri, psF32 zp_mean, psF32 zp_stdev, psS32 p2_version, psS32 p3_version, const char *label, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "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, "sigma_ra", PS_DATA_F32, NULL, sigma_ra)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sigma_ra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "sigma_dec", PS_DATA_F32, NULL, sigma_dec)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sigma_dec");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "nastro", PS_DATA_S32, NULL, nastro)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item nastro");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b1_uri", PS_DATA_STRING, NULL, b1_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b2_uri", PS_DATA_STRING, NULL, b2_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_uri");
        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, "p2_version", PS_DATA_S32, NULL, p2_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p3_version", PS_DATA_S32, NULL, p3_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
        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, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool p3ProcessedExpInsertObject(psDB *dbh, p3ProcessedExpRow *object)
{
    return p3ProcessedExpInsert(dbh, object->exp_tag, object->uri, object->recipe, object->bg, object->bg_stdev, object->bg_mean_stdev, object->sigma_ra, object->sigma_dec, object->nastro, object->b1_uri, object->b2_uri, object->zp_mean, object->zp_stdev, object->p2_version, object->p3_version, object->label, object->fault);
}

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

    return true;
}

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

    // move to (the first?) extension named  P3PROCESSEDEXP_TABLE_NAME
    if (!psFitsMoveExtName(fits, P3PROCESSEDEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P3PROCESSEDEXP_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, P3PROCESSEDEXP_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *p3ProcessedExpMetadataFromObject(const p3ProcessedExpRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "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, "sigma_ra", PS_DATA_F32, NULL, object->sigma_ra)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sigma_ra");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "sigma_dec", PS_DATA_F32, NULL, object->sigma_dec)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item sigma_dec");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "nastro", PS_DATA_S32, NULL, object->nastro)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item nastro");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b1_uri", PS_DATA_STRING, NULL, object->b1_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b2_uri", PS_DATA_STRING, NULL, object->b2_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_uri");
        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, "p2_version", PS_DATA_S32, NULL, object->p2_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p2_version");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p3_version", PS_DATA_S32, NULL, object->p3_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
        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, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

p3ProcessedExpRow *p3ProcessedExpObjectFromMetadata(psMetadata *md)
{

bool status = false;
    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
        return false;
    }
    char* 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;
    }
    psF32 sigma_ra = psMetadataLookupF32(&status, md, "sigma_ra");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sigma_ra");
        return false;
    }
    psF32 sigma_dec = psMetadataLookupF32(&status, md, "sigma_dec");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item sigma_dec");
        return false;
    }
    psS32 nastro = psMetadataLookupS32(&status, md, "nastro");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item nastro");
        return false;
    }
    char* b1_uri = psMetadataLookupPtr(&status, md, "b1_uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item b1_uri");
        return false;
    }
    char* b2_uri = psMetadataLookupPtr(&status, md, "b2_uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item b2_uri");
        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;
    }
    psS32 p2_version = psMetadataLookupS32(&status, md, "p2_version");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p2_version");
        return false;
    }
    psS32 p3_version = psMetadataLookupS32(&status, md, "p3_version");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p3_version");
        return false;
    }
    char* label = psMetadataLookupPtr(&status, md, "label");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item label");
        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 p3ProcessedExpRowAlloc(exp_tag, uri, recipe, bg, bg_stdev, bg_mean_stdev, sigma_ra, sigma_dec, nastro, b1_uri, b2_uri, zp_mean, zp_stdev, p2_version, p3_version, label, fault);
}
psArray *p3ProcessedExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, P3PROCESSEDEXP_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++) {
        p3ProcessedExpRow *object = p3ProcessedExpObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool p3ProcessedExpDeleteObject(psDB *dbh, const p3ProcessedExpRow *object)
{
    psMetadata *where = p3ProcessedExpMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, P3PROCESSEDEXP_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from p3ProcessedExp");
        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, "p3ProcessedExpRow object matched more then one row.  Check your database schema");
        return false;
    }

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

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

        deleted += count;
    }

    return deleted;
}
bool p3ProcessedExpPrintObjects(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 = p3ProcessedExpMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            P3PROCESSEDEXP_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 p3ProcessedExpPrintObject(FILE *stream, p3ProcessedExpRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = p3ProcessedExpMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void p3MaskRowFree(p3MaskRow *object);

p3MaskRow *p3MaskRowAlloc(const char *label)
{
    p3MaskRow       *_object;

    _object = psAlloc(sizeof(p3MaskRow));
    psMemSetDeallocator(_object, (psFreeFunc)p3MaskRowFree);

    _object->label = psStringCopy(label);

    return _object;
}

static void p3MaskRowFree(p3MaskRow *object)
{
    psFree(object->label);
}

bool p3MaskCreateTable(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, P3MASK_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool p3MaskDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, P3MASK_TABLE_NAME);
}

bool p3MaskInsert(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, P3MASK_TABLE_NAME, md);
    psFree(md);

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool p3MaskInsertObject(psDB *dbh, p3MaskRow *object)
{
    return p3MaskInsert(dbh, object->label);
}

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

    return true;
}

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

    // move to (the first?) extension named  P3MASK_TABLE_NAME
    if (!psFitsMoveExtName(fits, P3MASK_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P3MASK_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, P3MASK_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *p3MaskMetadataFromObject(const p3MaskRow *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;
}

p3MaskRow *p3MaskObjectFromMetadata(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 p3MaskRowAlloc(label);
}
psArray *p3MaskSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, P3MASK_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++) {
        p3MaskRow *object = p3MaskObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool p3MaskDeleteObject(psDB *dbh, const p3MaskRow *object)
{
    psMetadata *where = p3MaskMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, P3MASK_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from p3Mask");
        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, "p3MaskRow object matched more then one row.  Check your database schema");
        return false;
    }

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

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

        deleted += count;
    }

    return deleted;
}
bool p3MaskPrintObjects(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 = p3MaskMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            P3MASK_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 p3MaskPrintObject(FILE *stream, p3MaskRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = p3MaskMetadataFromObject(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(psS32 det_id, psS32 iteration, const char *det_type, const char *mode, const char *state, const char *workdir, const char *camera, const char *telescope, const char *exp_type, const char *filter, psF32 airmass_min, psF32 airmass_max, psF32 exp_time_min, psF32 exp_time_max, psF32 ccd_temp_min, psF32 ccd_temp_max, psF64 posang_min, psF64 posang_max, psTime* registered, psTime* time_begin, psTime* time_end, psTime* use_begin, psTime* use_end)
{
    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->workdir = psStringCopy(workdir);
    _object->camera = psStringCopy(camera);
    _object->telescope = psStringCopy(telescope);
    _object->exp_type = psStringCopy(exp_type);
    _object->filter = psStringCopy(filter);
    _object->airmass_min = airmass_min;
    _object->airmass_max = airmass_max;
    _object->exp_time_min = exp_time_min;
    _object->exp_time_max = exp_time_max;
    _object->ccd_temp_min = ccd_temp_min;
    _object->ccd_temp_max = ccd_temp_max;
    _object->posang_min = posang_min;
    _object->posang_max = posang_max;
    _object->registered = psTimeCopy(registered);
    _object->time_begin = psTimeCopy(time_begin);
    _object->time_end = psTimeCopy(time_end);
    _object->use_begin = psTimeCopy(use_begin);
    _object->use_end = psTimeCopy(use_end);

    return _object;
}

static void detRunRowFree(detRunRow *object)
{
    psFree(object->det_type);
    psFree(object->mode);
    psFree(object->state);
    psFree(object->workdir);
    psFree(object->camera);
    psFree(object->telescope);
    psFree(object->exp_type);
    psFree(object->filter);
    psFree(object->registered);
    psFree(object->time_begin);
    psFree(object->time_end);
    psFree(object->use_begin);
    psFree(object->use_end);
}

bool detRunCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S32, "Primary Key AUTO_INCREMENT", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_type", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "mode", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item mode");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "state", PS_DATA_STRING, "Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item state");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "workdir", PS_DATA_STRING, "destination for output files", "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, "XXX this should be dropped", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass_min", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass_max", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time_min", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time_max", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp_min", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp_max", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang_min", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item posang_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang_max", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item posang_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "time_begin", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item time_begin");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "time_end", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item time_end");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "use_begin", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item use_begin");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "use_end", PS_DATA_TIME, NULL, NULL)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item use_end");
        psFree(md);
        return false;
    }

    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, psS32 det_id, psS32 iteration, const char *det_type, const char *mode, const char *state, const char *workdir, const char *camera, const char *telescope, const char *exp_type, const char *filter, psF32 airmass_min, psF32 airmass_max, psF32 exp_time_min, psF32 exp_time_max, psF32 ccd_temp_min, psF32 ccd_temp_max, psF64 posang_min, psF64 posang_max, psTime* registered, psTime* time_begin, psTime* time_end, psTime* use_begin, psTime* use_end)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S32, 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, "workdir", PS_DATA_STRING, NULL, workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, exp_type)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, filter)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass_min", PS_DATA_F32, NULL, airmass_min)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass_max", PS_DATA_F32, NULL, airmass_max)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time_min", PS_DATA_F32, NULL, exp_time_min)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time_max", PS_DATA_F32, NULL, exp_time_max)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp_min", PS_DATA_F32, NULL, ccd_temp_min)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp_max", PS_DATA_F32, NULL, ccd_temp_max)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang_min", PS_DATA_F64, NULL, posang_min)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item posang_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang_max", PS_DATA_F64, NULL, posang_max)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item posang_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, registered)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "time_begin", PS_DATA_TIME, NULL, time_begin)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item time_begin");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "time_end", PS_DATA_TIME, NULL, time_end)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item time_end");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "use_begin", PS_DATA_TIME, NULL, use_begin)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item use_begin");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "use_end", PS_DATA_TIME, NULL, use_end)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item use_end");
        psFree(md);
        return false;
    }

    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->workdir, object->camera, object->telescope, object->exp_type, object->filter, object->airmass_min, object->airmass_max, object->exp_time_min, object->exp_time_max, object->ccd_temp_min, object->ccd_temp_max, object->posang_min, object->posang_max, object->registered, object->time_begin, object->time_end, object->use_begin, object->use_end);
}

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_S32, 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, "workdir", PS_DATA_STRING, NULL, object->workdir)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item workdir");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "camera", PS_DATA_STRING, NULL, object->camera)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item camera");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "telescope", PS_DATA_STRING, NULL, object->telescope)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item telescope");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_type", PS_DATA_STRING, NULL, object->exp_type)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_type");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "filter", PS_DATA_STRING, NULL, object->filter)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item filter");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass_min", PS_DATA_F32, NULL, object->airmass_min)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "airmass_max", PS_DATA_F32, NULL, object->airmass_max)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item airmass_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time_min", PS_DATA_F32, NULL, object->exp_time_min)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_time_max", PS_DATA_F32, NULL, object->exp_time_max)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_time_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp_min", PS_DATA_F32, NULL, object->ccd_temp_min)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ccd_temp_max", PS_DATA_F32, NULL, object->ccd_temp_max)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ccd_temp_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang_min", PS_DATA_F64, NULL, object->posang_min)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item posang_min");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "posang_max", PS_DATA_F64, NULL, object->posang_max)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item posang_max");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "registered", PS_DATA_TIME, NULL, object->registered)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item registered");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "time_begin", PS_DATA_TIME, NULL, object->time_begin)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item time_begin");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "time_end", PS_DATA_TIME, NULL, object->time_end)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item time_end");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "use_begin", PS_DATA_TIME, NULL, object->use_begin)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item use_begin");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "use_end", PS_DATA_TIME, NULL, object->use_end)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item use_end");
        psFree(md);
        return false;
    }


    return md;
}

detRunRow *detRunObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS32 det_id = psMetadataLookupS32(&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* workdir = psMetadataLookupPtr(&status, md, "workdir");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item workdir");
        return false;
    }
    char* camera = psMetadataLookupPtr(&status, md, "camera");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item camera");
        return false;
    }
    char* telescope = psMetadataLookupPtr(&status, md, "telescope");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item telescope");
        return false;
    }
    char* exp_type = psMetadataLookupPtr(&status, md, "exp_type");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_type");
        return false;
    }
    char* filter = psMetadataLookupPtr(&status, md, "filter");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item filter");
        return false;
    }
    psF32 airmass_min = psMetadataLookupF32(&status, md, "airmass_min");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass_min");
        return false;
    }
    psF32 airmass_max = psMetadataLookupF32(&status, md, "airmass_max");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item airmass_max");
        return false;
    }
    psF32 exp_time_min = psMetadataLookupF32(&status, md, "exp_time_min");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time_min");
        return false;
    }
    psF32 exp_time_max = psMetadataLookupF32(&status, md, "exp_time_max");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_time_max");
        return false;
    }
    psF32 ccd_temp_min = psMetadataLookupF32(&status, md, "ccd_temp_min");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ccd_temp_min");
        return false;
    }
    psF32 ccd_temp_max = psMetadataLookupF32(&status, md, "ccd_temp_max");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ccd_temp_max");
        return false;
    }
    psF64 posang_min = psMetadataLookupF64(&status, md, "posang_min");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item posang_min");
        return false;
    }
    psF64 posang_max = psMetadataLookupF64(&status, md, "posang_max");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item posang_max");
        return false;
    }
    psTime* registered = psMetadataLookupPtr(&status, md, "registered");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item registered");
        return false;
    }
    psTime* time_begin = psMetadataLookupPtr(&status, md, "time_begin");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item time_begin");
        return false;
    }
    psTime* time_end = psMetadataLookupPtr(&status, md, "time_end");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item time_end");
        return false;
    }
    psTime* use_begin = psMetadataLookupPtr(&status, md, "use_begin");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item use_begin");
        return false;
    }
    psTime* use_end = psMetadataLookupPtr(&status, md, "use_end");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item use_end");
        return false;
    }

    return detRunRowAlloc(det_id, iteration, det_type, mode, state, workdir, camera, telescope, exp_type, filter, airmass_min, airmass_max, exp_time_min, exp_time_max, ccd_temp_min, ccd_temp_max, posang_min, posang_max, registered, time_begin, time_end, use_begin, use_end);
}
psArray *detRunSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

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

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detRunRow *object = detRunObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

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

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

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

        deleted += count;
    }

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

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

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

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

    psMetadata *md = detRunMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void detInputExpRowFree(detInputExpRow *object);

detInputExpRow *detInputExpRowAlloc(psS32 det_id, psS32 iteration, const char *exp_tag, bool include)
{
    detInputExpRow  *_object;

    _object = psAlloc(sizeof(detInputExpRow));
    psMemSetDeallocator(_object, (psFreeFunc)detInputExpRowFree);

    _object->det_id = det_id;
    _object->iteration = iteration;
    _object->exp_tag = psStringCopy(exp_tag);
    _object->include = include;

    return _object;
}

static void detInputExpRowFree(detInputExpRow *object)
{
    psFree(object->exp_tag);
}

bool detInputExpCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "include", PS_DATA_BOOL, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item include");
        psFree(md);
        return false;
    }

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

    psFree(md);

    return status;
}

bool detInputExpDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, DETINPUTEXP_TABLE_NAME);
}

bool detInputExpInsert(psDB * dbh, psS32 det_id, psS32 iteration, const char *exp_tag, bool include)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S32, NULL, det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "include", PS_DATA_BOOL, NULL, include)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item include");
        psFree(md);
        return false;
    }

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool detInputExpInsertObject(psDB *dbh, detInputExpRow *object)
{
    return detInputExpInsert(dbh, object->det_id, object->iteration, object->exp_tag, object->include);
}

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

    return true;
}

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

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

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

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

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

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *detInputExpMetadataFromObject(const detInputExpRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S32, NULL, object->det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, object->iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "include", PS_DATA_BOOL, NULL, object->include)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item include");
        psFree(md);
        return false;
    }


    return md;
}

detInputExpRow *detInputExpObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS32 det_id = psMetadataLookupS32(&status, md, "det_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
        return false;
    }
    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
        return false;
    }
    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
        return false;
    }
    bool include = psMetadataLookupBool(&status, md, "include");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item include");
        return false;
    }

    return detInputExpRowAlloc(det_id, iteration, exp_tag, include);
}
psArray *detInputExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

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

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detInputExpRow *object = detInputExpObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

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

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

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

        deleted += count;
    }

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

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

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

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

    psMetadata *md = detInputExpMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void detProcessedImfileRowFree(detProcessedImfileRow *object);

detProcessedImfileRow *detProcessedImfileRowAlloc(psS32 det_id, const char *exp_tag, const char *class_id, const char *uri, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *b1_uri, const char *b2_uri, psS16 fault)
{
    detProcessedImfileRow *_object;

    _object = psAlloc(sizeof(detProcessedImfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)detProcessedImfileRowFree);

    _object->det_id = det_id;
    _object->exp_tag = psStringCopy(exp_tag);
    _object->class_id = psStringCopy(class_id);
    _object->uri = psStringCopy(uri);
    _object->recipe = psStringCopy(recipe);
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->bg_mean_stdev = bg_mean_stdev;
    _object->b1_uri = psStringCopy(b1_uri);
    _object->b2_uri = psStringCopy(b2_uri);
    _object->fault = fault;

    return _object;
}

static void detProcessedImfileRowFree(detProcessedImfileRow *object)
{
    psFree(object->exp_tag);
    psFree(object->class_id);
    psFree(object->uri);
    psFree(object->recipe);
    psFree(object->b1_uri);
    psFree(object->b2_uri);
}

bool detProcessedImfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b1_uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b2_uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "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, psS32 det_id, const char *exp_tag, const char *class_id, const char *uri, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *b1_uri, const char *b2_uri, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S32, NULL, det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b1_uri", PS_DATA_STRING, NULL, b1_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b2_uri", PS_DATA_STRING, NULL, b2_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_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, DETPROCESSEDIMFILE_TABLE_NAME, md);
    psFree(md);

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool detProcessedImfileInsertObject(psDB *dbh, detProcessedImfileRow *object)
{
    return detProcessedImfileInsert(dbh, object->det_id, object->exp_tag, object->class_id, object->uri, object->recipe, object->bg, object->bg_stdev, object->bg_mean_stdev, object->b1_uri, object->b2_uri, 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_S32, NULL, object->det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, object->recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b1_uri", PS_DATA_STRING, NULL, object->b1_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b2_uri", PS_DATA_STRING, NULL, object->b2_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_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;
}

detProcessedImfileRow *detProcessedImfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS32 det_id = psMetadataLookupS32(&status, md, "det_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
        return false;
    }
    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
        return false;
    }
    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
        return false;
    }
    char* b1_uri = psMetadataLookupPtr(&status, md, "b1_uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item b1_uri");
        return false;
    }
    char* b2_uri = psMetadataLookupPtr(&status, md, "b2_uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item b2_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 detProcessedImfileRowAlloc(det_id, exp_tag, class_id, uri, recipe, bg, bg_stdev, bg_mean_stdev, b1_uri, b2_uri, fault);
}
psArray *detProcessedImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

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

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detProcessedImfileRow *object = detProcessedImfileObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

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

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

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

        deleted += count;
    }

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

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

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

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

    psMetadata *md = detProcessedImfileMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void detProcessedExpRowFree(detProcessedExpRow *object);

detProcessedExpRow *detProcessedExpRowAlloc(psS32 det_id, const char *exp_tag, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *b1_uri, const char *b2_uri, psS16 fault)
{
    detProcessedExpRow *_object;

    _object = psAlloc(sizeof(detProcessedExpRow));
    psMemSetDeallocator(_object, (psFreeFunc)detProcessedExpRowFree);

    _object->det_id = det_id;
    _object->exp_tag = psStringCopy(exp_tag);
    _object->recipe = psStringCopy(recipe);
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->bg_mean_stdev = bg_mean_stdev;
    _object->b1_uri = psStringCopy(b1_uri);
    _object->b2_uri = psStringCopy(b2_uri);
    _object->fault = fault;

    return _object;
}

static void detProcessedExpRowFree(detProcessedExpRow *object)
{
    psFree(object->exp_tag);
    psFree(object->recipe);
    psFree(object->b1_uri);
    psFree(object->b2_uri);
}

bool detProcessedExpCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b1_uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b2_uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "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, psS32 det_id, const char *exp_tag, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *b1_uri, const char *b2_uri, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S32, NULL, det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b1_uri", PS_DATA_STRING, NULL, b1_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b2_uri", PS_DATA_STRING, NULL, b2_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_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, DETPROCESSEDEXP_TABLE_NAME, md);
    psFree(md);

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool detProcessedExpInsertObject(psDB *dbh, detProcessedExpRow *object)
{
    return detProcessedExpInsert(dbh, object->det_id, object->exp_tag, object->recipe, object->bg, object->bg_stdev, object->bg_mean_stdev, object->b1_uri, object->b2_uri, 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_S32, NULL, object->det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, object->recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b1_uri", PS_DATA_STRING, NULL, object->b1_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b2_uri", PS_DATA_STRING, NULL, object->b2_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_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;
}

detProcessedExpRow *detProcessedExpObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS32 det_id = psMetadataLookupS32(&status, md, "det_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
        return false;
    }
    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
        return false;
    }
    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
        return false;
    }
    char* b1_uri = psMetadataLookupPtr(&status, md, "b1_uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item b1_uri");
        return false;
    }
    char* b2_uri = psMetadataLookupPtr(&status, md, "b2_uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item b2_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 detProcessedExpRowAlloc(det_id, exp_tag, recipe, bg, bg_stdev, bg_mean_stdev, b1_uri, b2_uri, fault);
}
psArray *detProcessedExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

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

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detProcessedExpRow *object = detProcessedExpObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

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

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

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

        deleted += count;
    }

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

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

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

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

    psMetadata *md = detProcessedExpMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void detStackedImfileRowFree(detStackedImfileRow *object);

detStackedImfileRow *detStackedImfileRowAlloc(psS32 det_id, psS32 iteration, const char *class_id, const char *uri, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psS16 fault)
{
    detStackedImfileRow *_object;

    _object = psAlloc(sizeof(detStackedImfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)detStackedImfileRowFree);

    _object->det_id = det_id;
    _object->iteration = iteration;
    _object->class_id = psStringCopy(class_id);
    _object->uri = psStringCopy(uri);
    _object->recipe = psStringCopy(recipe);
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->bg_mean_stdev = bg_mean_stdev;
    _object->fault = fault;

    return _object;
}

static void detStackedImfileRowFree(detStackedImfileRow *object)
{
    psFree(object->class_id);
    psFree(object->uri);
    psFree(object->recipe);
}

bool detStackedImfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "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, psS32 det_id, psS32 iteration, const char *class_id, const char *uri, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S32, NULL, det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool detStackedImfileInsertObject(psDB *dbh, detStackedImfileRow *object)
{
    return detStackedImfileInsert(dbh, object->det_id, object->iteration, object->class_id, object->uri, object->recipe, object->bg, object->bg_stdev, object->bg_mean_stdev, object->fault);
}

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

    return true;
}

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

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

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

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

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

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *detStackedImfileMetadataFromObject(const detStackedImfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S32, NULL, object->det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, object->iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, object->recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, object->fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }


    return md;
}

detStackedImfileRow *detStackedImfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS32 det_id = psMetadataLookupS32(&status, md, "det_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
        return false;
    }
    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
        return false;
    }
    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return detStackedImfileRowAlloc(det_id, iteration, class_id, uri, recipe, bg, bg_stdev, bg_mean_stdev, fault);
}
psArray *detStackedImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

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

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detStackedImfileRow *object = detStackedImfileObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

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

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

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

        deleted += count;
    }

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

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

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

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

    psMetadata *md = detStackedImfileMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void detNormalizedStatImfileRowFree(detNormalizedStatImfileRow *object);

detNormalizedStatImfileRow *detNormalizedStatImfileRowAlloc(psS32 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_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "norm", PS_DATA_F32, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item norm");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "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, psS32 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_S32, 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_S32, 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;
    psS32 det_id = psMetadataLookupS32(&status, md, "det_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
        return false;
    }
    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
        return false;
    }
    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
        return false;
    }
    psF32 norm = psMetadataLookupF32(&status, md, "norm");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item norm");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return detNormalizedStatImfileRowAlloc(det_id, iteration, class_id, norm, fault);
}
psArray *detNormalizedStatImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

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

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detNormalizedStatImfileRow *object = detNormalizedStatImfileObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

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

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

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

        deleted += count;
    }

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

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

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

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

    psMetadata *md = detNormalizedStatImfileMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void detNormalizedImfileRowFree(detNormalizedImfileRow *object);

detNormalizedImfileRow *detNormalizedImfileRowAlloc(psS32 det_id, psS32 iteration, const char *class_id, const char *uri, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *b1_uri, const char *b2_uri, 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->b1_uri = psStringCopy(b1_uri);
    _object->b2_uri = psStringCopy(b2_uri);
    _object->fault = fault;

    return _object;
}

static void detNormalizedImfileRowFree(detNormalizedImfileRow *object)
{
    psFree(object->class_id);
    psFree(object->uri);
    psFree(object->b1_uri);
    psFree(object->b2_uri);
}

bool detNormalizedImfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b1_uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b2_uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "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, psS32 det_id, psS32 iteration, const char *class_id, const char *uri, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *b1_uri, const char *b2_uri, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S32, 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, "b1_uri", PS_DATA_STRING, NULL, b1_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b2_uri", PS_DATA_STRING, NULL, b2_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_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, 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->b1_uri, object->b2_uri, 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_S32, 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, "b1_uri", PS_DATA_STRING, NULL, object->b1_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b2_uri", PS_DATA_STRING, NULL, object->b2_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_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;
}

detNormalizedImfileRow *detNormalizedImfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS32 det_id = psMetadataLookupS32(&status, md, "det_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
        return false;
    }
    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
        return false;
    }
    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
        return false;
    }
    char* b1_uri = psMetadataLookupPtr(&status, md, "b1_uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item b1_uri");
        return false;
    }
    char* b2_uri = psMetadataLookupPtr(&status, md, "b2_uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item b2_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 detNormalizedImfileRowAlloc(det_id, iteration, class_id, uri, bg, bg_stdev, bg_mean_stdev, b1_uri, b2_uri, fault);
}
psArray *detNormalizedImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

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

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detNormalizedImfileRow *object = detNormalizedImfileObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

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

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

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

        deleted += count;
    }

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

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

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

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

    psMetadata *md = detNormalizedImfileMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void detNormalizedExpRowFree(detNormalizedExpRow *object);

detNormalizedExpRow *detNormalizedExpRowAlloc(psS32 det_id, psS32 iteration, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *b1_uri, const char *b2_uri, 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->b1_uri = psStringCopy(b1_uri);
    _object->b2_uri = psStringCopy(b2_uri);
    _object->fault = fault;

    return _object;
}

static void detNormalizedExpRowFree(detNormalizedExpRow *object)
{
    psFree(object->recipe);
    psFree(object->b1_uri);
    psFree(object->b2_uri);
}

bool detNormalizedExpCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b1_uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b2_uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "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, psS32 det_id, psS32 iteration, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *b1_uri, const char *b2_uri, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S32, 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, "b1_uri", PS_DATA_STRING, NULL, b1_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b2_uri", PS_DATA_STRING, NULL, b2_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_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, 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->b1_uri, object->b2_uri, 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_S32, 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, "b1_uri", PS_DATA_STRING, NULL, object->b1_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b2_uri", PS_DATA_STRING, NULL, object->b2_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_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;
}

detNormalizedExpRow *detNormalizedExpObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS32 det_id = psMetadataLookupS32(&status, md, "det_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
        return false;
    }
    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
        return false;
    }
    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
        return false;
    }
    char* b1_uri = psMetadataLookupPtr(&status, md, "b1_uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item b1_uri");
        return false;
    }
    char* b2_uri = psMetadataLookupPtr(&status, md, "b2_uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item b2_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 detNormalizedExpRowAlloc(det_id, iteration, recipe, bg, bg_stdev, bg_mean_stdev, b1_uri, b2_uri, fault);
}
psArray *detNormalizedExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

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

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detNormalizedExpRow *object = detNormalizedExpObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

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

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

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

        deleted += count;
    }

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

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

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

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

    psMetadata *md = detNormalizedExpMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void detResidImfileRowFree(detResidImfileRow *object);

detResidImfileRow *detResidImfileRowAlloc(psS32 det_id, psS32 iteration, const char *exp_tag, const char *class_id, const char *uri, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *b1_uri, const char *b2_uri, psS16 fault)
{
    detResidImfileRow *_object;

    _object = psAlloc(sizeof(detResidImfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)detResidImfileRowFree);

    _object->det_id = det_id;
    _object->iteration = iteration;
    _object->exp_tag = psStringCopy(exp_tag);
    _object->class_id = psStringCopy(class_id);
    _object->uri = psStringCopy(uri);
    _object->recipe = psStringCopy(recipe);
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->bg_mean_stdev = bg_mean_stdev;
    _object->b1_uri = psStringCopy(b1_uri);
    _object->b2_uri = psStringCopy(b2_uri);
    _object->fault = fault;

    return _object;
}

static void detResidImfileRowFree(detResidImfileRow *object)
{
    psFree(object->exp_tag);
    psFree(object->class_id);
    psFree(object->uri);
    psFree(object->recipe);
    psFree(object->b1_uri);
    psFree(object->b2_uri);
}

bool detResidImfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b1_uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b2_uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "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, psS32 det_id, psS32 iteration, const char *exp_tag, const char *class_id, const char *uri, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *b1_uri, const char *b2_uri, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S32, NULL, det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b1_uri", PS_DATA_STRING, NULL, b1_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b2_uri", PS_DATA_STRING, NULL, b2_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_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, DETRESIDIMFILE_TABLE_NAME, md);
    psFree(md);

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool detResidImfileInsertObject(psDB *dbh, detResidImfileRow *object)
{
    return detResidImfileInsert(dbh, object->det_id, object->iteration, object->exp_tag, object->class_id, object->uri, object->recipe, object->bg, object->bg_stdev, object->bg_mean_stdev, object->b1_uri, object->b2_uri, 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_S32, NULL, object->det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, object->iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, object->recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b1_uri", PS_DATA_STRING, NULL, object->b1_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b2_uri", PS_DATA_STRING, NULL, object->b2_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_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;
}

detResidImfileRow *detResidImfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS32 det_id = psMetadataLookupS32(&status, md, "det_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
        return false;
    }
    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
        return false;
    }
    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
        return false;
    }
    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }
    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
        return false;
    }
    char* b1_uri = psMetadataLookupPtr(&status, md, "b1_uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item b1_uri");
        return false;
    }
    char* b2_uri = psMetadataLookupPtr(&status, md, "b2_uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item b2_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 detResidImfileRowAlloc(det_id, iteration, exp_tag, class_id, uri, recipe, bg, bg_stdev, bg_mean_stdev, b1_uri, b2_uri, fault);
}
psArray *detResidImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

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

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detResidImfileRow *object = detResidImfileObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

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

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

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

        deleted += count;
    }

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

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

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

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

    psMetadata *md = detResidImfileMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void detResidExpRowFree(detResidExpRow *object);

detResidExpRow *detResidExpRowAlloc(psS32 det_id, psS32 iteration, const char *exp_tag, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *b1_uri, const char *b2_uri, bool accept, psS16 fault)
{
    detResidExpRow  *_object;

    _object = psAlloc(sizeof(detResidExpRow));
    psMemSetDeallocator(_object, (psFreeFunc)detResidExpRowFree);

    _object->det_id = det_id;
    _object->iteration = iteration;
    _object->exp_tag = psStringCopy(exp_tag);
    _object->recipe = psStringCopy(recipe);
    _object->bg = bg;
    _object->bg_stdev = bg_stdev;
    _object->bg_mean_stdev = bg_mean_stdev;
    _object->b1_uri = psStringCopy(b1_uri);
    _object->b2_uri = psStringCopy(b2_uri);
    _object->accept = accept;
    _object->fault = fault;

    return _object;
}

static void detResidExpRowFree(detResidExpRow *object)
{
    psFree(object->exp_tag);
    psFree(object->recipe);
    psFree(object->b1_uri);
    psFree(object->b2_uri);
}

bool detResidExpCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b1_uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b2_uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_uri");
        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, "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, psS32 det_id, psS32 iteration, const char *exp_tag, const char *recipe, psF64 bg, psF64 bg_stdev, psF64 bg_mean_stdev, const char *b1_uri, const char *b2_uri, bool accept, psS16 fault)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "det_id", PS_DATA_S32, NULL, det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b1_uri", PS_DATA_STRING, NULL, b1_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b2_uri", PS_DATA_STRING, NULL, b2_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "accept", PS_DATA_BOOL, NULL, accept)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item accept");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, NULL, fault)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item fault");
        psFree(md);
        return false;
    }

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool detResidExpInsertObject(psDB *dbh, detResidExpRow *object)
{
    return detResidExpInsert(dbh, object->det_id, object->iteration, object->exp_tag, object->recipe, object->bg, object->bg_stdev, object->bg_mean_stdev, object->b1_uri, object->b2_uri, 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_S32, NULL, object->det_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, NULL, object->iteration)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "recipe", PS_DATA_STRING, NULL, object->recipe)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item recipe");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, object->bg)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, object->bg_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, object->bg_mean_stdev)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b1_uri", PS_DATA_STRING, NULL, object->b1_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b1_uri");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "b2_uri", PS_DATA_STRING, NULL, object->b2_uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item b2_uri");
        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;
    psS32 det_id = psMetadataLookupS32(&status, md, "det_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
        return false;
    }
    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
        return false;
    }
    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
        return false;
    }
    char* recipe = psMetadataLookupPtr(&status, md, "recipe");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item recipe");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
        return false;
    }
    char* b1_uri = psMetadataLookupPtr(&status, md, "b1_uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item b1_uri");
        return false;
    }
    char* b2_uri = psMetadataLookupPtr(&status, md, "b2_uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item b2_uri");
        return false;
    }
    bool accept = psMetadataLookupBool(&status, md, "accept");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item accept");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return detResidExpRowAlloc(det_id, iteration, exp_tag, recipe, bg, bg_stdev, bg_mean_stdev, b1_uri, b2_uri, accept, fault);
}
psArray *detResidExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

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

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detResidExpRow *object = detResidExpObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

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

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

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

        deleted += count;
    }

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

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

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

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

    psMetadata *md = detResidExpMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void detRunSummaryRowFree(detRunSummaryRow *object);

detRunSummaryRow *detRunSummaryRowAlloc(psS32 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_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "iteration", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "bg_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "accept", PS_DATA_BOOL, NULL, 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item accept");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "fault", PS_DATA_S16, "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, psS32 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_S32, 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_S32, 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;
    psS32 det_id = psMetadataLookupS32(&status, md, "det_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
        return false;
    }
    psS32 iteration = psMetadataLookupS32(&status, md, "iteration");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
        return false;
    }
    psF64 bg = psMetadataLookupF64(&status, md, "bg");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg");
        return false;
    }
    psF64 bg_stdev = psMetadataLookupF64(&status, md, "bg_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_stdev");
        return false;
    }
    psF64 bg_mean_stdev = psMetadataLookupF64(&status, md, "bg_mean_stdev");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item bg_mean_stdev");
        return false;
    }
    bool accept = psMetadataLookupBool(&status, md, "accept");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item accept");
        return false;
    }
    psS16 fault = psMetadataLookupS16(&status, md, "fault");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item fault");
        return false;
    }

    return detRunSummaryRowAlloc(det_id, iteration, bg, bg_stdev, bg_mean_stdev, accept, fault);
}
psArray *detRunSummarySelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

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

    // convert psMetadata rows to row objects

    returnSet = psArrayAllocEmpty(rowSet->n);

    for (i = 0; i < rowSet->n; i++) {
        detRunSummaryRow *object = detRunSummaryObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

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

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

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

        deleted += count;
    }

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

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

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

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

    psMetadata *md = detRunSummaryMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void p4aRunRowFree(p4aRunRow *object);

p4aRunRow *p4aRunRowAlloc(psS32 p4a_id, const char *state, const char *workdir, bool magic)
{
    p4aRunRow       *_object;

    _object = psAlloc(sizeof(p4aRunRow));
    psMemSetDeallocator(_object, (psFreeFunc)p4aRunRowFree);

    _object->p4a_id = p4a_id;
    _object->state = psStringCopy(state);
    _object->workdir = psStringCopy(workdir);
    _object->magic = magic;

    return _object;
}

static void p4aRunRowFree(p4aRunRow *object)
{
    psFree(object->state);
    psFree(object->workdir);
}

bool p4aRunCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p4a_id", PS_DATA_S32, "Primary Key AUTO_INCREMENT", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p4a_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, "magic", PS_DATA_BOOL, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magic");
        psFree(md);
        return false;
    }

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

    psFree(md);

    return status;
}

bool p4aRunDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, P4ARUN_TABLE_NAME);
}

bool p4aRunInsert(psDB * dbh, psS32 p4a_id, const char *state, const char *workdir, bool magic)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p4a_id", PS_DATA_S32, NULL, p4a_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p4a_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, "magic", PS_DATA_BOOL, NULL, magic)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magic");
        psFree(md);
        return false;
    }

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool p4aRunInsertObject(psDB *dbh, p4aRunRow *object)
{
    return p4aRunInsert(dbh, object->p4a_id, object->state, object->workdir, object->magic);
}

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

    return true;
}

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

    // move to (the first?) extension named  P4ARUN_TABLE_NAME
    if (!psFitsMoveExtName(fits, P4ARUN_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P4ARUN_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, P4ARUN_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *p4aRunMetadataFromObject(const p4aRunRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p4a_id", PS_DATA_S32, NULL, object->p4a_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p4a_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, "magic", PS_DATA_BOOL, NULL, object->magic)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magic");
        psFree(md);
        return false;
    }


    return md;
}

p4aRunRow *p4aRunObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS32 p4a_id = psMetadataLookupS32(&status, md, "p4a_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p4a_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;
    }
    bool magic = psMetadataLookupBool(&status, md, "magic");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item magic");
        return false;
    }

    return p4aRunRowAlloc(p4a_id, state, workdir, magic);
}
psArray *p4aRunSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, P4ARUN_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++) {
        p4aRunRow *object = p4aRunObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool p4aRunDeleteObject(psDB *dbh, const p4aRunRow *object)
{
    psMetadata *where = p4aRunMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, P4ARUN_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from p4aRun");
        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, "p4aRunRow object matched more then one row.  Check your database schema");
        return false;
    }

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

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

        deleted += count;
    }

    return deleted;
}
bool p4aRunPrintObjects(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 = p4aRunMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            P4ARUN_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 p4aRunPrintObject(FILE *stream, p4aRunRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = p4aRunMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void p4aInputExpRowFree(p4aInputExpRow *object);

p4aInputExpRow *p4aInputExpRowAlloc(psS32 p4a_id, const char *exp_tag, psS32 p3_version)
{
    p4aInputExpRow  *_object;

    _object = psAlloc(sizeof(p4aInputExpRow));
    psMemSetDeallocator(_object, (psFreeFunc)p4aInputExpRowFree);

    _object->p4a_id = p4a_id;
    _object->exp_tag = psStringCopy(exp_tag);
    _object->p3_version = p3_version;

    return _object;
}

static void p4aInputExpRowFree(p4aInputExpRow *object)
{
    psFree(object->exp_tag);
}

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

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

    psFree(md);

    return status;
}

bool p4aInputExpDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, P4AINPUTEXP_TABLE_NAME);
}

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

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool p4aInputExpInsertObject(psDB *dbh, p4aInputExpRow *object)
{
    return p4aInputExpInsert(dbh, object->p4a_id, object->exp_tag, object->p3_version);
}

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

    return true;
}

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

    // move to (the first?) extension named  P4AINPUTEXP_TABLE_NAME
    if (!psFitsMoveExtName(fits, P4AINPUTEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P4AINPUTEXP_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, P4AINPUTEXP_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *p4aInputExpMetadataFromObject(const p4aInputExpRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p4a_id", PS_DATA_S32, NULL, object->p4a_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p4a_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p3_version", PS_DATA_S32, NULL, object->p3_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
        psFree(md);
        return false;
    }


    return md;
}

p4aInputExpRow *p4aInputExpObjectFromMetadata(psMetadata *md)
{

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

    return p4aInputExpRowAlloc(p4a_id, exp_tag, p3_version);
}
psArray *p4aInputExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, P4AINPUTEXP_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++) {
        p4aInputExpRow *object = p4aInputExpObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool p4aInputExpDeleteObject(psDB *dbh, const p4aInputExpRow *object)
{
    psMetadata *where = p4aInputExpMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, P4AINPUTEXP_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from p4aInputExp");
        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, "p4aInputExpRow object matched more then one row.  Check your database schema");
        return false;
    }

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

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

        deleted += count;
    }

    return deleted;
}
bool p4aInputExpPrintObjects(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 = p4aInputExpMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            P4AINPUTEXP_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 p4aInputExpPrintObject(FILE *stream, p4aInputExpRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = p4aInputExpMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void p4aScfileRowFree(p4aScfileRow *object);

p4aScfileRow *p4aScfileRowAlloc(psS32 p4a_id, const char *skycell_id, const char *tess_id, const char *exp_tag, psS32 p3_version, const char *uri, psF64 bg, psF64 bg_mean_stdev)
{
    p4aScfileRow    *_object;

    _object = psAlloc(sizeof(p4aScfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)p4aScfileRowFree);

    _object->p4a_id = p4a_id;
    _object->skycell_id = psStringCopy(skycell_id);
    _object->tess_id = psStringCopy(tess_id);
    _object->exp_tag = psStringCopy(exp_tag);
    _object->p3_version = p3_version;
    _object->uri = psStringCopy(uri);
    _object->bg = bg;
    _object->bg_mean_stdev = bg_mean_stdev;

    return _object;
}

static void p4aScfileRowFree(p4aScfileRow *object)
{
    psFree(object->skycell_id);
    psFree(object->tess_id);
    psFree(object->exp_tag);
    psFree(object->uri);
}

bool p4aScfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p4a_id", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p4a_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, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p3_version", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
        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_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }

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

    psFree(md);

    return status;
}

bool p4aScfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, P4ASCFILE_TABLE_NAME);
}

bool p4aScfileInsert(psDB * dbh, psS32 p4a_id, const char *skycell_id, const char *tess_id, const char *exp_tag, psS32 p3_version, const char *uri, psF64 bg, psF64 bg_mean_stdev)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p4a_id", PS_DATA_S32, NULL, p4a_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p4a_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, "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, "p3_version", PS_DATA_S32, NULL, p3_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
        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_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;
    }

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool p4aScfileInsertObject(psDB *dbh, p4aScfileRow *object)
{
    return p4aScfileInsert(dbh, object->p4a_id, object->skycell_id, object->tess_id, object->exp_tag, object->p3_version, object->uri, object->bg, object->bg_mean_stdev);
}

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

    return true;
}

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

    // move to (the first?) extension named  P4ASCFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, P4ASCFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P4ASCFILE_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, P4ASCFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *p4aScfileMetadataFromObject(const p4aScfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p4a_id", PS_DATA_S32, NULL, object->p4a_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p4a_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, "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, "p3_version", PS_DATA_S32, NULL, object->p3_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
        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_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;
    }


    return md;
}

p4aScfileRow *p4aScfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS32 p4a_id = psMetadataLookupS32(&status, md, "p4a_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p4a_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* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
        return false;
    }
    psS32 p3_version = psMetadataLookupS32(&status, md, "p3_version");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p3_version");
        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_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;
    }

    return p4aScfileRowAlloc(p4a_id, skycell_id, tess_id, exp_tag, p3_version, uri, bg, bg_mean_stdev);
}
psArray *p4aScfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, P4ASCFILE_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++) {
        p4aScfileRow *object = p4aScfileObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool p4aScfileDeleteObject(psDB *dbh, const p4aScfileRow *object)
{
    psMetadata *where = p4aScfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, P4ASCFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from p4aScfile");
        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, "p4aScfileRow object matched more then one row.  Check your database schema");
        return false;
    }

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

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

        deleted += count;
    }

    return deleted;
}
bool p4aScfilePrintObjects(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 = p4aScfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            P4ASCFILE_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 p4aScfilePrintObject(FILE *stream, p4aScfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = p4aScfileMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void p4bRunRowFree(p4bRunRow *object);

p4bRunRow *p4bRunRowAlloc(psS32 p4b_id, const char *state, const char *workdir, bool magic)
{
    p4bRunRow       *_object;

    _object = psAlloc(sizeof(p4bRunRow));
    psMemSetDeallocator(_object, (psFreeFunc)p4bRunRowFree);

    _object->p4b_id = p4b_id;
    _object->state = psStringCopy(state);
    _object->workdir = psStringCopy(workdir);
    _object->magic = magic;

    return _object;
}

static void p4bRunRowFree(p4bRunRow *object)
{
    psFree(object->state);
    psFree(object->workdir);
}

bool p4bRunCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p4b_id", PS_DATA_S32, "Primary Key AUTO_INCREMENT", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p4b_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, "magic", PS_DATA_BOOL, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magic");
        psFree(md);
        return false;
    }

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

    psFree(md);

    return status;
}

bool p4bRunDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, P4BRUN_TABLE_NAME);
}

bool p4bRunInsert(psDB * dbh, psS32 p4b_id, const char *state, const char *workdir, bool magic)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p4b_id", PS_DATA_S32, NULL, p4b_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p4b_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, "magic", PS_DATA_BOOL, NULL, magic)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magic");
        psFree(md);
        return false;
    }

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool p4bRunInsertObject(psDB *dbh, p4bRunRow *object)
{
    return p4bRunInsert(dbh, object->p4b_id, object->state, object->workdir, object->magic);
}

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

    return true;
}

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

    // move to (the first?) extension named  P4BRUN_TABLE_NAME
    if (!psFitsMoveExtName(fits, P4BRUN_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P4BRUN_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, P4BRUN_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *p4bRunMetadataFromObject(const p4bRunRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p4b_id", PS_DATA_S32, NULL, object->p4b_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p4b_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, "magic", PS_DATA_BOOL, NULL, object->magic)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magic");
        psFree(md);
        return false;
    }


    return md;
}

p4bRunRow *p4bRunObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS32 p4b_id = psMetadataLookupS32(&status, md, "p4b_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p4b_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;
    }
    bool magic = psMetadataLookupBool(&status, md, "magic");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item magic");
        return false;
    }

    return p4bRunRowAlloc(p4b_id, state, workdir, magic);
}
psArray *p4bRunSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, P4BRUN_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++) {
        p4bRunRow *object = p4bRunObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool p4bRunDeleteObject(psDB *dbh, const p4bRunRow *object)
{
    psMetadata *where = p4bRunMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, P4BRUN_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from p4bRun");
        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, "p4bRunRow object matched more then one row.  Check your database schema");
        return false;
    }

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

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

        deleted += count;
    }

    return deleted;
}
bool p4bRunPrintObjects(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 = p4bRunMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            P4BRUN_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 p4bRunPrintObject(FILE *stream, p4bRunRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = p4bRunMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void p4bInputScfileRowFree(p4bInputScfileRow *object);

p4bInputScfileRow *p4bInputScfileRowAlloc(psS32 p4b_id, const char *skycell_id, const char *tess_id, const char *exp_tag, psS32 p3_version, const char *kind)
{
    p4bInputScfileRow *_object;

    _object = psAlloc(sizeof(p4bInputScfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)p4bInputScfileRowFree);

    _object->p4b_id = p4b_id;
    _object->skycell_id = psStringCopy(skycell_id);
    _object->tess_id = psStringCopy(tess_id);
    _object->exp_tag = psStringCopy(exp_tag);
    _object->p3_version = p3_version;
    _object->kind = psStringCopy(kind);

    return _object;
}

static void p4bInputScfileRowFree(p4bInputScfileRow *object)
{
    psFree(object->skycell_id);
    psFree(object->tess_id);
    psFree(object->exp_tag);
    psFree(object->kind);
}

bool p4bInputScfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p4b_id", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p4b_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, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p3_version", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
        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, P4BINPUTSCFILE_TABLE_NAME, md);

    psFree(md);

    return status;
}

bool p4bInputScfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, P4BINPUTSCFILE_TABLE_NAME);
}

bool p4bInputScfileInsert(psDB * dbh, psS32 p4b_id, const char *skycell_id, const char *tess_id, const char *exp_tag, psS32 p3_version, const char *kind)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p4b_id", PS_DATA_S32, NULL, p4b_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p4b_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, "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, "p3_version", PS_DATA_S32, NULL, p3_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
        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, P4BINPUTSCFILE_TABLE_NAME, md);
    psFree(md);

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool p4bInputScfileInsertObject(psDB *dbh, p4bInputScfileRow *object)
{
    return p4bInputScfileInsert(dbh, object->p4b_id, object->skycell_id, object->tess_id, object->exp_tag, object->p3_version, object->kind);
}

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

    return true;
}

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

    // move to (the first?) extension named  P4BINPUTSCFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, P4BINPUTSCFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P4BINPUTSCFILE_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, P4BINPUTSCFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *p4bInputScfileMetadataFromObject(const p4bInputScfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p4b_id", PS_DATA_S32, NULL, object->p4b_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p4b_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, "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, "p3_version", PS_DATA_S32, NULL, object->p3_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
        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;
}

p4bInputScfileRow *p4bInputScfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS32 p4b_id = psMetadataLookupS32(&status, md, "p4b_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p4b_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* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
        return false;
    }
    psS32 p3_version = psMetadataLookupS32(&status, md, "p3_version");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p3_version");
        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 p4bInputScfileRowAlloc(p4b_id, skycell_id, tess_id, exp_tag, p3_version, kind);
}
psArray *p4bInputScfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, P4BINPUTSCFILE_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++) {
        p4bInputScfileRow *object = p4bInputScfileObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool p4bInputScfileDeleteObject(psDB *dbh, const p4bInputScfileRow *object)
{
    psMetadata *where = p4bInputScfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, P4BINPUTSCFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from p4bInputScfile");
        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, "p4bInputScfileRow object matched more then one row.  Check your database schema");
        return false;
    }

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

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

        deleted += count;
    }

    return deleted;
}
bool p4bInputScfilePrintObjects(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 = p4bInputScfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            P4BINPUTSCFILE_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 p4bInputScfilePrintObject(FILE *stream, p4bInputScfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = p4bInputScfileMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void p4bDiffScfileRowFree(p4bDiffScfileRow *object);

p4bDiffScfileRow *p4bDiffScfileRowAlloc(psS32 p4b_id, const char *skycell_id, const char *tess_id, const char *exp_tag, psS32 p3_version, const char *uri, psF64 bg, psF64 bg_mean_stdev)
{
    p4bDiffScfileRow *_object;

    _object = psAlloc(sizeof(p4bDiffScfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)p4bDiffScfileRowFree);

    _object->p4b_id = p4b_id;
    _object->skycell_id = psStringCopy(skycell_id);
    _object->tess_id = psStringCopy(tess_id);
    _object->exp_tag = psStringCopy(exp_tag);
    _object->p3_version = p3_version;
    _object->uri = psStringCopy(uri);
    _object->bg = bg;
    _object->bg_mean_stdev = bg_mean_stdev;

    return _object;
}

static void p4bDiffScfileRowFree(p4bDiffScfileRow *object)
{
    psFree(object->skycell_id);
    psFree(object->tess_id);
    psFree(object->exp_tag);
    psFree(object->uri);
}

bool p4bDiffScfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p4b_id", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p4b_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, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p3_version", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
        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_mean_stdev", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item bg_mean_stdev");
        psFree(md);
        return false;
    }

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

    psFree(md);

    return status;
}

bool p4bDiffScfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, P4BDIFFSCFILE_TABLE_NAME);
}

bool p4bDiffScfileInsert(psDB * dbh, psS32 p4b_id, const char *skycell_id, const char *tess_id, const char *exp_tag, psS32 p3_version, const char *uri, psF64 bg, psF64 bg_mean_stdev)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p4b_id", PS_DATA_S32, NULL, p4b_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p4b_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, "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, "p3_version", PS_DATA_S32, NULL, p3_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
        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_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;
    }

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool p4bDiffScfileInsertObject(psDB *dbh, p4bDiffScfileRow *object)
{
    return p4bDiffScfileInsert(dbh, object->p4b_id, object->skycell_id, object->tess_id, object->exp_tag, object->p3_version, object->uri, object->bg, object->bg_mean_stdev);
}

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

    return true;
}

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

    // move to (the first?) extension named  P4BDIFFSCFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, P4BDIFFSCFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P4BDIFFSCFILE_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, P4BDIFFSCFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *p4bDiffScfileMetadataFromObject(const p4bDiffScfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p4b_id", PS_DATA_S32, NULL, object->p4b_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p4b_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, "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, "p3_version", PS_DATA_S32, NULL, object->p3_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
        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_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;
    }


    return md;
}

p4bDiffScfileRow *p4bDiffScfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS32 p4b_id = psMetadataLookupS32(&status, md, "p4b_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p4b_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* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
        return false;
    }
    psS32 p3_version = psMetadataLookupS32(&status, md, "p3_version");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p3_version");
        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_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;
    }

    return p4bDiffScfileRowAlloc(p4b_id, skycell_id, tess_id, exp_tag, p3_version, uri, bg, bg_mean_stdev);
}
psArray *p4bDiffScfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, P4BDIFFSCFILE_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++) {
        p4bDiffScfileRow *object = p4bDiffScfileObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool p4bDiffScfileDeleteObject(psDB *dbh, const p4bDiffScfileRow *object)
{
    psMetadata *where = p4bDiffScfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, P4BDIFFSCFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from p4bDiffScfile");
        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, "p4bDiffScfileRow object matched more then one row.  Check your database schema");
        return false;
    }

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

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

        deleted += count;
    }

    return deleted;
}
bool p4bDiffScfilePrintObjects(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 = p4bDiffScfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            P4BDIFFSCFILE_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 p4bDiffScfilePrintObject(FILE *stream, p4bDiffScfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = p4bDiffScfileMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void p4cRunRowFree(p4cRunRow *object);

p4cRunRow *p4cRunRowAlloc(psS32 p4c_id, const char *state, const char *workdir, bool magic)
{
    p4cRunRow       *_object;

    _object = psAlloc(sizeof(p4cRunRow));
    psMemSetDeallocator(_object, (psFreeFunc)p4cRunRowFree);

    _object->p4c_id = p4c_id;
    _object->state = psStringCopy(state);
    _object->workdir = psStringCopy(workdir);
    _object->magic = magic;

    return _object;
}

static void p4cRunRowFree(p4cRunRow *object)
{
    psFree(object->state);
    psFree(object->workdir);
}

bool p4cRunCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p4c_id", PS_DATA_S32, "Primary Key AUTO_INCREMENT", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p4c_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, "magic", PS_DATA_BOOL, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magic");
        psFree(md);
        return false;
    }

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

    psFree(md);

    return status;
}

bool p4cRunDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, P4CRUN_TABLE_NAME);
}

bool p4cRunInsert(psDB * dbh, psS32 p4c_id, const char *state, const char *workdir, bool magic)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p4c_id", PS_DATA_S32, NULL, p4c_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p4c_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, "magic", PS_DATA_BOOL, NULL, magic)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magic");
        psFree(md);
        return false;
    }

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool p4cRunInsertObject(psDB *dbh, p4cRunRow *object)
{
    return p4cRunInsert(dbh, object->p4c_id, object->state, object->workdir, object->magic);
}

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

    return true;
}

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

    // move to (the first?) extension named  P4CRUN_TABLE_NAME
    if (!psFitsMoveExtName(fits, P4CRUN_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P4CRUN_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, P4CRUN_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *p4cRunMetadataFromObject(const p4cRunRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p4c_id", PS_DATA_S32, NULL, object->p4c_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p4c_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, "magic", PS_DATA_BOOL, NULL, object->magic)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item magic");
        psFree(md);
        return false;
    }


    return md;
}

p4cRunRow *p4cRunObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS32 p4c_id = psMetadataLookupS32(&status, md, "p4c_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p4c_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;
    }
    bool magic = psMetadataLookupBool(&status, md, "magic");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item magic");
        return false;
    }

    return p4cRunRowAlloc(p4c_id, state, workdir, magic);
}
psArray *p4cRunSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, P4CRUN_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++) {
        p4cRunRow *object = p4cRunObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool p4cRunDeleteObject(psDB *dbh, const p4cRunRow *object)
{
    psMetadata *where = p4cRunMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, P4CRUN_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from p4cRun");
        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, "p4cRunRow object matched more then one row.  Check your database schema");
        return false;
    }

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

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

        deleted += count;
    }

    return deleted;
}
bool p4cRunPrintObjects(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 = p4cRunMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            P4CRUN_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 p4cRunPrintObject(FILE *stream, p4cRunRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = p4cRunMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void p4cInputExpRowFree(p4cInputExpRow *object);

p4cInputExpRow *p4cInputExpRowAlloc(psS32 p4c_id, const char *exp_tag, psS32 p3_version)
{
    p4cInputExpRow  *_object;

    _object = psAlloc(sizeof(p4cInputExpRow));
    psMemSetDeallocator(_object, (psFreeFunc)p4cInputExpRowFree);

    _object->p4c_id = p4c_id;
    _object->exp_tag = psStringCopy(exp_tag);
    _object->p3_version = p3_version;

    return _object;
}

static void p4cInputExpRowFree(p4cInputExpRow *object)
{
    psFree(object->exp_tag);
}

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

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

    psFree(md);

    return status;
}

bool p4cInputExpDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, P4CINPUTEXP_TABLE_NAME);
}

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

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool p4cInputExpInsertObject(psDB *dbh, p4cInputExpRow *object)
{
    return p4cInputExpInsert(dbh, object->p4c_id, object->exp_tag, object->p3_version);
}

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

    return true;
}

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

    // move to (the first?) extension named  P4CINPUTEXP_TABLE_NAME
    if (!psFitsMoveExtName(fits, P4CINPUTEXP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P4CINPUTEXP_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, P4CINPUTEXP_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *p4cInputExpMetadataFromObject(const p4cInputExpRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p4c_id", PS_DATA_S32, NULL, object->p4c_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p4c_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p3_version", PS_DATA_S32, NULL, object->p3_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
        psFree(md);
        return false;
    }


    return md;
}

p4cInputExpRow *p4cInputExpObjectFromMetadata(psMetadata *md)
{

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

    return p4cInputExpRowAlloc(p4c_id, exp_tag, p3_version);
}
psArray *p4cInputExpSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, P4CINPUTEXP_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++) {
        p4cInputExpRow *object = p4cInputExpObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool p4cInputExpDeleteObject(psDB *dbh, const p4cInputExpRow *object)
{
    psMetadata *where = p4cInputExpMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, P4CINPUTEXP_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from p4cInputExp");
        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, "p4cInputExpRow object matched more then one row.  Check your database schema");
        return false;
    }

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

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

        deleted += count;
    }

    return deleted;
}
bool p4cInputExpPrintObjects(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 = p4cInputExpMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            P4CINPUTEXP_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 p4cInputExpPrintObject(FILE *stream, p4cInputExpRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = p4cInputExpMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void p4MagicMaskImfileRowFree(p4MagicMaskImfileRow *object);

p4MagicMaskImfileRow *p4MagicMaskImfileRowAlloc(psS32 p4c_id, const char *exp_tag, psS32 p3_version, const char *class_id, const char *uri)
{
    p4MagicMaskImfileRow *_object;

    _object = psAlloc(sizeof(p4MagicMaskImfileRow));
    psMemSetDeallocator(_object, (psFreeFunc)p4MagicMaskImfileRowFree);

    _object->p4c_id = p4c_id;
    _object->exp_tag = psStringCopy(exp_tag);
    _object->p3_version = p3_version;
    _object->class_id = psStringCopy(class_id);
    _object->uri = psStringCopy(uri);

    return _object;
}

static void p4MagicMaskImfileRowFree(p4MagicMaskImfileRow *object)
{
    psFree(object->exp_tag);
    psFree(object->class_id);
    psFree(object->uri);
}

bool p4MagicMaskImfileCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p4c_id", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p4c_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p3_version", PS_DATA_S32, "Primary Key", 0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, "255")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }

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

    psFree(md);

    return status;
}

bool p4MagicMaskImfileDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, P4MAGICMASKIMFILE_TABLE_NAME);
}

bool p4MagicMaskImfileInsert(psDB * dbh, psS32 p4c_id, const char *exp_tag, psS32 p3_version, const char *class_id, const char *uri)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p4c_id", PS_DATA_S32, NULL, p4c_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p4c_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p3_version", PS_DATA_S32, NULL, p3_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool p4MagicMaskImfileInsertObject(psDB *dbh, p4MagicMaskImfileRow *object)
{
    return p4MagicMaskImfileInsert(dbh, object->p4c_id, object->exp_tag, object->p3_version, object->class_id, object->uri);
}

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

    return true;
}

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

    // move to (the first?) extension named  P4MAGICMASKIMFILE_TABLE_NAME
    if (!psFitsMoveExtName(fits, P4MAGICMASKIMFILE_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", P4MAGICMASKIMFILE_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, P4MAGICMASKIMFILE_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *p4MagicMaskImfileMetadataFromObject(const p4MagicMaskImfileRow *object)
{
    psMetadata *md = psMetadataAlloc();
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p4c_id", PS_DATA_S32, NULL, object->p4c_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p4c_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "p3_version", PS_DATA_S32, NULL, object->p3_version)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item p3_version");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "uri", PS_DATA_STRING, NULL, object->uri)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item uri");
        psFree(md);
        return false;
    }


    return md;
}

p4MagicMaskImfileRow *p4MagicMaskImfileObjectFromMetadata(psMetadata *md)
{

bool status = false;
    psS32 p4c_id = psMetadataLookupS32(&status, md, "p4c_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p4c_id");
        return false;
    }
    char* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
        return false;
    }
    psS32 p3_version = psMetadataLookupS32(&status, md, "p3_version");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item p3_version");
        return false;
    }
    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
        return false;
    }
    char* uri = psMetadataLookupPtr(&status, md, "uri");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item uri");
        return false;
    }

    return p4MagicMaskImfileRowAlloc(p4c_id, exp_tag, p3_version, class_id, uri);
}
psArray *p4MagicMaskImfileSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, P4MAGICMASKIMFILE_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++) {
        p4MagicMaskImfileRow *object = p4MagicMaskImfileObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool p4MagicMaskImfileDeleteObject(psDB *dbh, const p4MagicMaskImfileRow *object)
{
    psMetadata *where = p4MagicMaskImfileMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, P4MAGICMASKIMFILE_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from p4MagicMaskImfile");
        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, "p4MagicMaskImfileRow object matched more then one row.  Check your database schema");
        return false;
    }

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

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

        deleted += count;
    }

    return deleted;
}
bool p4MagicMaskImfilePrintObjects(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 = p4MagicMaskImfileMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            P4MAGICMASKIMFILE_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 p4MagicMaskImfilePrintObject(FILE *stream, p4MagicMaskImfileRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = p4MagicMaskImfileMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void skyCellRowFree(skyCellRow *object);

skyCellRow *skyCellRowAlloc(const char *skycell_id, const char *tess_id, psF64 ra1, psF64 decl1, psF64 ra2, psF64 decl2, psF64 ra3, psF64 decl3, psF64 ra4, psF64 decl4)
{
    skyCellRow      *_object;

    _object = psAlloc(sizeof(skyCellRow));
    psMemSetDeallocator(_object, (psFreeFunc)skyCellRowFree);

    _object->skycell_id = psStringCopy(skycell_id);
    _object->tess_id = psStringCopy(tess_id);
    _object->ra1 = ra1;
    _object->decl1 = decl1;
    _object->ra2 = ra2;
    _object->decl2 = decl2;
    _object->ra3 = ra3;
    _object->decl3 = decl3;
    _object->ra4 = ra4;
    _object->decl4 = decl4;

    return _object;
}

static void skyCellRowFree(skyCellRow *object)
{
    psFree(object->skycell_id);
    psFree(object->tess_id);
}

bool skyCellCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    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, "ra1", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ra1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl1", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item decl1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra2", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ra2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl2", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item decl2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra3", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ra3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl3", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item decl3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra4", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ra4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl4", PS_DATA_F64, NULL, 0.0)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item decl4");
        psFree(md);
        return false;
    }

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

    psFree(md);

    return status;
}

bool skyCellDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, SKYCELL_TABLE_NAME);
}

bool skyCellInsert(psDB * dbh, const char *skycell_id, const char *tess_id, psF64 ra1, psF64 decl1, psF64 ra2, psF64 decl2, psF64 ra3, psF64 decl3, psF64 ra4, psF64 decl4)
{
    psMetadata *md = psMetadataAlloc();
    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, "ra1", PS_DATA_F64, NULL, ra1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ra1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl1", PS_DATA_F64, NULL, decl1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item decl1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra2", PS_DATA_F64, NULL, ra2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ra2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl2", PS_DATA_F64, NULL, decl2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item decl2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra3", PS_DATA_F64, NULL, ra3)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ra3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl3", PS_DATA_F64, NULL, decl3)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item decl3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra4", PS_DATA_F64, NULL, ra4)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ra4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl4", PS_DATA_F64, NULL, decl4)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item decl4");
        psFree(md);
        return false;
    }

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool skyCellInsertObject(psDB *dbh, skyCellRow *object)
{
    return skyCellInsert(dbh, object->skycell_id, object->tess_id, object->ra1, object->decl1, object->ra2, object->decl2, object->ra3, object->decl3, object->ra4, object->decl4);
}

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

    return true;
}

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

    // move to (the first?) extension named  SKYCELL_TABLE_NAME
    if (!psFitsMoveExtName(fits, SKYCELL_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", SKYCELL_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, SKYCELL_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *skyCellMetadataFromObject(const skyCellRow *object)
{
    psMetadata *md = psMetadataAlloc();
    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, "ra1", PS_DATA_F64, NULL, object->ra1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ra1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl1", PS_DATA_F64, NULL, object->decl1)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item decl1");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra2", PS_DATA_F64, NULL, object->ra2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ra2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl2", PS_DATA_F64, NULL, object->decl2)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item decl2");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra3", PS_DATA_F64, NULL, object->ra3)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ra3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl3", PS_DATA_F64, NULL, object->decl3)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item decl3");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "ra4", PS_DATA_F64, NULL, object->ra4)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item ra4");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "decl4", PS_DATA_F64, NULL, object->decl4)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item decl4");
        psFree(md);
        return false;
    }


    return md;
}

skyCellRow *skyCellObjectFromMetadata(psMetadata *md)
{

bool status = 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;
    }
    psF64 ra1 = psMetadataLookupF64(&status, md, "ra1");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra1");
        return false;
    }
    psF64 decl1 = psMetadataLookupF64(&status, md, "decl1");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl1");
        return false;
    }
    psF64 ra2 = psMetadataLookupF64(&status, md, "ra2");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra2");
        return false;
    }
    psF64 decl2 = psMetadataLookupF64(&status, md, "decl2");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl2");
        return false;
    }
    psF64 ra3 = psMetadataLookupF64(&status, md, "ra3");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra3");
        return false;
    }
    psF64 decl3 = psMetadataLookupF64(&status, md, "decl3");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl3");
        return false;
    }
    psF64 ra4 = psMetadataLookupF64(&status, md, "ra4");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item ra4");
        return false;
    }
    psF64 decl4 = psMetadataLookupF64(&status, md, "decl4");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item decl4");
        return false;
    }

    return skyCellRowAlloc(skycell_id, tess_id, ra1, decl1, ra2, decl2, ra3, decl3, ra4, decl4);
}
psArray *skyCellSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, SKYCELL_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++) {
        skyCellRow *object = skyCellObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool skyCellDeleteObject(psDB *dbh, const skyCellRow *object)
{
    psMetadata *where = skyCellMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, SKYCELL_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from skyCell");
        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, "skyCellRow object matched more then one row.  Check your database schema");
        return false;
    }

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

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

        deleted += count;
    }

    return deleted;
}
bool skyCellPrintObjects(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 = skyCellMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            SKYCELL_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 skyCellPrintObject(FILE *stream, skyCellRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = skyCellMetadataFromObject(object);

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

    psFree(md);

    return true;
}
static void skyCellMapRowFree(skyCellMapRow *object);

skyCellMapRow *skyCellMapRowAlloc(const char *skycell_id, const char *tess_id, const char *exp_tag, const char *class_id)
{
    skyCellMapRow   *_object;

    _object = psAlloc(sizeof(skyCellMapRow));
    psMemSetDeallocator(_object, (psFreeFunc)skyCellMapRowFree);

    _object->skycell_id = psStringCopy(skycell_id);
    _object->tess_id = psStringCopy(tess_id);
    _object->exp_tag = psStringCopy(exp_tag);
    _object->class_id = psStringCopy(class_id);

    return _object;
}

static void skyCellMapRowFree(skyCellMapRow *object)
{
    psFree(object->skycell_id);
    psFree(object->tess_id);
    psFree(object->exp_tag);
    psFree(object->class_id);
}

bool skyCellMapCreateTable(psDB *dbh)
{
    psMetadata *md = psMetadataAlloc();
    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, "exp_tag", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, "Primary Key", "64")) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }

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

    psFree(md);

    return status;
}

bool skyCellMapDropTable(psDB *dbh)
{
    return psDBDropTable(dbh, SKYCELLMAP_TABLE_NAME);
}

bool skyCellMapInsert(psDB * dbh, const char *skycell_id, const char *tess_id, const char *exp_tag, const char *class_id)
{
    psMetadata *md = psMetadataAlloc();
    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, "exp_tag", PS_DATA_STRING, NULL, exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }

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

    return status;
}

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

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

        deleted += count;
    }

    return deleted;
}
bool skyCellMapInsertObject(psDB *dbh, skyCellMapRow *object)
{
    return skyCellMapInsert(dbh, object->skycell_id, object->tess_id, object->exp_tag, object->class_id);
}

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

    return true;
}

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

    // move to (the first?) extension named  SKYCELLMAP_TABLE_NAME
    if (!psFitsMoveExtName(fits, SKYCELLMAP_TABLE_NAME)) {
        psError(PS_ERR_UNKNOWN, true, "failed to find FITS extension %s", SKYCELLMAP_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, SKYCELLMAP_TABLE_NAME, rowSet)) {
        psError(PS_ERR_UNKNOWN, false, "databse insert failed");
        psFree(rowSet);
        return false;
    }

    psFree(rowSet);

    return true;
}

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

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

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

    psFree(rowSet);

    return true;
}

psMetadata *skyCellMapMetadataFromObject(const skyCellMapRow *object)
{
    psMetadata *md = psMetadataAlloc();
    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, "exp_tag", PS_DATA_STRING, NULL, object->exp_tag)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item exp_tag");
        psFree(md);
        return false;
    }
    if (!psMetadataAdd(md, PS_LIST_TAIL, "class_id", PS_DATA_STRING, NULL, object->class_id)) {
        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
        psFree(md);
        return false;
    }


    return md;
}

skyCellMapRow *skyCellMapObjectFromMetadata(psMetadata *md)
{

bool status = 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* exp_tag = psMetadataLookupPtr(&status, md, "exp_tag");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item exp_tag");
        return false;
    }
    char* class_id = psMetadataLookupPtr(&status, md, "class_id");
    if (!status) {
        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
        return false;
    }

    return skyCellMapRowAlloc(skycell_id, tess_id, exp_tag, class_id);
}
psArray *skyCellMapSelectRowObjects(psDB *dbh, const psMetadata *where, unsigned long long limit)
{
    psArray         *rowSet;
    psArray         *returnSet;
    psU64           i;

    rowSet = psDBSelectRows(dbh, SKYCELLMAP_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++) {
        skyCellMapRow *object = skyCellMapObjectFromMetadata(rowSet->data[i]);
        psArrayAdd(returnSet, 0, object);
        psFree(object);
    }

    psFree(rowSet);

    return returnSet;
}
bool skyCellMapDeleteObject(psDB *dbh, const skyCellMapRow *object)
{
    psMetadata *where = skyCellMapMetadataFromObject(object);
    long long count = psDBDeleteRows(dbh, SKYCELLMAP_TABLE_NAME, where, 0);
    psFree(where);
    if (count < 0) {
        psError(PS_ERR_UNKNOWN, true, "failed to delete row from skyCellMap");
        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, "skyCellMapRow object matched more then one row.  Check your database schema");
        return false;
    }

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

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

        deleted += count;
    }

    return deleted;
}
bool skyCellMapPrintObjects(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 = skyCellMapMetadataFromObject(objects->data[i]);
        if (!psMetadataAddMetadata(
            output,
            PS_LIST_TAIL,
            SKYCELLMAP_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 skyCellMapPrintObject(FILE *stream, skyCellMapRow *object, bool mdcf)
{
    PS_ASSERT_PTR_NON_NULL(object, false);

    psMetadata *md = skyCellMapMetadataFromObject(object);

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

    psFree(md);

    return true;
}
