Index: trunk/ippdb/src/ippdb.c
===================================================================
--- trunk/ippdb/src/ippdb.c	(revision 8325)
+++ trunk/ippdb/src/ippdb.c	(revision 8368)
@@ -56,4 +56,6 @@
 #define DETSTACKEDIMFILE_TABLE_NAME "detStackedImfile"
 #define DETSTACKEDIMFILE_INDEX_NAME IPPDB_DEFAULT_INDEX_NAME
+#define DETNORMALIZEDSTATIMFILE_TABLE_NAME "detNormalizedStatImfile"
+#define DETNORMALIZEDSTATIMFILE_INDEX_NAME IPPDB_DEFAULT_INDEX_NAME
 #define DETNORMALIZEDIMFILE_TABLE_NAME "detNormalizedImfile"
 #define DETNORMALIZEDIMFILE_INDEX_NAME IPPDB_DEFAULT_INDEX_NAME
@@ -14583,4 +14585,476 @@
     return true;
 }
+static void detNormalizedStatImfileRowFree(detNormalizedStatImfileRow *object);
+
+detNormalizedStatImfileRow *detNormalizedStatImfileRowAlloc(psS32 det_id, psS32 iteration, const char *class_id, psF32 norm)
+{
+    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;
+
+    return object;
+}
+
+static void detNormalizedStatImfileRowFree(detNormalizedStatImfileRow *object)
+{
+    psFree(object->class_id);
+}
+
+bool detNormalizedStatImfileCreateTable(psDB *dbh)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAdd(md, PS_LIST_TAIL, DETNORMALIZEDSTATIMFILE_INDEX_NAME, PS_DATA_S32, "AUTO_INCREMENT", 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item %s", DETNORMALIZEDSTATIMFILE_INDEX_NAME);
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, "Primary Key", 0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, "Primary Key", "64")) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "norm", 0, NULL, 0.0)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item norm");
+        psFree(md);
+        return false;
+    }
+
+    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)
+{
+    psMetadata      *md;
+    bool            status;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return false;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "norm", 0, NULL, norm)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item norm");
+        psFree(md);
+        return false;
+    }
+
+    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 detNormalizedStatImfilePop(psDB *dbh, psS32 *det_id, psS32 *iteration, char **class_id, psF32 *norm)
+{
+    psArray         *rowSet;
+    psMetadata      *row;
+    psMetadata      *popped;
+    long            deleted;
+    bool            status;
+    int             rowID;
+
+    rowSet = psDBSelectRows(dbh, DETNORMALIZEDSTATIMFILE_TABLE_NAME, NULL, 1);
+    if (!rowSet) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETNORMALIZEDSTATIMFILE_INDEX_NAME);
+        psFree(rowSet);
+        return NULL;
+    }
+
+    row = psArrayGet(rowSet, 0);
+    psMemIncrRefCounter(row);
+    if (!row) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return NULL;
+    }
+    psFree(rowSet);
+
+    rowID = psMetadataLookupS32(&status, row, DETNORMALIZEDSTATIMFILE_INDEX_NAME);
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item %s", DETNORMALIZEDSTATIMFILE_INDEX_NAME);
+        psFree(row);
+        return NULL;
+    }
+
+    popped = psMetadataAlloc();
+    psMetadataAddS32(popped, PS_LIST_TAIL, DETNORMALIZEDSTATIMFILE_INDEX_NAME, 0, NULL, rowID);
+
+    deleted = psDBDeleteRows(dbh, DETNORMALIZEDSTATIMFILE_TABLE_NAME, popped, 0);
+    if (deleted != 1) {
+        psError(PS_ERR_UNKNOWN, false, "database failed to delete row");
+        psFree(popped);
+        psFree(row);
+        return NULL;
+    }
+
+    psFree(popped);
+
+    *det_id = psMetadataLookupS32(&status, row, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        psFree(row);
+        return false;
+    }
+    *iteration = psMetadataLookupS32(&status, row, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        psFree(row);
+        return false;
+    }
+    *class_id = psMetadataLookupPtr(&status, row, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        psFree(row);
+        return false;
+    }
+    *norm = psMetadataLookupF32(&status, row, "norm");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item norm");
+        psFree(row);
+        return false;
+    }
+
+    psFree(row);
+
+    return true;
+}
+
+bool detNormalizedStatImfileInsertObject(psDB *dbh, detNormalizedStatImfileRow *object)
+{
+    return detNormalizedStatImfileInsert(dbh, object->det_id, object->iteration, object->class_id, object->norm);
+}
+
+bool detNormalizedStatImfileInsertObjects(psDB *dbh, psArray *objects)
+{
+    for (long i = 0; i < psArrayLength(objects); i++) {
+        if (!detNormalizedStatImfileInsertObject(dbh, objects->data[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+detNormalizedStatImfileRow *detNormalizedStatImfilePopObject(psDB *dbh)
+{
+    psS32           det_id;
+    psS32           iteration;
+    char            class_id[256];
+    psF32           norm;
+
+    if (!detNormalizedStatImfilePop(dbh, &det_id, &iteration, (char **)&class_id, &norm)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to pop a database row");
+        return NULL;
+    }
+
+    return detNormalizedStatImfileRowAlloc(det_id, iteration, class_id, norm);
+}
+
+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 detNormalizedStatImfilePopFits(psDB *dbh, psFits *fits, unsigned long long limit)
+{
+    char            query[MAX_STRING_LENGTH];
+
+    if (!detNormalizedStatImfileSelectRowsFits(dbh, fits, NULL, limit)) {
+        psError(PS_ERR_UNKNOWN, true, "database error or table is empty");
+        return false;
+    }
+
+    // remove limit rows from the end of the database
+    if (snprintf(query, MAX_STRING_LENGTH,
+                "DELETE FROM %s ORDER BY %s DESC LIMIT %lld",
+                DETNORMALIZEDSTATIMFILE_TABLE_NAME, DETNORMALIZEDSTATIMFILE_INDEX_NAME, limit) < 0) {
+        psError(PS_ERR_UNKNOWN, true, "query value attempted to exceed %s bytes", MAX_STRING_LENGTH);
+        return false;
+    }
+            
+    if (!p_psDBRunQuery(dbh, query)) {
+        psError(PS_ERR_UNKNOWN, false, "database query failed");
+        return false;
+    }
+
+    return true;
+}
+
+bool detNormalizedStatImfileSelectRowsFits(psDB *dbh, psFits *fits, const psMetadata *where, unsigned long long limit)
+{
+    psArray         *rowSet;
+    psU64           i;
+
+    rowSet = psDBSelectRows(dbh, DETNORMALIZEDSTATIMFILE_TABLE_NAME, where, limit);
+    if (!rowSet) {
+        return false;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)rowSet->data[i], 0, DETNORMALIZEDSTATIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETNORMALIZEDSTATIMFILE_INDEX_NAME);
+            psFree(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;
+
+    md = psMetadataAlloc();
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "det_id", 0, NULL, object->det_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item det_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddS32(md, PS_LIST_TAIL, "iteration", 0, NULL, object->iteration)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item iteration");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddStr(md, PS_LIST_TAIL, "class_id", 0, NULL, object->class_id)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item class_id");
+        psFree(md);
+        return NULL;
+    }
+    if (!psMetadataAddF32(md, PS_LIST_TAIL, "norm", 0, NULL, object->norm)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to add item norm");
+        psFree(md);
+        return NULL;
+    }
+
+    return md;
+}
+
+detNormalizedStatImfileRow *detNormalizedStatImfileObjectFromMetadata(psMetadata *md)
+{
+    bool            status;
+    psS32           det_id;
+    psS32           iteration;
+    char            *class_id;
+    psF32           norm;
+
+    det_id = psMetadataLookupS32(&status, md, "det_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item det_id");
+        return false;
+    }
+    iteration = psMetadataLookupS32(&status, md, "iteration");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item iteration");
+        return false;
+    }
+    class_id = psMetadataLookupPtr(&status, md, "class_id");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item class_id");
+        return false;
+    }
+    norm = psMetadataLookupF32(&status, md, "norm");
+    if (!status) {
+        psError(PS_ERR_UNKNOWN, true, "failed to lookup value for item norm");
+        return false;
+    }
+
+    return detNormalizedStatImfileRowAlloc(det_id, iteration, class_id, norm);
+}
+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;
+    }
+
+    // strip index column
+    for (i = 0; i < rowSet->n; i++) {
+        if (!psMetadataRemove((psMetadata *)(rowSet->data[i]), 0, DETNORMALIZEDSTATIMFILE_INDEX_NAME)) {
+            psError(PS_ERR_UNKNOWN, true, "failed to remove item %s", DETNORMALIZEDSTATIMFILE_INDEX_NAME);
+            psFree(rowSet);
+            return false;
+        }
+    }
+
+    // convert psMetadata rows to row objects
+
+    returnSet = psArrayAlloc(rowSet->n);
+    returnSet->n = 0;
+
+    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;
+}
 static void detNormalizedImfileRowFree(detNormalizedImfileRow *object);
 
